[
  {
    "path": ".clang-format",
    "content": "Standard: Cpp03\nUseTab: ForIndentation\nTabWidth: 4\nIndentWidth: 4\nAccessModifierOffset: -4\nBreakBeforeBraces: Allman\nIndentCaseLabels: false\nColumnLimit: 0\nPointerAlignment: Left\nBreakConstructorInitializersBeforeComma: true\nNamespaceIndentation: None\nAlignEscapedNewlines: DontAlign\nAlignAfterOpenBracket: DontAlign\nIndentExternBlock: NoIndent\nMacros: [MESHOPTIMIZER_ALLOC_CALLCONV=&_&]\n"
  },
  {
    "path": ".editorconfig",
    "content": "# See https://editorconfig.org/ for more info\n\n[*]\ncharset = utf-8\nindent_style = tab\nindent_size = 4\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n"
  },
  {
    "path": ".git-blame-ignore-revs",
    "content": "# This file contains a list of Git commit hashes that should be hidden from the\n# regular Git history. Typically, this includes commits involving mass auto-formatting\n# or other normalizations. Commit hashes *must* use the full 40-character notation.\n# To apply the ignore list in your local Git client, you must run:\n#\n#   git config blame.ignoreRevsFile .git-blame-ignore-revs\n#\n# This file is automatically used by GitHub.com's blame view.\n\n# Convert CRLF to LF everywhere\nbb4aa0e1372751b74425e77c9a42f972971568bf\n\n# js: Reformat all sources with Prettier\n3dea31b5c248594a62f49a3e41fc88d7ceae2de3\n\n# Reformat workflow YAML files with Prettier\n52e8e1f61712928a36b5257a894bb098a4a98b22\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: 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.\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Questions and ideas\n    url: https://github.com/zeux/meshoptimizer/discussions\n    about: Please use GitHub Discussions if you have questions or ideas to discuss.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest a feature for this project; please use GitHub Discussions if your idea is not sufficiently concrete.\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n"
  },
  {
    "path": ".github/codecov.yml",
    "content": "comment: false\n\ncoverage:\n  status:\n    project: off\n    patch: off\n\nignore:\n  - demo\n  - extern\n  - tools\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: build\n\non:\n  push:\n    branches:\n      - 'master'\n    paths-ignore:\n      - '*.md'\n  pull_request:\n    paths-ignore:\n      - '*.md'\n\njobs:\n  unix:\n    strategy:\n      matrix:\n        os:\n          [\n            { name: ubuntu, version: ubuntu-latest },\n            { name: ubuntu-arm, version: ubuntu-24.04-arm },\n            { name: ubuntu-clang, version: ubuntu-latest },\n            { name: macos, version: macos-latest },\n          ]\n    name: ${{matrix.os.name}}\n    runs-on: ${{matrix.os.version}}\n    env:\n      werror: 1\n    steps:\n      - uses: actions/checkout@v4\n      - name: work around ASLR+ASAN compatibility\n        run: sudo sysctl -w vm.mmap_rnd_bits=28\n        if: matrix.os.name == 'ubuntu'\n      - name: enable clang\n        run: echo CXX=clang++ >Makefile.config\n        if: matrix.os.name == 'ubuntu-clang'\n      - name: make test\n        run: |\n          make -j2 config=sanitize test\n          make -j2 config=debug test\n          make -j2 config=release test\n          make -j2 config=coverage test\n          make -j2 config=coverage-scalar test\n      - name: make gltfpack\n        run: make -j2 config=release gltfpack\n      - name: upload coverage\n        run: |\n          find . -type f -name '*.gcno' -exec gcov -p {} +\n          sed -i -e \"s/#####\\(.*\\)\\(\\/\\/ unreachable.*\\)/    -\\1\\2/\" *.gcov\n          bash <(curl -s https://codecov.io/bash) -f './src*.gcov' -X search -t ${{secrets.CODECOV_TOKEN}} -B ${{github.ref}}\n        if: matrix.os.name != 'ubuntu-clang'\n\n  windows:\n    strategy:\n      matrix:\n        arch:\n          [\n            { name: windows, runner: windows-latest, arch: x64 },\n            { name: windows-x86, runner: windows-latest, arch: Win32 },\n            { name: windows-arm, runner: windows-11-arm, arch: ARM64 },\n          ]\n    name: ${{matrix.arch.name}}\n    runs-on: ${{matrix.arch.runner}}\n    steps:\n      - uses: actions/checkout@v4\n      - name: cmake configure\n        run: cmake . -DMESHOPT_BUILD_DEMO=ON -DMESHOPT_BUILD_GLTFPACK=ON -DMESHOPT_WERROR=ON -DCMAKE_MSVC_RUNTIME_LIBRARY=\"MultiThreaded$<$<CONFIG:Debug>:Debug>\" -A ${{matrix.arch.arch}}\n      - name: cmake test\n        shell: bash # necessary for fail-fast\n        run: |\n          cmake --build . -- -property:Configuration=Debug -verbosity:minimal\n          Debug/meshoptdemo.exe demo/pirate.obj\n          cmake --build . -- -property:Configuration=Release -verbosity:minimal\n          Release/meshoptdemo.exe demo/pirate.obj\n\n  nodejs:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: '16'\n      - name: test decoder\n        run: node js/meshopt_decoder.test.js\n      - name: test simd decoder\n        run: node --experimental-wasm-simd js/meshopt_decoder.test.js\n      - name: test encoder\n        run: node js/meshopt_encoder.test.js\n      - name: test simplifier\n        run: node js/meshopt_simplifier.test.js\n      - name: test clusterizer\n        run: node js/meshopt_clusterizer.test.js\n      - name: check es5\n        run: |\n          npm install -g es-check@7.2.1\n          npx es-check --module es5 js/meshopt_decoder.mjs js/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js\n          npx es-check --module es2020 gltf/library.js\n\n  gltfpack:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/checkout@v4\n        with:\n          repository: KhronosGroup/glTF-Sample-Assets\n          path: glTF-Sample-Assets\n      - name: work around ASLR+ASAN compatibility\n        run: sudo sysctl -w vm.mmap_rnd_bits=28\n      - name: make\n        run: make -j2 config=sanitize gltfpack\n      - name: test\n        run: find glTF-Sample-Assets -name '*.gltf' -or -name '*.glb' | xargs -P 2 -L 16 -d '\\n' ./gltfpack -cc -test\n      - name: pack\n        run: find glTF-Sample-Assets -name '*.gltf' | grep -v '\\-Draco/' | xargs -P 2 -L 16 -d '\\n' -I '{}' ./gltfpack -i '{}' -o '{}pack.gltf'\n      - name: validate\n        run: |\n          curl -sL $VALIDATOR | tar xJ\n          find glTF-Sample-Assets -name '*.gltfpack.gltf' | xargs -P 2 -L 1 -d '\\n' ./gltf_validator -r -a\n        env:\n          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\n\n  gltfpack-js:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: '18'\n      - name: install wasi\n        run: |\n          curl -sL https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$VERSION/wasi-sdk-$VERSION.0-x86_64-linux.deb > wasi-sdk.deb\n          sudo dpkg -i wasi-sdk.deb\n        env:\n          VERSION: 25\n      - name: build\n        run: |\n          make -j2 -B gltf/library.wasm js\n          git status\n      - name: test\n        run: |\n          node gltf/cli.js -i demo/pirate.obj -o pirate.glb -v\n          node gltf/cli.js -i `pwd`/pirate.glb -o pirate-repack.glb -cc -v\n          wc -c pirate.glb pirate-repack.glb\n          node js/meshopt_decoder.test.js\n          node js/meshopt_encoder.test.js\n          node js/meshopt_simplifier.test.js\n          node js/meshopt_clusterizer.test.js\n\n  gltfpack-full:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/checkout@v4\n        with:\n          repository: zeux/basis_universal\n          ref: gltfpack\n          path: basis_universal\n      - uses: actions/checkout@v4\n        with:\n          repository: webmproject/libwebp\n          ref: 1.6.0\n          path: libwebp\n      - name: cmake configure\n        run: cmake . -DMESHOPT_BUILD_GLTFPACK=ON -DMESHOPT_GLTFPACK_BASISU_PATH=basis_universal -DMESHOPT_GLTFPACK_LIBWEBP_PATH=libwebp\n      - name: cmake build\n        run: cmake --build . --target gltfpack -j 4\n\n  gltfpack-coverage:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/checkout@v4\n        with:\n          repository: zeux/basis_universal\n          ref: gltfpack\n          path: basis_universal\n      - uses: actions/checkout@v4\n        with:\n          repository: KhronosGroup/glTF-Sample-Assets\n          path: glTF-Sample-Assets\n      - name: build basisu\n        run: |\n          cd basis_universal\n          cmake . -D CMAKE_BUILD_TYPE=Debug\n          make -j4 basisu_encoder\n      - name: make\n        run: make -j2 config=coverage BASISU=basis_universal gltfpack\n      - name: test\n        run: |\n          find glTF-Sample-Assets -name '*.gltf' -or -name '*.glb' | xargs -P 2 -L 16 -d '\\n' ./gltfpack -cc -test\n          ./gltfpack -test demo/pirate.obj -si 0.5\n          ./gltfpack -test demo/pirate.obj -si 0.5 -slb -se 0.02\n          ./gltfpack -test demo/pirate.obj -si 0.1 -sa\n          ./gltfpack -test demo/pirate.obj -noq\n          ./gltfpack -test demo/pirate.obj -vp 16 -vt 16 -vn 16 -vc 16\n          ./gltfpack -test demo/pirate.obj -vpf -vtf -vnf\n          ./gltfpack -test demo/pirate.obj -vi -c\n          ./gltfpack -test glTF-Sample-Assets/Models/ABeautifulGame/glTF/ABeautifulGame.gltf -mi -c\n          ./gltfpack -test glTF-Sample-Assets/Models/ABeautifulGame/glTF/ABeautifulGame.gltf -kn -km -ke\n          ./gltfpack -test glTF-Sample-Assets/Models/ABeautifulGame/glTF/ABeautifulGame.gltf -si 0.1 -sp\n          ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -vpf -vtf -c\n          ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -vpf -vtf -cc\n          ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -tc\n          ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -tc -tq color 10 -tu normal,attrib -ts attrib 0.5 -tl color 512\n          ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -tr\n          ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -tc -ts 0.5 -tl 64\n          ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -tc color -tfy -tq 4 -tj 1\n          ./gltfpack -test glTF-Sample-Assets/Models/CesiumMan/glTF/CesiumMan.gltf -tu -ts 0.6 -tp -ar 0\n          ./gltfpack -test glTF-Sample-Assets/Models/PrimitiveModeNormalsTest/glTF/PrimitiveModeNormalsTest.gltf -si 0.5\n          ./gltfpack -test glTF-Sample-Assets/Models/VertexColorTest/glTF/VertexColorTest.gltf -si 0.5 -vc 12\n          ./gltfpack -test glTF-Sample-Assets/Models/SimpleMeshes/glTF/SimpleMeshes.gltf -mm\n          ./gltfpack -test glTF-Sample-Assets/Models/SimpleMeshes/glTF/SimpleMeshes.gltf -kn\n          echo newmtl Leather > demo/pirate.mtl\n          echo map_Kd leather.jpg >> demo/pirate.mtl\n          echo map_d leather.jpg >> demo/pirate.mtl\n          ./gltfpack -test demo/pirate.obj\n      - name: test output\n        run: |\n          ./gltfpack || true\n          ./gltfpack -h || true\n          ./gltfpack -i glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -o box.glb -vv -r box.json\n          ./gltfpack -i glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -o box.gltf -cf\n      - name: upload coverage\n        run: |\n          find . -type f -name '*.gcno' -exec gcov -p {} +\n          sed -i -e \"s/#####\\(.*\\)\\(\\/\\/ unreachable.*\\)/    -\\1\\2/\" *.gcov\n          bash <(curl -s https://codecov.io/bash) -f './gltf*.gcov' -X search -t ${{secrets.CODECOV_TOKEN}} -B ${{github.ref}}\n\n  macos-iphone:\n    runs-on: macos-14\n    steps:\n      - uses: actions/checkout@v4\n      - name: make\n        run: make -j2 config=iphone\n"
  },
  {
    "path": ".github/workflows/cifuzz.yml",
    "content": "name: CIFuzz\n\non:\n  push:\n    branches:\n      - 'master'\n    paths-ignore:\n      - '*.md'\n\njobs:\n  Fuzzing:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Build Fuzzers\n        id: build\n        uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master\n        with:\n          oss-fuzz-project-name: 'meshoptimizer'\n          dry-run: false\n          language: c++\n      - name: Run Fuzzers\n        uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master\n        with:\n          oss-fuzz-project-name: 'meshoptimizer'\n          fuzz-seconds: 30\n          dry-run: false\n          language: c++\n      - name: Upload Crash\n        uses: actions/upload-artifact@v4\n        if: failure() && steps.build.outcome == 'success'\n        with:\n          name: artifacts\n          path: ./out/artifacts\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: release\n\non:\n  push:\n    branches:\n      - 'master'\n    paths-ignore:\n      - '*.md'\n\njobs:\n  gltfpack:\n    strategy:\n      matrix:\n        os:\n          [\n            { name: windows, version: windows-latest },\n            { name: ubuntu, version: ubuntu-22.04 },\n            { name: macos, version: macos-14 },\n            { name: macos-intel, version: macos-14 },\n          ]\n    name: gltfpack-${{matrix.os.name}}\n    runs-on: ${{matrix.os.version}}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/checkout@v4\n        with:\n          repository: zeux/basis_universal\n          ref: gltfpack\n          path: basis_universal\n      - uses: actions/checkout@v4\n        with:\n          repository: webmproject/libwebp\n          ref: 1.6.0\n          path: libwebp\n      - name: cmake configure\n        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\n      - name: cmake configure x64\n        run: cmake . -DSSE=ON\n        if: matrix.os.name == 'ubuntu'\n      - name: cmake configure mac-x64\n        run: cmake . -DSSE=ON -DCMAKE_OSX_ARCHITECTURES=x86_64\n        if: matrix.os.name == 'macos-intel'\n      - name: cmake build\n        run: cmake --build . --target gltfpack --config Release -j 3\n      - uses: actions/upload-artifact@v4\n        with:\n          name: gltfpack-windows\n          path: Release/gltfpack.exe\n        if: matrix.os.name == 'windows'\n      - uses: actions/upload-artifact@v4\n        with:\n          name: gltfpack-${{matrix.os.name}}\n          path: gltfpack\n        if: matrix.os.name != 'windows'\n\n  nodejs:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: '18'\n      - name: install wasi\n        run: |\n          curl -sL https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$VERSION/wasi-sdk-$VERSION.0-x86_64-linux.deb > wasi-sdk.deb\n          sudo dpkg -i wasi-sdk.deb\n        env:\n          VERSION: 25\n      - name: build\n        run: |\n          make -j2 -B gltf/library.wasm js\n          git status\n      - name: npm pack\n        run: |\n          cp LICENSE.md gltf/\n          cp LICENSE.md js/\n          cd gltf && npm pack && cd ..\n          cd js && npm pack && cd ..\n      - uses: actions/upload-artifact@v4\n        with:\n          name: gltfpack-npm\n          path: gltf/gltfpack-*.tgz\n      - uses: actions/upload-artifact@v4\n        with:\n          name: meshoptimizer-npm\n          path: js/meshoptimizer-*.tgz\n"
  },
  {
    "path": ".gitignore",
    "content": "# IDE integrations\n/.cache/\n/.idea/\n/.vs/\n/.vscode/\n/.zed/\n\n# Build files\n/build/\n/cmake*/\n/out/\n/*.dSYM/\n/gltf/library.wasm\ncompile_commands.json\n\n# Test files\n/data/\n/*.log\n/perf.data*\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n\t\"useTabs\": true,\n\t\"tabWidth\": 4,\n\t\"semi\": true,\n\t\"singleQuote\": true,\n\t\"printWidth\": 150,\n\t\"trailingComma\": \"es5\",\n\t\"overrides\": [\n\t\t{\n\t\t\t\"files\": \"*.yml\",\n\t\t\t\"options\": {\n\t\t\t\t\"tabWidth\": 2\n\t\t\t}\n\t\t},\n\t]\n}\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.5...3.30)\n\nif(POLICY CMP0077)\n    cmake_policy(SET CMP0077 NEW) # Enables override of options from parent CMakeLists.txt\nendif()\n\nif(POLICY CMP0091)\n    cmake_policy(SET CMP0091 NEW) # Enables use of MSVC_RUNTIME_LIBRARY\nendif()\n\nif(POLICY CMP0092)\n    cmake_policy(SET CMP0092 NEW) # Enables clean /W4 override for MSVC\nendif()\n\nproject(meshoptimizer VERSION 1.0 LANGUAGES CXX)\n\noption(MESHOPT_BUILD_DEMO \"Build demo\" OFF)\noption(MESHOPT_BUILD_GLTFPACK \"Build gltfpack\" OFF)\noption(MESHOPT_BUILD_SHARED_LIBS \"Build shared libraries\" OFF)\noption(MESHOPT_STABLE_EXPORTS \"Only export stable APIs from shared library\" OFF)\noption(MESHOPT_WERROR \"Treat warnings as errors\" OFF)\noption(MESHOPT_INSTALL \"Install library\" ON)\n\n# Optional gltfpack components for texture compression support\nset(MESHOPT_GLTFPACK_BASISU_PATH \"\" CACHE STRING \"\") # Basis Universal, https://github.com/BinomialLLC/basis_universal\nset(MESHOPT_GLTFPACK_LIBWEBP_PATH \"\" CACHE STRING \"\") # libwebp, https://github.com/webmproject/libwebp\n\nset(SOURCES\n    src/meshoptimizer.h\n    src/allocator.cpp\n    src/clusterizer.cpp\n    src/indexanalyzer.cpp\n    src/indexcodec.cpp\n    src/indexgenerator.cpp\n    src/meshletcodec.cpp\n    src/meshletutils.cpp\n    src/opacitymap.cpp\n    src/overdrawoptimizer.cpp\n    src/partition.cpp\n    src/quantization.cpp\n    src/rasterizer.cpp\n    src/simplifier.cpp\n    src/spatialorder.cpp\n    src/stripifier.cpp\n    src/vcacheoptimizer.cpp\n    src/vertexcodec.cpp\n    src/vertexfilter.cpp\n    src/vfetchoptimizer.cpp\n)\n\nset(GLTF_SOURCES\n    gltf/animation.cpp\n    gltf/encodebasis.cpp\n    gltf/encodewebp.cpp\n    gltf/fileio.cpp\n    gltf/gltfpack.cpp\n    gltf/image.cpp\n    gltf/json.cpp\n    gltf/material.cpp\n    gltf/mesh.cpp\n    gltf/node.cpp\n    gltf/parseobj.cpp\n    gltf/parselib.cpp\n    gltf/parsegltf.cpp\n    gltf/stream.cpp\n    gltf/write.cpp\n)\n\nif(WIN32)\n    list(APPEND GLTF_SOURCES gltf/gltfpack.manifest)\nendif()\n\nif(MSVC)\n    add_compile_options(/W4)\nelse()\n    add_compile_options(-Wall -Wextra -Wshadow -Wno-missing-field-initializers)\nendif()\n\nif(MESHOPT_WERROR)\n    if(MSVC)\n        add_compile_options(/WX)\n    else()\n        add_compile_options(-Werror)\n    endif()\nendif()\n\nif(MESHOPT_BUILD_SHARED_LIBS)\n    add_library(meshoptimizer SHARED ${SOURCES})\nelse()\n    add_library(meshoptimizer STATIC ${SOURCES})\nendif()\n\ntarget_include_directories(meshoptimizer INTERFACE \"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>\")\n\nif(MESHOPT_BUILD_SHARED_LIBS)\n    set_target_properties(meshoptimizer PROPERTIES CXX_VISIBILITY_PRESET hidden)\n    set_target_properties(meshoptimizer PROPERTIES VISIBILITY_INLINES_HIDDEN ON)\n\n    # soversion may be requested via -DMESHOPT_SOVERSION=n; note that experimental APIs (marked with MESHOPTIMIZER_EXPERIMENTAL) are not ABI-stable\n    if(MESHOPT_SOVERSION)\n        set_target_properties(meshoptimizer PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${MESHOPT_SOVERSION})\n    endif()\n\n    if(WIN32)\n        target_compile_definitions(meshoptimizer INTERFACE \"MESHOPTIMIZER_API=__declspec(dllimport)\")\n        target_compile_definitions(meshoptimizer PRIVATE \"MESHOPTIMIZER_API=__declspec(dllexport)\")\n    else()\n        target_compile_definitions(meshoptimizer PUBLIC \"MESHOPTIMIZER_API=__attribute__((visibility(\\\"default\\\")))\")\n    endif()\n\n    target_compile_definitions(meshoptimizer PUBLIC MESHOPTIMIZER_ALLOC_EXPORT)\n\n    if(MESHOPT_STABLE_EXPORTS)\n\t\ttarget_compile_definitions(meshoptimizer PUBLIC \"MESHOPTIMIZER_EXPERIMENTAL=\")\n    endif()\nendif()\n\nif(MESHOPT_BUILD_DEMO)\n    add_executable(demo demo/main.cpp demo/nanite.cpp demo/tests.cpp tools/objloader.cpp)\n    set_target_properties(demo PROPERTIES CXX_STANDARD 11)\n    set_target_properties(demo PROPERTIES OUTPUT_NAME meshoptdemo)\n    target_link_libraries(demo meshoptimizer)\nendif()\n\nif(MESHOPT_BUILD_GLTFPACK)\n    add_executable(gltfpack ${GLTF_SOURCES})\n    set_target_properties(gltfpack PROPERTIES CXX_STANDARD 11)\n    target_link_libraries(gltfpack meshoptimizer)\n\n    if(MESHOPT_BUILD_SHARED_LIBS)\n        string(CONCAT RPATH \"$ORIGIN/../\" ${CMAKE_INSTALL_LIBDIR})\n        set_target_properties(gltfpack PROPERTIES INSTALL_RPATH ${RPATH})\n    endif()\n\n    if(NOT MESHOPT_GLTFPACK_BASISU_PATH STREQUAL \"\")\n        get_filename_component(BASISU_PATH ${MESHOPT_GLTFPACK_BASISU_PATH} ABSOLUTE)\n        if (NOT EXISTS ${BASISU_PATH})\n            message(FATAL_ERROR \"Basis Universal path ${BASISU_PATH} not found\")\n        endif()\n\n        if (NOT SSE AND NOT MSVC AND CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86_64\")\n            message(WARNING \"Building Basis Universal without SSE4.1 support; performance may be suboptimal\")\n        endif()\n\n        add_subdirectory(${BASISU_PATH} ${CMAKE_CURRENT_BINARY_DIR}/basisu EXCLUDE_FROM_ALL)\n\n        target_compile_definitions(gltfpack PRIVATE WITH_BASISU)\n        target_link_libraries(gltfpack basisu_encoder)\n        set_source_files_properties(gltf/encodebasis.cpp PROPERTIES INCLUDE_DIRECTORIES ${BASISU_PATH}) # necessary because basisu_encoder doesn't export include directories\n    endif()\n\n    if(NOT MESHOPT_GLTFPACK_LIBWEBP_PATH STREQUAL \"\")\n        get_filename_component(LIBWEBP_PATH ${MESHOPT_GLTFPACK_LIBWEBP_PATH} ABSOLUTE)\n        if (NOT EXISTS ${LIBWEBP_PATH})\n            message(FATAL_ERROR \"libwebp path ${LIBWEBP_PATH} not found\")\n        endif()\n\n        add_subdirectory(${LIBWEBP_PATH} ${CMAKE_CURRENT_BINARY_DIR}/libwebp EXCLUDE_FROM_ALL)\n\n        target_compile_definitions(gltfpack PRIVATE WITH_LIBWEBP)\n        target_link_libraries(gltfpack webp)\n\n        # when gltfpack is built with Basis Universal, we use Basis image decoders for PNG/JPEG support for ease of distribution\n        if(NOT MESHOPT_GLTFPACK_BASISU_PATH STREQUAL \"\")\n            target_compile_definitions(gltfpack PRIVATE WITH_LIBWEBP_BASIS)\n        else()\n            target_link_libraries(gltfpack imagedec)\n        endif()\n    endif()\n\n    if(NOT MESHOPT_GLTFPACK_BASISU_PATH STREQUAL \"\" OR NOT MESHOPT_GLTFPACK_LIBWEBP_PATH STREQUAL \"\")\n        find_package(Threads REQUIRED)\n        target_link_libraries(gltfpack Threads::Threads)\n    endif()\nendif()\n\nif(MESHOPT_INSTALL)\n\tinclude(GNUInstallDirs)\n\n\tinstall(TARGETS meshoptimizer EXPORT meshoptimizerTargets\n\t    COMPONENT meshoptimizer\n\t    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}\n\t    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}\n\t    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}\n\t    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})\n\n\tif(MESHOPT_BUILD_GLTFPACK)\n\t    install(TARGETS gltfpack RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})\n\tendif()\n\n\tinstall(FILES src/meshoptimizer.h COMPONENT meshoptimizer DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})\n\tinstall(EXPORT meshoptimizerTargets COMPONENT meshoptimizer DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/meshoptimizer NAMESPACE meshoptimizer::)\n\n\tif(MSVC)\n\t\tset(PDB_TARGETS meshoptimizer)\n\t\tif(MESHOPT_BUILD_GLTFPACK)\n\t\t    list(APPEND PDB_TARGETS gltfpack)\n\t\tendif()\n\n\t    foreach(TARGET ${PDB_TARGETS})\n\t        get_target_property(TARGET_TYPE ${TARGET} TYPE)\n\t        if(NOT ${TARGET_TYPE} STREQUAL \"STATIC_LIBRARY\")\n\t            install(FILES $<TARGET_PDB_FILE:${TARGET}> COMPONENT meshoptimizer DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)\n\t        endif()\n\t    endforeach(TARGET)\n\tendif()\n\n\tinclude(CMakePackageConfigHelpers)\n\n\t# CMake 3.18+ supports file(CONFIGURE OUTPUT file CONTENT content @ONLY) but we only require 3.5+\n\tfile(WRITE ${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfig.cmake\n\t\t\"include(\\\"\\${CMAKE_CURRENT_LIST_DIR}/meshoptimizerTargets.cmake\\\")\\n\")\n\n\twrite_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfigVersion.cmake COMPATIBILITY ExactVersion)\n\n\tinstall(FILES\n\t    ${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfig.cmake\n\t    ${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfigVersion.cmake\n\t    COMPONENT meshoptimizer\n\t    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/meshoptimizer)\nendif()\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Thanks for deciding to contribute to meshoptimizer! These guidelines will try to help make the process painless and efficient.\n\n## Questions\n\nIf you have a question regarding the library usage, please [open a GitHub issue](https://github.com/zeux/meshoptimizer/issues/new).\nSome questions just need answers, but it's nice to keep them for future reference in case other people want to know the same thing.\nSome questions help improve the library interface or documentation by inspiring future changes.\n\n## Bugs\n\nIf 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).\nIt 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.\n\nOf course, feel free to [create a pull request](https://help.github.com/articles/about-pull-requests/) to fix the bug yourself.\n\n## Features\n\nNew algorithms and improvements to existing algorithms are always welcome; you can open an issue or make the change yourself and submit a pull request.\n\nFor major features, consider opening an issue describing an improvement you'd like to see or make before opening a pull request.\nThis 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.\n\n## Code style\n\nContributions to this project are expected to follow the existing code style.\n`.clang-format` file mostly defines syntactic styling rules (you can run `make format` to format the code accordingly).\n\nAs 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.\n\n## Dependencies\n\nPlease note that this library uses C89 interface for all APIs and a C++98 implementation - C++11 features can not be used.\nThis choice is made to maximize compatibility to make sure that any toolchain, including legacy proprietary gaming console toolchains, can compile this code.\n\nAdditionally, the library code has zero external dependencies, does not depend on STL and does not use RTTI or exceptions.\nThis, 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.\n\nThe demo program uses STL since it serves as an example of usage and as a test harness, not as production-ready code.\n\n## Testing\n\nAll 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.\nYou can run the tests yourself using `make test` or building the demo program with `cmake -DBUILD_DEMO=ON` and running it.\n\nUnit 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.\n\n## Documentation\n\nDocumentation for this library resides in the `meshoptimizer.h` header, with examples as part of a usage manual available in `README.md`.\nChanges 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.\n\n## Sensitive communication\n\nIf 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).\n\n## Contributor agreement\n\nAny 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.\n\nYou 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.\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2016-2026 Arseny Kapoulkine\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "MAKEFLAGS+=-r -j\n\nconfig=debug\nfiles=demo/pirate.obj\n\nBUILD=build/$(config)\n\nLIBRARY_SOURCES=$(wildcard src/*.cpp)\nLIBRARY_OBJECTS=$(LIBRARY_SOURCES:%=$(BUILD)/%.o)\n\nDEMO_SOURCES=$(wildcard demo/*.c demo/*.cpp) tools/objloader.cpp\nDEMO_OBJECTS=$(DEMO_SOURCES:%=$(BUILD)/%.o)\n\nGLTFPACK_SOURCES=$(wildcard gltf/*.cpp)\nGLTFPACK_OBJECTS=$(GLTFPACK_SOURCES:%=$(BUILD)/%.o)\n\nOBJECTS=$(LIBRARY_OBJECTS) $(DEMO_OBJECTS) $(GLTFPACK_OBJECTS)\n\nLIBRARY=$(BUILD)/libmeshoptimizer.a\nDEMO=$(BUILD)/meshoptdemo\n\nCFLAGS=-g -Wall -Wextra -std=c89\nCXXFLAGS=-g -Wall -Wextra -Wshadow -Wno-missing-field-initializers\nLDFLAGS=\n\n$(LIBRARY_OBJECTS): CXXFLAGS+=-std=gnu++98\n$(DEMO_OBJECTS): CXXFLAGS+=-std=c++11\n$(GLTFPACK_OBJECTS): CXXFLAGS+=-std=c++11\n\nifdef BASISU\n    $(GLTFPACK_OBJECTS): CXXFLAGS+=-DWITH_BASISU\n    $(BUILD)/gltf/encodebasis.cpp.o: CXXFLAGS+=-I$(BASISU)\n    gltfpack: LDFLAGS+=-lpthread $(BASISU)/libbasisu_encoder.a\nendif\n\nWASI_SDK?=/opt/wasi-sdk\nWASMCC?=$(WASI_SDK)/bin/clang++\nWASIROOT?=$(WASI_SDK)/share/wasi-sysroot\n\nWASM_FLAGS=--target=wasm32-wasi --sysroot=$(WASIROOT)\nWASM_FLAGS+=-Wall -Wextra\nWASM_FLAGS+=-O3 -DNDEBUG -nostartfiles -nostdlib -Wl,--no-entry -Wl,-s\nWASM_FLAGS+=-mcpu=mvp # make sure clang doesn't use post-MVP features like sign extension\nWASM_FLAGS+=-fno-slp-vectorize -fno-vectorize -fno-unroll-loops\nWASM_FLAGS+=-Wl,-z -Wl,stack-size=36864 -Wl,--initial-memory=65536\nWASM_EXPORT_PREFIX=-Wl,--export\n\nWASM_DECODER_SOURCES=src/vertexcodec.cpp src/indexcodec.cpp src/vertexfilter.cpp tools/wasmstubs.cpp\nWASM_DECODER_EXPORTS=meshopt_decodeVertexBuffer meshopt_decodeIndexBuffer meshopt_decodeIndexSequence meshopt_decodeFilterOct meshopt_decodeFilterQuat meshopt_decodeFilterExp meshopt_decodeFilterColor sbrk __wasm_call_ctors\n\nWASM_ENCODER_SOURCES=src/vertexcodec.cpp src/indexcodec.cpp src/vertexfilter.cpp src/vcacheoptimizer.cpp src/vfetchoptimizer.cpp src/spatialorder.cpp tools/wasmstubs.cpp\nWASM_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\n\nWASM_SIMPLIFIER_SOURCES=src/simplifier.cpp src/vfetchoptimizer.cpp src/indexgenerator.cpp tools/wasmstubs.cpp\nWASM_SIMPLIFIER_EXPORTS=meshopt_simplify meshopt_simplifyWithAttributes meshopt_simplifyWithUpdate meshopt_simplifyScale meshopt_simplifyPoints meshopt_simplifySloppy meshopt_simplifyPrune meshopt_optimizeVertexFetchRemap meshopt_generatePositionRemap sbrk __wasm_call_ctors\n\nWASM_CLUSTERIZER_SOURCES=src/clusterizer.cpp src/meshletutils.cpp tools/wasmstubs.cpp\nWASM_CLUSTERIZER_EXPORTS=meshopt_buildMeshletsBound meshopt_buildMeshletsFlex meshopt_buildMeshletsSpatial meshopt_computeClusterBounds meshopt_computeMeshletBounds meshopt_computeSphereBounds meshopt_optimizeMeshlet sbrk __wasm_call_ctors\n\nifneq ($(werror),)\n\tCFLAGS+=-Werror\n\tCXXFLAGS+=-Werror\nendif\n\nifeq ($(config),iphone)\n\tIPHONESDK=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk\n\tCFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK)\n\tCXXFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK) -stdlib=libc++\n\tLDFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK) -L $(IPHONESDK)/usr/lib -mios-version-min=7.0\nendif\n\nifeq ($(config),trace)\n\tCXXFLAGS+=-DTRACE=1\nendif\n\nifeq ($(config),tracev)\n\tCXXFLAGS+=-DTRACE=2\nendif\n\nifeq ($(config),release)\n\tCXXFLAGS+=-O3 -DNDEBUG\nendif\n\nifeq ($(config),coverage)\n\tCXXFLAGS+=-coverage\n\tLDFLAGS+=-coverage\nendif\n\nifeq ($(config),release-avx)\n\tCXXFLAGS+=-O3 -DNDEBUG -mavx\nendif\n\nifeq ($(config),release-avx512)\n\tCXXFLAGS+=-O3 -DNDEBUG -mavx512vl -mavx512vbmi -mavx512vbmi2\nendif\n\nifeq ($(config),release-scalar)\n\tCXXFLAGS+=-O3 -DNDEBUG -DMESHOPTIMIZER_NO_SIMD\nendif\n\nifeq ($(config),coverage-scalar)\n\tCXXFLAGS+=-coverage -DMESHOPTIMIZER_NO_SIMD\n\tLDFLAGS+=-coverage\nendif\n\nifeq ($(config),sanitize)\n\tCXXFLAGS+=-fsanitize=address,undefined -fsanitize-undefined-trap-on-error\n\tLDFLAGS+=-fsanitize=address,undefined\nendif\n\nifeq ($(config),analyze)\n\tCXXFLAGS+=--analyze\nendif\n\nifeq ($(config),fuzz)\n    CXXFLAGS+=-O1 -fsanitize=address,fuzzer\n    LDFLAGS+=-fsanitize=address,fuzzer\n\n    $(GLTFPACK_OBJECTS): CXXFLAGS+=-DGLTFFUZZ\nendif\n\n-include Makefile.config\n\nall: $(DEMO)\n\ntest: $(DEMO)\n\t$(DEMO) $(files)\n\ncheck: $(DEMO)\n\t$(DEMO)\n\ndev: $(DEMO)\n\t$(DEMO) -d $(files)\n\nnanite: $(DEMO)\n\t$(DEMO) -n $(files)\n\nformat:\n\tclang-format -i src/*.h src/*.cpp demo/*.cpp gltf/*.h gltf/*.cpp\n\nformatjs:\n\tprettier -w js/*.js gltf/*.js demo/*.html js/*.ts\n\njs: js/meshopt_decoder.cjs js/meshopt_decoder.mjs js/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js\n\nsymbols: $(BUILD)/amalgamated.so\n\tnm $< -U -g\n\ngltfpack: $(BUILD)/gltfpack\n\tln -fs $^ $@\n\nifeq ($(config),fuzz)\ngltffuzz: $(BUILD)/gltfpack\n\tcp $^ $@\n\tmkdir -p /tmp/gltffuzz\n\tcp gltf/fuzz.glb /tmp/gltffuzz/\n\t./gltffuzz /tmp/gltffuzz -fork=16 -dict=gltf/fuzz.dict -ignore_crashes=1 -max_len=32768\nendif\n\n$(BUILD)/gltfpack: $(GLTFPACK_OBJECTS) $(LIBRARY)\n\t$(CXX) $^ $(LDFLAGS) -o $@\n\ngltfpack.wasm: gltf/library.wasm\n\ngltf/library.wasm: $(LIBRARY_SOURCES) $(GLTFPACK_SOURCES)\n\t$(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\n\nbuild/decoder_base.wasm: $(WASM_DECODER_SOURCES)\n\t@mkdir -p build\n\t$(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_DECODER_EXPORTS)) -o $@\n\nbuild/decoder_simd.wasm: $(WASM_DECODER_SOURCES)\n\t@mkdir -p build\n\t$(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_DECODER_EXPORTS)) -o $@ -msimd128 -mbulk-memory\n\nbuild/encoder.wasm: $(WASM_ENCODER_SOURCES)\n\t@mkdir -p build\n\t$(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_ENCODER_EXPORTS)) -lc -o $@\n\nbuild/simplifier.wasm: $(WASM_SIMPLIFIER_SOURCES)\n\t@mkdir -p build\n\t$(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_SIMPLIFIER_EXPORTS)) -lc -o $@\n\nbuild/clusterizer.wasm: $(WASM_CLUSTERIZER_SOURCES)\n\t@mkdir -p build\n\t$(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_CLUSTERIZER_EXPORTS)) -lc -o $@\n\njs/meshopt_decoder.mjs: build/decoder_base.wasm build/decoder_simd.wasm tools/wasmpack.py\n\tsed -i \"s#Built with clang.*#Built with $$($(WASMCC) --version | head -n 1 | sed 's/\\s\\+(.*//')#\" $@\n\tsed -i \"s#Built from meshoptimizer .*#Built from meshoptimizer $$(cat src/meshoptimizer.h | grep -Po '(?<=version )[0-9.]+')#\" $@\n\tpython3 tools/wasmpack.py patch $@ base <build/decoder_base.wasm\n\tpython3 tools/wasmpack.py patch $@ simd <build/decoder_simd.wasm\n\njs/meshopt_encoder.js: build/encoder.wasm tools/wasmpack.py\njs/meshopt_simplifier.js: build/simplifier.wasm tools/wasmpack.py\njs/meshopt_clusterizer.js: build/clusterizer.wasm tools/wasmpack.py\n\njs/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js:\n\tsed -i \"s#Built with clang.*#Built with $$($(WASMCC) --version | head -n 1 | sed 's/\\s\\+(.*//')#\" $@\n\tsed -i \"s#Built from meshoptimizer .*#Built from meshoptimizer $$(cat src/meshoptimizer.h | grep -Po '(?<=version )[0-9.]+')#\" $@\n\tpython3 tools/wasmpack.py patch $@ wasm <$<\n\njs/meshopt_decoder.cjs: js/meshopt_decoder.mjs\n\tsed '/export {/d' <$< >$@\n\techo \"if (typeof exports === 'object' && typeof module === 'object') module.exports = MeshoptDecoder;\" >>$@\n\techo \"else if (typeof define === 'function' && define['amd']) define([], function () { return MeshoptDecoder; });\" >>$@\n\techo \"else if (typeof exports === 'object') exports['MeshoptDecoder'] = MeshoptDecoder;\" >>$@\n\techo \"else (typeof self !== 'undefined' ? self : this).MeshoptDecoder = MeshoptDecoder;\" >>$@\n\n$(DEMO): $(DEMO_OBJECTS) $(LIBRARY)\n\t$(CXX) $^ $(LDFLAGS) -o $@\n\nvcachetuner: tools/vcachetuner.cpp tools/objloader.cpp $(LIBRARY)\n\t$(CXX) $^ -fopenmp $(CXXFLAGS) -std=c++11 $(LDFLAGS) -o $@\n\ncodecbench: tools/codecbench.cpp $(LIBRARY)\n\t$(CXX) $^ $(CXXFLAGS) $(LDFLAGS) -o $@\n\ncodecbench.js: tools/codecbench.cpp $(LIBRARY_SOURCES)\n\temcc $^ -O3 -g -DNDEBUG -s TOTAL_MEMORY=268435456 -s SINGLE_FILE=1 -o $@\n\ncodecbench-simd.js: tools/codecbench.cpp $(LIBRARY_SOURCES)\n\temcc $^ -O3 -g -DNDEBUG -s TOTAL_MEMORY=268435456 -s SINGLE_FILE=1 -msimd128 -o $@\n\ncodecbench.wasm: tools/codecbench.cpp $(LIBRARY_SOURCES)\n\t$(WASMCC) $^ -fno-exceptions --target=wasm32-wasi --sysroot=$(WASIROOT) -lc++ -lc++abi -O3 -g -DNDEBUG -o $@\n\ncodecbench-simd.wasm: tools/codecbench.cpp $(LIBRARY_SOURCES)\n\t$(WASMCC) $^ -fno-exceptions --target=wasm32-wasi --sysroot=$(WASIROOT) -lc++ -lc++abi -O3 -g -DNDEBUG -msimd128 -o $@\n\ncodectest: tools/codectest.cpp $(LIBRARY)\n\t$(CXX) $^ $(CXXFLAGS) $(LDFLAGS) -o $@\n\ncodecfuzz: tools/codecfuzz.cpp src/vertexcodec.cpp src/indexcodec.cpp src/meshletcodec.cpp\n\t$(CXX) $^ -fsanitize=fuzzer,address,undefined -O1 -g -o $@\n\nclusterfuzz: tools/clusterfuzz.cpp src/clusterizer.cpp src/partition.cpp\n\t$(CXX) $^ -fsanitize=fuzzer,address,undefined -O1 -g -o $@\n\nsimplifyfuzz: tools/simplifyfuzz.cpp src/simplifier.cpp\n\t$(CXX) $^ -fsanitize=fuzzer,address,undefined -O1 -g -o $@\n\n$(LIBRARY): $(LIBRARY_OBJECTS)\n\tar rcs $@ $^\n\n$(BUILD)/amalgamated.so: $(LIBRARY_SOURCES)\n\t@mkdir -p $(dir $@)\n\tcat $^ | $(CXX) $(CXXFLAGS) -x c++ - -I src/ -o $@ -shared -fPIC\n\n$(BUILD)/%.cpp.o: %.cpp\n\t@mkdir -p $(dir $@)\n\t$(CXX) $< $(CXXFLAGS) -c -MMD -MP -o $@\n\n$(BUILD)/%.c.o: %.c\n\t@mkdir -p $(dir $@)\n\t$(CC) $< $(CFLAGS) -c -MMD -MP -o $@\n\n-include $(OBJECTS:.o=.d)\n\nclean:\n\trm -rf $(BUILD)\n\n.PHONY: all clean format js\n"
  },
  {
    "path": "README.md",
    "content": "# 🐇 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)\n\n## Purpose\n\nWhen 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.\n\nThe 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).\n\nTwo 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.\n\n## Installing\n\nmeshoptimizer is hosted on GitHub; you can download the latest release using git:\n\n```\ngit clone -b v1.0 https://github.com/zeux/meshoptimizer.git\n```\n\nAlternatively you can [download the .zip archive from GitHub](https://github.com/zeux/meshoptimizer/archive/v1.0.zip).\n\nThe 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).\n\n[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.\n\n## Building\n\nmeshoptimizer 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:\n\n* Use CMake to build the library (either as a standalone project or as part of your project)\n* Add source files to your project's build system\n\nThe 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.\n\nTo use meshoptimizer functions, simply `#include` the header `meshoptimizer.h`; the library source is C++, but the header is C-compatible.\n\n## Core pipeline\n\nWhen optimizing a mesh, to maximize rendering efficiency you should typically feed it through a set of optimizations (the order is important!):\n\n1. Indexing\n2. Vertex cache optimization\n3. (optional) Overdraw optimization\n4. Vertex fetch optimization\n5. Vertex quantization\n6. (optional) Shadow indexing\n\n### Indexing\n\nMost 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:\n\n> 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`.\n\nFirst, generate a remap table from your existing vertex (and, optionally, index) data:\n\n```c++\nsize_t index_count = face_count * 3;\nsize_t unindexed_vertex_count = face_count * 3;\nstd::vector<unsigned int> remap(unindexed_vertex_count); // temporary remap table\nsize_t vertex_count = meshopt_generateVertexRemap(&remap[0], NULL, index_count,\n    &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex));\n```\n\nNote 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.\n\nAfter 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:\n\n```c++\nmeshopt_remapIndexBuffer(indices, NULL, index_count, &remap[0]);\nmeshopt_remapVertexBuffer(vertices, &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex), &remap[0]);\n```\n\nYou can then further optimize the resulting buffers by calling the other functions on them in-place.\n\n`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:\n\n```c++\nsize_t vertex_count = meshopt_generateVertexRemapCustom(&remap[0], NULL, index_count,\n    &unindexed_vertices[0].px, unindexed_vertex_count, sizeof(Vertex),\n    [&](unsigned int lhs, unsigned int rhs) -> bool {\n        const Vertex& lv = unindexed_vertices[lhs];\n        const Vertex& rv = unindexed_vertices[rhs];\n\n        return fabsf(lv.tx - rv.tx) < 1e-3f && fabsf(lv.ty - rv.ty) < 1e-3f;\n    });\n```\n\n### Vertex cache optimization\n\nWhen 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:\n\n```c++\nmeshopt_optimizeVertexCache(indices, indices, index_count, vertex_count);\n```\n\nThe 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.\n\n### Overdraw optimization\n\nAfter 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:\n\n```c++\nmeshopt_optimizeOverdraw(indices, indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), 1.05f);\n```\n\nThe 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`.\n\nWhen 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.\n\nNote 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.\n\n### Vertex fetch optimization\n\nAfter 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:\n\n```c++\nmeshopt_optimizeVertexFetch(vertices, indices, index_count, vertices, vertex_count, sizeof(Vertex));\n```\n\nThis 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.\n\nNote 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.\n\n### Vertex quantization\n\nTo 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.\n\nQuantization 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.\n\nThe 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:\n\n```c++\nunsigned int normal =\n    ((meshopt_quantizeSnorm(v.nx, 10) & 1023) << 20) |\n    ((meshopt_quantizeSnorm(v.ny, 10) & 1023) << 10) |\n     (meshopt_quantizeSnorm(v.nz, 10) & 1023);\n```\n\nand here's how you can quantize a position using half precision floats:\n\n```c++\nunsigned short px = meshopt_quantizeHalf(v.x);\nunsigned short py = meshopt_quantizeHalf(v.y);\nunsigned short pz = meshopt_quantizeHalf(v.z);\n```\n\nSince 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.\n\n### Shadow indexing\n\nMany 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.\n\nTo 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:\n\n```c++\nstd::vector<unsigned int> shadow_indices(index_count);\n// note: this assumes Vertex starts with float3 positions and should be adjusted accordingly for quantized positions\nmeshopt_generateShadowIndexBuffer(&shadow_indices[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(float) * 3, sizeof(Vertex));\n```\n\nBecause 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:\n\n```c++\nmeshopt_optimizeVertexCache(&shadow_indices[0], &shadow_indices[0], index_count, vertex_count);\n```\n\nIn 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.\n\nNote 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.\n\n## Clusterization\n\nWhile 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.\n\n### Mesh shading\n\nModern 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.\n\nUsing 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.\n\nTo 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.\n\nTo 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.\n\n```c++\nconst size_t max_vertices = 64;\nconst size_t max_triangles = 126; // note: in v0.25 or prior, max_triangles needs to be divisible by 4\nconst float cone_weight = 0.0f;\n\nsize_t max_meshlets = meshopt_buildMeshletsBound(indices.size(), max_vertices, max_triangles);\nstd::vector<meshopt_Meshlet> meshlets(max_meshlets);\nstd::vector<unsigned int> meshlet_vertices(indices.size());\nstd::vector<unsigned char> meshlet_triangles(indices.size()); // note: in v0.25 or prior, use indices.size() + max_meshlets * 3\n\nsize_t meshlet_count = meshopt_buildMeshlets(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices.data(),\n    indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex), max_vertices, max_triangles, cone_weight);\n```\n\nTo 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).\n\n> 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.\n\nEach 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:\n\n```c++\nconst meshopt_Meshlet& last = meshlets[meshlet_count - 1];\n\nmeshlet_vertices.resize(last.vertex_offset + last.vertex_count);\nmeshlet_triangles.resize(last.triangle_offset + last.triangle_count * 3);\nmeshlets.resize(meshlet_count);\n```\n\nDepending 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.\n\nFor 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:\n\n```c++\nmeshopt_optimizeMeshlet(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset], m.triangle_count, m.vertex_count);\n```\n\nDifferent 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):\n\n```glsl\nlayout(binding = 0) readonly buffer Meshlets { Meshlet meshlets[]; };\nlayout(binding = 1) readonly buffer MeshletVertices { uint meshlet_vertices[]; };\nlayout(binding = 2) readonly buffer MeshletTriangles { uint8_t meshlet_triangles[]; };\n\nvoid main() {\n    Meshlet meshlet = meshlets[gl_WorkGroupID.x];\n    SetMeshOutputsEXT(meshlet.vertex_count, meshlet.triangle_count);\n\n    for (uint i = gl_LocalInvocationIndex; i < meshlet.vertex_count; i += gl_WorkGroupSize.x) {\n        uint index = meshlet_vertices[meshlet.vertex_offset + i];\n        gl_MeshVerticesEXT[i].gl_Position = world_view_projection * vec4(vertex_positions[index], 1);\n    }\n\n    for (uint i = gl_LocalInvocationIndex; i < meshlet.triangle_count; i += gl_WorkGroupSize.x) {\n        uint offset = meshlet.triangle_offset + i * 3;\n        gl_PrimitiveTriangleIndicesEXT[i] = uvec3(\n            meshlet_triangles[offset], meshlet_triangles[offset + 1], meshlet_triangles[offset + 2]);\n    }\n}\n```\n\n> Note that DirectX 12 mesh shaders cannot index raw buffers using arbitrary byte offsets. Use a typed SRV buffer (`Buffer<uint>`) 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<uint16_t2>(triangle_offset & ~1)`, then extracting indices with bitwise operations based on `triangle_offset & 1`.\n\nAfter 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:\n\n```c++\nmeshopt_Bounds bounds = meshopt_computeMeshletBounds(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset],\n    m.triangle_count, &vertices[0].x, vertices.size(), sizeof(Vertex));\n```\n\nThe 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):\n\n```c++\nif (dot(normalize(cone_apex - camera_position), cone_axis) >= cone_cutoff) reject();\n```\n\nCluster culling should ideally run at a lower frequency than mesh shading, either using amplification/task shaders, or using a separate compute dispatch.\n\nBy 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`.\n\n### Clustered raytracing\n\nIn 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.\n\nWhen 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.\n\nTo 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:\n\n```c++\nconst size_t max_vertices = 64;\nconst size_t min_triangles = 16;\nconst size_t max_triangles = 64;\nconst float fill_weight = 0.5f;\n\nsize_t max_meshlets = meshopt_buildMeshletsBound(indices.size(), max_vertices, min_triangles); // note: use min_triangles to compute worst case bound\nstd::vector<meshopt_Meshlet> meshlets(max_meshlets);\nstd::vector<unsigned int> meshlet_vertices(indices.size());\nstd::vector<unsigned char> meshlet_triangles(indices.size()); // note: in v0.25 or prior, use indices.size() + max_meshlets * 3\n\nsize_t meshlet_count = meshopt_buildMeshletsSpatial(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices.data(),\n    indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex), max_vertices, min_triangles, max_triangles, fill_weight);\n```\n\nThe 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).\n\nThe `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.\n\nThe `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.\n\nWhen 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.\n\n### Point cloud clusterization\n\nBoth 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:\n\n```c++\nconst size_t cluster_size = 256;\n\nstd::vector<unsigned int> index(mesh.vertices.size());\nmeshopt_spatialClusterPoints(&index[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), cluster_size);\n```\n\nThe 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.\n\n### Cluster partitioning\n\nWhen 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:\n\n```c++\nconst size_t partition_size = 24;\n\nstd::vector<unsigned int> cluster_partitions(cluster_count);\nsize_t partition_count = meshopt_partitionClusters(&cluster_partitions[0], &cluster_indices[0], total_index_count,\n    &cluster_index_counts[0], cluster_count, &vertices[0].x, vertex_count, sizeof(Vertex), partition_size);\n```\n\nThe 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.\n\nTwo 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.\n\nIf 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.\n\nAfter 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`.\n\n## Mesh compression\n\nIn 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.\n\nAlternatively 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.\n\nTo 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.\n\n> 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).\n\n### Vertex compression\n\nThis 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:\n\n```c++\nstd::vector<unsigned char> vbuf(meshopt_encodeVertexBufferBound(vertex_count, sizeof(Vertex)));\nvbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), vertices, vertex_count, sizeof(Vertex)));\n```\n\nTo decode the data at runtime, call the decoding function:\n\n```c++\nint res = meshopt_decodeVertexBuffer(vertices, vertex_count, sizeof(Vertex), &vbuf[0], vbuf.size());\nassert(res == 0);\n```\n\nNote 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.\n\nDecoder 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.\n\nThe 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.\n\nIt 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.\n\nFor 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.\nFor 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.\nFor 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.\n\nWhen data is bit packed, specifying compression level 3 (via `meshopt_encodeVertexBufferLevel`) can improve the compression further by redistributing bits between components.\n\n### Index compression\n\nThis 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:\n\n```c++\nstd::vector<unsigned char> ibuf(meshopt_encodeIndexBufferBound(index_count, vertex_count));\nibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), indices, index_count));\n```\n\nTo decode the data at runtime, call the decoding function:\n\n```c++\nint res = meshopt_decodeIndexBuffer(indices, index_count, &ibuf[0], ibuf.size());\nassert(res == 0);\n```\n\nNote 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).\n\nDecoder 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.\n\nThe 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.\nTo 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.\n\nWhen 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.\n\nIndex 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.\n\n\n### Meshlet compression\n\nWhen 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.\n\nTo 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`:\n\n```c++\nstd::vector<unsigned char> mbuf(meshopt_encodeMeshletBound(max_vertices, max_triangles));\n\nfor (const meshopt_Meshlet& m : meshlets)\n{\n    size_t msize = meshopt_encodeMeshlet(&mbuf[0], mbuf.size(),\n        &meshlet_vertices[m.vertex_offset], m.vertex_count, &meshlet_triangles[m.triangle_offset], m.triangle_count);\n\n    // write m.vertex_count, m.triangle_count, msize and mbuf[0..msize-1] to the output stream\n}\n```\n\nTo decode the data at runtime, call the decoding function:\n\n```c++\nuint16_t* vertices = ...;\nuint8_t* triangles = ...;\n\n// automatically deduces `vertex_size=2` and `triangle_size=3` based on pointer types\nint res = meshopt_decodeMeshlet(vertices, m.vertex_count, triangles, m.triangle_count, stream, encoded_size);\nassert(res == 0);\n```\n\nVertex 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.\n\nWhen 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.\n\nDecoder 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.\n\n> 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.\n\nNote 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).\n\nMeshlets 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.\n\nThe 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.\n\n> Note: this codec is currently experimental and the data format and APIs are subject to change.\n\n### Point cloud compression\n\nThe 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.\n\nTo compress point clouds efficiently, it's recommended to first preprocess the points by sorting them using the spatial sort algorithm:\n\n```c++\nstd::vector<unsigned int> remap(point_count);\nmeshopt_spatialSortRemap(&remap[0], positions, point_count, sizeof(vec3));\n\n// for each attribute stream\nmeshopt_remapVertexBuffer(positions, positions, point_count, sizeof(vec3), &remap[0]);\n```\n\nAfter 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.\n\n### Vertex filters\n\nTo 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:\n\n- 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.\n- 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.\n- 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:\n    - `meshopt_EncodeExpSeparate` does not share exponents and results in the largest output\n    - `meshopt_EncodeExpSharedVector` shares exponents between different components of the same vector\n    - `meshopt_EncodeExpSharedComponent` shares exponents between the same component in different vectors\n    - `meshopt_EncodeExpClamped` does not share exponents but clamps the exponent range to reduce exponent entropy\n- 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.\n\nNote 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.\n\n### Versioning and compatibility\n\nThe following guarantees on data compatibility are provided for point releases (*no* guarantees are given for development branch):\n\n- Data encoded with older versions of the library can always be decoded with newer versions;\n- 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`).\n\nBy 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.\n\n## Simplification\n\nAll 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.\n\n### Basic simplification\n\nThis 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.\n\n```c++\nfloat threshold = 0.2f;\nsize_t target_index_count = size_t(index_count * threshold);\nfloat target_error = 1e-2f;\n\nstd::vector<unsigned int> lod(index_count);\nfloat lod_error = 0.f;\nlod.resize(meshopt_simplify(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex),\n    target_index_count, target_error, /* options= */ 0, &lod_error));\n```\n\nTarget 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.\n\nTo 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.\n\nThe 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.\n\nAlternatively, 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`.\n\nThe 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:\n\n```c++\n// lod_factor can be 1 or can be adjusted for more or less aggressive LOD selection\nfloat d = max(0, distance(camera_position, mesh_center) - mesh_radius);\nfloat e = d * (tan(camera_fovy / 2) * 2 / screen_height); // 1px in mesh space\nbool lod_ok = e * lod_factor >= lod_error;\n```\n\nWhen 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.\n\n### Attribute-aware simplification\n\nWhile `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`:\n\n```c++\nconst float nrm_weight = 0.5f;\nconst float attr_weights[3] = {nrm_weight, nrm_weight, nrm_weight};\n\nstd::vector<unsigned int> lod(index_count);\nfloat lod_error = 0.f;\nlod.resize(meshopt_simplifyWithAttributes(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex),\n    &vertices[0].nx, sizeof(Vertex), attr_weights, 3, /* vertex_lock= */ NULL,\n    target_index_count, target_error, /* options= */ 0, &lod_error));\n```\n\nThe 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).\n\nIncluding 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.\n\nBoth 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.\n\n### Permissive simplification\n\nBy 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:\n\n```c++\nstd::vector<unsigned int> lod(index_count);\nfloat lod_error = 0.f;\nlod.resize(meshopt_simplifyWithAttributes(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex),\n    &vertices[0].nx, sizeof(Vertex), attr_weights, 3, /* vertex_lock= */ NULL,\n    target_index_count, target_error, /* options= */ meshopt_SimplifyPermissive, &lod_error));\n```\n\nTo 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:\n\n```c++\nstd::vector<unsigned int> remap(vertices.size());\nmeshopt_generatePositionRemap(&remap[0], &vertices[0].px, vertices.size(), sizeof(Vertex));\n\nstd::vector<unsigned char> locks(vertices.size());\nfor (size_t i = 0; i < vertices.size(); ++i) {\n    unsigned int r = remap[i];\n\n    if (r != i && (vertices[r].tx != vertices[i].tx || vertices[r].ty != vertices[i].ty))\n        locks[i] |= meshopt_SimplifyVertex_Protect; // protect UV seams\n}\n```\n\nThis 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`).\n\n> 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.\n\n### Simplification with vertex update\n\nAll 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`:\n\n```c++\nindices.resize(meshopt_simplifyWithUpdate(&indices[0], indices.size(), &vertices[0].px, vertices.size(), sizeof(Vertex),\n    &vertices[0].nx, sizeof(Vertex), attr_weights, 3, /* vertex_lock= */ NULL,\n    target_index_count, target_error, /* options= */ 0, &result_error));\n```\n\nUnlike `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.\n\nSince 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).\n\nAttributes 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.\n\nUsing 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.\n\n### Advanced simplification\n\n`meshopt_simplify*` functions expose additional options and parameters that can be used to control the simplification process in more detail.\n\nFor basic customization, a number of options can be passed via `options` bitmask that adjust the behavior of the simplifier:\n\n- `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.\n- `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`.\n- `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.\n- `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.\n- `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.\n- `meshopt_SimplifyPermissive` allows collapses across attribute discontinuities, except for vertices that are tagged with `meshopt_SimplifyVertex_Protect` via `vertex_lock`.\n\nWhen 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.\n\nIn 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`.\n\nSimplification 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.\n\nWhen 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.\n\n### Point cloud simplification\n\nIn 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:\n\n```c++\nconst float color_weight = 1;\nstd::vector<unsigned int> indices(target_count);\nindices.resize(meshopt_simplifyPoints(&indices[0], &points[0].x, points.size(), sizeof(Point),\n    &points[0].r, sizeof(Point), color_weight, target_count));\n```\n\nThe 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.\n\n## Efficiency analyzers\n\nWhile 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.\n\n`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).\n\n`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.\n\n`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.\n\n`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.\n\nNote 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.\n\n## Deinterleaved geometry\n\nAll 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.\n\nMost 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.\n\nFor 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:\n\n```c++\nmeshopt_Stream streams[] = {\n    {&unindexed_pos[0], sizeof(float) * 3, sizeof(float) * 3},\n    {&unindexed_nrm[0], sizeof(float) * 3, sizeof(float) * 3},\n    {&unindexed_uv[0], sizeof(float) * 2, sizeof(float) * 2},\n};\n\nstd::vector<unsigned int> remap(index_count);\nsize_t vertex_count = meshopt_generateVertexRemapMulti(&remap[0], NULL, index_count, index_count, streams, sizeof(streams) / sizeof(streams[0]));\n```\n\nAfter 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.\n\nInstead 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.\n\nFinally, 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.\n\n## Specialized processing\n\nIn 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.\n\n### Triangle strip conversion\n\nOn most hardware, indexed triangle lists are the most efficient way to drive the GPU. However, in some cases triangle strips might prove beneficial:\n\n- On some older GPUs, triangle strips may be a bit more efficient to render\n- On extremely memory constrained systems, index buffers for triangle strips could save a bit of memory\n\nThis library provides an algorithm for converting a vertex cache optimized triangle list to a triangle strip:\n\n```c++\nstd::vector<unsigned int> strip(meshopt_stripifyBound(index_count));\nunsigned int restart_index = ~0u;\nsize_t strip_size = meshopt_stripify(&strip[0], indices, index_count, vertex_count, restart_index);\n```\n\nTypically 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.\nNote 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.\n\nTo 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.\n\n### Geometry shader adjacency\n\nFor algorithms that use geometry shaders and require adjacency information, this library can generate an index buffer with adjacency data:\n\n```c++\nstd::vector<unsigned int> adjacency(indices.size() * 2);\nmeshopt_generateAdjacencyIndexBuffer(&adjacency[0], &indices[0], indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex));\n```\n\nThis 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.\n\nNote that the use of geometry shaders may have a performance impact on some GPUs; in some cases alternative implementation strategies may be more efficient.\n\n### Tessellation with displacement mapping\n\nFor hardware tessellation with crack-free displacement mapping, this library can generate a special index buffer that supports PN-AEN tessellation:\n\n```c++\nstd::vector<unsigned int> tess(indices.size() * 4);\nmeshopt_generateTessellationIndexBuffer(&tess[0], &indices[0], indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex));\n```\n\nThis generates a 12-vertex patch for each input triangle with the following layout:\n\n- 0, 1, 2: original triangle vertices\n- 3, 4: opposing edge for edge 0, 1\n- 5, 6: opposing edge for edge 1, 2\n- 7, 8: opposing edge for edge 2, 0\n- 9, 10, 11: dominant vertices for corners 0, 1, 2\n\nThis 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).\n\n### Visibility buffers\n\nTo 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:\n\n```c++\nstd::vector<unsigned int> provoke(indices.size());\nstd::vector<unsigned int> reorder(vertices.size() + indices.size() / 3);\nreorder.resize(meshopt_generateProvokingIndexBuffer(&provoke[0], &reorder[0], &indices[0], indices.size(), vertices.size()));\n```\n\nThis generates a special index buffer along with a reorder table that satisfies two constraints:\n\n- `provoke[3 * tri] == tri`\n- `reorder[provoke[x]]` refers to the original triangle vertices\n\nTo 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.\n\n> 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.\n\nBecause 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.\n\n### Opacity micromaps\n\nWhen 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).\n\nGenerating 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.\n\nFirst, 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:\n\n```c++\nconst int states = 4; // 2-state or 4-state OMMs (used after measure)\nconst int max_level = 6; // max subdivision level\nconst float target_edge = 3.0f; // target 3x3px area for each microtriangle\nstd::vector<unsigned char> levels(indices.size() / 3);\nstd::vector<unsigned int> sources(indices.size() / 3);\nstd::vector<int> omm_indices(indices.size() / 3);\nsize_t omm_count = meshopt_opacityMapMeasure(&levels[0], &sources[0], &omm_indices[0], &indices[0], indices.size(),\n    &vertices[0].u, vertices.size(), sizeof(Vertex), texture_width, texture_height, max_level, target_edge);\n```\n\nEach 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`:\n\n```c++\nstd::vector<unsigned int> offsets(omm_count);\nsize_t data_size = 0;\n\nfor (size_t i = 0; i < omm_count; ++i)\n{\n    offsets[i] = unsigned(data_size);\n    data_size += meshopt_opacityMapEntrySize(levels[i], states);\n}\n```\n\nSecond, 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.\n\n```c++\nfor (size_t i = 0; i < omm_count; ++i)\n{\n    unsigned int tri = sources[i];\n    const float* uv0 = &vertices[indices[tri * 3 + 0]].u;\n    const float* uv1 = &vertices[indices[tri * 3 + 1]].u;\n    const float* uv2 = &vertices[indices[tri * 3 + 2]].u;\n\n    // texture addressing below assumes RGBA texture input without padding; +3 points to A\n    meshopt_opacityMapRasterize(&data[offsets[i]], levels[i], states, uv0, uv1, uv2,\n        texture.data() + 3, 4, texture_width * 4, texture_width, texture_height);\n}\n```\n\n> 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.\n\nAfter 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:\n\n```c++\nomm_count = meshopt_opacityMapCompact(&data[0], data_size, &levels[0], &offsets[0], omm_count, &omm_indices[0], indices.size() / 3, states);\ndata_size = (omm_count == 0) ? 0 : offsets[omm_count - 1] + meshopt_opacityMapEntrySize(levels[omm_count - 1], states);\n```\n\nAfter 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.\n\nAdditionally, 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).\n\nWhen 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.\n\n## Memory management\n\nMany 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.\n\n```c++\nmeshopt_setAllocator(malloc, free);\n```\n\n> 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.\n\nVertex and index decoders (`meshopt_decodeVertexBuffer`, `meshopt_decodeIndexBuffer`, `meshopt_decodeIndexSequence`) do not allocate memory and work completely within the buffer space provided via arguments.\n\nAll functions have bounded stack usage that does not exceed 32 KB for any algorithms.\n\n## Experimental APIs\n\nSeveral 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.\n\nAPIs 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.\n\nAPIs 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.\n\nApplications 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.\n\nCurrently, the following APIs are experimental:\n\n- `meshopt_SimplifyPermissive` mode for `meshopt_simplify*` functions (and associated `meshopt_SimplifyVertex_*` flags)\n- `meshopt_encodeMeshlet` and `meshopt_encodeMeshletBound` functions\n- `meshopt_decodeMeshlet` and `meshopt_decodeMeshletRaw` functions\n- `meshopt_extractMeshletIndices` function\n- `meshopt_opacityMap*` functions (`meshopt_opacityMapMeasure`, `meshopt_opacityMapRasterize`, `meshopt_opacityMapCompact`, `meshopt_opacityMapEntrySize`)\n\n## License\n\nThis library is available to anybody free of charge, under the terms of [MIT License](LICENSE.md).\n\nTo honor the license agreement, please include attribution into the user-facing product documentation and/or credits, for example using this or similar text:\n\n> Uses meshoptimizer. Copyright (c) 2016-2026, Arseny Kapoulkine\n"
  },
  {
    "path": "demo/ansi.c",
    "content": "/* This file makes sure the library can be used by C89 code */\n#include \"../src/meshoptimizer.h\"\n"
  },
  {
    "path": "demo/clusterlod.h",
    "content": "/**\n * clusterlod - a small \"library\"/example built on top of meshoptimizer to generate cluster LOD hierarchies\n * This is intended to either be used as is, or as a reference for implementing similar functionality in your engine.\n *\n * To use this code, you need to have one source file which includes meshoptimizer.h and defines CLUSTERLOD_IMPLEMENTATION\n * before including this file. Other source files in your project can just include this file and use the provided functions.\n *\n * Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\n * This code is distributed under the MIT License. See notice at the end of this file.\n */\n#pragma once\n\n#include <stddef.h>\n\nstruct clodConfig\n{\n\t// configuration of each cluster; maps to meshopt_buildMeshlets* parameters\n\tsize_t max_vertices;\n\tsize_t min_triangles;\n\tsize_t max_triangles;\n\n\t// partitioning setup; maps to meshopt_partitionClusters parameters (plus optional partition sorting)\n\t// 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)\n\tbool partition_spatial;\n\tbool partition_sort;\n\tsize_t partition_size;\n\n\t// clusterization setup; maps to meshopt_buildMeshletsSpatial / meshopt_buildMeshletsFlex\n\tbool cluster_spatial;\n\tfloat cluster_fill_weight;\n\tfloat cluster_split_factor;\n\n\t// every level aims to reduce the number of triangles by ratio, and considers clusters that don't reach the threshold stuck\n\tfloat simplify_ratio;\n\tfloat simplify_threshold;\n\n\t// to compute the error of simplified clusters, we use the formula that combines previous accumulated error as follows:\n\t// max(previous_error * simplify_error_merge_previous, current_error) + current_error * simplify_error_merge_additive\n\tfloat simplify_error_merge_previous;\n\tfloat simplify_error_merge_additive;\n\n\t// amplify the error of clusters that go through sloppy simplification to account for appearance degradation\n\tfloat simplify_error_factor_sloppy;\n\n\t// experimental: limit error by edge length, aiming to remove subpixel triangles even if the attribute error is high\n\tfloat simplify_error_edge_limit;\n\n\t// use permissive simplification instead of regular simplification (make sure to use attribute_protect_mask if this is set!)\n\tbool simplify_permissive;\n\n\t// use permissive or sloppy simplification but only if regular simplification gets stuck\n\tbool simplify_fallback_permissive;\n\tbool simplify_fallback_sloppy;\n\n\t// use regularization during simplification to make triangle density more uniform, at some cost to overall triangle count; recommended for deformable objects\n\tbool simplify_regularize;\n\n\t// should clodCluster::bounds be computed based on the geometry of each cluster\n\tbool optimize_bounds;\n\n\t// should clodCluster::indices be optimized for locality; helps with rasterization performance and ray tracing performance in fast-build modes\n\tbool optimize_clusters;\n};\n\nstruct clodMesh\n{\n\t// input triangle indices\n\tconst unsigned int* indices;\n\tsize_t index_count;\n\n\t// total vertex count\n\tsize_t vertex_count;\n\n\t// input vertex positions; must be 3 floats per vertex\n\tconst float* vertex_positions;\n\tsize_t vertex_positions_stride;\n\n\t// input vertex attributes; used for attribute-aware simplification and permissive simplification\n\tconst float* vertex_attributes;\n\tsize_t vertex_attributes_stride;\n\n\t// input vertex locks; allows to preserve additional seams (when not using attribute_protect_mask) or lock vertices via meshopt_SimplifyVertex_* flags\n\tconst unsigned char* vertex_lock;\n\n\t// attribute weights for attribute-aware simplification; maps to meshopt_simplifyWithAttributes parameters\n\tconst float* attribute_weights;\n\tsize_t attribute_count;\n\n\t// attribute mask to flag attribute discontinuities for permissive simplification; mask (1<<K) corresponds to attribute K\n\tunsigned int attribute_protect_mask;\n};\n\n// To compute approximate (perspective) projection error of a cluster in screen space (0..1; multiply by screen height to get pixels):\n// - camera_proj is projection[1][1], or cot(fovy/2); camera_znear is *positive* near plane distance\n// - for simplicity, we ignore perspective distortion and use rotationally invariant projection size estimation\n// - return: bounds.error / max(distance(bounds.center, camera_position) - bounds.radius, camera_znear) * (camera_proj * 0.5f)\nstruct clodBounds\n{\n\t// sphere bounds, in mesh coordinate space\n\tfloat center[3];\n\tfloat radius;\n\n\t// combined simplification error, in mesh coordinate space\n\tfloat error;\n};\n\nstruct clodCluster\n{\n\t// index of more refined group (with more triangles) that produced this cluster during simplification, or -1 for original geometry\n\tint refined;\n\n\t// cluster bounds; should only be used for culling, as bounds.error is not monotonic across DAG\n\tclodBounds bounds;\n\n\t// cluster indices; refer to the original mesh vertex buffer\n\tconst unsigned int* indices;\n\tsize_t index_count;\n\n\t// cluster vertex count; indices[] has vertex_count unique entries\n\tsize_t vertex_count;\n};\n\nstruct clodGroup\n{\n\t// DAG level the group was generated at\n\tint depth;\n\n\t// simplified group bounds (reflects error for clusters with clodCluster::refined == group id; error is FLT_MAX for terminal groups)\n\t// cluster should be rendered if:\n\t// 1. clodGroup::simplified for the group it's in is over error threshold\n\t// 2. cluster.refined is -1 *or* clodGroup::simplified for groups[cluster.refined].simplified is at or under error threshold\n\tclodBounds simplified;\n};\n\n// gets called for each group in sequence\n// returned value gets saved for clusters emitted from this group (clodCluster::refined)\ntypedef int (*clodOutput)(void* output_context, clodGroup group, const clodCluster* clusters, size_t cluster_count);\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\n// default configuration optimized for rasterization / raytracing\nclodConfig clodDefaultConfig(size_t max_triangles);\nclodConfig clodDefaultConfigRT(size_t max_triangles);\n\n// build cluster LOD hierarchy, calling output callbacks as new clusters and groups are generated\n// returns the total number of clusters produced\nsize_t clodBuild(clodConfig config, clodMesh mesh, void* output_context, clodOutput output_callback);\n\n// extract meshlet-local indices from cluster indices produced by clodBuild\n// fills triangles[] and vertices[] such that vertices[triangles[i]] == indices[i]\n// returns number of unique vertices (which will be equal to clodCluster::vertex_count)\nsize_t clodLocalIndices(unsigned int* vertices, unsigned char* triangles, const unsigned int* indices, size_t index_count);\n\n#ifdef __cplusplus\n} // extern \"C\"\n\ntemplate <typename Output>\nsize_t clodBuild(clodConfig config, clodMesh mesh, Output output)\n{\n\tstruct Call\n\t{\n\t\tstatic int output(void* output_context, clodGroup group, const clodCluster* clusters, size_t cluster_count)\n\t\t{\n\t\t\treturn (*static_cast<Output*>(output_context))(group, clusters, cluster_count);\n\t\t}\n\t};\n\n\treturn clodBuild(config, mesh, &output, &Call::output);\n}\n#endif\n\n#ifdef CLUSTERLOD_IMPLEMENTATION\n// For reference, see the original Nanite paper:\n// Brian Karis. Nanite: A Deep Dive. 2021\n#include <float.h>\n#include <math.h>\n#include <string.h>\n\n#include <algorithm>\n#include <vector>\n\nnamespace clod\n{\n\nstruct Cluster\n{\n\tsize_t vertices;\n\tstd::vector<unsigned int> indices;\n\n\tint group;\n\tint refined;\n\n\tclodBounds bounds;\n};\n\nstatic clodBounds boundsCompute(const clodMesh& mesh, const std::vector<unsigned int>& indices, float error)\n{\n\tmeshopt_Bounds bounds = meshopt_computeClusterBounds(&indices[0], indices.size(), mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride);\n\n\tclodBounds result;\n\tresult.center[0] = bounds.center[0];\n\tresult.center[1] = bounds.center[1];\n\tresult.center[2] = bounds.center[2];\n\tresult.radius = bounds.radius;\n\tresult.error = error;\n\treturn result;\n}\n\nstatic clodBounds boundsMerge(const std::vector<Cluster>& clusters, const std::vector<int>& group)\n{\n\tstd::vector<clodBounds> bounds(group.size());\n\tfor (size_t j = 0; j < group.size(); ++j)\n\t\tbounds[j] = clusters[group[j]].bounds;\n\n\tmeshopt_Bounds merged = meshopt_computeSphereBounds(&bounds[0].center[0], bounds.size(), sizeof(clodBounds), &bounds[0].radius, sizeof(clodBounds));\n\n\tclodBounds result = {};\n\tresult.center[0] = merged.center[0];\n\tresult.center[1] = merged.center[1];\n\tresult.center[2] = merged.center[2];\n\tresult.radius = merged.radius;\n\n\t// merged bounds error must be conservative wrt cluster errors\n\tresult.error = 0.f;\n\tfor (size_t j = 0; j < group.size(); ++j)\n\t\tresult.error = std::max(result.error, clusters[group[j]].bounds.error);\n\n\treturn result;\n}\n\nstatic std::vector<Cluster> clusterize(const clodConfig& config, const clodMesh& mesh, const unsigned int* indices, size_t index_count)\n{\n\tsize_t max_meshlets = meshopt_buildMeshletsBound(index_count, config.max_vertices, config.min_triangles);\n\n\tstd::vector<meshopt_Meshlet> meshlets(max_meshlets);\n\tstd::vector<unsigned int> meshlet_vertices(index_count);\n\n#if MESHOPTIMIZER_VERSION < 1000\n\tstd::vector<unsigned char> meshlet_triangles(index_count + max_meshlets * 3); // account for 4b alignment\n#else\n\tstd::vector<unsigned char> meshlet_triangles(index_count);\n#endif\n\n\tif (config.cluster_spatial)\n\t\tmeshlets.resize(meshopt_buildMeshletsSpatial(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices, index_count,\n\t\t    mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride,\n\t\t    config.max_vertices, config.min_triangles, config.max_triangles, config.cluster_fill_weight));\n\telse\n\t\tmeshlets.resize(meshopt_buildMeshletsFlex(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices, index_count,\n\t\t    mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride,\n\t\t    config.max_vertices, config.min_triangles, config.max_triangles, 0.f, config.cluster_split_factor));\n\n\tstd::vector<Cluster> clusters(meshlets.size());\n\n\tfor (size_t i = 0; i < meshlets.size(); ++i)\n\t{\n\t\tconst meshopt_Meshlet& meshlet = meshlets[i];\n\n\t\tif (config.optimize_clusters)\n\t\t\tmeshopt_optimizeMeshlet(&meshlet_vertices[meshlet.vertex_offset], &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count, meshlet.vertex_count);\n\n\t\tclusters[i].vertices = meshlet.vertex_count;\n\n\t\t// note: we discard meshlet-local indices; they can be recovered by the caller using clodLocalIndices\n\t\tclusters[i].indices.resize(meshlet.triangle_count * 3);\n\t\tfor (size_t j = 0; j < meshlet.triangle_count * 3; ++j)\n\t\t\tclusters[i].indices[j] = meshlet_vertices[meshlet.vertex_offset + meshlet_triangles[meshlet.triangle_offset + j]];\n\n\t\tclusters[i].group = -1;\n\t\tclusters[i].refined = -1;\n\t}\n\n\treturn clusters;\n}\n\nstatic std::vector<std::vector<int> > partition(const clodConfig& config, const clodMesh& mesh, const std::vector<Cluster>& clusters, const std::vector<int>& pending, const std::vector<unsigned int>& remap)\n{\n\tif (pending.size() <= config.partition_size)\n\t\treturn {pending};\n\n\tstd::vector<unsigned int> cluster_indices;\n\tstd::vector<unsigned int> cluster_counts(pending.size());\n\n\t// copy cluster index data into a flat array for partitioning\n\tsize_t total_index_count = 0;\n\tfor (size_t i = 0; i < pending.size(); ++i)\n\t\ttotal_index_count += clusters[pending[i]].indices.size();\n\n\tcluster_indices.reserve(total_index_count);\n\n\tfor (size_t i = 0; i < pending.size(); ++i)\n\t{\n\t\tconst Cluster& cluster = clusters[pending[i]];\n\n\t\tcluster_counts[i] = unsigned(cluster.indices.size());\n\n\t\tfor (size_t j = 0; j < cluster.indices.size(); ++j)\n\t\t\tcluster_indices.push_back(remap[cluster.indices[j]]);\n\t}\n\n\t// partition clusters into groups; the output is a partition id per cluster\n\tstd::vector<unsigned int> cluster_part(pending.size());\n\tsize_t partition_count = meshopt_partitionClusters(&cluster_part[0], &cluster_indices[0], cluster_indices.size(), &cluster_counts[0], cluster_counts.size(),\n\t    config.partition_spatial ? mesh.vertex_positions : NULL, remap.size(), mesh.vertex_positions_stride, config.partition_size);\n\n\t// preallocate partitions for worst case\n\tstd::vector<std::vector<int> > partitions(partition_count);\n\tfor (size_t i = 0; i < partition_count; ++i)\n\t\tpartitions[i].reserve(config.partition_size + config.partition_size / 3);\n\n\tstd::vector<unsigned int> partition_remap;\n\n\tif (config.partition_sort)\n\t{\n\t\t// compute partition points for sorting; any representative point will do, we use last cluster center for simplicity\n\t\tstd::vector<float> partition_point(partition_count * 3);\n\t\tfor (size_t i = 0; i < pending.size(); ++i)\n\t\t\tmemcpy(&partition_point[cluster_part[i] * 3], clusters[pending[i]].bounds.center, sizeof(float) * 3);\n\n\t\t// sort partitions spatially; the output is a remap table from old index (partition id) to new index\n\t\tpartition_remap.resize(partition_count);\n\t\tmeshopt_spatialSortRemap(partition_remap.data(), partition_point.data(), partition_count, sizeof(float) * 3);\n\t}\n\n\t// distribute clusters into partitions, applying spatial order if requested\n\tfor (size_t i = 0; i < pending.size(); ++i)\n\t\tpartitions[partition_remap.empty() ? cluster_part[i] : partition_remap[cluster_part[i]]].push_back(pending[i]);\n\n\treturn partitions;\n}\n\nstatic void lockBoundary(std::vector<unsigned char>& locks, const std::vector<std::vector<int> >& groups, const std::vector<Cluster>& clusters, const std::vector<unsigned int>& remap, const unsigned char* vertex_lock)\n{\n\t// for each remapped vertex, use bit 7 as temporary storage to indicate that the vertex has been used by a different group previously\n\tfor (size_t i = 0; i < locks.size(); ++i)\n\t\tlocks[i] &= ~((1 << 0) | (1 << 7));\n\n\tfor (size_t i = 0; i < groups.size(); ++i)\n\t{\n\t\t// mark all remapped vertices as locked if seen by a prior group\n\t\tfor (size_t j = 0; j < groups[i].size(); ++j)\n\t\t{\n\t\t\tconst Cluster& cluster = clusters[groups[i][j]];\n\n\t\t\tfor (size_t k = 0; k < cluster.indices.size(); ++k)\n\t\t\t{\n\t\t\t\tunsigned int v = cluster.indices[k];\n\t\t\t\tunsigned int r = remap[v];\n\n\t\t\t\tlocks[r] |= locks[r] >> 7;\n\t\t\t}\n\t\t}\n\n\t\t// mark all remapped vertices as seen\n\t\tfor (size_t j = 0; j < groups[i].size(); ++j)\n\t\t{\n\t\t\tconst Cluster& cluster = clusters[groups[i][j]];\n\n\t\t\tfor (size_t k = 0; k < cluster.indices.size(); ++k)\n\t\t\t{\n\t\t\t\tunsigned int v = cluster.indices[k];\n\t\t\t\tunsigned int r = remap[v];\n\n\t\t\t\tlocks[r] |= 1 << 7;\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (size_t i = 0; i < locks.size(); ++i)\n\t{\n\t\tunsigned int r = remap[i];\n\n\t\t// consistently lock all vertices with the same position; keep protect bit if set\n\t\tlocks[i] = (locks[r] & 1) | (locks[i] & meshopt_SimplifyVertex_Protect);\n\n\t\tif (vertex_lock)\n\t\t\tlocks[i] |= vertex_lock[i];\n\t}\n}\n\nstruct SloppyVertex\n{\n\tfloat x, y, z;\n\tunsigned int id;\n};\n\nstatic void simplifyFallback(std::vector<unsigned int>& lod, const clodMesh& mesh, const std::vector<unsigned int>& indices, const std::vector<unsigned char>& locks, size_t target_count, float* error)\n{\n\tstd::vector<SloppyVertex> subset(indices.size());\n\tstd::vector<unsigned char> subset_locks(indices.size());\n\n\tlod.resize(indices.size());\n\n\tsize_t positions_stride = mesh.vertex_positions_stride / sizeof(float);\n\n\t// deindex the mesh subset to avoid calling simplifySloppy on the entire vertex buffer (which is prohibitively expensive without sparsity)\n\tfor (size_t i = 0; i < indices.size(); ++i)\n\t{\n\t\tunsigned int v = indices[i];\n\t\tassert(v < mesh.vertex_count);\n\n\t\tsubset[i].x = mesh.vertex_positions[v * positions_stride + 0];\n\t\tsubset[i].y = mesh.vertex_positions[v * positions_stride + 1];\n\t\tsubset[i].z = mesh.vertex_positions[v * positions_stride + 2];\n\t\tsubset[i].id = v;\n\n\t\tsubset_locks[i] = locks[v];\n\t\tlod[i] = unsigned(i);\n\t}\n\n\tlod.resize(meshopt_simplifySloppy(&lod[0], &lod[0], lod.size(), &subset[0].x, subset.size(), sizeof(SloppyVertex), subset_locks.data(), target_count, FLT_MAX, error));\n\n\t// convert error to absolute\n\t*error *= meshopt_simplifyScale(&subset[0].x, subset.size(), sizeof(SloppyVertex));\n\n\t// restore original vertex indices\n\tfor (size_t i = 0; i < lod.size(); ++i)\n\t\tlod[i] = subset[lod[i]].id;\n}\n\nstatic std::vector<unsigned int> simplify(const clodConfig& config, const clodMesh& mesh, const std::vector<unsigned int>& indices, const std::vector<unsigned char>& locks, size_t target_count, float* error)\n{\n\tif (target_count > indices.size())\n\t\treturn indices;\n\n\tstd::vector<unsigned int> lod(indices.size());\n\n\tunsigned int options = meshopt_SimplifySparse | meshopt_SimplifyErrorAbsolute | (config.simplify_permissive ? meshopt_SimplifyPermissive : 0) | (config.simplify_regularize ? meshopt_SimplifyRegularize : 0);\n\n\tlod.resize(meshopt_simplifyWithAttributes(&lod[0], &indices[0], indices.size(),\n\t    mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride,\n\t    mesh.vertex_attributes, mesh.vertex_attributes_stride, mesh.attribute_weights, mesh.attribute_count,\n\t    &locks[0], target_count, FLT_MAX, options, error));\n\n\tif (lod.size() > target_count && config.simplify_fallback_permissive && !config.simplify_permissive)\n\t\tlod.resize(meshopt_simplifyWithAttributes(&lod[0], &indices[0], indices.size(),\n\t\t    mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride,\n\t\t    mesh.vertex_attributes, mesh.vertex_attributes_stride, mesh.attribute_weights, mesh.attribute_count,\n\t\t    &locks[0], target_count, FLT_MAX, options | meshopt_SimplifyPermissive, error));\n\n\t// while it's possible to call simplifySloppy directly, it doesn't support sparsity or absolute error, so we need to do some extra work\n\tif (lod.size() > target_count && config.simplify_fallback_sloppy)\n\t{\n\t\tsimplifyFallback(lod, mesh, indices, locks, target_count, error);\n\t\t*error *= config.simplify_error_factor_sloppy; // scale error up to account for appearance degradation\n\t}\n\n\t// optionally limit error by edge length, aiming to remove subpixel triangles even if the attribute error is high\n\tif (config.simplify_error_edge_limit > 0)\n\t{\n\t\tfloat max_edge_sq = 0;\n\n\t\tfor (size_t i = 0; i < indices.size(); i += 3)\n\t\t{\n\t\t\tunsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2];\n\t\t\tassert(a < mesh.vertex_count && b < mesh.vertex_count && c < mesh.vertex_count);\n\n\t\t\tconst float* va = &mesh.vertex_positions[a * (mesh.vertex_positions_stride / sizeof(float))];\n\t\t\tconst float* vb = &mesh.vertex_positions[b * (mesh.vertex_positions_stride / sizeof(float))];\n\t\t\tconst float* vc = &mesh.vertex_positions[c * (mesh.vertex_positions_stride / sizeof(float))];\n\n\t\t\t// compute squared edge lengths\n\t\t\tfloat 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]);\n\t\t\tfloat 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]);\n\t\t\tfloat 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]);\n\n\t\t\tfloat emax = std::max(std::max(eab, eac), ebc);\n\t\t\tfloat emin = std::min(std::min(eab, eac), ebc);\n\n\t\t\t// 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\n\t\t\tmax_edge_sq = std::max(max_edge_sq, std::max(emin, emax / 4));\n\t\t}\n\n\t\t// adjust the error to limit it for dense clusters based on edge lengths\n\t\t*error = std::min(*error, sqrtf(max_edge_sq) * config.simplify_error_edge_limit);\n\t}\n\n\treturn lod;\n}\n\nstatic int outputGroup(const clodConfig& config, const clodMesh& mesh, const std::vector<Cluster>& clusters, const std::vector<int>& group, const clodBounds& simplified, int depth, void* output_context, clodOutput output_callback)\n{\n\tstd::vector<clodCluster> group_clusters(group.size());\n\n\tfor (size_t i = 0; i < group.size(); ++i)\n\t{\n\t\tconst Cluster& cluster = clusters[group[i]];\n\t\tclodCluster& result = group_clusters[i];\n\n\t\tresult.refined = cluster.refined;\n\t\tresult.bounds = (config.optimize_bounds && cluster.refined != -1) ? boundsCompute(mesh, cluster.indices, cluster.bounds.error) : cluster.bounds;\n\t\tresult.indices = cluster.indices.data();\n\t\tresult.index_count = cluster.indices.size();\n\t\tresult.vertex_count = cluster.vertices;\n\t}\n\n\treturn output_callback ? output_callback(output_context, {depth, simplified}, group_clusters.data(), group_clusters.size()) : -1;\n}\n\n} // namespace clod\n\nclodConfig clodDefaultConfig(size_t max_triangles)\n{\n\tassert(max_triangles >= 4 && max_triangles <= 256);\n\n\tclodConfig config = {};\n\tconfig.max_vertices = max_triangles;\n\tconfig.min_triangles = max_triangles / 3;\n\tconfig.max_triangles = max_triangles;\n\n#if MESHOPTIMIZER_VERSION < 1000\n\tconfig.min_triangles &= ~3; // account for 4b alignment\n#endif\n\n\tconfig.partition_spatial = true;\n\tconfig.partition_size = 16;\n\n\tconfig.cluster_spatial = false;\n\tconfig.cluster_split_factor = 2.0f;\n\n\tconfig.optimize_clusters = true;\n\n\tconfig.simplify_ratio = 0.5f;\n\tconfig.simplify_threshold = 0.85f;\n\tconfig.simplify_error_merge_previous = 1.0f;\n\tconfig.simplify_error_factor_sloppy = 2.0f;\n\tconfig.simplify_permissive = true;\n\tconfig.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\n\tconfig.simplify_fallback_sloppy = true;\n\n\treturn config;\n}\n\nclodConfig clodDefaultConfigRT(size_t max_triangles)\n{\n\tclodConfig config = clodDefaultConfig(max_triangles);\n\n\t// 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\n\tconfig.min_triangles = max_triangles / 4;\n\n\t// 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\n\tconfig.max_vertices = std::min(size_t(256), max_triangles * 2);\n\n\tconfig.cluster_spatial = true;\n\tconfig.cluster_fill_weight = 0.5f;\n\n\treturn config;\n}\n\nsize_t clodBuild(clodConfig config, clodMesh mesh, void* output_context, clodOutput output_callback)\n{\n\tusing namespace clod;\n\n\tassert(mesh.vertex_attributes_stride % sizeof(float) == 0);\n\tassert(mesh.attribute_count * sizeof(float) <= mesh.vertex_attributes_stride);\n\tassert(mesh.attribute_protect_mask < (1u << (mesh.vertex_attributes_stride / sizeof(float))));\n\n\tstd::vector<unsigned char> locks(mesh.vertex_count);\n\n\t// for cluster connectivity, we need a position-only remap that maps vertices with the same position to the same index\n\tstd::vector<unsigned int> remap(mesh.vertex_count);\n\tmeshopt_generatePositionRemap(&remap[0], mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride);\n\n\t// set up protect bits on UV seams for permissive mode\n\tif (mesh.attribute_protect_mask)\n\t{\n\t\tsize_t max_attributes = mesh.vertex_attributes_stride / sizeof(float);\n\n\t\tfor (size_t i = 0; i < mesh.vertex_count; ++i)\n\t\t{\n\t\t\tunsigned int r = remap[i]; // canonical vertex with the same position\n\n\t\t\tfor (size_t j = 0; j < max_attributes; ++j)\n\t\t\t\tif (r != i && (mesh.attribute_protect_mask & (1u << j)) && mesh.vertex_attributes[i * max_attributes + j] != mesh.vertex_attributes[r * max_attributes + j])\n\t\t\t\t\tlocks[i] |= meshopt_SimplifyVertex_Protect;\n\t\t}\n\t}\n\n\t// initial clusterization splits the original mesh\n\tstd::vector<Cluster> clusters = clusterize(config, mesh, mesh.indices, mesh.index_count);\n\n\t// compute initial precise bounds; subsequent bounds will be using group-merged bounds\n\tfor (Cluster& cluster : clusters)\n\t\tcluster.bounds = boundsCompute(mesh, cluster.indices, 0.f);\n\n\tstd::vector<int> pending(clusters.size());\n\tfor (size_t i = 0; i < clusters.size(); ++i)\n\t\tpending[i] = int(i);\n\n\tint depth = 0;\n\n\t// merge and simplify clusters until we can't merge anymore\n\twhile (pending.size() > 1)\n\t{\n\t\tstd::vector<std::vector<int> > groups = partition(config, mesh, clusters, pending, remap);\n\n\t\tpending.clear();\n\n\t\t// mark boundaries between groups with a lock bit to avoid gaps in simplified result\n\t\tlockBoundary(locks, groups, clusters, remap, mesh.vertex_lock);\n\n\t\t// every group needs to be simplified now\n\t\tfor (size_t i = 0; i < groups.size(); ++i)\n\t\t{\n\t\t\tstd::vector<unsigned int> merged;\n\t\t\tmerged.reserve(groups[i].size() * config.max_triangles * 3);\n\t\t\tfor (size_t j = 0; j < groups[i].size(); ++j)\n\t\t\t\tmerged.insert(merged.end(), clusters[groups[i][j]].indices.begin(), clusters[groups[i][j]].indices.end());\n\n\t\t\tsize_t target_size = size_t((merged.size() / 3) * config.simplify_ratio) * 3;\n\n\t\t\t// enforce bounds and error monotonicity\n\t\t\t// note: it is incorrect to use the precise bounds of the merged or simplified mesh, because this may violate monotonicity\n\t\t\tclodBounds bounds = boundsMerge(clusters, groups[i]);\n\n\t\t\tfloat error = 0.f;\n\t\t\tstd::vector<unsigned int> simplified = simplify(config, mesh, merged, locks, target_size, &error);\n\t\t\tif (simplified.size() > merged.size() * config.simplify_threshold)\n\t\t\t{\n\t\t\t\tbounds.error = FLT_MAX; // terminal group, won't simplify further\n\t\t\t\toutputGroup(config, mesh, clusters, groups[i], bounds, depth, output_context, output_callback);\n\t\t\t\tcontinue; // simplification is stuck; abandon the merge\n\t\t\t}\n\n\t\t\t// enforce error monotonicity (with an optional hierarchical factor to separate transitions more)\n\t\t\tbounds.error = std::max(bounds.error * config.simplify_error_merge_previous, error) + error * config.simplify_error_merge_additive;\n\n\t\t\t// output the new group with all clusters; the resulting id will be recorded in new clusters as clodCluster::refined\n\t\t\tint refined = outputGroup(config, mesh, clusters, groups[i], bounds, depth, output_context, output_callback);\n\n\t\t\t// discard clusters from the group - they won't be used anymore\n\t\t\tfor (size_t j = 0; j < groups[i].size(); ++j)\n\t\t\t\tclusters[groups[i][j]].indices = std::vector<unsigned int>();\n\n\t\t\tstd::vector<Cluster> split = clusterize(config, mesh, simplified.data(), simplified.size());\n\n\t\t\tfor (Cluster& cluster : split)\n\t\t\t{\n\t\t\t\tcluster.refined = refined;\n\n\t\t\t\t// 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\n\t\t\t\tcluster.bounds = bounds;\n\n\t\t\t\t// enqueue new cluster for further processing\n\t\t\t\tclusters.push_back(std::move(cluster));\n\t\t\t\tpending.push_back(int(clusters.size()) - 1);\n\t\t\t}\n\t\t}\n\n\t\tdepth++;\n\t}\n\n\tif (pending.size())\n\t{\n\t\tassert(pending.size() == 1);\n\t\tconst Cluster& cluster = clusters[pending[0]];\n\n\t\tclodBounds bounds = cluster.bounds;\n\t\tbounds.error = FLT_MAX; // terminal group, won't simplify further\n\n\t\toutputGroup(config, mesh, clusters, pending, bounds, depth, output_context, output_callback);\n\t}\n\n\treturn clusters.size();\n}\n\nsize_t clodLocalIndices(unsigned int* vertices, unsigned char* triangles, const unsigned int* indices, size_t index_count)\n{\n\treturn meshopt_extractMeshletIndices(vertices, triangles, indices, index_count);\n}\n#endif\n\n/**\n * Copyright (c) 2016-2026 Arseny Kapoulkine\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n */\n"
  },
  {
    "path": "demo/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<title>meshoptimizer - demo</title>\n\t\t<meta charset=\"utf-8\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0\" />\n\t\t<style>\n\t\t\tbody {\n\t\t\t\tfont-family: Monospace;\n\t\t\t\tbackground-color: #000;\n\t\t\t\tcolor: #fff;\n\t\t\t\tmargin: 0px;\n\t\t\t\toverflow: hidden;\n\t\t\t}\n\t\t\t#info {\n\t\t\t\tcolor: #fff;\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 10px;\n\t\t\t\twidth: 100%;\n\t\t\t\ttext-align: center;\n\t\t\t\tz-index: 100;\n\t\t\t\tdisplay: block;\n\t\t\t}\n\t\t\t#info a,\n\t\t\t.button {\n\t\t\t\tcolor: #f00;\n\t\t\t\tfont-weight: bold;\n\t\t\t\ttext-decoration: underline;\n\t\t\t\tcursor: pointer;\n\t\t\t}\n\t\t</style>\n\n\t\t<script async src=\"https://cdn.jsdelivr.net/npm/es-module-shims@2.0.10/dist/es-module-shims.min.js\"></script>\n\t\t<script type=\"importmap\">\n\t\t\t{\n\t\t\t\t\"imports\": {\n\t\t\t\t\t\"three\": \"https://cdn.jsdelivr.net/npm/three@0.174.0/build/three.module.js\",\n\t\t\t\t\t\"three-examples/\": \"https://cdn.jsdelivr.net/npm/three@0.174.0/examples/jsm/\"\n\t\t\t\t}\n\t\t\t}\n\t\t</script>\n\t</head>\n\n\t<body>\n\t\t<div id=\"info\">\n\t\t\t<a href=\"https://github.com/zeux/meshoptimizer\" target=\"_blank\" rel=\"noopener\">meshoptimizer</a>\n\t\t</div>\n\n\t\t<script type=\"module\">\n\t\t\timport * as THREE from 'three';\n\t\t\timport { GLTFLoader } from 'three-examples/loaders/GLTFLoader.js';\n\t\t\timport { MeshoptDecoder } from '../js/meshopt_decoder.mjs';\n\n\t\t\tvar container;\n\n\t\t\tvar camera, scene, renderer, mixer, clock;\n\n\t\t\tvar windowHalfX = window.innerWidth / 2;\n\t\t\tvar windowHalfY = window.innerHeight / 2;\n\n\t\t\tinit();\n\t\t\tanimate();\n\n\t\t\tfunction init() {\n\t\t\t\tcontainer = document.createElement('div');\n\t\t\t\tdocument.body.appendChild(container);\n\n\t\t\t\tcamera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 100);\n\t\t\t\tcamera.position.y = 1.0;\n\t\t\t\tcamera.position.z = 3.0;\n\n\t\t\t\tscene = new THREE.Scene();\n\t\t\t\tscene.background = new THREE.Color(0x300a24);\n\n\t\t\t\tvar ambientLight = new THREE.AmbientLight(0xcccccc, 1);\n\t\t\t\tscene.add(ambientLight);\n\n\t\t\t\tvar pointLight = new THREE.PointLight(0xffffff, 4);\n\t\t\t\tpointLight.position.set(3, 3, 0);\n\t\t\t\tpointLight.decay = 0.5;\n\t\t\t\tcamera.add(pointLight);\n\t\t\t\tscene.add(camera);\n\n\t\t\t\tvar onProgress = function (xhr) {};\n\t\t\t\tvar onError = function (e) {\n\t\t\t\t\tconsole.log(e);\n\t\t\t\t};\n\n\t\t\t\tvar loader = new GLTFLoader();\n\t\t\t\tloader.setMeshoptDecoder(MeshoptDecoder);\n\t\t\t\tloader.load(\n\t\t\t\t\t'pirate.glb',\n\t\t\t\t\tfunction (gltf) {\n\t\t\t\t\t\tvar bbox = new THREE.Box3().setFromObject(gltf.scene);\n\t\t\t\t\t\tvar scale = 2 / (bbox.max.y - bbox.min.y);\n\n\t\t\t\t\t\tgltf.scene.scale.set(scale, scale, scale);\n\t\t\t\t\t\tgltf.scene.position.set(0, 0, 0);\n\n\t\t\t\t\t\tscene.add(gltf.scene);\n\n\t\t\t\t\t\tmixer = new THREE.AnimationMixer(gltf.scene);\n\n\t\t\t\t\t\tif (gltf.animations.length) {\n\t\t\t\t\t\t\tmixer.clipAction(gltf.animations[gltf.animations.length - 1]).play();\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tonProgress,\n\t\t\t\t\tonError\n\t\t\t\t);\n\n\t\t\t\trenderer = new THREE.WebGLRenderer();\n\t\t\t\trenderer.setPixelRatio(window.devicePixelRatio);\n\t\t\t\trenderer.setSize(window.innerWidth, window.innerHeight);\n\t\t\t\tcontainer.appendChild(renderer.domElement);\n\n\t\t\t\twindow.addEventListener('resize', onWindowResize, false);\n\n\t\t\t\tclock = new THREE.Clock();\n\t\t\t}\n\n\t\t\tfunction onWindowResize() {\n\t\t\t\twindowHalfX = window.innerWidth / 2;\n\t\t\t\twindowHalfY = window.innerHeight / 2;\n\n\t\t\t\tcamera.aspect = window.innerWidth / window.innerHeight;\n\t\t\t\tcamera.updateProjectionMatrix();\n\n\t\t\t\trenderer.setSize(window.innerWidth, window.innerHeight);\n\t\t\t}\n\n\t\t\tfunction animate() {\n\t\t\t\tif (mixer) {\n\t\t\t\t\tmixer.update(clock.getDelta());\n\t\t\t\t}\n\n\t\t\t\trequestAnimationFrame(animate);\n\t\t\t\trender();\n\t\t\t}\n\n\t\t\tfunction render() {\n\t\t\t\trenderer.render(scene, camera);\n\t\t\t}\n\t\t</script>\n\t</body>\n</html>\n"
  },
  {
    "path": "demo/main.cpp",
    "content": "#include \"../src/meshoptimizer.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <stdio.h>\n#include <string.h>\n#include <time.h>\n\n#include <vector>\n\n#include \"../extern/fast_obj.h\"\n\n#define SDEFL_IMPLEMENTATION\n#include \"../extern/sdefl.h\"\n\n// This file uses assert() to verify algorithm correctness\n#undef NDEBUG\n#include <assert.h>\n\n#if defined(__linux__)\ndouble timestamp()\n{\n\ttimespec ts;\n\tclock_gettime(CLOCK_MONOTONIC, &ts);\n\treturn double(ts.tv_sec) + 1e-9 * double(ts.tv_nsec);\n}\n#elif defined(_WIN32)\nstruct LARGE_INTEGER\n{\n\t__int64 QuadPart;\n};\nextern \"C\" __declspec(dllimport) int __stdcall QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount);\nextern \"C\" __declspec(dllimport) int __stdcall QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency);\n\ndouble timestamp()\n{\n\tLARGE_INTEGER freq, counter;\n\tQueryPerformanceFrequency(&freq);\n\tQueryPerformanceCounter(&counter);\n\treturn double(counter.QuadPart) / double(freq.QuadPart);\n}\n#else\ndouble timestamp()\n{\n\treturn double(clock()) / double(CLOCKS_PER_SEC);\n}\n#endif\n\nstruct Vertex\n{\n\tfloat px, py, pz;\n\tfloat nx, ny, nz;\n\tfloat tx, ty;\n};\n\nstruct Mesh\n{\n\tstd::vector<Vertex> vertices;\n\tstd::vector<unsigned int> indices;\n};\n\nunion Triangle\n{\n\tVertex v[3];\n\tchar data[sizeof(Vertex) * 3];\n};\n\nMesh parseObj(const char* path, double& reindex)\n{\n\tfastObjMesh* obj = fast_obj_read(path);\n\tif (!obj)\n\t{\n\t\tprintf(\"Error loading %s: file not found\\n\", path);\n\t\treturn Mesh();\n\t}\n\n\tsize_t total_indices = 0;\n\n\tfor (unsigned int i = 0; i < obj->face_count; ++i)\n\t\tif (obj->face_vertices[i] > 2)\n\t\t\ttotal_indices += 3 * (obj->face_vertices[i] - 2);\n\n\tstd::vector<Vertex> vertices(total_indices);\n\n\tsize_t vertex_offset = 0;\n\tsize_t index_offset = 0;\n\n\tfor (unsigned int i = 0; i < obj->face_count; ++i)\n\t{\n\t\tif (obj->face_vertices[i] <= 2)\n\t\t\tcontinue;\n\n\t\tfor (unsigned int j = 0; j < obj->face_vertices[i]; ++j)\n\t\t{\n\t\t\tfastObjIndex gi = obj->indices[index_offset + j];\n\n\t\t\tVertex v =\n\t\t\t    {\n\t\t\t        obj->positions[gi.p * 3 + 0],\n\t\t\t        obj->positions[gi.p * 3 + 1],\n\t\t\t        obj->positions[gi.p * 3 + 2],\n\t\t\t        obj->normals[gi.n * 3 + 0],\n\t\t\t        obj->normals[gi.n * 3 + 1],\n\t\t\t        obj->normals[gi.n * 3 + 2],\n\t\t\t        obj->texcoords[gi.t * 2 + 0],\n\t\t\t        obj->texcoords[gi.t * 2 + 1],\n\t\t\t    };\n\n\t\t\t// triangulate polygon on the fly; offset-3 is always the first polygon vertex\n\t\t\tif (j >= 3)\n\t\t\t{\n\t\t\t\tvertices[vertex_offset + 0] = vertices[vertex_offset - 3];\n\t\t\t\tvertices[vertex_offset + 1] = vertices[vertex_offset - 1];\n\t\t\t\tvertex_offset += 2;\n\t\t\t}\n\n\t\t\tvertices[vertex_offset] = v;\n\t\t\tvertex_offset++;\n\t\t}\n\n\t\tindex_offset += obj->face_vertices[i];\n\t}\n\n\tfast_obj_destroy(obj);\n\n\treindex = timestamp();\n\n\tMesh result;\n\n\t// empty mesh\n\tif (total_indices == 0)\n\t\treturn result;\n\n\tstd::vector<unsigned int> remap(total_indices);\n\n\tsize_t total_vertices = meshopt_generateVertexRemap(&remap[0], NULL, total_indices, &vertices[0], total_indices, sizeof(Vertex));\n\n\tresult.indices.resize(total_indices);\n\tmeshopt_remapIndexBuffer(&result.indices[0], NULL, total_indices, &remap[0]);\n\n\tresult.vertices.resize(total_vertices);\n\tmeshopt_remapVertexBuffer(&result.vertices[0], &vertices[0], total_indices, sizeof(Vertex), &remap[0]);\n\n\treturn result;\n}\n\nvoid dumpObj(const std::vector<Vertex>& vertices, const std::vector<unsigned int>& indices, bool recomputeNormals = false)\n{\n\tstd::vector<float> normals;\n\n\tif (recomputeNormals)\n\t{\n\t\tnormals.resize(vertices.size() * 3);\n\n\t\tfor (size_t i = 0; i < indices.size(); i += 3)\n\t\t{\n\t\t\tunsigned int a = indices[i], b = indices[i + 1], c = indices[i + 2];\n\n\t\t\tconst Vertex& va = vertices[a];\n\t\t\tconst Vertex& vb = vertices[b];\n\t\t\tconst Vertex& vc = vertices[c];\n\n\t\t\tfloat nx = (vb.py - va.py) * (vc.pz - va.pz) - (vb.pz - va.pz) * (vc.py - va.py);\n\t\t\tfloat ny = (vb.pz - va.pz) * (vc.px - va.px) - (vb.px - va.px) * (vc.pz - va.pz);\n\t\t\tfloat nz = (vb.px - va.px) * (vc.py - va.py) - (vb.py - va.py) * (vc.px - va.px);\n\n\t\t\tfor (int k = 0; k < 3; ++k)\n\t\t\t{\n\t\t\t\tunsigned int index = indices[i + k];\n\n\t\t\t\tnormals[index * 3 + 0] += nx;\n\t\t\t\tnormals[index * 3 + 1] += ny;\n\t\t\t\tnormals[index * 3 + 2] += nz;\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (size_t i = 0; i < vertices.size(); ++i)\n\t{\n\t\tconst Vertex& v = vertices[i];\n\n\t\tfloat nx = v.nx, ny = v.ny, nz = v.nz;\n\n\t\tif (recomputeNormals)\n\t\t{\n\t\t\tnx = normals[i * 3 + 0];\n\t\t\tny = normals[i * 3 + 1];\n\t\t\tnz = normals[i * 3 + 2];\n\n\t\t\tfloat l = sqrtf(nx * nx + ny * ny + nz * nz);\n\t\t\tfloat s = l == 0.f ? 0.f : 1.f / l;\n\n\t\t\tnx *= s;\n\t\t\tny *= s;\n\t\t\tnz *= s;\n\t\t}\n\n\t\tfprintf(stderr, \"v %f %f %f\\n\", v.px, v.py, v.pz);\n\t\tfprintf(stderr, \"vn %f %f %f\\n\", nx, ny, nz);\n\t}\n\n\tfor (size_t i = 0; i < indices.size(); i += 3)\n\t{\n\t\tunsigned int a = indices[i], b = indices[i + 1], c = indices[i + 2];\n\n\t\tfprintf(stderr, \"f %d//%d %d//%d %d//%d\\n\", a + 1, a + 1, b + 1, b + 1, c + 1, c + 1);\n\t}\n}\n\nvoid dumpObj(const char* section, const std::vector<unsigned int>& indices)\n{\n\tfprintf(stderr, \"o %s\\n\", section);\n\n\tfor (size_t j = 0; j < indices.size(); j += 3)\n\t{\n\t\tunsigned int a = indices[j], b = indices[j + 1], c = indices[j + 2];\n\n\t\tfprintf(stderr, \"f %d//%d %d//%d %d//%d\\n\", a + 1, a + 1, b + 1, b + 1, c + 1, c + 1);\n\t}\n}\n\nstruct PackedVertex\n{\n\tunsigned short px, py, pz;\n\tunsigned short pw; // padding to 4b boundary\n\tsigned char nx, ny, nz, nw;\n\tunsigned short tx, ty;\n};\n\nvoid packMesh(std::vector<PackedVertex>& pv, const std::vector<Vertex>& vertices)\n{\n\tfor (size_t i = 0; i < vertices.size(); ++i)\n\t{\n\t\tconst Vertex& vi = vertices[i];\n\t\tPackedVertex& pvi = pv[i];\n\n\t\tpvi.px = meshopt_quantizeHalf(vi.px);\n\t\tpvi.py = meshopt_quantizeHalf(vi.py);\n\t\tpvi.pz = meshopt_quantizeHalf(vi.pz);\n\t\tpvi.pw = 0;\n\n\t\tpvi.nx = char(meshopt_quantizeSnorm(vi.nx, 8));\n\t\tpvi.ny = char(meshopt_quantizeSnorm(vi.ny, 8));\n\t\tpvi.nz = char(meshopt_quantizeSnorm(vi.nz, 8));\n\t\tpvi.nw = 0;\n\n\t\tpvi.tx = meshopt_quantizeHalf(vi.tx);\n\t\tpvi.ty = meshopt_quantizeHalf(vi.ty);\n\t}\n}\n\nstruct PackedVertexOct\n{\n\tunsigned short px, py, pz;\n\tsigned char nu, nv; // octahedron encoded normal, aliases .pw\n\tunsigned short tx, ty;\n};\n\nvoid packMesh(std::vector<PackedVertexOct>& pv, const std::vector<Vertex>& vertices)\n{\n\tfor (size_t i = 0; i < vertices.size(); ++i)\n\t{\n\t\tconst Vertex& vi = vertices[i];\n\t\tPackedVertexOct& pvi = pv[i];\n\n\t\tpvi.px = meshopt_quantizeHalf(vi.px);\n\t\tpvi.py = meshopt_quantizeHalf(vi.py);\n\t\tpvi.pz = meshopt_quantizeHalf(vi.pz);\n\n\t\tfloat nsum = fabsf(vi.nx) + fabsf(vi.ny) + fabsf(vi.nz);\n\t\tfloat nx = vi.nx / nsum;\n\t\tfloat ny = vi.ny / nsum;\n\t\tfloat nz = vi.nz;\n\n\t\tfloat nu = nz >= 0 ? nx : (1 - fabsf(ny)) * (nx >= 0 ? 1 : -1);\n\t\tfloat nv = nz >= 0 ? ny : (1 - fabsf(nx)) * (ny >= 0 ? 1 : -1);\n\n\t\tpvi.nu = char(meshopt_quantizeSnorm(nu, 8));\n\t\tpvi.nv = char(meshopt_quantizeSnorm(nv, 8));\n\n\t\tpvi.tx = meshopt_quantizeHalf(vi.tx);\n\t\tpvi.ty = meshopt_quantizeHalf(vi.ty);\n\t}\n}\n\nvoid simplify(const Mesh& mesh, float threshold = 0.2f, unsigned int options = 0)\n{\n\tMesh lod;\n\n\tdouble start = timestamp();\n\n\tsize_t target_index_count = size_t(mesh.indices.size() * threshold);\n\tfloat target_error = 1e-2f;\n\tfloat result_error = 0;\n\n\tlod.indices.resize(mesh.indices.size()); // note: simplify needs space for index_count elements in the destination array, not target_index_count\n\tlod.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));\n\n\tlod.vertices.resize(lod.indices.size() < mesh.vertices.size() ? lod.indices.size() : mesh.vertices.size()); // note: this is just to reduce the cost of resize()\n\tlod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex)));\n\n\tdouble end = timestamp();\n\n\tprintf(\"%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec\\n\",\n\t    \"Simplify\",\n\t    int(mesh.indices.size() / 3), int(lod.indices.size() / 3),\n\t    result_error * 100,\n\t    (end - start) * 1000);\n}\n\nvoid simplifyAttr(const Mesh& mesh, float threshold = 0.2f, unsigned int options = 0)\n{\n\tMesh lod;\n\n\tdouble start = timestamp();\n\n\tsize_t target_index_count = size_t(mesh.indices.size() * threshold);\n\tfloat target_error = 1e-2f;\n\tfloat result_error = 0;\n\n\tconst float nrm_weight = 0.5f;\n\tconst float attr_weights[3] = {nrm_weight, nrm_weight, nrm_weight};\n\n\tlod.indices.resize(mesh.indices.size()); // note: simplify needs space for index_count elements in the destination array, not target_index_count\n\tlod.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));\n\n\tlod.vertices.resize(lod.indices.size() < mesh.vertices.size() ? lod.indices.size() : mesh.vertices.size()); // note: this is just to reduce the cost of resize()\n\tlod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex)));\n\n\tdouble end = timestamp();\n\n\tprintf(\"%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec\\n\",\n\t    \"SimplifyAttr\",\n\t    int(mesh.indices.size() / 3), int(lod.indices.size() / 3),\n\t    result_error * 100,\n\t    (end - start) * 1000);\n}\n\nvoid simplifyUpdate(const Mesh& mesh, float threshold = 0.2f, unsigned int options = 0)\n{\n\tMesh lod;\n\n\tdouble start = timestamp();\n\n\tsize_t target_index_count = size_t(mesh.indices.size() * threshold);\n\tfloat target_error = 1e-2f;\n\tfloat result_error = 0;\n\n\tconst float nrm_weight = 0.5f;\n\tconst float attr_weights[3] = {nrm_weight, nrm_weight, nrm_weight};\n\n\tlod = mesh; // start from the original mesh\n\tlod.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));\n\n\tlod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex)));\n\n\tfor (size_t i = 0; i < lod.vertices.size(); ++i)\n\t{\n\t\t// update normals\n\t\tVertex& v = lod.vertices[i];\n\t\tfloat nl = sqrtf(v.nx * v.nx + v.ny * v.ny + v.nz * v.nz);\n\t\tif (nl > 0)\n\t\t{\n\t\t\tv.nx /= nl;\n\t\t\tv.ny /= nl;\n\t\t\tv.nz /= nl;\n\t\t}\n\t}\n\n\tdouble end = timestamp();\n\n\tprintf(\"%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec\\n\",\n\t    \"SimplifyUpdt\",\n\t    int(mesh.indices.size() / 3), int(lod.indices.size() / 3),\n\t    result_error * 100,\n\t    (end - start) * 1000);\n}\n\nvoid simplifySloppy(const Mesh& mesh, float threshold = 0.2f)\n{\n\tMesh lod;\n\n\tdouble start = timestamp();\n\n\tsize_t target_index_count = size_t(mesh.indices.size() * threshold);\n\tfloat target_error = 1e-1f;\n\tfloat result_error = 0;\n\n\tlod.indices.resize(mesh.indices.size()); // note: simplify needs space for index_count elements in the destination array, not target_index_count\n\tlod.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));\n\n\tlod.vertices.resize(lod.indices.size() < mesh.vertices.size() ? lod.indices.size() : mesh.vertices.size()); // note: this is just to reduce the cost of resize()\n\tlod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex)));\n\n\tdouble end = timestamp();\n\n\tprintf(\"%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec\\n\",\n\t    \"SimplifyS\",\n\t    int(mesh.indices.size() / 3), int(lod.indices.size() / 3),\n\t    result_error * 100,\n\t    (end - start) * 1000);\n}\n\nvoid simplifyPoints(const Mesh& mesh, float threshold = 0.2f)\n{\n\tdouble start = timestamp();\n\n\tsize_t target_vertex_count = size_t(mesh.vertices.size() * threshold);\n\tif (target_vertex_count == 0)\n\t\treturn;\n\n\tstd::vector<unsigned int> indices(target_vertex_count);\n\tindices.resize(meshopt_simplifyPoints(&indices[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), NULL, 0, 0, target_vertex_count));\n\n\tdouble end = timestamp();\n\n\tprintf(\"%-9s: %d points => %d points in %.2f msec\\n\",\n\t    \"SimplifyP\",\n\t    int(mesh.vertices.size()), int(indices.size()), (end - start) * 1000);\n}\n\nvoid simplifyComplete(const Mesh& mesh)\n{\n\tstatic const size_t lod_count = 5;\n\n\tdouble start = timestamp();\n\n\t// generate 4 LOD levels (1-4), with each subsequent LOD using 70% triangles\n\t// note that each LOD uses the same (shared) vertex buffer\n\tstd::vector<unsigned int> lods[lod_count];\n\n\tlods[0] = mesh.indices;\n\n\tfor (size_t i = 1; i < lod_count; ++i)\n\t{\n\t\tstd::vector<unsigned int>& lod = lods[i];\n\n\t\tfloat threshold = powf(0.7f, float(i));\n\t\tsize_t target_index_count = size_t(mesh.indices.size() * threshold) / 3 * 3;\n\t\tfloat target_error = 1e-2f;\n\n\t\t// we can simplify all the way from base level or from the last result\n\t\t// simplifying from the base level sometimes produces better results, but simplifying from last level is faster\n\t\tconst std::vector<unsigned int>& source = lods[i - 1];\n\n\t\tif (source.size() < target_index_count)\n\t\t\ttarget_index_count = source.size();\n\n\t\tlod.resize(source.size());\n\t\tlod.resize(meshopt_simplify(&lod[0], &source[0], source.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count, target_error));\n\t}\n\n\tdouble middle = timestamp();\n\n\t// optimize each individual LOD for vertex cache & overdraw\n\tfor (size_t i = 0; i < lod_count; ++i)\n\t{\n\t\tstd::vector<unsigned int>& lod = lods[i];\n\n\t\tmeshopt_optimizeVertexCache(&lod[0], &lod[0], lod.size(), mesh.vertices.size());\n\t\tmeshopt_optimizeOverdraw(&lod[0], &lod[0], lod.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), 1.0f);\n\t}\n\n\t// concatenate all LODs into one IB\n\t// note: the order of concatenation is important - since we optimize the entire IB for vertex fetch,\n\t// putting coarse LODs first makes sure that the vertex range referenced by them is as small as possible\n\t// some GPUs process the entire range referenced by the index buffer region so doing this optimizes the vertex transform\n\t// cost for coarse LODs\n\t// this order also produces much better vertex fetch cache coherency for coarse LODs (since they're essentially optimized first)\n\t// somewhat surprisingly, the vertex fetch cache coherency for fine LODs doesn't seem to suffer that much.\n\tsize_t lod_index_offsets[lod_count] = {};\n\tsize_t lod_index_counts[lod_count] = {};\n\tsize_t total_index_count = 0;\n\n\tfor (int i = lod_count - 1; i >= 0; --i)\n\t{\n\t\tlod_index_offsets[i] = total_index_count;\n\t\tlod_index_counts[i] = lods[i].size();\n\n\t\ttotal_index_count += lods[i].size();\n\t}\n\n\tstd::vector<unsigned int> indices(total_index_count);\n\n\tfor (size_t i = 0; i < lod_count; ++i)\n\t{\n\t\tmemcpy(&indices[lod_index_offsets[i]], &lods[i][0], lods[i].size() * sizeof(lods[i][0]));\n\t}\n\n\tstd::vector<Vertex> vertices = mesh.vertices;\n\n\t// vertex fetch optimization should go last as it depends on the final index order\n\t// note that the order of LODs above affects vertex fetch results\n\tmeshopt_optimizeVertexFetch(&vertices[0], &indices[0], indices.size(), &vertices[0], vertices.size(), sizeof(Vertex));\n\n\tdouble end = timestamp();\n\n\tprintf(\"%-9s: %d triangles => %d LOD levels down to %d triangles in %.2f msec, optimized in %.2f msec\\n\",\n\t    \"SimplifyC\",\n\t    int(lod_index_counts[0]) / 3, int(lod_count), int(lod_index_counts[lod_count - 1]) / 3,\n\t    (middle - start) * 1000, (end - middle) * 1000);\n\n\t// for using LOD data at runtime, in addition to vertices and indices you have to save lod_index_offsets/lod_index_counts.\n\n\t{\n\t\tmeshopt_VertexCacheStatistics vcs0 = meshopt_analyzeVertexCache(&indices[lod_index_offsets[0]], lod_index_counts[0], vertices.size(), 16, 0, 0);\n\t\tmeshopt_VertexFetchStatistics vfs0 = meshopt_analyzeVertexFetch(&indices[lod_index_offsets[0]], lod_index_counts[0], vertices.size(), sizeof(Vertex));\n\t\tmeshopt_VertexCacheStatistics vcsN = meshopt_analyzeVertexCache(&indices[lod_index_offsets[lod_count - 1]], lod_index_counts[lod_count - 1], vertices.size(), 16, 0, 0);\n\t\tmeshopt_VertexFetchStatistics vfsN = meshopt_analyzeVertexFetch(&indices[lod_index_offsets[lod_count - 1]], lod_index_counts[lod_count - 1], vertices.size(), sizeof(Vertex));\n\n\t\ttypedef PackedVertexOct PV;\n\n\t\tstd::vector<PV> pv(vertices.size());\n\t\tpackMesh(pv, vertices);\n\n\t\tstd::vector<unsigned char> vbuf(meshopt_encodeVertexBufferBound(vertices.size(), sizeof(PV)));\n\t\tvbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &pv[0], vertices.size(), sizeof(PV)));\n\n\t\tstd::vector<unsigned char> ibuf(meshopt_encodeIndexBufferBound(indices.size(), vertices.size()));\n\t\tibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), &indices[0], indices.size()));\n\n\t\tprintf(\"%-9s  ACMR %f...%f Overfetch %f..%f Codec VB %.1f bits/vertex IB %.1f bits/triangle\\n\",\n\t\t    \"\",\n\t\t    vcs0.acmr, vcsN.acmr, vfs0.overfetch, vfsN.overfetch,\n\t\t    double(vbuf.size()) / double(vertices.size()) * 8,\n\t\t    double(ibuf.size()) / double(indices.size() / 3) * 8);\n\t}\n}\n\nvoid simplifyClusters(const Mesh& mesh, float threshold = 0.2f)\n{\n\tconst size_t max_vertices = 64;\n\tconst size_t max_triangles = 64;\n\tconst size_t target_group_size = 8;\n\n\tdouble start = timestamp();\n\n\t// build clusters (meshlets) out of the mesh\n\tsize_t max_meshlets = meshopt_buildMeshletsBound(mesh.indices.size(), max_vertices, max_triangles);\n\tstd::vector<meshopt_Meshlet> meshlets(max_meshlets);\n\tstd::vector<unsigned int> meshlet_vertices(mesh.indices.size());\n\tstd::vector<unsigned char> meshlet_triangles(mesh.indices.size());\n\n\tmeshlets.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));\n\n\tdouble middle = timestamp();\n\n\t// generate position remap; we'll use that to partition clusters using position-only adjacency\n\tstd::vector<unsigned int> remap(mesh.vertices.size());\n\tmeshopt_generatePositionRemap(&remap[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex));\n\n\t// partition clusters in groups; each group will be simplified separately and the boundaries between groups will be preserved\n\tstd::vector<unsigned int> cluster_indices;\n\tcluster_indices.reserve(mesh.indices.size()); // slight underestimate, vector should realloc once\n\tstd::vector<unsigned int> cluster_sizes(meshlets.size());\n\n\tfor (size_t i = 0; i < meshlets.size(); ++i)\n\t{\n\t\tconst meshopt_Meshlet& m = meshlets[i];\n\n\t\tfor (size_t j = 0; j < m.triangle_count * 3; ++j)\n\t\t{\n\t\t\tunsigned int v = meshlet_vertices[m.vertex_offset + meshlet_triangles[m.triangle_offset + j]];\n\n\t\t\t// use the first vertex with equivalent position so that cluster adjacency ignores attribute seams\n\t\t\tcluster_indices.push_back(remap[v]);\n\t\t}\n\n\t\tcluster_sizes[i] = m.triangle_count * 3;\n\t}\n\n\tstd::vector<unsigned int> partition(meshlets.size());\n\tsize_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);\n\n\t// convert partitions to linked lists to make it easier to iterate over (vectors of vectors would work too)\n\tstd::vector<int> partnext(meshlets.size(), -1);\n\tstd::vector<int> partlast(partition_count, -1);\n\n\tfor (size_t i = 0; i < meshlets.size(); ++i)\n\t{\n\t\tunsigned int part = partition[i];\n\n\t\tif (partlast[part] >= 0)\n\t\t\tpartnext[partlast[part]] = int(i);\n\n\t\tpartlast[part] = int(i);\n\t\tpartnext[i] = -1;\n\t}\n\n\tdouble parttime = timestamp();\n\n\tfloat scale = meshopt_simplifyScale(&mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex));\n\n\tstd::vector<unsigned int> lod;\n\tlod.reserve(mesh.indices.size());\n\n\tfloat error = 0.f;\n\n\tfor (size_t i = 0; i < meshlets.size(); ++i)\n\t{\n\t\tif (partlast[partition[i]] < 0)\n\t\t\tcontinue; // part of a group that was already processed\n\n\t\t// mark group as processed\n\t\tpartlast[partition[i]] = -1;\n\n\t\tsize_t group_offset = lod.size();\n\n\t\tfor (int j = int(i); j >= 0; j = partnext[j])\n\t\t{\n\t\t\tconst meshopt_Meshlet& m = meshlets[j];\n\n\t\t\tfor (size_t k = 0; k < m.triangle_count * 3; ++k)\n\t\t\t\tlod.push_back(meshlet_vertices[m.vertex_offset + meshlet_triangles[m.triangle_offset + k]]);\n\t\t}\n\n\t\tsize_t group_triangles = (lod.size() - group_offset) / 3;\n\n\t\t// simplify the group, preserving the border vertices\n\t\t// note: this technically also locks the exterior border; a full mesh analysis (see clusterlod.h / lockBoundary) would work better for some meshes\n\t\tunsigned int options = meshopt_SimplifyLockBorder | meshopt_SimplifySparse | meshopt_SimplifyErrorAbsolute;\n\n\t\tfloat group_target_error = 1e-2f * scale;\n\t\tsize_t group_target = size_t(float(group_triangles) * threshold) * 3;\n\t\tfloat group_error = 0.f;\n\t\tsize_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);\n\n\t\terror = group_error > error ? group_error : error;\n\n\t\t// simplified group is available in lod[group_offset..group_offset + group_size]\n\t\tlod.resize(group_offset + group_size);\n\t}\n\n\tdouble end = timestamp();\n\n\tprintf(\"%-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\",\n\t    \"SimplifyG\",\n\t    int(mesh.indices.size() / 3), int(lod.size() / 3),\n\t    error / scale * 100,\n\t    (end - parttime) * 1000, (middle - start) * 1000, (parttime - middle) * 1000,\n\t    int(meshlets.size()), int(partition_count), double(meshlets.size()) / double(partition_count));\n}\n\nvoid optimize(const Mesh& mesh, bool fifo = false)\n{\n\tMesh copy = mesh;\n\t// note: we assume that the mesh is already optimally indexed (via parseObj); if that is not the case, you'd need to reindex first\n\n\tdouble start = timestamp();\n\n\t// vertex cache optimization should go first as it provides starting order for overdraw\n\t// 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\n\tif (fifo)\n\t\tmeshopt_optimizeVertexCacheFifo(&copy.indices[0], &copy.indices[0], copy.indices.size(), copy.vertices.size(), /* cache_size= */ 16);\n\telse\n\t\tmeshopt_optimizeVertexCache(&copy.indices[0], &copy.indices[0], copy.indices.size(), copy.vertices.size());\n\n\t// reorder indices for overdraw, balancing overdraw and vertex cache efficiency\n\tconst float kThreshold = 1.01f; // allow up to 1% worse ACMR to get more reordering opportunities for overdraw\n\tmeshopt_optimizeOverdraw(&copy.indices[0], &copy.indices[0], copy.indices.size(), &copy.vertices[0].px, copy.vertices.size(), sizeof(Vertex), kThreshold);\n\n\t// vertex fetch optimization should go last as it depends on the final index order\n\tmeshopt_optimizeVertexFetch(&copy.vertices[0], &copy.indices[0], copy.indices.size(), &copy.vertices[0], copy.vertices.size(), sizeof(Vertex));\n\n\tdouble end = timestamp();\n\n\tmeshopt_VertexCacheStatistics vcs = meshopt_analyzeVertexCache(&copy.indices[0], copy.indices.size(), copy.vertices.size(), 16, 0, 0);\n\tmeshopt_VertexFetchStatistics vfs = meshopt_analyzeVertexFetch(&copy.indices[0], copy.indices.size(), copy.vertices.size(), sizeof(Vertex));\n\tmeshopt_OverdrawStatistics os = meshopt_analyzeOverdraw(&copy.indices[0], copy.indices.size(), &copy.vertices[0].px, copy.vertices.size(), sizeof(Vertex));\n\n\tmeshopt_VertexCacheStatistics vcs_nv = meshopt_analyzeVertexCache(&copy.indices[0], copy.indices.size(), copy.vertices.size(), 32, 32, 32);\n\tmeshopt_VertexCacheStatistics vcs_amd = meshopt_analyzeVertexCache(&copy.indices[0], copy.indices.size(), copy.vertices.size(), 14, 64, 128);\n\tmeshopt_VertexCacheStatistics vcs_intel = meshopt_analyzeVertexCache(&copy.indices[0], copy.indices.size(), copy.vertices.size(), 128, 0, 0);\n\n\tprintf(\"Optimize%s: ACMR %f ATVR %f (NV %f AMD %f Intel %f) overfetch %f overdraw %f in %.2f msec\\n\",\n\t    fifo ? \"F\" : \" \",\n\t    vcs.acmr, vcs.atvr, vcs_nv.atvr, vcs_amd.atvr, vcs_intel.atvr, vfs.overfetch, os.overdraw, (end - start) * 1000);\n}\n\ntemplate <typename T>\nsize_t compress(const std::vector<T>& data, int level = SDEFL_LVL_DEF)\n{\n\tstd::vector<unsigned char> cbuf(sdefl_bound(int(data.size() * sizeof(T))));\n\tsdefl s = {};\n\treturn sdeflate(&s, &cbuf[0], reinterpret_cast<const unsigned char*>(&data[0]), int(data.size() * sizeof(T)), level);\n}\n\nvoid encodeIndex(const std::vector<unsigned int>& indices, size_t vertex_count, char desc)\n{\n\t// allocate result outside of the timing loop to exclude memset() from decode timing\n\tstd::vector<unsigned int> result(indices.size());\n\n\tdouble start = timestamp();\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexBufferBound(indices.size(), vertex_count));\n\tbuffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), &indices[0], indices.size()));\n\n\tdouble middle = timestamp();\n\n\tint res = meshopt_decodeIndexBuffer(&result[0], indices.size(), &buffer[0], buffer.size());\n\tassert(res == 0);\n\t(void)res;\n\n\tdouble end = timestamp();\n\n\tsize_t csize = compress(buffer);\n\n\tfor (size_t i = 0; i < indices.size(); i += 3)\n\t{\n\t\tassert(\n\t\t    (result[i + 0] == indices[i + 0] && result[i + 1] == indices[i + 1] && result[i + 2] == indices[i + 2]) ||\n\t\t    (result[i + 1] == indices[i + 0] && result[i + 2] == indices[i + 1] && result[i + 0] == indices[i + 2]) ||\n\t\t    (result[i + 2] == indices[i + 0] && result[i + 0] == indices[i + 1] && result[i + 1] == indices[i + 2]));\n\t}\n\n\tprintf(\"IdxCodec%c: %.1f bits/triangle (post-deflate %.1f bits/triangle); encode %.2f msec (%.3f GB/s), decode %.2f msec (%.2f GB/s)\\n\",\n\t    desc,\n\t    double(buffer.size() * 8) / double(indices.size() / 3),\n\t    double(csize * 8) / double(indices.size() / 3),\n\t    (middle - start) * 1000,\n\t    (double(result.size() * 4) / 1e9) / (middle - start),\n\t    (end - middle) * 1000,\n\t    (double(result.size() * 4) / 1e9) / (end - middle));\n}\n\nvoid encodeIndex(const Mesh& mesh, char desc)\n{\n\tencodeIndex(mesh.indices, mesh.vertices.size(), desc);\n}\n\nvoid encodeIndexSequence(const std::vector<unsigned int>& data, size_t vertex_count, char desc)\n{\n\t// allocate result outside of the timing loop to exclude memset() from decode timing\n\tstd::vector<unsigned int> result(data.size());\n\n\tdouble start = timestamp();\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexSequenceBound(data.size(), vertex_count));\n\tbuffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), &data[0], data.size()));\n\n\tdouble middle = timestamp();\n\n\tint res = meshopt_decodeIndexSequence(&result[0], data.size(), &buffer[0], buffer.size());\n\tassert(res == 0);\n\t(void)res;\n\n\tdouble end = timestamp();\n\n\tsize_t csize = compress(buffer);\n\n\tassert(memcmp(&data[0], &result[0], data.size() * sizeof(unsigned int)) == 0);\n\n\tprintf(\"IdxCodec%c: %.1f bits/index (post-deflate %.1f bits/index); encode %.2f msec (%.3f GB/s), decode %.2f msec (%.2f GB/s)\\n\",\n\t    desc,\n\t    double(buffer.size() * 8) / double(data.size()),\n\t    double(csize * 8) / double(data.size()),\n\t    (middle - start) * 1000,\n\t    (double(result.size() * 4) / 1e9) / (middle - start),\n\t    (end - middle) * 1000,\n\t    (double(result.size() * 4) / 1e9) / (end - middle));\n}\n\ntemplate <typename V, typename T>\nstatic 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)\n{\n\tV rv[256];\n\tT rt[sizeof(T) == 1 ? 256 * 3 : 256];\n\n\tint rc = meshopt_decodeMeshlet(rv, vertex_count, rt, triangle_count, data, size);\n\tassert(rc == 0);\n\n\tfor (size_t j = 0; j < vertex_count; ++j)\n\t\tassert(rv[j] == V(vertices[j]));\n\n\tfor (size_t j = 0; j < triangle_count; ++j)\n\t{\n\t\tunsigned int a = triangles[j * 3 + 0];\n\t\tunsigned int b = triangles[j * 3 + 1];\n\t\tunsigned int c = triangles[j * 3 + 2];\n\n\t\tunsigned int tri = sizeof(T) == 1 ? rt[j * 3] | (rt[j * 3 + 1] << 8) | (rt[j * 3 + 2] << 16) : rt[j];\n\n\t\tunsigned int abc = (a << 0) | (b << 8) | (c << 16);\n\t\tunsigned int bca = (b << 0) | (c << 8) | (a << 16);\n\t\tunsigned int cba = (c << 0) | (a << 8) | (b << 16);\n\n\t\tassert(tri == abc || tri == bca || tri == cba);\n\t}\n}\n\nvoid encodeMeshlets(const Mesh& mesh, size_t max_vertices, size_t max_triangles, bool reorder = true)\n{\n\tsize_t max_meshlets = meshopt_buildMeshletsBound(mesh.indices.size(), max_vertices, max_triangles);\n\tstd::vector<meshopt_Meshlet> meshlets(max_meshlets);\n\tstd::vector<unsigned int> meshlet_vertices(mesh.indices.size());\n\tstd::vector<unsigned char> meshlet_triangles(mesh.indices.size());\n\n\tmeshlets.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));\n\n\tif (meshlets.size())\n\t{\n\t\tconst meshopt_Meshlet& last = meshlets.back();\n\n\t\t// this is an example of how to trim the vertex/triangle arrays when copying data out to GPU storage\n\t\tmeshlet_vertices.resize(last.vertex_offset + last.vertex_count);\n\t\tmeshlet_triangles.resize(last.triangle_offset + last.triangle_count * 3);\n\n\t\t// TODO: over-allocate meshlet_vertices to multiple of 3 to make meshopt_optimizeVertexFetch below work without assertions\n\t\tmeshlet_vertices.resize((meshlet_vertices.size() + 2) / 3 * 3);\n\t}\n\n\tstd::vector<unsigned char> cbuf(meshopt_encodeMeshletBound(max_vertices, max_triangles));\n\n\t// optimize each meshlet for locality; this is important for performance, and critical for good compression\n\tfor (size_t i = 0; i < meshlets.size(); ++i)\n\t\tmeshopt_optimizeMeshlet(&meshlet_vertices[meshlets[i].vertex_offset], &meshlet_triangles[meshlets[i].triangle_offset], meshlets[i].triangle_count, meshlets[i].vertex_count);\n\n\t// optimize the order of vertex references within each meshlet and globally; this is valuable for access locality and critical for compression of vertex references\n\t// 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\n\tstd::vector<Vertex> vertices = mesh.vertices;\n\tif (reorder)\n\t\tmeshopt_optimizeVertexFetch(&vertices[0], &meshlet_vertices[0], meshlet_vertices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex));\n\n\tsize_t mbst = 0;\n\n\tstd::vector<unsigned char> packed;\n\n\tfor (size_t i = 0; i < meshlets.size(); ++i)\n\t{\n\t\tconst meshopt_Meshlet& meshlet = meshlets[i];\n\n\t\tsize_t mbs = meshopt_encodeMeshlet(&cbuf[0], cbuf.size(), &meshlet_vertices[meshlet.vertex_offset], meshlet.vertex_count, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count);\n\t\tassert(mbs > 0);\n\n\t\t// 24-bit header: 7 bit (vertex_count-1), 7 bit (triangle_count-1), 10 bit size\n\t\t// fits up to 128v/128t meshlet with 1024 bytes of encoded data; meshopt_encodeMeshletBound(128,128) < 1000\n\t\tassert(size_t(meshlet.vertex_count - 1) < 128 && size_t(meshlet.triangle_count - 1) < 128 && mbs < 1024);\n\t\tunsigned int header = ((meshlet.vertex_count - 1) & 0x7f) | (((meshlet.triangle_count - 1) & 0x7f) << 7) | ((unsigned(mbs) & 0x3ff) << 14);\n\t\tpacked.push_back((unsigned char)(header & 0xff));\n\t\tpacked.push_back((unsigned char)((header >> 8) & 0xff));\n\t\tpacked.push_back((unsigned char)((header >> 16) & 0xff));\n\t\tpacked.insert(packed.end(), &cbuf[0], &cbuf[mbs]);\n\n\t\tvalidateDecodeMeshlet<unsigned int, unsigned int>(&cbuf[0], mbs, &meshlet_vertices[meshlet.vertex_offset], meshlet.vertex_count, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count);\n\t\tvalidateDecodeMeshlet<unsigned int, unsigned char>(&cbuf[0], mbs, &meshlet_vertices[meshlet.vertex_offset], meshlet.vertex_count, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count);\n\t\tvalidateDecodeMeshlet<unsigned short, unsigned int>(&cbuf[0], mbs, &meshlet_vertices[meshlet.vertex_offset], meshlet.vertex_count, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count);\n\t\tvalidateDecodeMeshlet<unsigned short, unsigned char>(&cbuf[0], mbs, &meshlet_vertices[meshlet.vertex_offset], meshlet.vertex_count, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count);\n\n\t\tmbst += mbs;\n\t}\n\n\tsize_t mbc = compress(packed);\n\n\tprintf(\"MeshletCodec (%d/%d): %d meshlets, %d bytes/meshlet; %d bytes, %.1f bits/triangle\\n\",\n\t    int(max_vertices), int(max_triangles),\n\t    int(meshlets.size()),\n\t    int(mbst / meshlets.size()),\n\t    int(mbst), double(mbst * 8) / double(mesh.indices.size() / 3));\n\tprintf(\"MeshletCodec (%d/%d, packed): %d bytes/meshlet, %.1f bits/triangle; post-deflate: %d bytes/meshlet, %.1f bits/triangle)\\n\",\n\t    int(max_vertices), int(max_triangles),\n\t    int(packed.size() / meshlets.size()), double(packed.size() * 8) / double(mesh.indices.size() / 3),\n\t    int(mbc / meshlets.size()), double(mbc * 8) / double(mesh.indices.size() / 3));\n\n#if !TRACE\n\tdouble mbtime = 0;\n\n\tfor (int i = 0; i < 10; ++i)\n\t{\n\t\tunsigned int rv[256];\n\t\tunsigned int rt[256];\n\t\tdouble t0 = timestamp();\n\t\tunsigned char* p = &packed[0];\n\t\tfor (size_t j = 0; j < meshlets.size(); ++j)\n\t\t{\n\t\t\tunsigned int header = p[0] | (p[1] << 8) | (p[2] << 16);\n\t\t\tsize_t vertex_count = (header & 0x7f) + 1;\n\t\t\tsize_t triangle_count = ((header >> 7) & 0x7f) + 1;\n\t\t\tsize_t size = (header >> 14) & 0x3ff;\n\t\t\tmeshopt_decodeMeshletRaw(rv, vertex_count, rt, triangle_count, p + 3, size);\n\t\t\tp += 3 + size;\n\t\t}\n\t\tdouble t1 = timestamp();\n\n\t\tmbtime = (mbtime == 0 || t1 - t0 < mbtime) ? (t1 - t0) : mbtime;\n\t}\n\n\tprintf(\"MeshletCodec (%d/%d, packed): decode time %.3f msec, %.3fB tri/sec, %.1f ns/meshlet\\n\",\n\t    int(max_vertices), int(max_triangles),\n\t    mbtime * 1000, double(mesh.indices.size() / 3) / 1e9 / mbtime, mbtime * 1e9 / double(meshlets.size()));\n#endif\n}\n\ntemplate <typename PV>\nvoid packVertex(const Mesh& mesh, const char* pvn)\n{\n\tstd::vector<PV> pv(mesh.vertices.size());\n\tpackMesh(pv, mesh.vertices);\n\n\tsize_t csize = compress(pv);\n\n\tprintf(\"VtxPack%s  : %.1f bits/vertex (post-deflate %.1f bits/vertex)\\n\", pvn,\n\t    double(pv.size() * sizeof(PV) * 8) / double(mesh.vertices.size()),\n\t    double(csize * 8) / double(mesh.vertices.size()));\n}\n\ntemplate <typename PV>\nvoid encodeVertex(const Mesh& mesh, const char* pvn, int level = 2)\n{\n\tstd::vector<PV> pv(mesh.vertices.size());\n\tpackMesh(pv, mesh.vertices);\n\n\t// allocate result outside of the timing loop to exclude memset() from decode timing\n\tstd::vector<PV> result(mesh.vertices.size());\n\n\tdouble start = timestamp();\n\n\tstd::vector<unsigned char> vbuf(meshopt_encodeVertexBufferBound(mesh.vertices.size(), sizeof(PV)));\n\tvbuf.resize(meshopt_encodeVertexBufferLevel(&vbuf[0], vbuf.size(), &pv[0], mesh.vertices.size(), sizeof(PV), level, -1));\n\n\tdouble middle = timestamp();\n\n\tint res = meshopt_decodeVertexBuffer(&result[0], mesh.vertices.size(), sizeof(PV), &vbuf[0], vbuf.size());\n\tassert(res == 0);\n\t(void)res;\n\n\tdouble end = timestamp();\n\n\tassert(memcmp(&pv[0], &result[0], pv.size() * sizeof(PV)) == 0);\n\n\tsize_t csize = compress(vbuf);\n\n\tprintf(\"VtxCodec%1s: %.1f bits/vertex (post-deflate %.1f bits/vertex); encode %.2f msec (%.3f GB/s), decode %.2f msec (%.2f GB/s)\\n\", pvn,\n\t    double(vbuf.size() * 8) / double(mesh.vertices.size()),\n\t    double(csize * 8) / double(mesh.vertices.size()),\n\t    (middle - start) * 1000,\n\t    (double(result.size() * sizeof(PV)) / 1e9) / (middle - start),\n\t    (end - middle) * 1000,\n\t    (double(result.size() * sizeof(PV)) / 1e9) / (end - middle));\n}\n\nvoid stripify(const Mesh& mesh, bool use_restart, char desc)\n{\n\tunsigned int restart_index = use_restart ? ~0u : 0;\n\n\t// note: input mesh is assumed to be optimized for vertex cache and vertex fetch\n\tdouble start = timestamp();\n\tstd::vector<unsigned int> strip(meshopt_stripifyBound(mesh.indices.size()));\n\tstrip.resize(meshopt_stripify(&strip[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size(), restart_index));\n\tdouble end = timestamp();\n\n\tsize_t restarts = 0;\n\tfor (size_t i = 0; i < strip.size(); ++i)\n\t\trestarts += use_restart && strip[i] == restart_index;\n\n\tMesh copy = mesh;\n\tcopy.indices.resize(meshopt_unstripify(&copy.indices[0], &strip[0], strip.size(), restart_index));\n\tassert(copy.indices.size() <= meshopt_unstripifyBound(strip.size()));\n\n\tmeshopt_VertexCacheStatistics vcs = meshopt_analyzeVertexCache(&copy.indices[0], mesh.indices.size(), mesh.vertices.size(), 16, 0, 0);\n\tmeshopt_VertexCacheStatistics vcs_nv = meshopt_analyzeVertexCache(&copy.indices[0], mesh.indices.size(), mesh.vertices.size(), 32, 32, 32);\n\tmeshopt_VertexCacheStatistics vcs_amd = meshopt_analyzeVertexCache(&copy.indices[0], mesh.indices.size(), mesh.vertices.size(), 14, 64, 128);\n\tmeshopt_VertexCacheStatistics vcs_intel = meshopt_analyzeVertexCache(&copy.indices[0], mesh.indices.size(), mesh.vertices.size(), 128, 0, 0);\n\n\tprintf(\"Stripify%c: ACMR %f ATVR %f (NV %f AMD %f Intel %f); %.1f run avg, %d strip indices (%.1f%%) in %.2f msec\\n\",\n\t    desc,\n\t    vcs.acmr, vcs.atvr, vcs_nv.atvr, vcs_amd.atvr, vcs_intel.atvr,\n\t    use_restart ? double(strip.size() - restarts) / double(restarts + 1) : 0,\n\t    int(strip.size()), double(strip.size()) / double(mesh.indices.size()) * 100,\n\t    (end - start) * 1000);\n}\n\nvoid shadow(const Mesh& mesh)\n{\n\t// note: input mesh is assumed to be optimized for vertex cache and vertex fetch\n\n\tdouble start = timestamp();\n\t// this index buffer can be used for position-only rendering using the same vertex data that the original index buffer uses\n\tstd::vector<unsigned int> shadow_indices(mesh.indices.size());\n\tmeshopt_generateShadowIndexBuffer(&shadow_indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(float) * 3, sizeof(Vertex));\n\tdouble end = timestamp();\n\n\t// while you can't optimize the vertex data after shadow IB was constructed, you can and should optimize the shadow IB for vertex cache\n\t// this is valuable even if the original indices array was optimized for vertex cache!\n\tmeshopt_optimizeVertexCache(&shadow_indices[0], &shadow_indices[0], shadow_indices.size(), mesh.vertices.size());\n\n\tmeshopt_VertexCacheStatistics vcs = meshopt_analyzeVertexCache(&mesh.indices[0], mesh.indices.size(), mesh.vertices.size(), 16, 0, 0);\n\tmeshopt_VertexCacheStatistics vcss = meshopt_analyzeVertexCache(&shadow_indices[0], shadow_indices.size(), mesh.vertices.size(), 16, 0, 0);\n\n\tstd::vector<char> shadow_flags(mesh.vertices.size());\n\tsize_t shadow_vertices = 0;\n\n\tfor (size_t i = 0; i < shadow_indices.size(); ++i)\n\t{\n\t\tunsigned int index = shadow_indices[i];\n\t\tshadow_vertices += 1 - shadow_flags[index];\n\t\tshadow_flags[index] = 1;\n\t}\n\n\tprintf(\"ShadowIB : ACMR %f (%.2fx improvement); %d shadow vertices (%.2fx improvement) in %.2f msec\\n\",\n\t    vcss.acmr, double(vcs.vertices_transformed) / double(vcss.vertices_transformed),\n\t    int(shadow_vertices), double(mesh.vertices.size()) / double(shadow_vertices),\n\t    (end - start) * 1000);\n}\n\nstatic int follow(int* parents, int index)\n{\n\twhile (index != parents[index])\n\t{\n\t\tint parent = parents[index];\n\t\tparents[index] = parents[parent];\n\t\tindex = parent;\n\t}\n\n\treturn index;\n}\n\nvoid meshlets(const Mesh& mesh, bool scan = false, bool uniform = false, bool flex = false, bool spatial = false, bool dump = false)\n{\n\t// NVidia recommends 64/126; we also test uniform configuration with 64/64 which is better for earlier AMD GPUs\n\tconst size_t max_vertices = 64;\n\tconst size_t max_triangles = uniform ? 64 : 126;\n\tconst size_t min_triangles = spatial ? 16 : (uniform ? 24 : 32); // only used in flex/spatial modes\n\n\t// note: should be set to 0 unless cone culling is used at runtime!\n\tconst float cone_weight = 0.25f;\n\tconst float split_factor = flex ? 2.0f : 0.0f;\n\n\t// note: input mesh is assumed to be optimized for vertex cache and vertex fetch\n\tdouble start = timestamp();\n\tsize_t max_meshlets = meshopt_buildMeshletsBound(mesh.indices.size(), max_vertices, min_triangles);\n\tstd::vector<meshopt_Meshlet> meshlets(max_meshlets);\n\tstd::vector<unsigned int> meshlet_vertices(mesh.indices.size());\n\tstd::vector<unsigned char> meshlet_triangles(mesh.indices.size());\n\n\tif (scan)\n\t\tmeshlets.resize(meshopt_buildMeshletsScan(&meshlets[0], &meshlet_vertices[0], &meshlet_triangles[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size(), max_vertices, max_triangles));\n\telse if (flex)\n\t\tmeshlets.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));\n\telse if (spatial)\n\t\tmeshlets.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));\n\telse // note: equivalent to the call of buildMeshletsFlex() with split_factor = 0 and min_triangles = max_triangles\n\t\tmeshlets.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));\n\n\tif (!dump)\n\t\tfor (size_t i = 0; i < meshlets.size(); ++i)\n\t\t\tmeshopt_optimizeMeshlet(&meshlet_vertices[meshlets[i].vertex_offset], &meshlet_triangles[meshlets[i].triangle_offset], meshlets[i].triangle_count, meshlets[i].vertex_count);\n\n\tif (meshlets.size())\n\t{\n\t\tconst meshopt_Meshlet& last = meshlets.back();\n\n\t\t// this is an example of how to trim the vertex/triangle arrays when copying data out to GPU storage\n\t\tmeshlet_vertices.resize(last.vertex_offset + last.vertex_count);\n\t\tmeshlet_triangles.resize(last.triangle_offset + last.triangle_count * 3);\n\t}\n\n\tdouble end = timestamp();\n\n\tif (dump)\n\t\tdumpObj(mesh.vertices, std::vector<unsigned>());\n\n\tdouble avg_vertices = 0;\n\tdouble avg_triangles = 0;\n\tdouble avg_boundary = 0;\n\tdouble avg_connected = 0;\n\tsize_t not_full = 0;\n\n\tstd::vector<int> boundary(mesh.vertices.size());\n\n\tfor (size_t i = 0; i < meshlets.size(); ++i)\n\t{\n\t\tconst meshopt_Meshlet& m = meshlets[i];\n\n\t\tfor (unsigned int j = 0; j < m.vertex_count; ++j)\n\t\t\tboundary[meshlet_vertices[m.vertex_offset + j]]++;\n\t}\n\n\tstd::vector<unsigned int> cluster;\n\n\tfor (size_t i = 0; i < meshlets.size(); ++i)\n\t{\n\t\tconst meshopt_Meshlet& m = meshlets[i];\n\n\t\tif (dump)\n\t\t{\n\t\t\tcluster.clear();\n\t\t\tfor (unsigned int j = 0; j < m.triangle_count * 3; ++j)\n\t\t\t\tcluster.push_back(meshlet_vertices[m.vertex_offset + meshlet_triangles[m.triangle_offset + j]]);\n\n\t\t\tchar cname[32];\n\t\t\tsnprintf(cname, sizeof(cname), \"ml_%d\\n\", int(i));\n\t\t\tdumpObj(cname, cluster);\n\t\t}\n\n\t\tavg_vertices += m.vertex_count;\n\t\tavg_triangles += m.triangle_count;\n\t\tnot_full += uniform ? m.triangle_count < max_triangles : m.vertex_count < max_vertices;\n\n\t\tfor (unsigned int j = 0; j < m.vertex_count; ++j)\n\t\t\tif (boundary[meshlet_vertices[m.vertex_offset + j]] > 1)\n\t\t\t\tavg_boundary += 1;\n\n\t\t// union-find vertices to check if the meshlet is connected\n\t\tint parents[256];\n\t\tfor (unsigned int j = 0; j < m.vertex_count; ++j)\n\t\t\tparents[j] = int(j);\n\n\t\tfor (unsigned int j = 0; j < m.triangle_count * 3; ++j)\n\t\t{\n\t\t\tint v0 = meshlet_triangles[m.triangle_offset + j];\n\t\t\tint v1 = meshlet_triangles[m.triangle_offset + j + (j % 3 == 2 ? -2 : 1)];\n\n\t\t\tv0 = follow(parents, v0);\n\t\t\tv1 = follow(parents, v1);\n\n\t\t\tparents[v0] = v1;\n\t\t}\n\n\t\tint roots = 0;\n\t\tfor (unsigned int j = 0; j < m.vertex_count; ++j)\n\t\t\troots += follow(parents, j) == int(j);\n\n\t\tassert(roots != 0);\n\t\tavg_connected += roots;\n\t}\n\n\tavg_vertices /= double(meshlets.size());\n\tavg_triangles /= double(meshlets.size());\n\tavg_boundary /= double(meshlets.size());\n\tavg_connected /= double(meshlets.size());\n\n\tprintf(\"Meshlets%c: %d meshlets (avg vertices %.1f, avg triangles %.1f, avg boundary %.1f, avg connected %.2f, not full %d) in %.2f msec\\n\",\n\t    scan ? 'S' : (flex ? 'F' : (spatial ? 'X' : (uniform ? 'U' : ' '))),\n\t    int(meshlets.size()), avg_vertices, avg_triangles, avg_boundary, avg_connected, int(not_full), (end - start) * 1000);\n\n\tfloat camera[3] = {100, 100, 100};\n\n\tsize_t rejected = 0;\n\tsize_t accepted = 0;\n\tdouble radius_mean = 0;\n\tdouble cone_mean = 0;\n\n\tstd::vector<float> radii(meshlets.size());\n\tstd::vector<float> cones(meshlets.size());\n\n\tdouble startc = timestamp();\n\tfor (size_t i = 0; i < meshlets.size(); ++i)\n\t{\n\t\tconst meshopt_Meshlet& m = meshlets[i];\n\n\t\tmeshopt_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));\n\n\t\tradii[i] = bounds.radius;\n\t\tcones[i] = 90.f - acosf(bounds.cone_cutoff) * (180.f / 3.1415926f);\n\n\t\tradius_mean += radii[i];\n\t\tcone_mean += cones[i];\n\n\t\t// trivial accept: we can't ever backface cull this meshlet\n\t\taccepted += (bounds.cone_cutoff >= 1);\n\n\t\t// perspective projection: dot(normalize(cone_apex - camera_position), cone_axis) > cone_cutoff\n\t\t// alternative formulation for perspective projection that doesn't use apex (and uses cluster bounding sphere instead):\n\t\t// dot(normalize(center - camera_position), cone_axis) > cone_cutoff + radius / length(center - camera_position)\n\t\tfloat cview[3] = {bounds.center[0] - camera[0], bounds.center[1] - camera[1], bounds.center[2] - camera[2]};\n\t\tfloat cviewlength = sqrtf(cview[0] * cview[0] + cview[1] * cview[1] + cview[2] * cview[2]);\n\n\t\trejected += cview[0] * bounds.cone_axis[0] + cview[1] * bounds.cone_axis[1] + cview[2] * bounds.cone_axis[2] >= bounds.cone_cutoff * cviewlength + bounds.radius;\n\t}\n\tdouble endc = timestamp();\n\n\tradius_mean /= double(meshlets.size());\n\tcone_mean /= double(meshlets.size());\n\n\tdouble radius_variance = 0;\n\n\tfor (size_t i = 0; i < meshlets.size(); ++i)\n\t\tradius_variance += (radii[i] - radius_mean) * (radii[i] - radius_mean);\n\n\tradius_variance /= double(meshlets.size() - 1);\n\n\tdouble radius_stddev = sqrt(radius_variance);\n\n\tsize_t meshlets_std = 0;\n\n\tfor (size_t i = 0; i < meshlets.size(); ++i)\n\t\tmeshlets_std += radii[i] < radius_mean + radius_stddev;\n\n\tprintf(\"Bounds   : radius mean %f stddev %f; %.1f%% meshlets under 1σ; cone angle %.1f°; cone reject %.1f%% trivial accept %.1f%% in %.2f msec\\n\",\n\t    radius_mean, radius_stddev,\n\t    double(meshlets_std) / double(meshlets.size()) * 100,\n\t    cone_mean, double(rejected) / double(meshlets.size()) * 100, double(accepted) / double(meshlets.size()) * 100,\n\t    (endc - startc) * 1000);\n}\n\nvoid spatialSort(const Mesh& mesh)\n{\n\ttypedef PackedVertexOct PV;\n\n\tstd::vector<PV> pv(mesh.vertices.size());\n\tpackMesh(pv, mesh.vertices);\n\n\tdouble start = timestamp();\n\n\tstd::vector<unsigned int> remap(mesh.vertices.size());\n\tmeshopt_spatialSortRemap(&remap[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex));\n\n\tdouble end = timestamp();\n\n\tmeshopt_remapVertexBuffer(&pv[0], &pv[0], mesh.vertices.size(), sizeof(PV), &remap[0]);\n\n\tstd::vector<unsigned char> vbuf(meshopt_encodeVertexBufferBound(mesh.vertices.size(), sizeof(PV)));\n\tvbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &pv[0], mesh.vertices.size(), sizeof(PV)));\n\n\tsize_t csize = compress(vbuf);\n\n\tprintf(\"Spatial  : %.1f bits/vertex (post-deflate %.1f bits/vertex); sort %.2f msec\\n\",\n\t    double(vbuf.size() * 8) / double(mesh.vertices.size()),\n\t    double(csize * 8) / double(mesh.vertices.size()),\n\t    (end - start) * 1000);\n}\n\nvoid spatialSortTriangles(const Mesh& mesh)\n{\n\ttypedef PackedVertexOct PV;\n\n\tMesh copy = mesh;\n\n\tdouble start = timestamp();\n\n\tmeshopt_spatialSortTriangles(&copy.indices[0], &copy.indices[0], mesh.indices.size(), &copy.vertices[0].px, copy.vertices.size(), sizeof(Vertex));\n\n\tdouble end = timestamp();\n\n\tmeshopt_optimizeVertexCache(&copy.indices[0], &copy.indices[0], copy.indices.size(), copy.vertices.size());\n\tmeshopt_optimizeVertexFetch(&copy.vertices[0], &copy.indices[0], copy.indices.size(), &copy.vertices[0], copy.vertices.size(), sizeof(Vertex));\n\n\tstd::vector<PV> pv(mesh.vertices.size());\n\tpackMesh(pv, copy.vertices);\n\n\tstd::vector<unsigned char> vbuf(meshopt_encodeVertexBufferBound(mesh.vertices.size(), sizeof(PV)));\n\tvbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &pv[0], mesh.vertices.size(), sizeof(PV)));\n\n\tstd::vector<unsigned char> ibuf(meshopt_encodeIndexBufferBound(mesh.indices.size(), mesh.vertices.size()));\n\tibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), &copy.indices[0], mesh.indices.size()));\n\n\tsize_t csizev = compress(vbuf);\n\tsize_t csizei = compress(ibuf);\n\n\tprintf(\"SpatialT : %.1f bits/vertex (post-deflate %.1f bits/vertex); %.1f bits/triangle (post-deflate %.1f bits/triangle); sort %.2f msec\\n\",\n\t    double(vbuf.size() * 8) / double(mesh.vertices.size()),\n\t    double(csizev * 8) / double(mesh.vertices.size()),\n\t    double(ibuf.size() * 8) / double(mesh.indices.size() / 3),\n\t    double(csizei * 8) / double(mesh.indices.size() / 3),\n\t    (end - start) * 1000);\n}\n\nvoid spatialClusterPoints(const Mesh& mesh, size_t cluster_size)\n{\n\ttypedef PackedVertexOct PV;\n\n\tdouble start = timestamp();\n\n\tstd::vector<unsigned int> index(mesh.vertices.size());\n\tmeshopt_spatialClusterPoints(&index[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), cluster_size);\n\n\tdouble end = timestamp();\n\n\tstd::vector<PV> pv(mesh.vertices.size());\n\tpackMesh(pv, mesh.vertices);\n\n\tstd::vector<PV> pvo(mesh.vertices.size());\n\tfor (size_t i = 0; i < index.size(); ++i)\n\t\tpvo[i] = pv[index[i]];\n\n\tstd::vector<unsigned char> vbuf(meshopt_encodeVertexBufferBound(mesh.vertices.size(), sizeof(PV)));\n\tvbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &pvo[0], mesh.vertices.size(), sizeof(PV)));\n\n\tsize_t csize = compress(vbuf);\n\n\tprintf(\"SpatialCP: %.1f bits/vertex (post-deflate %.1f bits/vertex); sort %.2f msec\\n\",\n\t    double(vbuf.size() * 8) / double(mesh.vertices.size()),\n\t    double(csize * 8) / double(mesh.vertices.size()),\n\t    (end - start) * 1000);\n}\n\nvoid tessellationAdjacency(const Mesh& mesh)\n{\n\tdouble start = timestamp();\n\n\t// 12 indices per input triangle\n\tstd::vector<unsigned int> tessib(mesh.indices.size() * 4);\n\tmeshopt_generateTessellationIndexBuffer(&tessib[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex));\n\n\tdouble middle = timestamp();\n\n\t// 6 indices per input triangle\n\tstd::vector<unsigned int> adjib(mesh.indices.size() * 2);\n\tmeshopt_generateAdjacencyIndexBuffer(&adjib[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex));\n\n\tdouble end = timestamp();\n\n\tprintf(\"Tesselltn: %d patches in %.2f msec\\n\", int(mesh.indices.size() / 3), (middle - start) * 1000);\n\tprintf(\"Adjacency: %d patches in %.2f msec\\n\", int(mesh.indices.size() / 3), (end - middle) * 1000);\n}\n\nvoid provoking(const Mesh& mesh)\n{\n\tdouble start = timestamp();\n\n\t// worst case number of vertices: vertex count + triangle count\n\tstd::vector<unsigned int> pib(mesh.indices.size());\n\tstd::vector<unsigned int> reorder(mesh.vertices.size() + mesh.indices.size() / 3);\n\n\tsize_t pcount = meshopt_generateProvokingIndexBuffer(&pib[0], &reorder[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size());\n\treorder.resize(pcount);\n\n\tdouble end = timestamp();\n\n\t// validate invariant: pib[i] == i/3 for provoking vertices\n\tfor (size_t i = 0; i < mesh.indices.size(); i += 3)\n\t\tassert(pib[i] == i / 3);\n\n\t// validate invariant: reorder[pib[x]] == ib[x] modulo triangle rotation\n\t// note: this is technically not promised by the interface (it may reorder triangles!), it just happens to hold right now\n\tfor (size_t i = 0; i < mesh.indices.size(); i += 3)\n\t{\n\t\tunsigned int a = mesh.indices[i + 0], b = mesh.indices[i + 1], c = mesh.indices[i + 2];\n\t\tunsigned int ra = reorder[pib[i + 0]], rb = reorder[pib[i + 1]], rc = reorder[pib[i + 2]];\n\n\t\tassert((a == ra && b == rb && c == rc) || (a == rb && b == rc && c == ra) || (a == rc && b == ra && c == rb));\n\t}\n\n\t// best case number of vertices: max(vertex count, triangle count), assuming non-redundant indexing (all vertices are used)\n\t// note: this is a lower bound, and it's not theoretically possible on some meshes;\n\t// 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\n\t// however, the best case for that union is 44 vertices (24 cube vertices + 20 icosahedron vertices due to provoking invariant)\n\tsize_t bestv = mesh.vertices.size() > mesh.indices.size() / 3 ? mesh.vertices.size() : mesh.indices.size() / 3;\n\n\tprintf(\"Provoking: %d triangles / %d vertices (+%.1f%% extra) in %.2f msec\\n\",\n\t    int(mesh.indices.size() / 3), int(pcount), double(pcount) / double(bestv) * 100.0 - 100.0, (end - start) * 1000);\n}\n\nstatic int reindexCompare(void* context, unsigned int lhs, unsigned int rhs)\n{\n\tconst Vertex* vertices = static_cast<Vertex*>(context);\n\tconst Vertex& lv = vertices[lhs];\n\tconst Vertex& rv = vertices[rhs];\n\n\tfloat ln = lv.nx * lv.nx + lv.ny * lv.ny + lv.nz * lv.nz;\n\tfloat rn = rv.nx * rv.nx + rv.ny * rv.ny + rv.nz * rv.nz;\n\n\t// 1/1024px UV tolerance, 3 degree normal tolerance\n\treturn fabsf(lv.tx - rv.tx) < 1e-3f &&\n\t       fabsf(lv.ty - rv.ty) < 1e-3f &&\n\t       (lv.nx * rv.nx + lv.ny * rv.ny + lv.nz * rv.nz >= 0.9986f * sqrtf(ln * rn));\n}\n\nvoid reindexFuzzy(const Mesh& mesh)\n{\n\tstd::vector<PackedVertex> pv(mesh.vertices.size());\n\tpackMesh(pv, mesh.vertices);\n\n\tstd::vector<unsigned int> remap(mesh.vertices.size());\n\n\tdouble start = timestamp();\n\n\tsize_t up = meshopt_generateVertexRemap(&remap[0], &mesh.indices[0], mesh.indices.size(), &pv[0], mesh.vertices.size(), sizeof(PackedVertex));\n\n\tdouble middle = timestamp();\n\n\tsize_t uf = meshopt_generateVertexRemapCustom(&remap[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), reindexCompare, const_cast<Vertex*>(&mesh.vertices[0]));\n\n\tdouble end = timestamp();\n\n\tprintf(\"ReindexQ : %d vertices => %d unique vertices in %.2f msec\\n\",\n\t    int(mesh.vertices.size()), int(up), (middle - start) * 1000);\n\tprintf(\"ReindexF : %d vertices => %d unique vertices in %.2f msec\\n\",\n\t    int(mesh.vertices.size()), int(uf), (end - middle) * 1000);\n}\n\nvoid coverage(const Mesh& mesh)\n{\n\tdouble start = timestamp();\n\n\tmeshopt_CoverageStatistics cs = meshopt_analyzeCoverage(&mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex));\n\n\tdouble end = timestamp();\n\n\tprintf(\"Coverage : X %.1f%% Y %.1f%% Z %.1f%% in %.2f msec\\n\",\n\t    cs.coverage[0] * 100, cs.coverage[1] * 100, cs.coverage[2] * 100, (end - start) * 1000);\n}\n\nvoid nanite(const std::vector<Vertex>& vertices, const std::vector<unsigned int>& indices); // nanite.cpp\n\nbool loadMesh(Mesh& mesh, const char* path)\n{\n\tdouble start = timestamp();\n\tdouble middle;\n\tmesh = parseObj(path, middle);\n\tdouble end = timestamp();\n\n\tif (mesh.vertices.empty())\n\t{\n\t\tprintf(\"Mesh %s is empty, skipping\\n\", path);\n\t\treturn false;\n\t}\n\n\tprintf(\"# %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);\n\treturn true;\n}\n\nvoid processDeinterleaved(const char* path)\n{\n\t// Most algorithms in the library work out of the box with deinterleaved geometry, but some require slightly special treatment;\n\t// this code runs a simplified version of complete opt. pipeline using deinterleaved geo. There's no compression performed but you\n\t// can trivially run it by quantizing all elements and running meshopt_encodeVertexBuffer once for each vertex stream.\n\tfastObjMesh* obj = fast_obj_read(path);\n\tif (!obj)\n\t{\n\t\tprintf(\"Error loading %s: file not found\\n\", path);\n\t\treturn;\n\t}\n\n\tsize_t total_indices = 0;\n\n\tfor (unsigned int i = 0; i < obj->face_count; ++i)\n\t\ttotal_indices += 3 * (obj->face_vertices[i] - 2);\n\n\tstd::vector<float> unindexed_pos(total_indices * 3);\n\tstd::vector<float> unindexed_nrm(total_indices * 3);\n\tstd::vector<float> unindexed_uv(total_indices * 2);\n\n\tsize_t vertex_offset = 0;\n\tsize_t index_offset = 0;\n\n\tfor (unsigned int i = 0; i < obj->face_count; ++i)\n\t{\n\t\tfor (unsigned int j = 0; j < obj->face_vertices[i]; ++j)\n\t\t{\n\t\t\tfastObjIndex gi = obj->indices[index_offset + j];\n\n\t\t\t// triangulate polygon on the fly; offset-3 is always the first polygon vertex\n\t\t\tif (j >= 3)\n\t\t\t{\n\t\t\t\tmemcpy(&unindexed_pos[(vertex_offset + 0) * 3], &unindexed_pos[(vertex_offset - 3) * 3], 3 * sizeof(float));\n\t\t\t\tmemcpy(&unindexed_nrm[(vertex_offset + 0) * 3], &unindexed_nrm[(vertex_offset - 3) * 3], 3 * sizeof(float));\n\t\t\t\tmemcpy(&unindexed_uv[(vertex_offset + 0) * 2], &unindexed_uv[(vertex_offset - 3) * 2], 2 * sizeof(float));\n\t\t\t\tmemcpy(&unindexed_pos[(vertex_offset + 1) * 3], &unindexed_pos[(vertex_offset - 1) * 3], 3 * sizeof(float));\n\t\t\t\tmemcpy(&unindexed_nrm[(vertex_offset + 1) * 3], &unindexed_nrm[(vertex_offset - 1) * 3], 3 * sizeof(float));\n\t\t\t\tmemcpy(&unindexed_uv[(vertex_offset + 1) * 2], &unindexed_uv[(vertex_offset - 1) * 2], 2 * sizeof(float));\n\t\t\t\tvertex_offset += 2;\n\t\t\t}\n\n\t\t\tmemcpy(&unindexed_pos[vertex_offset * 3], &obj->positions[gi.p * 3], 3 * sizeof(float));\n\t\t\tmemcpy(&unindexed_nrm[vertex_offset * 3], &obj->normals[gi.n * 3], 3 * sizeof(float));\n\t\t\tmemcpy(&unindexed_uv[vertex_offset * 2], &obj->texcoords[gi.t * 2], 2 * sizeof(float));\n\t\t\tvertex_offset++;\n\t\t}\n\n\t\tindex_offset += obj->face_vertices[i];\n\t}\n\n\tfast_obj_destroy(obj);\n\n\tdouble start = timestamp();\n\n\tmeshopt_Stream streams[] = {\n\t    {&unindexed_pos[0], sizeof(float) * 3, sizeof(float) * 3},\n\t    {&unindexed_nrm[0], sizeof(float) * 3, sizeof(float) * 3},\n\t    {&unindexed_uv[0], sizeof(float) * 2, sizeof(float) * 2},\n\t};\n\n\tstd::vector<unsigned int> remap(total_indices);\n\n\tsize_t total_vertices = meshopt_generateVertexRemapMulti(&remap[0], NULL, total_indices, total_indices, streams, sizeof(streams) / sizeof(streams[0]));\n\n\tstd::vector<unsigned int> indices(total_indices);\n\tmeshopt_remapIndexBuffer(&indices[0], NULL, total_indices, &remap[0]);\n\n\tstd::vector<float> pos(total_vertices * 3);\n\tmeshopt_remapVertexBuffer(&pos[0], &unindexed_pos[0], total_indices, sizeof(float) * 3, &remap[0]);\n\n\tstd::vector<float> nrm(total_vertices * 3);\n\tmeshopt_remapVertexBuffer(&nrm[0], &unindexed_nrm[0], total_indices, sizeof(float) * 3, &remap[0]);\n\n\tstd::vector<float> uv(total_vertices * 2);\n\tmeshopt_remapVertexBuffer(&uv[0], &unindexed_uv[0], total_indices, sizeof(float) * 2, &remap[0]);\n\n\tdouble reindex = timestamp();\n\n\tmeshopt_optimizeVertexCache(&indices[0], &indices[0], total_indices, total_vertices);\n\n\tmeshopt_optimizeVertexFetchRemap(&remap[0], &indices[0], total_indices, total_vertices);\n\tmeshopt_remapVertexBuffer(&pos[0], &pos[0], total_vertices, sizeof(float) * 3, &remap[0]);\n\tmeshopt_remapVertexBuffer(&nrm[0], &nrm[0], total_vertices, sizeof(float) * 3, &remap[0]);\n\tmeshopt_remapVertexBuffer(&uv[0], &uv[0], total_vertices, sizeof(float) * 2, &remap[0]);\n\n\tdouble optimize = timestamp();\n\n\t// note: since shadow index buffer is computed based on regular vertex/index buffer, the stream points at the indexed data - not unindexed_pos\n\tmeshopt_Stream shadow_stream = {&pos[0], sizeof(float) * 3, sizeof(float) * 3};\n\n\tstd::vector<unsigned int> shadow_indices(total_indices);\n\tmeshopt_generateShadowIndexBufferMulti(&shadow_indices[0], &indices[0], total_indices, total_vertices, &shadow_stream, 1);\n\n\tmeshopt_optimizeVertexCache(&shadow_indices[0], &shadow_indices[0], total_indices, total_vertices);\n\n\tdouble shadow = timestamp();\n\n\tprintf(\"Deintrlvd: %d vertices, reindexed in %.2f msec, optimized in %.2f msec, generated & optimized shadow indices in %.2f msec\\n\",\n\t    int(total_vertices), (reindex - start) * 1000, (optimize - reindex) * 1000, (shadow - optimize) * 1000);\n}\n\nvoid process(const char* path)\n{\n\tMesh mesh;\n\tif (!loadMesh(mesh, path))\n\t\treturn;\n\n\toptimize(mesh);\n\toptimize(mesh, /* fifo= */ true);\n\n\tMesh copy = mesh;\n\tmeshopt_optimizeVertexCache(&copy.indices[0], &copy.indices[0], copy.indices.size(), copy.vertices.size());\n\tmeshopt_optimizeVertexFetch(&copy.vertices[0], &copy.indices[0], copy.indices.size(), &copy.vertices[0], copy.vertices.size(), sizeof(Vertex));\n\n\tMesh copystrip = mesh;\n\tmeshopt_optimizeVertexCacheStrip(&copystrip.indices[0], &copystrip.indices[0], copystrip.indices.size(), copystrip.vertices.size());\n\tmeshopt_optimizeVertexFetch(&copystrip.vertices[0], &copystrip.indices[0], copystrip.indices.size(), &copystrip.vertices[0], copystrip.vertices.size(), sizeof(Vertex));\n\n\tstripify(copy, false, ' ');\n\tstripify(copy, /* use_restart= */ true, 'R');\n\tstripify(copystrip, /* use_restart= */ true, 'S');\n\n\tmeshlets(copy, /* scan= */ true);\n\tmeshlets(copy, /* scan= */ false);\n\tmeshlets(copy, /* scan= */ false, /* uniform= */ true);\n\tmeshlets(copy, /* scan= */ false, /* uniform= */ false, /* flex= */ true);\n\tmeshlets(copy, /* scan= */ false, /* uniform= */ true, /* flex= */ false, /* spatial= */ true);\n\n\tshadow(copy);\n\ttessellationAdjacency(copy);\n\tprovoking(copy);\n\n\tencodeIndex(copy, ' ');\n\tencodeIndex(copystrip, 'S');\n\n\tstd::vector<unsigned int> strip(meshopt_stripifyBound(copystrip.indices.size()));\n\tstrip.resize(meshopt_stripify(&strip[0], &copystrip.indices[0], copystrip.indices.size(), copystrip.vertices.size(), 0));\n\n\tencodeIndexSequence(strip, copystrip.vertices.size(), 'D');\n\n\tpackVertex<PackedVertex>(copy, \"\");\n\tencodeVertex<PackedVertex>(copy, \"\");\n\tencodeVertex<PackedVertexOct>(copy, \"O\");\n\n\tencodeMeshlets(mesh, 64, 96);\n\n\tsimplify(mesh);\n\tsimplify(mesh, 0.1f, meshopt_SimplifyPrune);\n\tsimplifyAttr(mesh);\n\tsimplifyAttr(mesh, 0.1f, meshopt_SimplifyPermissive);\n\tsimplifyUpdate(mesh);\n\tsimplifyUpdate(mesh, 0.1f, meshopt_SimplifyPermissive);\n\tsimplifySloppy(mesh);\n\tsimplifyComplete(mesh);\n\tsimplifyPoints(mesh);\n\tsimplifyClusters(mesh);\n\n\tspatialSort(mesh);\n\tspatialSortTriangles(mesh);\n\tspatialClusterPoints(mesh, 64);\n\n\treindexFuzzy(mesh);\n\tcoverage(mesh);\n\n\tif (path)\n\t\tprocessDeinterleaved(path);\n}\n\nvoid processDev(const char* path)\n{\n\tMesh mesh;\n\tif (!loadMesh(mesh, path))\n\t\treturn;\n\n\tencodeMeshlets(mesh, 32, 48);\n\tencodeMeshlets(mesh, 64, 64);\n\tencodeMeshlets(mesh, 64, 96);\n}\n\nvoid processNanite(const char* path)\n{\n\tMesh mesh;\n\tif (!loadMesh(mesh, path))\n\t\treturn;\n\n\tnanite(mesh.vertices, mesh.indices);\n}\n\nint main(int argc, char** argv)\n{\n\tvoid runTests();\n\n\tif (argc == 1)\n\t{\n\t\trunTests();\n\t}\n\telse\n\t{\n\t\tif (strcmp(argv[1], \"-d\") == 0)\n\t\t{\n\t\t\tfor (int i = 2; i < argc; ++i)\n\t\t\t\tprocessDev(argv[i]);\n\t\t}\n\t\telse if (strcmp(argv[1], \"-n\") == 0)\n\t\t{\n\t\t\tfor (int i = 2; i < argc; ++i)\n\t\t\t\tprocessNanite(argv[i]);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor (int i = 1; i < argc; ++i)\n\t\t\t\tprocess(argv[i]);\n\n\t\t\trunTests();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "demo/meshletdec.slang",
    "content": "/**\n * meshletdec.slang - an example GPU decoder for meshlet data encoded using meshopt_encodeMeshlet\n * This is intended to be used as a starting point for applications that want to decode meshlet data on the GPU.\n *\n * The shader exposes an entrypoint, decodeMeshlets, that decodes a set of meshlets; each meshlet is decoded independently,\n * and the output vertex/triangle data is written as uint32 per element (triangle data is written as 0xccbbaa).\n * This matches the output format for meshopt_decodeMeshlet with vertex_size=4 triangle_size=4. If alternative formats are\n * needed, the code should be changed to output them; note that for triangle data, it may make sense to output data to shared\n * memory to be able to use larger aligned 32-bit writes to global memory after that.\n *\n * Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\n * This code is distributed under the MIT License. See notice at the end of this file.\n */\n\nstruct MeshletDesc\n{\n\tuint stream_offset;\n\tuint output_offset;\n\tuint16_t encoded_size;\n\tuint8_t vertex_count;\n\tuint8_t triangle_count;\n};\n\n[[vk::binding(0)]]\nStructuredBuffer<uint8_t> gStream : register(t0);\n[[vk::binding(1)]]\nStructuredBuffer<MeshletDesc> gMeshlets : register(t1);\n[[vk::binding(2)]]\nRWStructuredBuffer<uint> gOutput : register(u2);\n[[vk::binding(3)]]\ncbuffer MeshletConfigCB : register(b3) { uint gMeshletCount; }\n\nuint decodeVertices(uint out_vertices, uint ctrl, uint data, uint bound, uint vertex_count)\n{\n\tuint last = ~0u;\n\n\tfor (uint i = 0; i < vertex_count; i += 4)\n\t{\n\t\tif (data > bound)\n\t\t\treturn ~0u;\n\n\t\tuint code4 = uint(gStream[ctrl + i / 4]);\n\n\t\tfor (int k = 0; k < 4; ++k)\n\t\t{\n\t\t\tint code = ((code4 >> k) & 1) | ((code4 >> (k + 3)) & 2);\n\t\t\tint length = code4 == 0xff ? 4 : code;\n\n\t\t\t// branchlessly read up to 4 bytes\n\t\t\tuint mask = (length == 4) ? ~0u : (1 << (8 * length)) - 1;\n\t\t\tuint v = (uint(gStream[data + 0]) | (uint(gStream[data + 1]) << 8) | (uint(gStream[data + 2]) << 16) | (uint(gStream[data + 3]) << 24)) & mask;\n\n\t\t\t// unzigzag + 1\n\t\t\tuint d = (v >> 1) ^ -int(v & 1);\n\t\t\tuint r = last + d + 1;\n\n\t\t\tif (i + k < vertex_count)\n\t\t\t\tgOutput[out_vertices + i + k] = r;\n\n\t\t\tdata += length;\n\t\t\tlast = r;\n\t\t}\n\t}\n\n\treturn data;\n}\n\nuint decodeTriangle(uint code, uint extra0, uint extra1, uint extra2, inout uint fifo0, inout uint fifo1, inout uint fifo2, inout uint next, inout uint extra)\n{\n\t// reuse: 0-1 extra vertices\n\tuint fifo = code < 4 ? fifo0 : (code < 8 ? fifo1 : fifo2);\n\tuint edge = fifo >> ((code << 3) & 16); // shift by 16 if bit 1 is set (odd edge for each triangle)\n\tuint c_reuse = (code & 1) == 1 ? extra0 : next;\n\n\t// restart: 0-3 extra vertices\n\tuint extran = code & 3;\n\tuint a = extran > 0 ? extra0 : next;\n\tuint b = extran > 1 ? extra1 : next + (1 - extran);\n\tuint c = extran > 2 ? extra2 : next + (2 - extran);\n\n\t// select between reuse and restart and repack triangle into edge format (0xcbac)\n\ta = code >= 12 ? a : (edge >> 8) & 0xff;\n\tb = code >= 12 ? b : edge & 0xff;\n\tc = code >= 12 ? c : c_reuse;\n\n\tuint tri = c | (a << 8) | (b << 16) | (c << 24);\n\n\t// advance next/extra; reuse codes use 1 lsb for extra count, restart codes use 2 lsbs\n\tuint extrab = code < 12 ? 1 : 3;\n\tnext += extrab - code & extrab;\n\textra += code & extrab;\n\n\t// rotate fifo\n\tfifo2 = fifo1;\n\tfifo1 = fifo0;\n\tfifo0 = tri;\n\n\t// output triangle is stored without extra edge vertex (0xcbac => 0xcba)\n\treturn tri >> 8;\n}\n\nuint decodeTriangles(uint out_triangles, uint codes, uint extra, uint bound, uint triangle_count)\n{\n\tuint next = 0;\n\tuint fifo0 = 0, fifo1 = 0, fifo2 = 0; // two edge fifo entries in one uint: 0xcbac\n\n\tfor (uint i = 0; i < triangle_count; i += 2)\n\t{\n\t\tif (extra > bound)\n\t\t\treturn ~0u;\n\n\t\tuint codeg = uint(gStream[codes + i / 2]);\n\n\t\t// first triangle\n\t\tuint extra0 = uint(gStream[extra + 0]);\n\t\tuint extra1 = uint(gStream[extra + 1]);\n\t\tuint extra2 = uint(gStream[extra + 2]);\n\t\tuint tri = decodeTriangle(codeg & 15, extra0, extra1, extra2, fifo0, fifo1, fifo2, next, extra);\n\n\t\tgOutput[out_triangles + i] = tri;\n\n\t\t// second triangle, if any\n\t\textra0 = uint(gStream[extra + 0]);\n\t\textra1 = uint(gStream[extra + 1]);\n\t\textra2 = uint(gStream[extra + 2]);\n\t\ttri = decodeTriangle(codeg >> 4, extra0, extra1, extra2, fifo0, fifo1, fifo2, next, extra);\n\n\t\tif (i + 1 < triangle_count)\n\t\t\tgOutput[out_triangles + i + 1] = tri;\n\t}\n\n\treturn extra;\n}\n\nint decodeMeshlet(uint out_vertices, uint vertex_count, uint out_triangles, uint triangle_count, uint buffer, uint buffer_size)\n{\n\tuint codes_size = (triangle_count + 1) / 2;\n\tuint ctrl_size = (vertex_count + 3) / 4;\n\tuint gap_size = (codes_size + ctrl_size < 16) ? 16 - (codes_size + ctrl_size) : 0;\n\n\tif (buffer_size < codes_size + ctrl_size + gap_size)\n\t\treturn -2;\n\n\tuint end = buffer + buffer_size;\n\tuint codes = end - codes_size;\n\tuint ctrl = codes - ctrl_size;\n\tuint data = buffer;\n\n\t// gap ensures we have at least 16 bytes available after bound; this allows decoder to over-read safely\n\tuint bound = ctrl - gap_size;\n\n\tdata = decodeVertices(out_vertices, ctrl, data, bound, vertex_count);\n\tif (data == ~0u)\n\t\treturn -2;\n\n\tdata = decodeTriangles(out_triangles, codes, data, bound, triangle_count);\n\tif (data == ~0u)\n\t\treturn -2;\n\n\treturn (data == bound) ? 0 : -3;\n}\n\n[shader(\"compute\")]\n[numthreads(32, 1, 1)]\nvoid decodeMeshlets(uint3 dispatch_thread_id: SV_DispatchThreadID)\n{\n\tuint meshlet_count = gMeshletCount;\n\n\tuint tid = dispatch_thread_id.x;\n\tif (tid >= meshlet_count)\n\t\treturn;\n\n\tMeshletDesc desc = gMeshlets[tid];\n\tuint out_vertices = desc.output_offset;\n\tuint out_triangles = desc.output_offset + uint(desc.vertex_count);\n\n\tint rc = decodeMeshlet(out_vertices, uint(desc.vertex_count), out_triangles, uint(desc.triangle_count), desc.stream_offset, uint(desc.encoded_size));\n\n\t// if decoding failed, we write 0xff.. to the first word of the output data\n\t// this can be adjusted arbitrarily; for example, a separate buffer with a single status for the entire stream could be used\n\t// note that decoding fails only if the input data is corrupt; so this may not be required at all depending on the requirements\n\tif (rc < 0)\n\t\tgOutput[desc.output_offset] = ~0u;\n}\n\n/**\n * Copyright (c) 2016-2026 Arseny Kapoulkine\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n */\n"
  },
  {
    "path": "demo/nanite.cpp",
    "content": "// For reference, see the original Nanite paper:\n// Brian Karis. Nanite: A Deep Dive. 2021\n#include \"../src/meshoptimizer.h\"\n\n#include <float.h>\n#include <math.h>\n#include <stdio.h>\n#include <string.h>\n\n#include <algorithm>\n#include <vector>\n\n#define CLUSTERLOD_IMPLEMENTATION\n#include \"clusterlod.h\"\n\nstruct Vertex\n{\n\tfloat px, py, pz;\n\tfloat nx, ny, nz;\n\tfloat tx, ty;\n};\n\n// computes approximate (perspective) projection error of a cluster in screen space (0..1; multiply by screen height to get pixels)\n// camera_proj is projection[1][1], or cot(fovy/2); camera_znear is *positive* near plane distance\n// for DAG cut to be valid, boundsError must be monotonic: it must return a larger error for parent cluster\n// for simplicity, we ignore perspective distortion and use rotationally invariant projection size estimation\nstatic float boundsError(const clodBounds& bounds, float camera_x, float camera_y, float camera_z, float camera_proj, float camera_znear)\n{\n\tfloat dx = bounds.center[0] - camera_x, dy = bounds.center[1] - camera_y, dz = bounds.center[2] - camera_z;\n\tfloat d = sqrtf(dx * dx + dy * dy + dz * dz) - bounds.radius;\n\treturn bounds.error / (d > camera_znear ? d : camera_znear) * (camera_proj * 0.5f);\n}\n\nstatic int measureComponents(std::vector<int>& parents, const std::vector<unsigned int>& indices, const std::vector<unsigned int>& remap);\nstatic size_t measureBoundary(std::vector<int>& used, const std::vector<std::vector<unsigned int> >& clusters, const std::vector<unsigned int>& remap);\nstatic float sahOverhead(const std::vector<std::vector<unsigned int> >& clusters, const std::vector<Vertex>& vertices);\n\nvoid dumpObj(const std::vector<Vertex>& vertices, const std::vector<unsigned int>& indices, bool recomputeNormals = false);\nvoid dumpObj(const char* section, const std::vector<unsigned int>& indices);\n\nvoid nanite(const std::vector<Vertex>& vertices, const std::vector<unsigned int>& indices)\n{\n#ifdef _MSC_VER\n\tstatic const char* dump = NULL; // tired of C4996\n\tstatic const char* clrt = NULL;\n#else\n\tstatic const char* dump = getenv(\"DUMP\");\n\tstatic const char* clrt = getenv(\"CLRT\");\n#endif\n\n\tclodConfig config = clodDefaultConfig(/*max_triangles=*/128);\n\n\tif (clrt)\n\t{\n\t\tconfig = clodDefaultConfigRT(config.max_triangles);\n\t\tconfig.cluster_fill_weight = float(atof(clrt));\n\t}\n\n\tconst float attribute_weights[3] = {0.5f, 0.5f, 0.5f};\n\n\tclodMesh mesh = {};\n\tmesh.indices = indices.data();\n\tmesh.index_count = indices.size();\n\tmesh.vertex_count = vertices.size();\n\tmesh.vertex_positions = &vertices[0].px;\n\tmesh.vertex_positions_stride = sizeof(Vertex);\n\tmesh.vertex_attributes = &vertices[0].nx;\n\tmesh.vertex_attributes_stride = sizeof(Vertex);\n\tmesh.attribute_weights = attribute_weights;\n\tmesh.attribute_count = sizeof(attribute_weights) / sizeof(attribute_weights[0]);\n\tmesh.attribute_protect_mask = (1 << 3) | (1 << 4); // protect UV seams, maps to Vertex::tx/ty\n\n\tstruct Stats\n\t{\n\t\tsize_t groups;\n\t\tsize_t clusters;\n\t\tsize_t triangles;\n\t\tsize_t vertices;\n\n\t\tsize_t full_clusters;\n\t\tsize_t singleton_groups;\n\n\t\tsize_t stuck_clusters;\n\t\tsize_t stuck_triangles;\n\n\t\tdouble radius;\n\n\t\tstd::vector<std::vector<unsigned int> > indices; // for detailed connectivity analysis\n\t};\n\n\tstd::vector<Stats> stats;\n\tstd::vector<clodBounds> groups;\n\n\tstd::vector<std::vector<unsigned int> > cut;\n\tint cut_level = dump ? atoi(dump) : -2;\n\n\t// for testing purposes, we can compute a DAG cut from a given viewpoint and dump it as an OBJ\n\tfloat maxx = 0.f, maxy = 0.f, maxz = 0.f;\n\tfor (size_t i = 0; i < vertices.size(); ++i)\n\t{\n\t\tmaxx = std::max(maxx, vertices[i].px * 2);\n\t\tmaxy = std::max(maxy, vertices[i].py * 2);\n\t\tmaxz = std::max(maxz, vertices[i].pz * 2);\n\t}\n\n\tfloat threshold = 2e-3f; // 2 pixels at 1080p\n\tfloat fovy = 60.f;\n\tfloat znear = 1e-2f;\n\tfloat proj = 1.f / tanf(fovy * 3.1415926f / 180.f * 0.5f);\n\n\tclodBuild(config, mesh, [&](clodGroup group, const clodCluster* clusters, size_t cluster_count) -> int { // clang-format!\n\t\tif (stats.size() <= size_t(group.depth))\n\t\t\tstats.push_back({});\n\n\t\tStats& level = stats[group.depth];\n\n\t\tlevel.groups++;\n\t\tlevel.clusters += cluster_count;\n\t\tif (group.simplified.error == FLT_MAX)\n\t\t\tlevel.stuck_clusters += cluster_count;\n\t\tlevel.singleton_groups += cluster_count == 1;\n\n\t\tfor (size_t i = 0; i < cluster_count; ++i)\n\t\t{\n\t\t\tconst clodCluster& cluster = clusters[i];\n\n\t\t\tlevel.triangles += cluster.index_count / 3;\n\t\t\tif (group.simplified.error == FLT_MAX)\n\t\t\t\tlevel.stuck_triangles += cluster.index_count / 3;\n\t\t\tlevel.vertices += cluster.vertex_count;\n\n\t\t\tlevel.full_clusters += (cluster.index_count == config.max_triangles * 3);\n\t\t\tlevel.radius += cluster.bounds.radius;\n\n\t\t\tlevel.indices.push_back(std::vector<unsigned int>(cluster.indices, cluster.indices + cluster.index_count));\n\n\t\t\t// when requesting DAG cut at a given level, we need to render all terminal clusters at lower depth as well\n\t\t\tif (cut_level >= 0 && (group.depth == cut_level || (group.depth < cut_level && group.simplified.error == FLT_MAX)))\n\t\t\t\tcut.push_back(std::vector<unsigned int>(cluster.indices, cluster.indices + cluster.index_count));\n\n\t\t\t// when requesting DAG cut from a viewpoint, we need to check if each cluster is the least detailed cluster that passes the error threshold\n\t\t\tif (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)\n\t\t\t\tcut.push_back(std::vector<unsigned int>(cluster.indices, cluster.indices + cluster.index_count));\n\t\t}\n\n\t\tlevel.indices.push_back(std::vector<unsigned int>()); // mark end of group for measureBoundary\n\n\t\tgroups.push_back(group.simplified);\n\t\treturn int(groups.size() - 1);\n\t});\n\n\t// for cluster connectivity analysis and boundary statistics, we need a position-only remap that maps vertices with the same position to the same index\n\tstd::vector<unsigned int> remap(vertices.size());\n\tmeshopt_generatePositionRemap(&remap[0], &vertices[0].px, vertices.size(), sizeof(Vertex));\n\n\tstd::vector<int> used(vertices.size());\n\n\tsize_t lowest_clusters = 0;\n\tsize_t lowest_triangles = 0;\n\n\tfor (size_t i = 0; i < stats.size(); ++i)\n\t{\n\t\tStats& level = stats[i];\n\n\t\tlowest_clusters += level.stuck_clusters;\n\t\tlowest_triangles += level.stuck_triangles;\n\n\t\tsize_t connected = 0;\n\t\tfor (const auto& cluster : level.indices)\n\t\t\tconnected += measureComponents(used, cluster, remap);\n\n\t\tsize_t boundary = measureBoundary(used, level.indices, remap);\n\t\tfloat saho = clrt ? sahOverhead(level.indices, vertices) : 0.f;\n\n\t\tdouble inv_clusters = 1.0 / double(level.clusters);\n\n\t\tprintf(\"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\",\n\t\t    int(i), int(level.clusters),\n\t\t    double(level.full_clusters) * inv_clusters * 100, double(level.triangles) * inv_clusters, double(level.vertices) * inv_clusters,\n\t\t    double(connected) * inv_clusters, double(boundary) * inv_clusters,\n\t\t    double(level.clusters) / double(level.groups), int(level.singleton_groups),\n\t\t    saho, level.radius * inv_clusters,\n\t\t    int(level.triangles));\n\t\tif (level.stuck_clusters && level.clusters > 1)\n\t\t\tprintf(\"; stuck %d clusters (%d triangles)\", int(level.stuck_clusters), int(level.stuck_triangles));\n\t\tprintf(\"\\n\");\n\t}\n\n\tprintf(\"lowest lod: %d clusters, %d triangles\\n\", int(lowest_clusters), int(lowest_triangles));\n\n\tif (cut_level >= -1)\n\t{\n\t\tsize_t cut_tris = 0;\n\t\tfor (auto& cluster : cut)\n\t\t\tcut_tris += cluster.size() / 3;\n\n\t\tif (cut_level >= 0)\n\t\t\tprintf(\"cut (level %d): %d triangles\\n\", cut_level, int(cut_tris));\n\t\telse\n\t\t\tprintf(\"cut (error %.3f): %d triangles\\n\", threshold, int(cut_tris));\n\n\t\tdumpObj(vertices, std::vector<unsigned int>());\n\n\t\tfor (auto& cluster : cut)\n\t\t\tdumpObj(\"cluster\", cluster);\n\t}\n}\n\n// What follows is code that is helpful for collecting metrics, visualizing cuts, etc.\n// This code is not used in the actual clustering implementation and can be ignored.\nstatic int follow(std::vector<int>& parents, int index)\n{\n\twhile (index != parents[index])\n\t{\n\t\tint parent = parents[index];\n\t\tparents[index] = parents[parent];\n\t\tindex = parent;\n\t}\n\n\treturn index;\n}\n\nstatic int measureComponents(std::vector<int>& parents, const std::vector<unsigned int>& indices, const std::vector<unsigned int>& remap)\n{\n\tassert(parents.size() == remap.size());\n\n\tfor (size_t i = 0; i < indices.size(); ++i)\n\t{\n\t\tunsigned int v = remap[indices[i]];\n\t\tparents[v] = v;\n\t}\n\n\tfor (size_t i = 0; i < indices.size(); ++i)\n\t{\n\t\tint v0 = remap[indices[i]];\n\t\tint v1 = remap[indices[i + (i % 3 == 2 ? -2 : 1)]];\n\n\t\tv0 = follow(parents, v0);\n\t\tv1 = follow(parents, v1);\n\n\t\tparents[v0] = v1;\n\t}\n\n\tfor (size_t i = 0; i < indices.size(); ++i)\n\t{\n\t\tunsigned int v = remap[indices[i]];\n\t\tparents[v] = follow(parents, v);\n\t}\n\n\tint roots = 0;\n\tfor (size_t i = 0; i < indices.size(); ++i)\n\t{\n\t\tunsigned int v = remap[indices[i]];\n\t\troots += parents[v] == int(v);\n\t\tparents[v] = -1; // make sure we only count each root once\n\t}\n\n\treturn roots;\n}\n\nstatic size_t measureBoundary(std::vector<int>& used, const std::vector<std::vector<unsigned int> >& clusters, const std::vector<unsigned int>& remap)\n{\n\tfor (size_t i = 0; i < used.size(); ++i)\n\t\tused[i] = -1;\n\n\t// mark vertices that are used by multiple groups with -2\n\tint group = 0;\n\n\tfor (size_t i = 0; i < clusters.size(); ++i)\n\t{\n\t\tgroup += clusters[i].empty();\n\n\t\tfor (size_t j = 0; j < clusters[i].size(); ++j)\n\t\t{\n\t\t\tunsigned int v = remap[clusters[i][j]];\n\n\t\t\tused[v] = (used[v] == -1 || used[v] == group) ? group : -2;\n\t\t}\n\t}\n\n\tsize_t result = 0;\n\n\tfor (size_t i = 0; i < clusters.size(); ++i)\n\t{\n\t\t// count vertices that are used by multiple groups and change marks to -1\n\t\tfor (size_t j = 0; j < clusters[i].size(); ++j)\n\t\t{\n\t\t\tunsigned int v = remap[clusters[i][j]];\n\n\t\t\tresult += (used[v] == -2);\n\t\t\tused[v] = (used[v] == -2) ? -1 : used[v];\n\t\t}\n\n\t\t// change marks back from -1 to -2 for the next pass\n\t\tfor (size_t j = 0; j < clusters[i].size(); ++j)\n\t\t{\n\t\t\tunsigned int v = remap[clusters[i][j]];\n\n\t\t\tused[v] = (used[v] == -1) ? -2 : used[v];\n\t\t}\n\t}\n\n\treturn int(result);\n}\n\nstruct Box\n{\n\tfloat min[3];\n\tfloat max[3];\n};\n\nstatic const Box kDummyBox = {{FLT_MAX, FLT_MAX, FLT_MAX}, {-FLT_MAX, -FLT_MAX, -FLT_MAX}};\n\nstatic void mergeBox(Box& box, const Box& other)\n{\n\tfor (int k = 0; k < 3; ++k)\n\t{\n\t\tbox.min[k] = other.min[k] < box.min[k] ? other.min[k] : box.min[k];\n\t\tbox.max[k] = other.max[k] > box.max[k] ? other.max[k] : box.max[k];\n\t}\n}\n\ninline float surface(const Box& box)\n{\n\tfloat sx = box.max[0] - box.min[0], sy = box.max[1] - box.min[1], sz = box.max[2] - box.min[2];\n\treturn sx * sy + sx * sz + sy * sz;\n}\n\nstatic float sahCost(const Box* boxes, unsigned int* order, unsigned int* temp, size_t count)\n{\n\tBox total = boxes[order[0]];\n\tfor (size_t i = 1; i < count; ++i)\n\t\tmergeBox(total, boxes[order[i]]);\n\n\tint best_axis = -1;\n\tint best_bin = -1;\n\tfloat best_cost = FLT_MAX;\n\n\tconst int kBins = 15;\n\n\tfor (int axis = 0; axis < 3; ++axis)\n\t{\n\t\tBox bins[kBins];\n\t\tunsigned int counts[kBins] = {};\n\n\t\tfloat extent = total.max[axis] - total.min[axis];\n\t\tif (extent <= 0.f)\n\t\t\tcontinue;\n\n\t\tfor (int i = 0; i < kBins; ++i)\n\t\t\tbins[i] = kDummyBox;\n\n\t\tfor (size_t i = 0; i < count; ++i)\n\t\t{\n\t\t\tunsigned int index = order[i];\n\t\t\tfloat p = (boxes[index].min[axis] + boxes[index].max[axis]) * 0.5f;\n\t\t\tint bin = int((p - total.min[axis]) / extent * (kBins - 1) + 0.5f);\n\t\t\tassert(bin >= 0 && bin < kBins);\n\n\t\t\tmergeBox(bins[bin], boxes[index]);\n\t\t\tcounts[bin]++;\n\t\t}\n\n\t\tBox laccum = kDummyBox, raccum = kDummyBox;\n\t\tsize_t lcount = 0, rcount = 0;\n\t\tfloat costs[kBins] = {};\n\n\t\tfor (int i = 0; i < kBins - 1; ++i)\n\t\t{\n\t\t\tmergeBox(laccum, bins[i]);\n\t\t\tmergeBox(raccum, bins[kBins - 1 - i]);\n\n\t\t\tlcount += counts[i];\n\t\t\tcosts[i] += lcount ? surface(laccum) * lcount : 0.f;\n\t\t\trcount += counts[kBins - 1 - i];\n\t\t\tcosts[kBins - 2 - i] += rcount ? surface(raccum) * rcount : 0.f;\n\t\t}\n\n\t\tfor (int i = 0; i < kBins - 1; ++i)\n\t\t\tif (costs[i] < best_cost)\n\t\t\t{\n\t\t\t\tbest_cost = costs[i];\n\t\t\t\tbest_bin = i;\n\t\t\t\tbest_axis = axis;\n\t\t\t}\n\t}\n\n\tif (best_axis == -1)\n\t\treturn surface(total) * float(count);\n\n\tfloat best_extent = total.max[best_axis] - total.min[best_axis];\n\n\tsize_t offset0 = 0, offset1 = count;\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned int index = order[i];\n\t\tfloat p = (boxes[index].min[best_axis] + boxes[index].max[best_axis]) * 0.5f;\n\t\tint bin = int((p - total.min[best_axis]) / best_extent * (kBins - 1) + 0.5f);\n\t\tassert(bin >= 0 && bin < kBins);\n\n\t\tif (bin <= best_bin)\n\t\t\ttemp[offset0++] = index;\n\t\telse\n\t\t\ttemp[--offset1] = index;\n\t}\n\n\tassert(offset0 == offset1);\n\n\tif (offset0 == 0 || offset0 == count)\n\t\treturn surface(total) * float(count);\n\n\treturn surface(total) + sahCost(boxes, temp, order, offset0) + sahCost(boxes, temp + offset0, order + offset0, count - offset0);\n}\n\nstatic float sahCost(const Box* boxes, size_t count)\n{\n\tif (count == 0)\n\t\treturn 0.f;\n\n\tstd::vector<unsigned int> order(count);\n\tfor (size_t i = 0; i < count; ++i)\n\t\torder[i] = unsigned(i);\n\n\tstd::vector<unsigned int> temp(count);\n\treturn sahCost(boxes, &order[0], &temp[0], count);\n}\n\nstatic float sahOverhead(const std::vector<std::vector<unsigned int> >& clusters, const std::vector<Vertex>& vertices)\n{\n\tstd::vector<Box> all_tris, cluster_tris, cluster_boxes;\n\n\tfloat sahc = 0.f;\n\n\tfor (size_t i = 0; i < clusters.size(); ++i)\n\t{\n\t\tif (clusters[i].empty())\n\t\t\tcontinue;\n\n\t\tcluster_tris.clear();\n\n\t\tBox cluster_box = kDummyBox;\n\n\t\tfor (size_t k = 0; k < clusters[i].size(); k += 3)\n\t\t{\n\t\t\tBox box = kDummyBox;\n\n\t\t\tfor (int v = 0; v < 3; ++v)\n\t\t\t{\n\t\t\t\tconst Vertex& vertex = vertices[clusters[i][k + v]];\n\n\t\t\t\tBox p = {{vertex.px, vertex.py, vertex.pz}, {vertex.px, vertex.py, vertex.pz}};\n\t\t\t\tmergeBox(box, p);\n\t\t\t}\n\n\t\t\tmergeBox(cluster_box, box);\n\n\t\t\tall_tris.push_back(box);\n\t\t\tcluster_tris.push_back(box);\n\t\t}\n\n\t\tcluster_boxes.push_back(cluster_box);\n\n\t\tsahc += sahCost(&cluster_tris[0], cluster_tris.size());\n\t\tsahc -= surface(cluster_box); // box will be accounted for in tlas\n\t}\n\n\tsahc += sahCost(&cluster_boxes[0], cluster_boxes.size());\n\n\tfloat saht = sahCost(all_tris.data(), all_tris.size());\n\n\treturn sahc / saht;\n}\n"
  },
  {
    "path": "demo/pirate.obj",
    "content": "# Blender v2.79 (sub 0) OBJ File: 'pirate.blend'\n# www.blender.org\n# Pirate by Clint Bellanger\n# https://opengameart.org/content/pirate\n# Distributed under CC-BY-SA 3.0\nmtllib pirate.mtl\no Pirate_Sphere.001\nv -0.086616 1.541207 0.048687\nv 0.000000 1.521174 0.062100\nv 0.086616 1.541207 0.048687\nv -0.086616 1.467515 0.104849\nv -0.000000 1.460505 0.104473\nv 0.086616 1.467515 0.104849\nv -0.075497 1.360887 0.136036\nv 0.000000 1.353877 0.135660\nv 0.075497 1.360887 0.136036\nv -0.062354 1.259913 0.142685\nv 0.000000 1.259005 0.147301\nv 0.062354 1.259913 0.142685\nv 0.000000 1.173866 0.151424\nv 0.058120 1.175055 0.151800\nv -0.058120 1.175056 0.151800\nv 0.236368 0.092607 -0.020215\nv 0.244447 0.050649 0.055733\nv 0.160035 0.050649 0.068862\nv 0.158087 0.096969 -0.009511\nv 0.231907 0.050962 0.096377\nv 0.171927 0.050962 0.109907\nv 0.198856 0.056750 0.063817\nv 0.200434 0.099614 -0.008311\nv 0.203658 0.053735 0.117253\nv 0.228066 0.128577 -0.048921\nv 0.191707 0.128577 -0.030516\nv 0.155310 0.128577 -0.040374\nv 0.243809 0.048939 -0.062636\nv 0.259032 0.034710 0.052294\nv 0.229486 0.128577 -0.086203\nv 0.215595 0.128577 -0.123264\nv 0.215978 0.054773 -0.140594\nv 0.166330 0.128577 -0.141207\nv 0.168375 0.056821 -0.160469\nv 0.138088 0.128577 -0.118289\nv 0.138284 0.053982 -0.136358\nv 0.140126 0.128577 -0.082307\nv 0.145418 0.049313 -0.053791\nv 0.142801 0.033877 0.063946\nv 0.241657 0.038506 0.108880\nv 0.163953 0.037606 0.125343\nv 0.203658 0.036372 0.132175\nv 0.240419 0.028025 -0.064598\nv 0.255085 0.016737 0.039452\nv 0.214742 0.028524 -0.135022\nv 0.170136 0.028524 -0.153646\nv 0.141940 0.028524 -0.131052\nv 0.153248 0.027295 -0.055831\nv 0.145661 0.016737 0.050411\nv 0.238718 0.016737 0.097813\nv 0.164969 0.016737 0.113922\nv 0.202729 0.016737 0.120323\nv 0.242952 0.028025 -0.066538\nv 0.258034 0.016737 0.042949\nv 0.215666 0.028524 -0.141348\nv 0.168821 0.028524 -0.160906\nv 0.139209 0.028524 -0.137178\nv 0.150897 0.027295 -0.057712\nv 0.143524 0.016737 0.054426\nv 0.240914 0.016737 0.103692\nv 0.164210 0.016737 0.120066\nv 0.203423 0.016737 0.126789\nv 0.258034 -0.001088 0.025696\nv 0.242952 0.013555 -0.070778\nv 0.215666 0.014054 -0.141348\nv 0.168821 0.014054 -0.160906\nv 0.139209 0.014054 -0.137178\nv 0.150897 0.012826 -0.061952\nv 0.143524 -0.001088 0.037173\nv 0.240914 -0.000249 0.103692\nv 0.164210 -0.000249 0.120066\nv 0.203423 -0.000249 0.126789\nv 0.242952 -0.000311 -0.070778\nv 0.215666 0.000188 -0.141348\nv 0.168821 0.000188 -0.160906\nv 0.139209 0.000188 -0.137178\nv 0.150897 -0.001041 -0.061952\nv 0.240407 0.067287 0.012779\nv 0.157989 0.070033 0.027636\nv 0.199645 0.074798 0.025643\nv 0.254610 0.041825 -0.006398\nv 0.148123 0.042231 -0.000025\nv 0.250941 0.022381 -0.013800\nv 0.154169 0.022381 -0.007738\nv 0.253682 0.022381 -0.013022\nv 0.151925 0.022381 -0.006671\nv 0.253682 0.005814 -0.021702\nv 0.151925 0.005814 -0.015351\nv 0.218563 0.209613 -0.046429\nv 0.188733 0.208412 -0.021345\nv 0.148752 0.208412 -0.032174\nv 0.230231 0.208412 -0.082515\nv 0.214972 0.208412 -0.125426\nv 0.160857 0.208412 -0.142935\nv 0.129834 0.208412 -0.119961\nv 0.132073 0.208412 -0.078236\nv 0.184486 0.296737 -0.005237\nv 0.225084 0.299002 -0.034678\nv 0.140596 0.296697 -0.019914\nv 0.236935 0.298150 -0.081582\nv 0.217650 0.297630 -0.136721\nv 0.158346 0.295603 -0.157599\nv 0.110046 0.294732 -0.129811\nv 0.112876 0.294808 -0.076172\nv 0.180417 0.388284 0.013613\nv 0.219940 0.392383 -0.019552\nv 0.129895 0.386004 -0.002073\nv 0.236443 0.395326 -0.069991\nv 0.219625 0.400030 -0.135825\nv 0.144725 0.400515 -0.158651\nv 0.091043 0.394958 -0.126268\nv 0.093351 0.389803 -0.062979\nv 0.141647 0.491328 -0.114189\nv 0.205701 0.490563 -0.094984\nv 0.172100 0.480332 0.031533\nv 0.088130 0.486878 -0.030961\nv 0.124555 0.485816 0.028877\nv 0.085756 0.492816 -0.089075\nv 0.138819 0.500966 -0.123683\nv 0.212734 0.500023 -0.101320\nv 0.229349 0.492623 -0.040656\nv 0.212929 0.490855 0.006320\nv 0.174315 0.487937 0.044232\nv 0.078510 0.469649 -0.030733\nv 0.118959 0.469167 0.037975\nv 0.075626 0.476194 -0.096815\nv 0.135258 0.486616 -0.136305\nv 0.219656 0.487755 -0.110271\nv 0.239055 0.480310 -0.041331\nv 0.221618 0.477597 0.013127\nv 0.176187 0.472900 0.055532\nv 0.073827 0.311152 -0.055639\nv 0.116017 0.309592 0.028478\nv 0.066537 0.324125 -0.137977\nv 0.244434 0.339411 -0.148584\nv 0.277813 0.321962 -0.063759\nv 0.262464 0.316457 0.009962\nv 0.191486 0.312070 0.054821\nv 0.178630 0.357606 -0.193864\nv 0.123421 0.350867 -0.185934\nv 0.118294 0.398249 0.026084\nv 0.078910 0.396001 -0.044382\nv 0.074782 0.405104 -0.114011\nv 0.225539 0.420431 -0.125449\nv 0.254013 0.412894 -0.052038\nv 0.236933 0.409543 0.008791\nv 0.180250 0.404852 0.047154\nv 0.153892 0.427512 -0.158986\nv 0.129930 0.424512 -0.155542\nv 0.204864 0.482881 -0.001814\nv 0.129203 0.478420 0.018134\nv 0.219844 0.485703 -0.041760\nv 0.095093 0.485847 -0.085518\nv 0.097055 0.480678 -0.033262\nv 0.058986 1.778749 -0.098358\nv 0.079459 1.777951 -0.069506\nv 0.089357 1.778162 -0.031658\nv 0.086722 1.779085 0.007426\nv 0.072214 1.784068 0.043280\nv 0.056289 1.778569 0.078209\nv 0.030933 1.778055 0.100438\nv 0.000029 1.777957 0.107919\nv -0.030113 1.780207 0.099812\nv -0.055386 1.784491 0.077437\nv -0.075219 1.785706 0.045458\nv -0.088151 1.781940 0.008471\nv -0.090065 1.778396 -0.031092\nv -0.079190 1.777552 -0.068779\nv -0.058930 1.778485 -0.099223\nv -0.031333 1.780597 -0.112832\nv 0.000044 1.782147 -0.114884\nv 0.031409 1.780958 -0.111651\nv 0.058347 1.849556 -0.086516\nv 0.078385 1.847862 -0.063548\nv 0.086845 1.848279 -0.030617\nv 0.083311 1.849049 0.003948\nv 0.071195 1.849480 0.037787\nv 0.053178 1.848875 0.067663\nv 0.029426 1.848109 0.088584\nv 0.000478 1.848497 0.095845\nv -0.027984 1.853117 0.087607\nv -0.052069 1.858957 0.066812\nv -0.071600 1.860267 0.037959\nv -0.084706 1.855989 0.004879\nv -0.087909 1.850094 -0.029810\nv -0.078406 1.847763 -0.063610\nv -0.058167 1.849198 -0.087595\nv -0.030896 1.852027 -0.097518\nv 0.000108 1.853595 -0.099203\nv 0.031101 1.852409 -0.096369\nv 0.065910 1.863193 -0.055048\nv 0.049138 1.864533 -0.074499\nv 0.073086 1.863481 -0.027452\nv 0.070484 1.864051 0.001907\nv 0.060498 1.864257 0.030547\nv 0.045081 1.863910 0.055529\nv 0.024683 1.863350 0.072867\nv 0.000521 1.863915 0.078932\nv -0.023521 1.867598 0.072098\nv -0.044097 1.871953 0.054804\nv -0.060628 1.872897 0.030575\nv -0.071485 1.869625 0.002596\nv -0.073853 1.865274 -0.026848\nv -0.065981 1.863099 -0.055126\nv -0.048960 1.864257 -0.075334\nv -0.026002 1.866220 -0.084925\nv 0.000102 1.867361 -0.086874\nv 0.026196 1.866493 -0.084095\nv 0.000089 1.863019 -0.010396\nv 0.163089 1.835772 -0.135080\nv 0.189568 1.760501 -0.131727\nv -0.008953 1.770214 0.212054\nv -0.203693 1.757624 -0.117194\nv -0.163052 1.839092 -0.132407\nv -0.081971 1.883991 -0.105049\nv 0.000092 1.900686 -0.082792\nv 0.082159 1.884257 -0.104242\nv 0.142218 1.770391 -0.134324\nv 0.074281 1.779322 -0.133913\nv 0.000114 1.782525 -0.124317\nv -0.074089 1.778930 -0.135083\nv -0.141499 1.772850 -0.124412\nv 0.178914 1.767742 -0.065645\nv 0.146196 1.778066 -0.011723\nv 0.101668 1.784760 0.048946\nv 0.072940 1.777907 0.117313\nv 0.029853 1.778522 0.166702\nv -0.042368 1.777305 0.168503\nv -0.071558 1.781615 0.116535\nv -0.105547 1.786529 0.050382\nv -0.147493 1.778899 -0.010879\nv -0.179551 1.768847 -0.059894\nv 0.192030 1.774595 -0.157698\nv -0.196140 1.771098 -0.150907\nv -0.148438 1.795914 -0.157062\nv -0.077901 1.801581 -0.157771\nv 0.000121 1.806535 -0.143703\nv 0.078096 1.802044 -0.156394\nv 0.148773 1.792710 -0.160503\nv 0.055593 1.796899 0.190306\nv 0.091024 1.800365 0.132154\nv 0.120949 1.807803 0.061471\nv 0.167953 1.800547 -0.003569\nv 0.202690 1.790509 -0.058345\nv 0.219763 1.772986 -0.103608\nv 0.025376 1.782599 0.225570\nv 0.041662 1.883651 0.108372\nv 0.065429 1.899024 0.027469\nv 0.122970 1.883781 -0.034211\nv 0.189371 1.834813 -0.085696\nv 0.026824 1.840460 0.191371\nv -0.189957 1.838185 -0.083454\nv -0.028163 1.840382 0.191146\nv -0.040263 1.886331 0.107609\nv -0.065407 1.903963 0.027451\nv -0.123778 1.884084 -0.033638\nv -0.225231 1.766756 -0.101532\nv -0.030773 1.782936 0.225767\nv -0.057529 1.797016 0.189845\nv -0.089435 1.804814 0.131241\nv -0.124267 1.811475 0.061137\nv -0.169496 1.801613 -0.002586\nv -0.203903 1.793766 -0.056224\nv -0.032356 1.740167 0.086580\nv -0.025976 1.740167 0.085311\nv -0.020567 1.740167 0.081697\nv -0.016953 1.740167 0.076288\nv -0.015684 1.740167 0.069908\nv -0.020567 1.751956 0.069908\nv -0.021465 1.751059 0.076288\nv -0.024020 1.748503 0.081697\nv -0.027845 1.744678 0.085311\nv -0.032356 1.746547 0.085311\nv -0.032356 1.751956 0.081697\nv -0.032356 1.755570 0.076288\nv -0.032356 1.756839 0.069908\nv -0.044145 1.751956 0.069908\nv -0.043248 1.751059 0.076288\nv -0.040692 1.748503 0.081697\nv -0.036868 1.744678 0.085311\nv -0.038736 1.740167 0.085311\nv -0.044145 1.740167 0.081697\nv -0.047759 1.740167 0.076288\nv -0.049028 1.740167 0.069908\nv -0.044145 1.728378 0.069908\nv -0.043248 1.729275 0.076288\nv -0.040692 1.731831 0.081697\nv -0.036868 1.735656 0.085311\nv -0.032356 1.733787 0.085311\nv -0.032356 1.728378 0.081697\nv -0.032356 1.724764 0.076288\nv -0.032356 1.723495 0.069908\nv -0.020567 1.728378 0.069908\nv -0.021465 1.729275 0.076288\nv -0.024020 1.731831 0.081697\nv -0.027845 1.735656 0.085311\nv 0.044282 1.751956 0.069841\nv 0.043385 1.751058 0.076221\nv 0.040829 1.748503 0.081630\nv 0.037004 1.744678 0.085244\nv 0.032493 1.746547 0.085244\nv 0.032493 1.751956 0.081630\nv 0.032493 1.755570 0.076221\nv 0.032493 1.756839 0.069841\nv 0.020704 1.751956 0.069841\nv 0.021601 1.751059 0.076221\nv 0.024157 1.748503 0.081630\nv 0.027982 1.744678 0.085244\nv 0.032493 1.740167 0.086513\nv 0.026113 1.740167 0.085244\nv 0.020704 1.740167 0.081630\nv 0.017090 1.740167 0.076221\nv 0.015821 1.740167 0.069841\nv 0.020704 1.728378 0.069841\nv 0.021601 1.729275 0.076221\nv 0.024157 1.731831 0.081630\nv 0.027982 1.735656 0.085244\nv 0.032493 1.733787 0.085244\nv 0.032493 1.728378 0.081630\nv 0.032493 1.724764 0.076221\nv 0.032493 1.723495 0.069841\nv 0.044282 1.728378 0.069841\nv 0.043385 1.729275 0.076221\nv 0.040829 1.731831 0.081630\nv 0.037004 1.735656 0.085244\nv 0.038873 1.740167 0.085244\nv 0.044282 1.740167 0.081630\nv 0.047896 1.740167 0.076221\nv 0.049165 1.740167 0.069841\nv -0.100955 0.562082 -0.055519\nv -0.139085 0.568177 -0.073664\nv -0.177215 0.574273 -0.055519\nv -0.193009 0.576798 -0.011716\nv -0.177215 0.574273 0.032088\nv -0.139085 0.568177 0.053507\nv -0.100955 0.562082 0.032088\nv -0.085161 0.559557 -0.011716\nv -0.081718 0.630116 -0.061458\nv -0.128839 0.636041 -0.079377\nv -0.175961 0.641966 -0.061458\nv -0.195479 0.644420 -0.005828\nv -0.175961 0.641966 0.049802\nv -0.128839 0.636678 0.070935\nv -0.081718 0.630116 0.049802\nv -0.062199 0.627662 -0.005828\nv -0.046760 0.778096 -0.068404\nv -0.107066 0.784189 -0.097090\nv -0.167371 0.790282 -0.068404\nv -0.192350 0.792806 0.000850\nv -0.167371 0.790282 0.070104\nv -0.107066 0.784189 0.100473\nv -0.046760 0.778096 0.070104\nv -0.021781 0.775572 0.000850\nv -0.097675 0.876684 -0.106817\nv -0.165198 0.882608 -0.075282\nv -0.193168 0.885063 0.000850\nv -0.165198 0.882608 0.076982\nv -0.097675 0.876684 0.111042\nv -0.000000 0.868305 0.000850\nv -0.117521 0.480258 -0.093913\nv -0.158176 0.484839 -0.114096\nv -0.198830 0.484932 -0.093318\nv -0.215669 0.480481 -0.043750\nv -0.198830 0.474096 0.005572\nv -0.158176 0.469515 0.025755\nv -0.117522 0.469422 0.004977\nv -0.100682 0.473872 -0.044591\nv -0.145262 0.534017 -0.085305\nv -0.110702 0.528678 -0.066932\nv -0.179822 0.537111 -0.066647\nv -0.194137 0.536148 -0.021887\nv -0.179822 0.531693 0.022755\nv -0.145262 0.526355 0.041128\nv -0.110702 0.523260 0.022470\nv -0.096387 0.524223 -0.022290\nv -0.080202 0.951451 -0.140820\nv -0.152695 0.957376 -0.100630\nv -0.182723 0.959830 -0.003602\nv -0.152695 0.957376 0.093426\nv -0.080202 0.951451 0.133616\nv -0.063169 1.040780 -0.133246\nv -0.130693 1.046705 -0.092997\nv -0.158662 1.049159 0.004173\nv -0.130693 1.046705 0.101343\nv -0.063169 1.040780 0.141593\nv -0.058120 1.123998 -0.103349\nv -0.125903 1.123998 -0.066587\nv -0.153979 1.123998 0.022164\nv -0.125903 1.123998 0.110916\nv -0.058120 1.123998 0.147678\nv 0.000000 0.953011 -0.127545\nv 0.000000 1.039823 -0.129410\nv 0.000000 1.123998 -0.098510\nv -0.065920 0.700805 -0.063973\nv -0.118799 0.706729 -0.091123\nv -0.171678 0.712654 -0.063973\nv -0.193581 0.715108 -0.002489\nv -0.171678 0.712654 0.058995\nv -0.118799 0.706729 0.084462\nv -0.065920 0.700805 0.058995\nv -0.044016 0.698351 -0.002489\nv 0.100955 0.562082 -0.055519\nv 0.139085 0.568177 -0.073664\nv 0.177215 0.574273 -0.055519\nv 0.193009 0.576798 -0.011716\nv 0.177215 0.574273 0.032088\nv 0.139085 0.568177 0.053507\nv 0.100955 0.562082 0.032088\nv 0.085161 0.559557 -0.011716\nv 0.081718 0.630116 -0.061458\nv 0.128839 0.636041 -0.079377\nv 0.175961 0.641966 -0.061458\nv 0.195479 0.644420 -0.005828\nv 0.175961 0.641966 0.049802\nv 0.128839 0.636678 0.070935\nv 0.081718 0.630116 0.049802\nv 0.062199 0.627662 -0.005828\nv 0.046760 0.778096 -0.068404\nv 0.107066 0.784189 -0.097090\nv 0.167371 0.790282 -0.068404\nv 0.192350 0.792806 0.000850\nv 0.167371 0.790282 0.070104\nv 0.107066 0.784189 0.100473\nv 0.046760 0.778096 0.070104\nv 0.021781 0.775572 0.000850\nv -0.000000 0.870759 -0.075282\nv 0.097675 0.876684 -0.106817\nv 0.165198 0.882608 -0.075282\nv 0.193168 0.885063 0.000850\nv 0.165198 0.882608 0.076982\nv 0.097675 0.876684 0.111042\nv -0.000000 0.876858 0.083081\nv 0.117521 0.480258 -0.093913\nv 0.158176 0.484839 -0.114096\nv 0.198830 0.484932 -0.093318\nv 0.215669 0.480481 -0.043750\nv 0.198830 0.474096 0.005572\nv 0.158176 0.469515 0.025755\nv 0.117522 0.469422 0.004977\nv 0.100682 0.473872 -0.044591\nv 0.145262 0.534017 -0.085305\nv 0.110702 0.528678 -0.066932\nv 0.179822 0.537111 -0.066647\nv 0.194137 0.536148 -0.021887\nv 0.179822 0.531693 0.022755\nv 0.145262 0.526355 0.041128\nv 0.110702 0.523260 0.022470\nv 0.096387 0.524223 -0.022290\nv 0.080202 0.951451 -0.140820\nv 0.152695 0.957376 -0.100630\nv 0.182723 0.959830 -0.003602\nv 0.152695 0.957376 0.093426\nv 0.080202 0.951451 0.133616\nv 0.063169 1.040780 -0.133246\nv 0.130693 1.046705 -0.092997\nv 0.158662 1.049159 0.004173\nv 0.130693 1.046705 0.101343\nv 0.063169 1.040780 0.141593\nv 0.058120 1.123998 -0.103349\nv 0.125903 1.123998 -0.066587\nv 0.153979 1.123998 0.022164\nv 0.125903 1.123998 0.110916\nv 0.058120 1.123998 0.147678\nv -0.000000 0.940066 0.136277\nv 0.000000 1.038324 0.144000\nv 0.000000 1.123998 0.147301\nv 0.065920 0.700805 -0.063973\nv 0.118799 0.706729 -0.091123\nv 0.171678 0.712654 -0.063973\nv 0.193581 0.715108 -0.002489\nv 0.171678 0.712654 0.058995\nv 0.118799 0.706729 0.084462\nv 0.065920 0.700805 0.058995\nv 0.044016 0.698351 -0.002489\nv 0.134275 1.530679 -0.176386\nv 0.172047 1.568379 -0.134707\nv 0.183198 1.581694 -0.087722\nv 0.183899 1.582353 -0.037207\nv 0.175968 1.570682 0.009387\nv 0.154211 1.545088 0.050300\nv 0.127586 1.516320 0.085776\nv 0.090502 1.480065 0.118043\nv 0.048199 1.439660 0.139210\nv 0.004523 1.397720 0.152934\nv -0.046749 1.346165 0.168389\nv -0.100793 1.286092 0.157968\nv -0.142447 1.245674 0.120062\nv -0.172509 1.215475 0.065591\nv -0.184084 1.200966 0.010595\nv -0.189708 1.193441 -0.037206\nv -0.183778 1.199644 -0.083800\nv -0.161577 1.220932 -0.127256\nv -0.125529 1.256296 -0.164504\nv -0.082149 1.299133 -0.193113\nv -0.040545 1.343281 -0.203115\nv 0.004932 1.390315 -0.209998\nv 0.049917 1.437272 -0.211098\nv 0.091361 1.483013 -0.199357\nv 0.085005 1.564355 -0.174013\nv 0.115650 1.595672 -0.131694\nv 0.128002 1.611723 -0.087199\nv 0.130140 1.610316 -0.037207\nv 0.123674 1.601299 0.009387\nv 0.103645 1.579924 0.048363\nv 0.078677 1.552097 0.085776\nv 0.047546 1.519726 0.111321\nv 0.006099 1.478006 0.130594\nv -0.039131 1.432221 0.142984\nv -0.086835 1.384427 0.149293\nv -0.134336 1.337502 0.138490\nv -0.172510 1.300595 0.106263\nv -0.197874 1.272699 0.058544\nv -0.209579 1.258838 0.009387\nv -0.212954 1.250003 -0.037206\nv -0.207024 1.256206 -0.083800\nv -0.189637 1.274392 -0.127219\nv -0.162299 1.301960 -0.167083\nv -0.125744 1.339687 -0.198121\nv -0.084020 1.384709 -0.206740\nv -0.038853 1.432207 -0.209998\nv 0.006131 1.479163 -0.211098\nv 0.048350 1.524587 -0.199694\nv 0.080027 1.559096 -0.168589\nv 0.109462 1.589176 -0.127940\nv 0.121327 1.604594 -0.085200\nv 0.123380 1.603243 -0.037181\nv 0.117169 1.594581 0.007574\nv 0.097931 1.574050 0.045012\nv 0.073948 1.547321 0.080948\nv 0.044027 1.516185 0.105412\nv 0.004235 1.476154 0.123997\nv -0.039210 1.432176 0.135898\nv -0.085042 1.386244 0.141913\nv -0.130640 1.341176 0.131492\nv -0.167326 1.305744 0.100627\nv -0.191689 1.278949 0.054791\nv -0.202932 1.265635 0.007574\nv -0.206174 1.257149 -0.037181\nv -0.200478 1.263107 -0.081936\nv -0.183777 1.280576 -0.123641\nv -0.157525 1.307029 -0.161982\nv -0.122402 1.343267 -0.191843\nv -0.082329 1.386534 -0.199939\nv -0.038943 1.432166 -0.203012\nv 0.004265 1.477266 -0.204210\nv 0.044818 1.520897 -0.193256\nv 0.128970 1.525091 -0.170732\nv 0.165253 1.561306 -0.130695\nv 0.175966 1.574095 -0.085562\nv 0.176639 1.574728 -0.037037\nv 0.169020 1.563518 0.007721\nv 0.148120 1.538932 0.047022\nv 0.122544 1.511298 0.081100\nv 0.086903 1.476429 0.112024\nv 0.046286 1.437658 0.132429\nv 0.004330 1.397371 0.145612\nv -0.044931 1.347823 0.160413\nv -0.096818 1.290121 0.150358\nv -0.136849 1.251315 0.114035\nv -0.165727 1.222306 0.061711\nv -0.176846 1.208368 0.008882\nv -0.182248 1.201140 -0.037037\nv -0.176552 1.207098 -0.081795\nv -0.155226 1.227548 -0.123538\nv -0.120598 1.261518 -0.159318\nv -0.078927 1.302668 -0.186801\nv -0.038970 1.345081 -0.196253\nv 0.004724 1.390260 -0.202879\nv 0.047935 1.435364 -0.204077\nv 0.087747 1.479303 -0.192798\nv -0.236368 0.092607 -0.020215\nv -0.244447 0.050649 0.055733\nv -0.160035 0.050649 0.068862\nv -0.158087 0.096969 -0.009511\nv -0.231907 0.050962 0.096377\nv -0.171927 0.050962 0.109907\nv -0.198856 0.056750 0.063817\nv -0.200434 0.099614 -0.008311\nv -0.203658 0.053735 0.117253\nv -0.228066 0.128577 -0.048921\nv -0.191707 0.128577 -0.030516\nv -0.155310 0.128577 -0.040374\nv -0.243809 0.048939 -0.062636\nv -0.259032 0.034710 0.052294\nv -0.229486 0.128577 -0.086203\nv -0.215595 0.128577 -0.123264\nv -0.215978 0.054773 -0.140594\nv -0.166330 0.128577 -0.141207\nv -0.168375 0.056821 -0.160469\nv -0.138088 0.128577 -0.118289\nv -0.138284 0.053982 -0.136358\nv -0.140126 0.128577 -0.082307\nv -0.145418 0.049313 -0.053791\nv -0.142801 0.033877 0.063946\nv -0.241657 0.038506 0.108880\nv -0.163953 0.037606 0.125343\nv -0.203658 0.036372 0.132175\nv -0.240419 0.028025 -0.064598\nv -0.255085 0.016737 0.039452\nv -0.214742 0.028524 -0.135022\nv -0.170136 0.028524 -0.153646\nv -0.141940 0.028524 -0.131052\nv -0.153248 0.027295 -0.055831\nv -0.145661 0.016737 0.050411\nv -0.238718 0.016737 0.097813\nv -0.164969 0.016737 0.113922\nv -0.202729 0.016737 0.120323\nv -0.242952 0.028025 -0.066538\nv -0.258034 0.016737 0.042949\nv -0.215666 0.028524 -0.141348\nv -0.168821 0.028524 -0.160906\nv -0.139209 0.028524 -0.137178\nv -0.150897 0.027295 -0.057712\nv -0.143524 0.016737 0.054426\nv -0.240914 0.016737 0.103692\nv -0.164210 0.016737 0.120066\nv -0.203423 0.016737 0.126789\nv -0.258034 -0.001088 0.025696\nv -0.242952 0.013555 -0.070778\nv -0.215666 0.014054 -0.141348\nv -0.168821 0.014054 -0.160906\nv -0.139209 0.014054 -0.137178\nv -0.150897 0.012826 -0.061952\nv -0.143524 -0.001088 0.037173\nv -0.240914 -0.000249 0.103692\nv -0.164210 -0.000249 0.120066\nv -0.203423 -0.000249 0.126789\nv -0.242952 -0.000311 -0.070778\nv -0.215666 0.000188 -0.141348\nv -0.168821 0.000188 -0.160906\nv -0.139209 0.000188 -0.137178\nv -0.150897 -0.001041 -0.061952\nv -0.240407 0.067287 0.012779\nv -0.157989 0.070033 0.027636\nv -0.199645 0.074798 0.025643\nv -0.254610 0.041825 -0.006398\nv -0.148123 0.042231 -0.000025\nv -0.250941 0.022381 -0.013800\nv -0.154169 0.022381 -0.007738\nv -0.253682 0.022381 -0.013022\nv -0.151925 0.022381 -0.006671\nv -0.253682 0.005814 -0.021702\nv -0.151925 0.005814 -0.015351\nv -0.218563 0.209613 -0.046429\nv -0.188733 0.208412 -0.021345\nv -0.148752 0.208412 -0.032174\nv -0.230231 0.208412 -0.082515\nv -0.214972 0.208412 -0.125426\nv -0.160857 0.208412 -0.142935\nv -0.129834 0.208412 -0.119961\nv -0.132073 0.208412 -0.078236\nv -0.184486 0.296737 -0.005237\nv -0.225084 0.299002 -0.034678\nv -0.140596 0.296697 -0.019914\nv -0.236935 0.298150 -0.081582\nv -0.217650 0.297630 -0.136721\nv -0.158346 0.295603 -0.157599\nv -0.110046 0.294732 -0.129811\nv -0.112876 0.294808 -0.076172\nv -0.180417 0.388284 0.013613\nv -0.219940 0.392383 -0.019552\nv -0.129895 0.386004 -0.002073\nv -0.236443 0.395326 -0.069991\nv -0.219625 0.400030 -0.135825\nv -0.144725 0.400515 -0.158651\nv -0.091043 0.394958 -0.126268\nv -0.093351 0.389803 -0.062979\nv -0.141647 0.491328 -0.114189\nv -0.205701 0.490563 -0.094984\nv -0.172100 0.480332 0.031533\nv -0.088130 0.486878 -0.030961\nv -0.124555 0.485816 0.028877\nv -0.085756 0.492816 -0.089075\nv -0.138819 0.500966 -0.123683\nv -0.212734 0.500023 -0.101320\nv -0.229349 0.492623 -0.040656\nv -0.212929 0.490855 0.006320\nv -0.174315 0.487937 0.044232\nv -0.078510 0.469649 -0.030733\nv -0.118959 0.469167 0.037975\nv -0.075626 0.476194 -0.096815\nv -0.135258 0.486616 -0.136305\nv -0.219656 0.487755 -0.110271\nv -0.239055 0.480310 -0.041331\nv -0.221618 0.477597 0.013127\nv -0.176187 0.472900 0.055532\nv -0.073827 0.311152 -0.055639\nv -0.116017 0.309592 0.028478\nv -0.066537 0.324125 -0.137977\nv -0.244434 0.339411 -0.148584\nv -0.277813 0.321962 -0.063759\nv -0.262464 0.316457 0.009962\nv -0.191486 0.312070 0.054821\nv -0.178630 0.357606 -0.193864\nv -0.123421 0.350867 -0.185934\nv -0.118294 0.398249 0.026084\nv -0.078910 0.396001 -0.044382\nv -0.074782 0.405104 -0.114011\nv -0.225539 0.420431 -0.125449\nv -0.254013 0.412894 -0.052038\nv -0.236933 0.409543 0.008791\nv -0.180250 0.404852 0.047154\nv -0.153892 0.427512 -0.158986\nv -0.129930 0.424512 -0.155542\nv -0.204864 0.482881 -0.001814\nv -0.129203 0.478420 0.018134\nv -0.219844 0.485703 -0.041760\nv -0.095093 0.485847 -0.085518\nv -0.097055 0.480678 -0.033262\nv -0.055529 1.493171 0.092907\nv -0.055632 1.451355 0.129636\nv -0.040152 1.385805 0.146414\nv -0.031399 1.323361 0.152706\nv -0.026148 1.252309 0.159674\nv -0.023899 1.183441 0.163047\nv -0.023899 1.119257 0.159816\nv -0.035756 1.024998 0.164243\nv -0.057075 0.882970 0.166144\nv -0.087342 0.767263 0.175068\nv -0.130256 0.600958 0.203083\nv -0.263084 0.602740 0.125120\nv -0.303530 0.629328 -0.012713\nv -0.279738 0.659615 -0.142583\nv -0.180476 0.680835 -0.218781\nv -0.052293 0.696431 -0.229468\nv -0.000000 0.903639 -0.186417\nv -0.000000 1.019671 -0.164335\nv 0.000000 1.119257 -0.127489\nv -0.021313 0.802760 -0.209410\nv 0.000000 1.183441 -0.137360\nv 0.000000 1.376735 -0.198452\nv 0.000000 1.451759 -0.198452\nv 0.000000 1.524567 -0.186393\nv -0.200106 0.766720 0.128702\nv -0.256106 0.773491 0.005349\nv -0.222970 0.788889 -0.105964\nv -0.139229 0.802427 -0.183604\nv -0.166305 0.883253 0.130780\nv -0.216473 0.887417 0.005856\nv -0.194182 0.897068 -0.096436\nv -0.113426 0.903290 -0.164762\nv -0.136724 1.018924 0.134398\nv -0.186892 1.015852 0.017915\nv -0.164600 1.017062 -0.083171\nv -0.086115 1.020203 -0.143668\nv -0.121152 1.119257 0.137785\nv -0.172670 1.119257 0.018168\nv -0.149779 1.119257 -0.074245\nv -0.077684 1.119257 -0.119280\nv -0.120262 1.183441 0.143953\nv -0.173867 1.183441 0.019491\nv -0.162576 1.183441 -0.083776\nv -0.083964 1.183441 -0.137626\nv -0.120061 1.252504 0.134398\nv -0.185320 1.251011 0.010953\nv -0.181651 1.243637 -0.097475\nv -0.101614 1.250648 -0.152374\nv -0.130367 1.318038 0.132279\nv -0.212258 1.300892 0.010329\nv -0.206866 1.296449 -0.103040\nv -0.124560 1.308405 -0.178665\nv -0.309009 1.485512 -0.042012\nv -0.299405 1.454274 0.011206\nv -0.262364 1.400470 0.013574\nv -0.246013 1.343623 -0.013905\nv -0.232733 1.329661 -0.096472\nv -0.249925 1.351043 -0.156010\nv -0.280159 1.425431 -0.159697\nv -0.300754 1.477066 -0.123508\nv -0.358489 1.441829 -0.053517\nv -0.341332 1.415725 -0.016209\nv -0.312568 1.371026 0.007202\nv -0.295059 1.326406 -0.025422\nv -0.284217 1.313647 -0.093763\nv -0.293527 1.334403 -0.159059\nv -0.329213 1.395204 -0.158847\nv -0.350392 1.432640 -0.118576\nv -0.153061 1.396596 0.117845\nv -0.234974 1.401947 0.041187\nv -0.232898 1.345370 -0.160297\nv -0.135208 1.361853 -0.200577\nv -0.418271 1.393451 -0.067665\nv -0.404607 1.371110 -0.028798\nv -0.378331 1.331015 -0.009391\nv -0.358364 1.296543 -0.036435\nv -0.352312 1.285127 -0.093088\nv -0.362546 1.300656 -0.147216\nv -0.392129 1.351058 -0.147040\nv -0.412726 1.386348 -0.113657\nv -0.238603 1.451604 0.050911\nv -0.247663 1.426305 -0.187326\nv -0.137532 1.441997 -0.201822\nv -0.161963 1.461164 0.112142\nv -0.249717 1.546276 -0.020199\nv -0.242352 1.535791 -0.122329\nv -0.164602 1.516542 0.061273\nv -0.126600 1.523267 -0.170931\nv -0.096686 1.597695 -0.035012\nv -0.091318 1.612576 -0.091144\nv -0.079937 1.584289 0.006058\nv -0.052429 1.559129 0.039906\nv -0.059511 1.620940 -0.125205\nv -0.127213 1.655769 -0.030739\nv -0.121658 1.675305 -0.095090\nv -0.109973 1.639888 0.018116\nv -0.080687 1.612360 0.057116\nv -0.088142 1.685365 -0.137990\nv 0.000000 1.693757 -0.156027\nv -0.165594 1.623511 -0.109647\nv -0.170923 1.602495 -0.021107\nv -0.104190 1.560981 0.096990\nv -0.146253 1.584960 0.045949\nv -0.097500 1.639982 -0.170080\nv 0.000000 1.645165 -0.191679\nv -0.168111 1.573843 -0.106737\nv -0.178915 1.570728 -0.027279\nv -0.128327 1.552606 0.035805\nv -0.057487 1.531572 0.066407\nv -0.100267 1.576621 -0.151559\nv -0.470204 1.368917 -0.069429\nv -0.454158 1.346519 -0.036405\nv -0.426572 1.304602 -0.020104\nv -0.408229 1.267183 -0.043107\nv -0.405618 1.253222 -0.091268\nv -0.419175 1.267909 -0.137238\nv -0.448776 1.321324 -0.137073\nv -0.467372 1.359826 -0.108665\nv -0.509538 1.307247 -0.023719\nv -0.521968 1.326629 -0.055033\nv -0.487954 1.270359 -0.008292\nv -0.472451 1.236567 -0.030094\nv -0.469302 1.223250 -0.075754\nv -0.479316 1.235488 -0.119334\nv -0.503579 1.283145 -0.119189\nv -0.519343 1.317980 -0.092259\nv -0.576228 1.275383 -0.040033\nv -0.563878 1.261239 -0.011934\nv -0.544363 1.233828 0.001865\nv -0.532909 1.208638 -0.017657\nv -0.533244 1.198450 -0.058594\nv -0.544480 1.207245 -0.097677\nv -0.564582 1.242551 -0.097592\nv -0.575978 1.268567 -0.073467\nv -0.621947 1.243404 -0.024871\nv -0.606588 1.231470 0.001990\nv -0.584973 1.207811 0.013254\nv -0.574175 1.185285 -0.008347\nv -0.577637 1.175574 -0.050069\nv -0.592578 1.182565 -0.088309\nv -0.614130 1.213647 -0.085271\nv -0.624485 1.236964 -0.058985\nv -0.548048 1.283423 -0.000028\nv -0.569435 1.303588 -0.033291\nv -0.519235 1.245224 0.013920\nv -0.506848 1.210827 -0.012829\nv -0.513994 1.197634 -0.064495\nv -0.535664 1.210644 -0.111848\nv -0.562988 1.259448 -0.108086\nv -0.574545 1.294888 -0.075536\nv -0.518948 1.363826 -0.037351\nv -0.490553 1.334002 0.007940\nv -0.453133 1.278392 0.026932\nv -0.438403 1.229340 -0.009490\nv -0.449396 1.211424 -0.079838\nv -0.478726 1.231261 -0.144314\nv -0.513263 1.301594 -0.139192\nv -0.526753 1.352042 -0.094871\nv 0.055529 1.493171 0.092907\nv 0.055632 1.451355 0.129636\nv 0.040152 1.385805 0.146414\nv 0.031399 1.323361 0.152706\nv 0.026148 1.252309 0.159674\nv 0.023899 1.183441 0.163047\nv 0.023899 1.119257 0.159816\nv 0.035756 1.024998 0.164243\nv 0.057075 0.882970 0.166144\nv 0.087342 0.767263 0.175068\nv 0.130256 0.600958 0.203083\nv 0.263084 0.602740 0.125120\nv 0.303530 0.629328 -0.012713\nv 0.279738 0.659615 -0.142583\nv 0.180476 0.680835 -0.218781\nv 0.052293 0.696431 -0.229468\nv 0.021313 0.802760 -0.209410\nv 0.000000 1.254463 -0.158657\nv -0.000000 1.315609 -0.183981\nv 0.200106 0.766720 0.128702\nv 0.256106 0.773491 0.005349\nv 0.222970 0.788889 -0.105964\nv 0.139229 0.802427 -0.183604\nv 0.166305 0.883253 0.130780\nv 0.216473 0.887417 0.005856\nv 0.194182 0.897068 -0.096436\nv 0.113426 0.903290 -0.164762\nv 0.136724 1.018924 0.134398\nv 0.186892 1.015852 0.017915\nv 0.164600 1.017062 -0.083171\nv 0.086115 1.020203 -0.143668\nv 0.121152 1.119257 0.137785\nv 0.172670 1.119257 0.018168\nv 0.149779 1.119257 -0.074245\nv 0.077684 1.119257 -0.119280\nv 0.120262 1.183441 0.143953\nv 0.173867 1.183441 0.019491\nv 0.162576 1.183441 -0.083776\nv 0.083964 1.183441 -0.137626\nv 0.120061 1.252504 0.134398\nv 0.185320 1.251011 0.010953\nv 0.181651 1.243637 -0.097475\nv 0.101614 1.250648 -0.152374\nv 0.130367 1.318038 0.132279\nv 0.212258 1.300892 0.010329\nv 0.206866 1.296449 -0.103040\nv 0.124560 1.308405 -0.178665\nv 0.309009 1.485512 -0.042012\nv 0.299405 1.454274 0.011206\nv 0.262364 1.400470 0.013574\nv 0.246013 1.343623 -0.013905\nv 0.232733 1.329661 -0.096472\nv 0.249925 1.351043 -0.156010\nv 0.280159 1.425431 -0.159697\nv 0.300754 1.477066 -0.123508\nv 0.358489 1.441829 -0.053517\nv 0.341332 1.415725 -0.016209\nv 0.312568 1.371026 0.007202\nv 0.295059 1.326406 -0.025422\nv 0.284217 1.313647 -0.093763\nv 0.293527 1.334403 -0.159059\nv 0.329213 1.395204 -0.158847\nv 0.350392 1.432640 -0.118576\nv 0.153061 1.396596 0.117845\nv 0.234974 1.401947 0.041187\nv 0.232898 1.345370 -0.160297\nv 0.135208 1.361853 -0.200577\nv 0.418271 1.393451 -0.067665\nv 0.404607 1.371110 -0.028798\nv 0.378331 1.331015 -0.009391\nv 0.358364 1.296543 -0.036435\nv 0.352312 1.285127 -0.093088\nv 0.362546 1.300656 -0.147216\nv 0.392129 1.351058 -0.147040\nv 0.412726 1.386348 -0.113657\nv 0.238603 1.451604 0.050911\nv 0.247663 1.426305 -0.187326\nv 0.137532 1.441997 -0.201822\nv 0.161963 1.461164 0.112142\nv 0.249717 1.546276 -0.020199\nv 0.242352 1.535791 -0.122329\nv 0.164602 1.516542 0.061273\nv 0.126600 1.523267 -0.170931\nv 0.096686 1.597695 -0.035012\nv 0.091318 1.612576 -0.091144\nv 0.079937 1.584289 0.006058\nv 0.052429 1.559129 0.039906\nv 0.059511 1.620940 -0.125205\nv 0.000000 1.628451 -0.141560\nv 0.127213 1.655769 -0.030739\nv 0.121658 1.675305 -0.095090\nv 0.109973 1.639888 0.018116\nv 0.080687 1.612360 0.057116\nv 0.088142 1.685365 -0.137990\nv 0.165594 1.623511 -0.109647\nv 0.170923 1.602495 -0.021107\nv 0.104190 1.560981 0.096990\nv 0.146253 1.584960 0.045949\nv 0.097500 1.639982 -0.170080\nv 0.168111 1.573843 -0.106737\nv 0.178915 1.570728 -0.027279\nv 0.128327 1.552606 0.035805\nv 0.057487 1.531572 0.066407\nv 0.000000 1.584163 -0.163783\nv 0.100267 1.576621 -0.151559\nv 0.470204 1.368917 -0.069429\nv 0.454158 1.346519 -0.036405\nv 0.426572 1.304602 -0.020104\nv 0.408229 1.267183 -0.043107\nv 0.405618 1.253222 -0.091268\nv 0.419175 1.267909 -0.137238\nv 0.448776 1.321324 -0.137073\nv 0.467372 1.359826 -0.108665\nv 0.509538 1.307247 -0.023719\nv 0.521968 1.326629 -0.055033\nv 0.487954 1.270359 -0.008292\nv 0.472451 1.236567 -0.030094\nv 0.469302 1.223250 -0.075754\nv 0.479316 1.235488 -0.119334\nv 0.503579 1.283145 -0.119189\nv 0.519343 1.317980 -0.092259\nv 0.576228 1.275383 -0.040033\nv 0.563878 1.261239 -0.011934\nv 0.544363 1.233828 0.001865\nv 0.532909 1.208638 -0.017657\nv 0.533244 1.198450 -0.058594\nv 0.544480 1.207245 -0.097677\nv 0.564582 1.242551 -0.097592\nv 0.575978 1.268567 -0.073467\nv 0.621947 1.243404 -0.024871\nv 0.606588 1.231470 0.001990\nv 0.584973 1.207811 0.013254\nv 0.574175 1.185285 -0.008347\nv 0.577637 1.175574 -0.050069\nv 0.592578 1.182565 -0.088309\nv 0.614130 1.213647 -0.085271\nv 0.624485 1.236964 -0.058985\nv 0.548048 1.283423 -0.000028\nv 0.569435 1.303588 -0.033291\nv 0.519235 1.245224 0.013920\nv 0.506848 1.210827 -0.012829\nv 0.513994 1.197634 -0.064495\nv 0.535664 1.210644 -0.111848\nv 0.562988 1.259448 -0.108086\nv 0.574545 1.294888 -0.075536\nv 0.518948 1.363826 -0.037351\nv 0.490553 1.334002 0.007940\nv 0.453133 1.278392 0.026932\nv 0.438403 1.229340 -0.009490\nv 0.449396 1.211424 -0.079838\nv 0.478726 1.231261 -0.144314\nv 0.513263 1.301594 -0.139192\nv 0.526753 1.352042 -0.094871\nv 0.088152 1.183441 -0.144985\nv 0.170685 1.183441 -0.088437\nv 0.182540 1.183441 0.020003\nv 0.126261 1.183441 0.150700\nv 0.081559 1.119257 -0.128551\nv 0.157250 1.119257 -0.078428\nv 0.181283 1.119257 0.018614\nv 0.127195 1.119257 0.144223\nv 0.025091 1.119257 0.167358\nv 0.025091 1.183441 0.170750\nv -0.088152 1.183441 -0.144985\nv -0.170685 1.183441 -0.088437\nv -0.182540 1.183441 0.020003\nv -0.126261 1.183441 0.150700\nv -0.081559 1.119257 -0.128551\nv -0.157250 1.119257 -0.078428\nv -0.181283 1.119257 0.018614\nv -0.127195 1.119257 0.144223\nv -0.000000 1.183441 -0.144198\nv -0.000000 1.119257 -0.134141\nv -0.025091 1.119257 0.167358\nv -0.025091 1.183441 0.170750\nv 0.032665 1.515867 0.061271\nv 0.000046 1.506738 0.057291\nv 0.009008 1.509201 0.059572\nv 0.014802 1.510792 0.060556\nv 0.022246 1.512831 0.061265\nv 0.004174 1.507801 0.058344\nv 0.047380 1.520089 0.058843\nv 0.086181 1.529302 0.036547\nv 0.104020 1.533417 0.016887\nv 0.127030 1.565279 -0.112151\nv 0.140316 1.569182 -0.078594\nv 0.140484 1.563938 -0.046658\nv 0.132480 1.553798 -0.020614\nv 0.119583 1.541506 -0.002499\nv 0.103516 1.555723 -0.139450\nv 0.674888 1.110830 -0.003673\nv 0.612610 1.157199 0.002101\nv 0.674545 1.117330 0.019286\nv 0.706858 1.099530 0.023730\nv 0.710139 1.097504 0.045483\nv 0.669417 1.155014 0.039740\nv 0.647492 1.157972 0.056044\nv 0.661499 1.177832 0.004751\nv 0.635999 1.142938 0.053810\nv 0.729386 1.080314 0.059558\nv 0.719635 1.088437 0.052730\nv 0.744902 1.067028 -0.001493\nv 0.726869 1.071270 -0.000494\nv 0.736876 1.060203 0.001079\nv 0.743075 1.053009 0.001969\nv 0.645372 1.134767 0.068654\nv 0.662357 1.126353 0.097420\nv 0.657118 1.139621 0.080730\nv 0.732353 1.052126 -0.034486\nv 0.713486 1.062962 -0.030949\nv 0.718813 1.054992 -0.030781\nv 0.724174 1.048125 -0.032724\nv 0.754945 1.052232 0.000265\nv 0.753210 1.051807 0.042524\nv 0.743049 1.070876 0.068770\nv 0.748944 1.075721 0.067038\nv 0.631187 1.182153 -0.060137\nv 0.608335 1.195320 -0.067070\nv 0.559506 1.202946 -0.041688\nv 0.562544 1.220983 -0.017718\nv 0.669640 1.104069 -0.024403\nv 0.627276 1.139434 0.009177\nv 0.714807 1.090642 0.027332\nv 0.695859 1.107043 0.027280\nv 0.727431 1.078475 0.031722\nv 0.696927 1.098358 0.001796\nv 0.703119 1.103989 0.038953\nv 0.633594 1.210668 -0.010901\nv 0.647811 1.194846 -0.002174\nv 0.652695 1.145887 0.071041\nv 0.712533 1.113137 0.036251\nv 0.719345 1.105960 0.042952\nv 0.728522 1.096502 0.050164\nv 0.737676 1.087669 0.056634\nv 0.705954 1.119005 0.023964\nv 0.716645 1.109843 0.021743\nv 0.725072 1.099576 0.024312\nv 0.747342 1.073881 0.033310\nv 0.736533 1.086177 0.029001\nv 0.696296 1.140563 0.016160\nv 0.708298 1.111171 -0.002280\nv 0.696457 1.132692 -0.009182\nv 0.708394 1.090153 -0.032321\nv 0.703739 1.097623 -0.031627\nv 0.715028 1.083526 -0.002103\nv 0.706621 1.091983 -0.003424\nv 0.724658 1.092521 -0.005640\nv 0.716293 1.102370 -0.006407\nv 0.735778 1.079242 -0.003190\nv 0.698534 1.082554 -0.028880\nv 0.690590 1.094611 -0.022870\nv 0.694321 1.089775 -0.029227\nv 0.705918 1.072451 -0.029914\nv 0.715476 1.079874 -0.032419\nv 0.650145 1.128751 0.079602\nv 0.661139 1.136280 0.088389\nv 0.664916 1.135274 0.096804\nv 0.655866 1.126043 0.089251\nv 0.727975 1.061423 -0.033305\nv 0.722408 1.069734 -0.032870\nv 0.750177 1.058739 -0.000720\nv 0.748876 1.047540 0.001710\nv 0.760269 1.057284 0.040446\nv 0.754922 1.065134 0.037294\nv 0.746595 1.058580 0.040406\nv 0.738799 1.066683 0.037092\nv 0.736467 1.074060 0.064212\nv 0.744538 1.081398 0.061437\nv 0.615434 1.228177 -0.023378\nv 0.590023 1.247385 -0.040312\nv 0.661068 1.157391 -0.045104\nv 0.664961 1.169456 -0.023274\nv 0.587070 1.181785 -0.037609\nv 0.559761 1.211244 -0.028605\nv 0.585196 1.196178 -0.011534\nv 0.633854 1.137880 -0.012377\nv 0.601909 1.175540 -0.005262\nv 0.578364 1.210416 -0.074700\nv 0.608685 1.163981 -0.034361\nv 0.621257 1.146775 -0.032980\nv 0.633379 1.131144 -0.031188\nv 0.703711 1.088645 -0.016576\nv 0.712716 1.079507 -0.014927\nv 0.725322 1.067865 -0.012271\nv 0.735923 1.057540 -0.009999\nv 0.742203 1.050307 -0.008593\nv 0.748456 1.045616 -0.006070\nv 0.755212 1.050032 -0.008025\nv 0.750431 1.057000 -0.010678\nv 0.744125 1.064766 -0.012315\nv 0.733938 1.075834 -0.014611\nv 0.722202 1.088450 -0.018332\nv 0.712501 1.098042 -0.021141\nv 0.700170 1.105421 -0.026439\nv 0.690631 1.125018 -0.031907\nv 0.646118 1.169501 -0.050893\nv 0.562504 1.198289 -0.055965\nv 0.591163 1.179253 -0.050869\nv 0.612975 1.162797 -0.047605\nv 0.625289 1.146584 -0.048665\nv 0.636769 1.132086 -0.048725\nv 0.668763 1.105041 -0.043369\nv 0.681188 1.094929 -0.040700\nv 0.687931 1.088314 -0.040485\nv 0.693752 1.080715 -0.040848\nv 0.701580 1.070576 -0.041451\nv 0.709330 1.060843 -0.041866\nv 0.715570 1.053672 -0.041779\nv 0.721939 1.047881 -0.041068\nv 0.729140 1.052229 -0.043852\nv 0.724430 1.060406 -0.044051\nv 0.718454 1.068123 -0.043548\nv 0.711056 1.077947 -0.043821\nv 0.703314 1.088221 -0.043762\nv 0.697523 1.096518 -0.043640\nv 0.691512 1.105148 -0.044287\nv 0.680400 1.116818 -0.047024\nv 0.649712 1.143939 -0.055466\nv 0.635474 1.155394 -0.056575\nv 0.619869 1.167972 -0.057263\nv 0.597704 1.182203 -0.061944\nv 0.568559 1.198926 -0.068300\nv 0.585402 1.188237 -0.024350\nv 0.604116 1.167808 -0.019721\nv 0.618151 1.151636 -0.015785\nv 0.707523 1.094909 0.010309\nv 0.715423 1.085616 0.013751\nv 0.727984 1.074069 0.020076\nv 0.739291 1.062430 0.025747\nv 0.746843 1.054016 0.029661\nv 0.753531 1.048433 0.033598\nv 0.760509 1.053722 0.031717\nv 0.755461 1.060904 0.027049\nv 0.748087 1.069597 0.022795\nv 0.737262 1.081977 0.017412\nv 0.726102 1.095113 0.011160\nv 0.717710 1.104636 0.006157\nv 0.651211 1.185025 -0.031012\nv 0.635891 1.199200 -0.039893\nv 0.615718 1.214473 -0.051091\nv 0.587649 1.231131 -0.065039\nv 0.580376 1.244796 -0.016765\nv 0.601626 1.222414 -0.000946\nv 0.617504 1.203980 0.011000\nv 0.630214 1.188406 0.019663\nv 0.645689 1.174979 0.034668\nv 0.687072 1.141182 0.038127\nv 0.701374 1.129937 0.043063\nv 0.709271 1.120481 0.048463\nv 0.716513 1.111768 0.053868\nv 0.726257 1.101887 0.060552\nv 0.735765 1.092832 0.066725\nv 0.742516 1.086052 0.071015\nv 0.747885 1.079823 0.074469\nv 0.741875 1.074101 0.076535\nv 0.734655 1.079178 0.074287\nv 0.727307 1.085463 0.069924\nv 0.717230 1.094135 0.063806\nv 0.707014 1.103360 0.056808\nv 0.698643 1.110361 0.051242\nv 0.688986 1.116561 0.045938\nv 0.674013 1.125840 0.039401\nv 0.623163 1.149869 0.034216\nv 0.613844 1.170335 0.018360\nv 0.604447 1.187261 0.007201\nv 0.588880 1.207248 -0.000362\nv 0.568976 1.232436 -0.011231\nv 0.679594 1.158810 0.010768\nv 0.680715 1.150920 -0.015581\nv 0.676328 1.141581 -0.037838\nv 0.665879 1.130452 -0.051364\nv 0.652273 1.117288 -0.046885\nv 0.650507 1.117073 -0.028105\nv 0.655369 1.126643 -0.009846\nv 0.653305 1.130948 0.012378\nv 0.652383 1.133142 0.035792\nv 0.651913 1.134051 0.048879\nv 0.656959 1.129360 0.062812\nv 0.662192 1.124307 0.073167\nv 0.667196 1.122067 0.083716\nv 0.670769 1.123390 0.093898\nv 0.674276 1.132866 0.093584\nv 0.670998 1.133584 0.083875\nv 0.667633 1.136297 0.075134\nv 0.663312 1.140577 0.065521\nv 0.661870 1.147834 0.051705\nv 0.066200 1.524831 0.051173\nv -0.000140 1.518403 -0.153647\nv 0.013576 1.521601 -0.156987\nv 0.030145 1.528860 -0.161555\nv 0.051938 1.537383 -0.163007\nv 0.077462 1.546570 -0.156404\nv -0.032671 1.515867 0.061272\nv -0.009013 1.509201 0.059572\nv -0.014807 1.510792 0.060556\nv -0.022251 1.512831 0.061265\nv -0.004179 1.507801 0.058344\nv -0.047385 1.520089 0.058843\nv -0.086187 1.529302 0.036547\nv -0.104025 1.533417 0.016887\nv -0.127035 1.565279 -0.112151\nv -0.140321 1.569182 -0.078594\nv -0.140489 1.563938 -0.046658\nv -0.132486 1.553798 -0.020614\nv -0.119588 1.541506 -0.002499\nv -0.103521 1.555723 -0.139450\nv -0.674908 1.110830 -0.003673\nv -0.612625 1.157199 0.002101\nv -0.674578 1.117330 0.019286\nv -0.706865 1.099530 0.023730\nv -0.710172 1.097507 0.045484\nv -0.669610 1.155001 0.039719\nv -0.647512 1.157971 0.056042\nv -0.661505 1.177832 0.004751\nv -0.636011 1.142938 0.053810\nv -0.729412 1.080313 0.059557\nv -0.719668 1.088437 0.052730\nv -0.744914 1.067028 -0.001493\nv -0.726889 1.071270 -0.000494\nv -0.736885 1.060203 0.001079\nv -0.743082 1.053009 0.001969\nv -0.645423 1.134767 0.068654\nv -0.662370 1.126353 0.097420\nv -0.657138 1.139620 0.080730\nv -0.732395 1.052120 -0.034486\nv -0.713543 1.062960 -0.030947\nv -0.718822 1.054992 -0.030781\nv -0.724193 1.048124 -0.032724\nv -0.755011 1.052230 0.000263\nv -0.753218 1.051807 0.042524\nv -0.743069 1.070876 0.068770\nv -0.748987 1.075720 0.067041\nv -0.631193 1.182153 -0.060137\nv -0.608340 1.195320 -0.067070\nv -0.559511 1.202946 -0.041688\nv -0.562549 1.220983 -0.017718\nv -0.669655 1.104069 -0.024403\nv -0.627285 1.139434 0.009177\nv -0.714824 1.090642 0.027332\nv -0.695874 1.107042 0.027280\nv -0.727443 1.078475 0.031723\nv -0.696946 1.098358 0.001796\nv -0.703138 1.103988 0.038953\nv -0.633599 1.210668 -0.010901\nv -0.647816 1.194846 -0.002174\nv -0.652935 1.145912 0.071046\nv -0.712544 1.113137 0.036251\nv -0.719353 1.105960 0.042952\nv -0.728530 1.096502 0.050164\nv -0.737684 1.087669 0.056634\nv -0.706039 1.119006 0.023963\nv -0.716664 1.109843 0.021743\nv -0.725084 1.099576 0.024312\nv -0.747346 1.073881 0.033310\nv -0.736545 1.086177 0.029001\nv -0.696304 1.140563 0.016160\nv -0.708312 1.111171 -0.002280\nv -0.696464 1.132692 -0.009182\nv -0.708408 1.090153 -0.032321\nv -0.703756 1.097623 -0.031627\nv -0.715046 1.083527 -0.002103\nv -0.706627 1.091983 -0.003424\nv -0.724670 1.092521 -0.005639\nv -0.716310 1.102371 -0.006407\nv -0.735788 1.079242 -0.003190\nv -0.698594 1.082554 -0.028880\nv -0.690607 1.094611 -0.022870\nv -0.694353 1.089775 -0.029227\nv -0.705983 1.072452 -0.029916\nv -0.715489 1.079874 -0.032419\nv -0.650156 1.128751 0.079602\nv -0.661221 1.136286 0.088391\nv -0.665107 1.135262 0.096802\nv -0.655875 1.126043 0.089251\nv -0.727985 1.061423 -0.033305\nv -0.722420 1.069735 -0.032870\nv -0.750205 1.058737 -0.000720\nv -0.748885 1.047540 0.001710\nv -0.760363 1.057276 0.040441\nv -0.754941 1.065133 0.037294\nv -0.746603 1.058580 0.040406\nv -0.738816 1.066683 0.037093\nv -0.736501 1.074059 0.064212\nv -0.744545 1.081398 0.061437\nv -0.615439 1.228176 -0.023378\nv -0.590028 1.247385 -0.040312\nv -0.661077 1.157391 -0.045104\nv -0.664968 1.169456 -0.023274\nv -0.587108 1.181786 -0.037609\nv -0.559767 1.211244 -0.028605\nv -0.585218 1.196178 -0.011534\nv -0.633879 1.137881 -0.012377\nv -0.601942 1.175540 -0.005261\nv -0.578369 1.210416 -0.074700\nv -0.608698 1.163982 -0.034361\nv -0.621275 1.146775 -0.032980\nv -0.633387 1.131144 -0.031187\nv -0.703722 1.088645 -0.016576\nv -0.712738 1.079508 -0.014927\nv -0.725348 1.067866 -0.012270\nv -0.735938 1.057540 -0.009999\nv -0.742232 1.050307 -0.008593\nv -0.748464 1.045616 -0.006070\nv -0.755247 1.050032 -0.008025\nv -0.750448 1.057000 -0.010678\nv -0.744135 1.064765 -0.012314\nv -0.733948 1.075834 -0.014611\nv -0.722212 1.088450 -0.018333\nv -0.712516 1.098042 -0.021142\nv -0.700181 1.105421 -0.026440\nv -0.690639 1.125018 -0.031907\nv -0.646125 1.169501 -0.050893\nv -0.562509 1.198289 -0.055965\nv -0.591175 1.179253 -0.050869\nv -0.612984 1.162797 -0.047605\nv -0.625322 1.146585 -0.048662\nv -0.636816 1.132088 -0.048720\nv -0.668772 1.105041 -0.043369\nv -0.681213 1.094929 -0.040701\nv -0.687948 1.088314 -0.040485\nv -0.693788 1.080716 -0.040849\nv -0.701636 1.070577 -0.041453\nv -0.709365 1.060843 -0.041866\nv -0.715577 1.053672 -0.041779\nv -0.721946 1.047881 -0.041068\nv -0.729147 1.052229 -0.043852\nv -0.724438 1.060406 -0.044051\nv -0.718462 1.068123 -0.043548\nv -0.711063 1.077947 -0.043821\nv -0.703320 1.088221 -0.043762\nv -0.697532 1.096518 -0.043640\nv -0.691530 1.105148 -0.044287\nv -0.680408 1.116818 -0.047024\nv -0.649726 1.143939 -0.055466\nv -0.635483 1.155394 -0.056575\nv -0.619877 1.167972 -0.057263\nv -0.597710 1.182203 -0.061944\nv -0.568564 1.198926 -0.068300\nv -0.585425 1.188238 -0.024350\nv -0.604139 1.167808 -0.019720\nv -0.618167 1.151636 -0.015785\nv -0.707533 1.094909 0.010309\nv -0.715452 1.085618 0.013752\nv -0.728003 1.074068 0.020076\nv -0.739337 1.062427 0.025747\nv -0.746857 1.054017 0.029662\nv -0.753558 1.048432 0.033598\nv -0.760582 1.053724 0.031725\nv -0.755472 1.060903 0.027049\nv -0.748096 1.069598 0.022795\nv -0.737275 1.081977 0.017413\nv -0.726112 1.095113 0.011160\nv -0.717731 1.104637 0.006156\nv -0.651217 1.185025 -0.031012\nv -0.635896 1.199200 -0.039893\nv -0.615722 1.214473 -0.051091\nv -0.587654 1.231131 -0.065039\nv -0.580381 1.244796 -0.016766\nv -0.601631 1.222414 -0.000946\nv -0.617509 1.203980 0.011000\nv -0.630220 1.188406 0.019663\nv -0.645694 1.174979 0.034668\nv -0.687078 1.141182 0.038127\nv -0.701396 1.129938 0.043064\nv -0.709294 1.120480 0.048462\nv -0.716520 1.111768 0.053868\nv -0.726269 1.101887 0.060552\nv -0.735777 1.092833 0.066725\nv -0.742548 1.086050 0.071014\nv -0.747970 1.079823 0.074468\nv -0.741887 1.074101 0.076535\nv -0.734661 1.079178 0.074287\nv -0.727315 1.085463 0.069923\nv -0.717241 1.094135 0.063806\nv -0.707020 1.103360 0.056808\nv -0.698654 1.110362 0.051242\nv -0.689008 1.116561 0.045938\nv -0.674040 1.125840 0.039400\nv -0.623180 1.149869 0.034216\nv -0.613861 1.170335 0.018360\nv -0.604456 1.187261 0.007201\nv -0.588886 1.207248 -0.000362\nv -0.568981 1.232436 -0.011231\nv -0.679600 1.158810 0.010768\nv -0.680724 1.150920 -0.015581\nv -0.676335 1.141581 -0.037838\nv -0.665889 1.130452 -0.051364\nv -0.652305 1.117288 -0.046884\nv -0.650516 1.117073 -0.028105\nv -0.655411 1.126643 -0.009845\nv -0.653438 1.130962 0.012381\nv -0.652550 1.133137 0.035798\nv -0.652047 1.134045 0.048879\nv -0.657034 1.129361 0.062811\nv -0.662200 1.124307 0.073166\nv -0.667215 1.122067 0.083716\nv -0.670780 1.123390 0.093898\nv -0.674303 1.132869 0.093584\nv -0.671023 1.133583 0.083875\nv -0.667644 1.136297 0.075134\nv -0.663358 1.140578 0.065521\nv -0.661928 1.147833 0.051706\nv -0.066206 1.524831 0.051173\nv -0.013581 1.521601 -0.156987\nv -0.030150 1.528860 -0.161555\nv -0.051943 1.537383 -0.163007\nv -0.077467 1.546570 -0.156404\nv 0.016440 1.733953 0.092814\nv 0.015479 1.744801 0.094338\nv 0.022064 1.748498 0.095013\nv 0.031923 1.750293 0.093778\nv 0.041516 1.749222 0.088251\nv 0.048488 1.744817 0.081332\nv 0.048449 1.732886 0.080714\nv 0.042298 1.728872 0.085420\nv 0.032368 1.727176 0.089015\nv 0.022517 1.729830 0.091192\nv 0.011617 1.730172 0.100463\nv 0.010992 1.750319 0.102809\nv 0.021362 1.754496 0.104559\nv 0.034352 1.757105 0.100859\nv 0.046749 1.755080 0.090966\nv 0.054885 1.748453 0.078446\nv 0.055073 1.728446 0.075963\nv 0.047762 1.722077 0.084227\nv 0.033745 1.717348 0.091640\nv 0.019883 1.723545 0.095647\nv 0.005542 1.731350 0.109217\nv 0.003955 1.751374 0.104446\nv -0.000033 1.731820 0.112244\nv -0.000105 1.751439 0.104896\nv 0.011277 1.712724 0.113847\nv 0.005793 1.713540 0.119628\nv -0.000010 1.714100 0.122622\nv 0.010974 1.720073 0.108118\nv 0.005564 1.722013 0.114535\nv -0.000038 1.723076 0.117868\nv 0.016942 1.715278 0.101556\nv 0.024824 1.707887 0.096007\nv 0.031870 1.698761 0.092161\nv 0.040197 1.681297 0.083590\nv 0.030561 1.678311 0.091347\nv 0.023189 1.694570 0.096951\nv 0.018821 1.701539 0.101010\nv 0.016750 1.709550 0.107386\nv 0.010757 1.766776 0.105238\nv 0.021538 1.769955 0.103235\nv 0.036005 1.771319 0.097074\nv 0.050902 1.766775 0.085155\nv 0.060005 1.755731 0.069133\nv 0.004166 1.765125 0.105608\nv -0.000087 1.764755 0.106067\nv 0.042235 1.671245 0.079756\nv 0.033290 1.645675 0.087024\nv 0.023829 1.641648 0.097000\nv 0.012188 1.648334 0.102717\nv 0.000008 1.650845 0.105491\nv 0.000043 1.654477 0.104249\nv 0.012162 1.653005 0.100362\nv 0.021983 1.650285 0.094783\nv 0.029301 1.652337 0.090062\nv 0.035372 1.669856 0.086431\nv 0.000017 1.635961 0.107843\nv 0.016596 1.619360 0.058151\nv -0.000035 1.615885 0.064632\nv 0.025469 1.624636 0.049003\nv 0.035695 1.638153 0.079040\nv 0.026228 1.631845 0.092068\nv 0.000031 1.626929 0.104330\nv 0.050788 1.739956 0.078037\nv 0.056690 1.740914 0.071917\nv 0.013528 1.740929 0.093875\nv 0.008377 1.742533 0.099944\nv 0.003869 1.743630 0.103772\nv -0.000034 1.743795 0.104751\nv 0.026682 1.667361 0.091784\nv 0.019117 1.667678 0.097280\nv 0.011413 1.669253 0.102415\nv 0.000059 1.669251 0.104884\nv 0.026193 1.658541 0.092529\nv 0.000054 1.658008 0.106506\nv 0.012084 1.657874 0.102816\nv 0.020448 1.657203 0.096986\nv 0.027022 1.672997 0.093577\nv 0.020145 1.676308 0.100352\nv 0.018876 1.669444 0.098634\nv 0.011636 1.679527 0.108647\nv 0.000025 1.680331 0.109883\nv 0.000030 1.670997 0.106078\nv 0.010806 1.670935 0.104084\nv 0.030894 1.668774 0.090028\nv 0.026374 1.666418 0.091716\nv 0.029954 1.661002 0.089655\nv 0.033811 1.656891 0.086084\nv 0.039465 1.652656 0.079524\nv 0.010589 1.683931 0.107093\nv -0.000056 1.684216 0.108220\nv 0.023845 1.666535 0.093650\nv 0.024028 1.668292 0.094088\nv -0.000036 1.706416 0.126284\nv 0.006607 1.706497 0.123249\nv 0.011152 1.706459 0.116966\nv 0.014360 1.704753 0.110387\nv 0.014849 1.700772 0.105939\nv 0.011544 1.697742 0.105502\nv 0.013785 1.695702 0.103717\nv 0.007485 1.696371 0.107887\nv 0.007592 1.693601 0.107562\nv 0.004247 1.695858 0.110665\nv -0.000073 1.692964 0.110126\nv 0.020020 1.681538 0.099803\nv 0.015806 1.691922 0.101714\nv 0.008225 1.690942 0.106929\nv 0.000007 1.690820 0.108241\nv 0.013676 1.637056 0.105332\nv 0.014754 1.628197 0.101139\nv 0.008131 1.616610 0.063153\nv 0.000053 1.664440 0.108414\nv 0.012267 1.664506 0.104780\nv 0.019951 1.664088 0.098265\nv 0.024738 1.663814 0.093677\nv 0.027689 1.664449 0.091295\nv 0.028215 1.667919 0.091592\nv 0.025251 1.669996 0.094481\nv 0.019731 1.672104 0.100195\nv 0.011260 1.674506 0.106957\nv 0.000034 1.674524 0.109320\nv 0.035604 1.632976 0.035981\nv 0.048029 1.646799 0.019060\nv 0.061889 1.665129 0.002744\nv 0.065894 1.680989 -0.008410\nv 0.070251 1.685087 0.002219\nv 0.045012 1.649855 0.063508\nv 0.049767 1.674830 0.068819\nv 0.049602 1.685919 0.074742\nv 0.043287 1.705239 0.087040\nv 0.054608 1.713185 0.077067\nv 0.063460 1.723354 0.060011\nv 0.062580 1.744276 0.060069\nv 0.058550 1.682395 0.053832\nv 0.058485 1.693517 0.061171\nv 0.066773 1.693695 0.034745\nv 0.066196 1.703305 0.042174\nv 0.009686 1.643924 0.106047\nv 0.000003 1.644385 0.107544\nv 0.003994 1.697403 0.117907\nv -0.000093 1.696027 0.119655\nv 0.005509 1.700325 0.121720\nv -0.000081 1.699480 0.124631\nv 0.008564 1.701084 0.115837\nv 0.011250 1.701146 0.110996\nv 0.011209 1.700174 0.107676\nv 0.009250 1.699946 0.105040\nv 0.005696 1.699643 0.114717\nv 0.004806 1.698991 0.109536\nv 0.006359 1.698992 0.106701\nv 0.006177 1.701333 0.111647\nv 0.005263 1.701355 0.108796\nv 0.006007 1.701244 0.106868\nv 0.007585 1.702590 0.106285\nv 0.009251 1.702575 0.107954\nv 0.008477 1.702143 0.110056\nv 0.007672 1.701306 0.112274\nv 0.006667 1.702509 0.109000\nv 0.005152 1.700781 0.110806\nv 0.004549 1.698954 0.112277\nv 0.003329 1.696164 0.114065\nv -0.000090 1.694631 0.114814\nv 0.021837 1.737594 0.086253\nv 0.020275 1.739011 0.085949\nv 0.021592 1.740106 0.086528\nv 0.025422 1.742281 0.087338\nv 0.031555 1.743967 0.087440\nv 0.037824 1.743250 0.086457\nv 0.041881 1.741717 0.084111\nv 0.043163 1.740476 0.082369\nv 0.041668 1.738321 0.083256\nv 0.037765 1.736606 0.085061\nv 0.031964 1.735716 0.086364\nv 0.025909 1.736576 0.086677\nv 0.032153 1.732865 0.087641\nv 0.024682 1.733953 0.088036\nv 0.019644 1.735680 0.087783\nv 0.017496 1.739742 0.087959\nv 0.018864 1.742605 0.087640\nv 0.023800 1.745699 0.088220\nv 0.031516 1.746377 0.089036\nv 0.039002 1.745482 0.086631\nv 0.044082 1.742891 0.082938\nv 0.045832 1.740236 0.080902\nv 0.044092 1.736379 0.082290\nv 0.039462 1.733871 0.085429\nv 0.022404 1.737726 0.084972\nv 0.021954 1.740060 0.085420\nv 0.025368 1.742523 0.086120\nv 0.031372 1.744508 0.086249\nv 0.037616 1.743875 0.085027\nv 0.041484 1.742346 0.083176\nv 0.042448 1.740651 0.081731\nv 0.040869 1.737930 0.082242\nv 0.037351 1.736270 0.083567\nv 0.031849 1.735163 0.084875\nv 0.025985 1.736292 0.085109\nv 0.022726 1.737138 0.084041\nv 0.022044 1.739008 0.084359\nv 0.022144 1.740324 0.084669\nv 0.025069 1.742093 0.084169\nv 0.031153 1.743902 0.083249\nv 0.037458 1.743570 0.082281\nv 0.040698 1.742257 0.081774\nv 0.041019 1.740608 0.080540\nv 0.040371 1.737388 0.080969\nv 0.037359 1.735746 0.081271\nv 0.031690 1.733979 0.082093\nv 0.025410 1.735014 0.083248\nv 0.031258 1.740910 0.080416\nv 0.037411 1.740976 0.079929\nv 0.024765 1.739979 0.082100\nv 0.013064 1.786527 0.099713\nv 0.024720 1.787806 0.094843\nv 0.039612 1.786279 0.085965\nv 0.053593 1.778310 0.072103\nv 0.063618 1.763804 0.055698\nv 0.005313 1.785273 0.101430\nv -0.000040 1.784905 0.101766\nv 0.069849 1.728509 0.035613\nv 0.067525 1.749283 0.044865\nv 0.069880 1.704149 0.015914\nv 0.069980 1.712265 0.021754\nv 0.063120 1.652046 -0.043685\nv 0.063008 1.674361 -0.046297\nv 0.058976 1.634891 -0.002158\nv 0.029928 1.541102 0.033169\nv 0.048332 1.537609 0.028333\nv 0.065688 1.620044 -0.039398\nv 0.046793 1.528980 0.043839\nv 0.025353 1.524534 0.046158\nv -0.000048 1.613263 0.052220\nv 0.000002 1.535384 0.030170\nv 0.045401 1.638162 0.011128\nv 0.031140 1.628047 0.028755\nv 0.006394 1.614430 0.051175\nv 0.013189 1.617340 0.047018\nv 0.020781 1.621769 0.039483\nv 0.021818 1.541617 0.034247\nv 0.017871 1.523124 0.045326\nv 0.015043 1.540731 0.032244\nv 0.008479 1.540329 0.028899\nv 0.034863 1.526842 0.046472\nv 0.038922 1.539646 0.031007\nv 0.064868 1.628958 -0.017339\nv 0.068341 1.654261 -0.026186\nv 0.069373 1.675792 -0.030955\nv -0.000122 1.597369 0.036863\nv 0.004866 1.597711 0.035834\nv 0.010364 1.598515 0.032876\nv 0.016194 1.599834 0.027885\nv 0.023492 1.601210 0.021346\nv 0.034423 1.600878 0.016177\nv 0.047989 1.598148 0.012879\nv 0.058352 1.591414 0.003279\nv 0.063578 1.580765 -0.009546\nv -0.000004 1.566682 0.024621\nv 0.003601 1.566476 0.024446\nv 0.007553 1.564867 0.024184\nv 0.012312 1.562691 0.024284\nv 0.018551 1.562169 0.024909\nv 0.026544 1.562406 0.026839\nv 0.036683 1.561321 0.026377\nv 0.046650 1.557509 0.022140\nv 0.054687 1.551300 0.016522\nv 0.073030 1.758262 0.018127\nv 0.068029 1.771070 0.041876\nv 0.065141 1.712848 -0.004466\nv 0.073107 1.717853 0.005914\nv 0.059566 1.789673 0.058214\nv 0.068132 1.697142 -0.017676\nv 0.045207 1.801670 0.071881\nv 0.030267 1.807253 0.081848\nv -0.000032 1.810426 0.092106\nv 0.017393 1.809724 0.088634\nv 0.007525 1.810372 0.091503\nv 0.075382 1.763160 -0.000406\nv 0.074420 1.781548 0.004536\nv 0.070293 1.807330 0.013168\nv 0.058346 1.828248 0.021167\nv 0.041753 1.841151 0.027304\nv 0.025715 1.848985 0.031977\nv 0.011688 1.853004 0.034738\nv -0.000045 1.854177 0.035574\nv 0.069759 1.702470 -0.021451\nv 0.076863 1.748759 -0.022851\nv 0.076214 1.734640 -0.035953\nv 0.074620 1.725802 -0.035785\nv 0.073190 1.716841 -0.031757\nv 0.076942 1.742609 -0.031703\nv 0.076281 1.720511 -0.006288\nv 0.072562 1.732126 0.018956\nv 0.071177 1.754128 0.030026\nv 0.071046 1.776379 0.028348\nv 0.064527 1.798235 0.043434\nv 0.049998 1.813690 0.055664\nv 0.034880 1.822871 0.065535\nv 0.020838 1.827950 0.072250\nv 0.009365 1.830476 0.075801\nv -0.000033 1.831153 0.076778\nv 0.070919 1.710329 0.003169\nv 0.065640 1.691155 -0.015431\nv 0.070634 1.690918 0.008014\nv 0.066767 1.678397 0.022452\nv 0.056647 1.662779 0.040915\nv 0.072247 1.720858 -0.007201\nv 0.074912 1.732527 0.002836\nv 0.075896 1.740303 0.004206\nv 0.076434 1.750853 -0.003857\nv 0.067814 1.682050 -0.004032\nv 0.064286 1.667920 0.008371\nv 0.052101 1.650279 0.026220\nv 0.039832 1.636121 0.044662\nv 0.029329 1.627494 0.059672\nv 0.019541 1.622101 0.070804\nv 0.010024 1.619549 0.077228\nv 0.000015 1.618811 0.079257\nv 0.071620 1.709066 -0.026083\nv 0.060788 1.530576 0.036368\nv 0.075430 1.534566 0.022602\nv 0.057237 1.536253 0.026327\nv 0.100306 1.554460 -0.010734\nv 0.064605 1.543825 0.015811\nv 0.078590 1.567150 -0.015633\nv 0.068113 1.698900 -0.033429\nv 0.067511 1.707214 -0.034044\nv 0.072331 1.743066 -0.033188\nv 0.073759 1.751898 -0.028361\nv 0.068296 1.715531 -0.034993\nv 0.069271 1.724566 -0.035956\nv 0.070643 1.734196 -0.035757\nv 0.074989 1.759156 -0.020124\nv 0.069015 1.689374 -0.032719\nv 0.075628 1.762365 -0.010271\nv 0.075342 1.781265 -0.007950\nv 0.072496 1.808179 -0.003886\nv 0.061768 1.830952 0.000108\nv 0.045307 1.845691 0.003215\nv 0.028351 1.854537 0.005435\nv 0.012963 1.859086 0.006719\nv -0.000061 1.860283 0.007078\nv -0.000230 1.688992 -0.093111\nv -0.000290 1.788235 -0.108869\nv 0.060154 1.763633 -0.080008\nv 0.065012 1.818998 -0.044311\nv 0.074245 1.770705 -0.031645\nv 0.075491 1.777991 -0.020273\nv 0.072928 1.796076 -0.038015\nv 0.073628 1.804859 -0.021311\nv 0.063929 1.828358 -0.022211\nv 0.068877 1.735349 -0.048209\nv 0.072646 1.760114 -0.040338\nv 0.071072 1.747939 -0.045843\nv 0.064210 1.802779 -0.061622\nv 0.070342 1.782480 -0.050941\nv 0.065477 1.750265 -0.064323\nv 0.067762 1.766621 -0.059519\nv 0.062446 1.783468 -0.073207\nv -0.000070 1.852281 -0.057453\nv 0.033397 1.845215 -0.053288\nv 0.050801 1.835601 -0.049425\nv 0.048165 1.844258 -0.022996\nv 0.030846 1.853708 -0.023926\nv 0.016113 1.850606 -0.056290\nv 0.014451 1.859137 -0.024854\nv -0.000069 1.860602 -0.025234\nv 0.034035 1.782497 -0.102196\nv 0.035259 1.828087 -0.077296\nv 0.052199 1.818518 -0.070858\nv 0.049278 1.774579 -0.093016\nv 0.051606 1.796924 -0.085165\nv 0.035500 1.805825 -0.093529\nv -0.000114 1.834591 -0.083489\nv 0.017288 1.833013 -0.081681\nv 0.016834 1.786892 -0.107275\nv 0.017430 1.810189 -0.098312\nv -0.000225 1.811400 -0.099945\nv 0.041588 1.677303 -0.075621\nv 0.051134 1.727628 -0.080832\nv 0.064123 1.712345 -0.047994\nv 0.066245 1.723488 -0.048520\nv 0.058460 1.720116 -0.065133\nv 0.062011 1.734618 -0.065733\nv 0.055962 1.744715 -0.082023\nv 0.063127 1.701350 -0.047602\nv 0.063175 1.689497 -0.047362\nv 0.046828 1.711698 -0.078268\nv 0.055125 1.706358 -0.063619\nv 0.054023 1.675254 -0.063166\nv 0.053212 1.692068 -0.062668\nv 0.042852 1.695734 -0.075625\nv -0.000301 1.746679 -0.109680\nv 0.027626 1.740944 -0.101918\nv 0.040563 1.734637 -0.093729\nv 0.044964 1.753551 -0.094829\nv 0.031096 1.760520 -0.104153\nv 0.013885 1.745159 -0.107758\nv 0.015607 1.764973 -0.110046\nv -0.000355 1.766522 -0.111703\nv 0.019183 1.684644 -0.089803\nv 0.024547 1.722875 -0.098107\nv 0.036450 1.717302 -0.089902\nv 0.030659 1.680693 -0.085473\nv 0.032466 1.700086 -0.086016\nv 0.021594 1.704793 -0.093737\nv -0.000239 1.728580 -0.105104\nv 0.012187 1.727068 -0.103302\nv 0.009029 1.687848 -0.092277\nv 0.010591 1.708549 -0.098352\nv -0.000257 1.709927 -0.099816\nv 0.000020 1.520793 0.043117\nv 0.011564 1.522348 0.044505\nv 0.005722 1.521526 0.043516\nv 0.088852 1.575770 -0.132210\nv 0.052399 1.610813 -0.118401\nv 0.054458 1.628022 -0.092308\nv 0.066325 1.628566 -0.066877\nv 0.117309 1.585153 -0.077249\nv 0.110944 1.567968 -0.026859\nv 0.071153 1.595545 -0.124303\nv 0.090386 1.605099 -0.072884\nv 0.086150 1.583089 -0.028796\nv 0.056826 1.653671 -0.063934\nv 0.044108 1.655068 -0.079535\nv 0.082673 1.604229 -0.100343\nv 0.107148 1.583321 -0.107287\nv 0.008800 1.661542 -0.092443\nv -0.000168 1.662050 -0.092204\nv 0.032176 1.658535 -0.091053\nv 0.019401 1.660454 -0.093737\nv 0.009332 1.634341 -0.100294\nv -0.000038 1.633839 -0.098851\nv 0.034686 1.635475 -0.101899\nv 0.020422 1.635603 -0.102955\nv 0.009940 1.609310 -0.111180\nv -0.000007 1.608010 -0.109334\nv 0.035436 1.615047 -0.117918\nv 0.021072 1.612992 -0.115948\nv -0.000167 1.538961 -0.145279\nv 0.012223 1.541756 -0.147605\nv 0.026971 1.548670 -0.151627\nv 0.011210 1.564107 -0.134853\nv -0.000133 1.562011 -0.133070\nv 0.024171 1.569839 -0.139188\nv 0.039722 1.577489 -0.141734\nv 0.045780 1.557310 -0.152380\nv 0.056400 1.585571 -0.137865\nv 0.067302 1.566757 -0.146987\nv 0.047841 1.598169 -0.130298\nv 0.035728 1.596246 -0.130847\nv 0.021966 1.591440 -0.127763\nv 0.010810 1.587211 -0.122010\nv -0.000120 1.585393 -0.119345\nv -0.000037 1.844370 0.058206\nv 0.010542 1.843437 0.057247\nv 0.023309 1.840075 0.053884\nv 0.038387 1.833530 0.047842\nv 0.054304 1.822209 0.039422\nv 0.067169 1.803566 0.028582\nv 0.072953 1.779848 0.016483\nv 0.074419 1.761748 0.008789\nv 0.076858 1.747115 0.001483\nv 0.070263 1.697215 0.012293\nv 0.066949 1.685391 0.028486\nv 0.057849 1.672101 0.047424\nv 0.048762 1.663781 0.064639\nv 0.042141 1.661730 0.077786\nv 0.035892 1.662841 0.084875\nv 0.031603 1.664481 0.088914\nv 0.028961 1.665901 0.090707\nv 0.027337 1.666702 0.091190\nv 0.076444 1.750926 -0.011798\nv -0.000060 1.582050 0.030366\nv 0.004349 1.582245 0.029321\nv 0.008983 1.582512 0.026869\nv 0.014108 1.582812 0.023709\nv 0.021065 1.582801 0.020904\nv 0.030804 1.582183 0.020995\nv 0.042676 1.580210 0.019778\nv 0.053207 1.574617 0.012935\nv 0.060546 1.565681 0.004096\nv 0.072045 1.554122 0.000164\nv 0.088515 1.542491 0.005471\nv 0.005047 1.550807 0.024670\nv 0.000005 1.546272 0.025246\nv 0.000011 1.554737 0.023735\nv 0.002948 1.556099 0.023814\nv 0.000038 1.620693 0.093186\nv 0.012678 1.621893 0.090585\nv 0.023411 1.625304 0.082758\nv 0.033717 1.631166 0.069837\nv 0.043489 1.640605 0.053099\nv 0.055302 1.654711 0.033052\nv 0.065942 1.672331 0.015393\nv 0.075455 1.724851 -0.016817\nv 0.077993 1.733473 -0.008316\nv 0.079963 1.744299 -0.010268\nv 0.084202 1.747008 -0.020146\nv 0.085646 1.751063 -0.016501\nv 0.083964 1.745605 -0.005179\nv 0.081348 1.737237 -0.002213\nv 0.078995 1.737107 -0.006182\nv 0.084682 1.745129 -0.027761\nv 0.084040 1.741523 -0.031278\nv 0.083199 1.735492 -0.033578\nv 0.083904 1.736295 -0.037335\nv 0.085018 1.743047 -0.034405\nv 0.085676 1.749057 -0.028961\nv 0.078676 1.730526 -0.002604\nv 0.075795 1.721044 -0.010951\nv 0.079993 1.754382 -0.014894\nv 0.079132 1.735682 -0.036989\nv 0.080014 1.743198 -0.034047\nv 0.080149 1.751023 -0.027795\nv 0.079769 1.752202 -0.006075\nv 0.081446 1.728202 -0.034219\nv 0.080140 1.719759 -0.031680\nv 0.078578 1.711046 -0.027873\nv 0.076120 1.703245 -0.022557\nv 0.077901 1.701380 -0.027078\nv 0.079617 1.709586 -0.032314\nv 0.080856 1.718512 -0.036285\nv 0.082016 1.727798 -0.038393\nv 0.073498 1.701292 -0.026845\nv 0.075137 1.709569 -0.030627\nv 0.076303 1.718309 -0.034170\nv 0.077470 1.727204 -0.036750\nv 0.076110 1.696404 -0.015891\nv 0.075880 1.688338 -0.007354\nv 0.073928 1.685553 -0.014040\nv 0.076520 1.693935 -0.021143\nv 0.072249 1.694338 -0.021957\nv 0.070352 1.688843 -0.014849\nv 0.070346 1.703779 0.001170\nv 0.066794 1.705050 -0.005241\nv 0.072327 1.697160 -0.002218\nv 0.071613 1.702806 -0.009547\nv 0.065883 1.680467 -0.012559\nv 0.061759 1.661888 -0.003208\nv 0.066269 1.658645 -0.012145\nv 0.068068 1.679207 -0.019108\nv 0.067028 1.690578 -0.021785\nv 0.066656 1.697872 -0.022940\nv 0.067306 1.704369 -0.024368\nv 0.068792 1.711066 -0.026890\nv 0.070821 1.718288 -0.031645\nv 0.072122 1.727282 -0.034617\nv 0.073296 1.735921 -0.033303\nv 0.074178 1.743700 -0.027957\nv 0.074239 1.750685 -0.020016\nv 0.074834 1.753667 -0.010693\nv 0.075364 1.753619 -0.002392\nv 0.075229 1.751125 0.004691\nv 0.074194 1.744087 0.011074\nv 0.073994 1.733394 0.008456\nv 0.072240 1.741722 0.023885\nv 0.068946 1.738042 0.039760\nv 0.063459 1.734822 0.057789\nv 0.056725 1.734630 0.071748\nv 0.050466 1.736369 0.078056\nv 0.045657 1.738340 0.080686\nv 0.043110 1.739510 0.082231\nv 0.042455 1.739267 0.081454\nv 0.040939 1.738749 0.080182\nv 0.037268 1.737952 0.079761\nv 0.031389 1.737208 0.080290\nv 0.025047 1.737210 0.081703\nv 0.022245 1.738082 0.083969\nv 0.020310 1.738237 0.085922\nv 0.017843 1.737850 0.088142\nv 0.013913 1.737745 0.093533\nv 0.008596 1.737475 0.099514\nv 0.004697 1.738221 0.105789\nv -0.000022 1.738311 0.107677\nv 0.020758 1.738982 0.084846\nv 0.021009 1.738169 0.084825\nv 0.083884 1.728019 -0.027851\nv 0.082049 1.720263 -0.025463\nv 0.077695 1.713336 -0.021535\nv 0.074529 1.707918 -0.016279\nv 0.066732 1.712506 -0.009922\nv 0.072644 1.714223 -0.014272\nv 0.075886 1.719035 -0.018948\nv 0.080961 1.727326 -0.020681\nv 0.079311 1.722572 -0.021539\nv 0.079757 1.739110 -0.015888\nv 0.085101 1.742390 -0.024457\nv 0.085178 1.734921 -0.027381\nv 0.082519 1.732832 -0.018880\nv 0.075867 1.729004 -0.012865\nv 0.077068 1.726102 -0.005939\nv 0.074239 1.726985 -0.002165\nv 0.073959 1.726861 0.002416\nv 0.072695 1.724917 0.012615\nv 0.069925 1.720304 0.029019\nv 0.065473 1.712886 0.050301\nv 0.057624 1.703466 0.068903\nv 0.048045 1.695440 0.081280\nv 0.036614 1.690315 0.087982\nv 0.027304 1.687463 0.094477\nv 0.018346 1.687018 0.100441\nv 0.009477 1.687585 0.106700\nv -0.000053 1.687639 0.107997\nv 0.090927 1.597029 -0.047136\nv 0.117632 1.578977 -0.049122\nv -0.000134 1.607983 0.045941\nv 0.006127 1.608663 0.044819\nv 0.012520 1.610448 0.041153\nv 0.019181 1.612788 0.034426\nv 0.027034 1.616058 0.024984\nv 0.038442 1.617996 0.013932\nv 0.053218 1.615282 0.006285\nv 0.061660 1.608403 -0.006838\nv 0.065602 1.597407 -0.023120\nv 0.026164 1.666994 0.090746\nv 0.023149 1.667386 0.092814\nv 0.025362 1.667083 0.091259\nv 0.023135 1.667651 0.093041\nv 0.025489 1.667201 0.091265\nv 0.000041 1.670299 0.103229\nv 0.010442 1.669980 0.101243\nv 0.018221 1.668650 0.096556\nv 0.018678 1.668534 0.095726\nv 0.010660 1.669179 0.099465\nv 0.000064 1.668598 0.101684\nv -0.016542 1.733953 0.092813\nv -0.015484 1.744801 0.094338\nv -0.022069 1.748498 0.095013\nv -0.031927 1.750293 0.093778\nv -0.041523 1.749222 0.088251\nv -0.048497 1.744817 0.081332\nv -0.048467 1.732886 0.080713\nv -0.042318 1.728872 0.085420\nv -0.032400 1.727176 0.089015\nv -0.022610 1.729828 0.091188\nv -0.011625 1.730172 0.100463\nv -0.011005 1.750319 0.102809\nv -0.021379 1.754496 0.104559\nv -0.034375 1.757106 0.100859\nv -0.046776 1.755080 0.090966\nv -0.054908 1.748453 0.078446\nv -0.055157 1.728446 0.075963\nv -0.047877 1.722077 0.084230\nv -0.033859 1.717347 0.091638\nv -0.019945 1.723545 0.095648\nv -0.005558 1.731350 0.109217\nv -0.004015 1.751374 0.104444\nv -0.011457 1.712728 0.113853\nv -0.005815 1.713540 0.119628\nv -0.010982 1.720073 0.108118\nv -0.005593 1.722012 0.114534\nv -0.016982 1.715278 0.101556\nv -0.024901 1.707887 0.096006\nv -0.031904 1.698761 0.092161\nv -0.040209 1.681297 0.083590\nv -0.030639 1.678311 0.091348\nv -0.023311 1.694571 0.096956\nv -0.018928 1.701537 0.101013\nv -0.016962 1.709539 0.107375\nv -0.010763 1.766776 0.105238\nv -0.021546 1.769955 0.103235\nv -0.036087 1.771319 0.097074\nv -0.050916 1.766775 0.085155\nv -0.060022 1.755731 0.069133\nv -0.004196 1.765125 0.105607\nv -0.042283 1.671245 0.079756\nv -0.033294 1.645675 0.087024\nv -0.023843 1.641648 0.097000\nv -0.012206 1.648334 0.102717\nv -0.012180 1.653005 0.100362\nv -0.021988 1.650285 0.094783\nv -0.029306 1.652337 0.090062\nv -0.035388 1.669856 0.086431\nv -0.016602 1.619360 0.058151\nv -0.025474 1.624636 0.049003\nv -0.035703 1.638153 0.079040\nv -0.026234 1.631845 0.092068\nv -0.050793 1.739956 0.078037\nv -0.056709 1.740914 0.071917\nv -0.013574 1.740929 0.093875\nv -0.008409 1.742533 0.099944\nv -0.003882 1.743630 0.103772\nv -0.026687 1.667361 0.091784\nv -0.019123 1.667678 0.097280\nv -0.011419 1.669253 0.102415\nv -0.026213 1.658541 0.092529\nv -0.012117 1.657874 0.102816\nv -0.020521 1.657203 0.096986\nv -0.027027 1.672997 0.093577\nv -0.020150 1.676308 0.100352\nv -0.018881 1.669444 0.098634\nv -0.011641 1.679527 0.108647\nv -0.010811 1.670935 0.104084\nv -0.030902 1.668774 0.090028\nv -0.026379 1.666418 0.091716\nv -0.029959 1.661002 0.089655\nv -0.033816 1.656891 0.086084\nv -0.039478 1.652656 0.079524\nv -0.010594 1.683931 0.107093\nv -0.023851 1.666535 0.093650\nv -0.024032 1.668292 0.094088\nv -0.006619 1.706497 0.123248\nv -0.011264 1.706459 0.116965\nv -0.014554 1.704753 0.110388\nv -0.014923 1.700773 0.105940\nv -0.011562 1.697742 0.105502\nv -0.013797 1.695702 0.103717\nv -0.007490 1.696371 0.107887\nv -0.007597 1.693601 0.107562\nv -0.004252 1.695858 0.110665\nv -0.020025 1.681538 0.099803\nv -0.015811 1.691922 0.101714\nv -0.008231 1.690942 0.106929\nv -0.013687 1.637056 0.105332\nv -0.014759 1.628197 0.101139\nv -0.008136 1.616610 0.063153\nv -0.012272 1.664506 0.104780\nv -0.019982 1.664088 0.098265\nv -0.024751 1.663814 0.093677\nv -0.027694 1.664449 0.091295\nv -0.028220 1.667919 0.091592\nv -0.025256 1.669996 0.094481\nv -0.019736 1.672104 0.100195\nv -0.011265 1.674506 0.106957\nv -0.035610 1.632976 0.035981\nv -0.048034 1.646799 0.019060\nv -0.061928 1.665128 0.002743\nv -0.065957 1.680984 -0.008410\nv -0.070460 1.685080 0.002219\nv -0.045017 1.649855 0.063508\nv -0.049780 1.674830 0.068819\nv -0.049637 1.685919 0.074742\nv -0.043342 1.705239 0.087040\nv -0.054704 1.713185 0.077066\nv -0.063626 1.723354 0.060011\nv -0.062615 1.744277 0.060070\nv -0.058563 1.682395 0.053832\nv -0.058500 1.693517 0.061171\nv -0.066917 1.693695 0.034745\nv -0.066305 1.703305 0.042174\nv -0.009701 1.643924 0.106047\nv -0.003999 1.697403 0.117907\nv -0.005514 1.700325 0.121720\nv -0.008569 1.701084 0.115837\nv -0.011268 1.701146 0.110996\nv -0.011231 1.700174 0.107676\nv -0.009263 1.699946 0.105040\nv -0.005701 1.699643 0.114717\nv -0.004811 1.698991 0.109536\nv -0.006364 1.698992 0.106701\nv -0.006182 1.701333 0.111647\nv -0.005268 1.701355 0.108796\nv -0.006012 1.701244 0.106868\nv -0.007591 1.702590 0.106285\nv -0.009266 1.702575 0.107954\nv -0.008487 1.702143 0.110057\nv -0.007678 1.701306 0.112274\nv -0.006673 1.702509 0.109000\nv -0.005157 1.700781 0.110806\nv -0.004555 1.698954 0.112277\nv -0.003334 1.696164 0.114065\nv -0.021851 1.737594 0.086253\nv -0.020283 1.739011 0.085949\nv -0.021599 1.740106 0.086528\nv -0.025427 1.742281 0.087338\nv -0.031560 1.743967 0.087440\nv -0.037829 1.743250 0.086457\nv -0.041888 1.741717 0.084111\nv -0.043171 1.740476 0.082369\nv -0.041684 1.738321 0.083256\nv -0.037792 1.736606 0.085061\nv -0.031990 1.735716 0.086364\nv -0.025928 1.736576 0.086677\nv -0.032185 1.732865 0.087641\nv -0.024712 1.733953 0.088036\nv -0.019678 1.735680 0.087784\nv -0.017509 1.739742 0.087959\nv -0.018869 1.742605 0.087640\nv -0.023805 1.745699 0.088220\nv -0.031521 1.746377 0.089036\nv -0.039007 1.745482 0.086631\nv -0.044087 1.742891 0.082938\nv -0.045838 1.740236 0.080902\nv -0.044112 1.736378 0.082289\nv -0.039494 1.733870 0.085429\nv -0.022416 1.737726 0.084972\nv -0.021962 1.740060 0.085420\nv -0.025373 1.742523 0.086120\nv -0.031377 1.744508 0.086249\nv -0.037621 1.743875 0.085027\nv -0.041489 1.742346 0.083176\nv -0.042455 1.740651 0.081731\nv -0.040885 1.737930 0.082242\nv -0.037377 1.736271 0.083567\nv -0.031874 1.735163 0.084875\nv -0.026000 1.736292 0.085110\nv -0.022737 1.737138 0.084041\nv -0.022052 1.739008 0.084359\nv -0.022150 1.740324 0.084669\nv -0.025074 1.742093 0.084169\nv -0.031158 1.743902 0.083249\nv -0.037463 1.743570 0.082281\nv -0.040703 1.742257 0.081774\nv -0.041026 1.740608 0.080540\nv -0.040387 1.737388 0.080969\nv -0.037370 1.735746 0.081271\nv -0.031695 1.733979 0.082093\nv -0.025418 1.735014 0.083247\nv -0.031263 1.740910 0.080416\nv -0.037416 1.740976 0.079929\nv -0.024770 1.739979 0.082100\nv -0.013069 1.786527 0.099713\nv -0.024759 1.787806 0.094843\nv -0.039835 1.786281 0.085968\nv -0.053661 1.778309 0.072102\nv -0.063634 1.763804 0.055698\nv -0.005318 1.785273 0.101430\nv -0.070145 1.728509 0.035612\nv -0.067574 1.749283 0.044865\nv -0.069933 1.704149 0.015914\nv -0.070041 1.712265 0.021753\nv -0.063126 1.652046 -0.043685\nv -0.063014 1.674361 -0.046297\nv -0.058981 1.634891 -0.002158\nv -0.029933 1.541102 0.033169\nv -0.048337 1.537609 0.028333\nv -0.065693 1.620044 -0.039398\nv -0.046798 1.528980 0.043839\nv -0.025358 1.524534 0.046158\nv -0.045406 1.638162 0.011128\nv -0.031166 1.628047 0.028755\nv -0.006441 1.614429 0.051175\nv -0.013224 1.617340 0.047018\nv -0.020806 1.621769 0.039483\nv -0.021823 1.541617 0.034247\nv -0.017876 1.523124 0.045326\nv -0.015049 1.540731 0.032244\nv -0.008485 1.540329 0.028899\nv -0.034868 1.526842 0.046472\nv -0.038927 1.539646 0.031007\nv -0.064873 1.628958 -0.017339\nv -0.068348 1.654261 -0.026186\nv -0.069381 1.675792 -0.030955\nv -0.005057 1.597715 0.035828\nv -0.010495 1.598517 0.032872\nv -0.016260 1.599836 0.027883\nv -0.023527 1.601210 0.021346\nv -0.034439 1.600878 0.016177\nv -0.047995 1.598148 0.012879\nv -0.058357 1.591414 0.003279\nv -0.063583 1.580765 -0.009546\nv -0.003614 1.566476 0.024446\nv -0.007563 1.564867 0.024184\nv -0.012320 1.562691 0.024284\nv -0.018559 1.562169 0.024909\nv -0.026556 1.562406 0.026839\nv -0.036690 1.561321 0.026377\nv -0.046656 1.557509 0.022140\nv -0.054692 1.551300 0.016522\nv -0.073035 1.758262 0.018127\nv -0.068084 1.771070 0.041875\nv -0.065477 1.712833 -0.004526\nv -0.073198 1.717853 0.005915\nv -0.059684 1.789672 0.058213\nv -0.068208 1.697144 -0.017675\nv -0.045375 1.801670 0.071882\nv -0.030359 1.807252 0.081847\nv -0.017400 1.809724 0.088634\nv -0.007537 1.810372 0.091503\nv -0.075387 1.763160 -0.000406\nv -0.074425 1.781548 0.004536\nv -0.070298 1.807330 0.013168\nv -0.058351 1.828248 0.021167\nv -0.041758 1.841151 0.027304\nv -0.025720 1.848985 0.031977\nv -0.011693 1.853004 0.034738\nv -0.069789 1.702471 -0.021450\nv -0.076874 1.748758 -0.022851\nv -0.076228 1.734640 -0.035953\nv -0.074637 1.725803 -0.035785\nv -0.073208 1.716842 -0.031757\nv -0.076955 1.742609 -0.031703\nv -0.076349 1.720509 -0.006286\nv -0.072818 1.732124 0.018954\nv -0.071191 1.754128 0.030026\nv -0.071070 1.776379 0.028348\nv -0.064633 1.798235 0.043433\nv -0.050047 1.813689 0.055665\nv -0.034947 1.822871 0.065535\nv -0.020846 1.827950 0.072250\nv -0.009370 1.830476 0.075801\nv -0.070968 1.710329 0.003169\nv -0.065724 1.691149 -0.015432\nv -0.070868 1.690916 0.008013\nv -0.066931 1.678397 0.022452\nv -0.056658 1.662779 0.040915\nv -0.072473 1.720847 -0.007229\nv -0.075044 1.732521 0.002831\nv -0.075944 1.740302 0.004206\nv -0.076442 1.750853 -0.003857\nv -0.067962 1.682032 -0.004031\nv -0.064396 1.667919 0.008371\nv -0.052123 1.650279 0.026220\nv -0.039838 1.636121 0.044662\nv -0.029334 1.627494 0.059672\nv -0.019546 1.622101 0.070804\nv -0.010029 1.619549 0.077228\nv -0.071660 1.709066 -0.026082\nv -0.060794 1.530576 0.036368\nv -0.075436 1.534566 0.022602\nv -0.057243 1.536253 0.026327\nv -0.100311 1.554460 -0.010734\nv -0.064610 1.543825 0.015811\nv -0.078595 1.567150 -0.015633\nv -0.068128 1.698900 -0.033429\nv -0.067528 1.707215 -0.034044\nv -0.072343 1.743066 -0.033188\nv -0.073771 1.751898 -0.028361\nv -0.068313 1.715531 -0.034993\nv -0.069285 1.724566 -0.035955\nv -0.070656 1.734196 -0.035756\nv -0.074998 1.759156 -0.020123\nv -0.069027 1.689374 -0.032719\nv -0.075634 1.762365 -0.010271\nv -0.075347 1.781265 -0.007950\nv -0.072501 1.808179 -0.003886\nv -0.061773 1.830952 0.000108\nv -0.045312 1.845691 0.003215\nv -0.028356 1.854537 0.005435\nv -0.012969 1.859086 0.006719\nv -0.060159 1.763633 -0.080008\nv -0.065017 1.818998 -0.044311\nv -0.074253 1.770705 -0.031645\nv -0.075498 1.777990 -0.020273\nv -0.072933 1.796076 -0.038015\nv -0.073633 1.804859 -0.021311\nv -0.063934 1.828358 -0.022211\nv -0.068889 1.735349 -0.048209\nv -0.072655 1.760113 -0.040338\nv -0.071083 1.747939 -0.045842\nv -0.064215 1.802779 -0.061622\nv -0.070347 1.782480 -0.050941\nv -0.065484 1.750265 -0.064323\nv -0.067768 1.766621 -0.059519\nv -0.062451 1.783468 -0.073207\nv -0.033402 1.845215 -0.053288\nv -0.050806 1.835601 -0.049425\nv -0.048170 1.844258 -0.022996\nv -0.030851 1.853708 -0.023926\nv -0.016118 1.850606 -0.056290\nv -0.014456 1.859137 -0.024854\nv -0.034040 1.782497 -0.102196\nv -0.035264 1.828087 -0.077296\nv -0.052204 1.818518 -0.070858\nv -0.049283 1.774579 -0.093016\nv -0.051611 1.796924 -0.085165\nv -0.035505 1.805825 -0.093529\nv -0.017293 1.833013 -0.081681\nv -0.016839 1.786892 -0.107275\nv -0.017435 1.810189 -0.098312\nv -0.041593 1.677303 -0.075621\nv -0.051139 1.727628 -0.080832\nv -0.064135 1.712345 -0.047994\nv -0.066256 1.723488 -0.048520\nv -0.058466 1.720116 -0.065133\nv -0.062018 1.734618 -0.065733\nv -0.055967 1.744715 -0.082023\nv -0.063136 1.701350 -0.047602\nv -0.063182 1.689497 -0.047362\nv -0.046833 1.711698 -0.078268\nv -0.055131 1.706358 -0.063619\nv -0.054028 1.675254 -0.063166\nv -0.053219 1.692068 -0.062668\nv -0.042857 1.695734 -0.075625\nv -0.027631 1.740944 -0.101918\nv -0.040568 1.734637 -0.093729\nv -0.044969 1.753551 -0.094829\nv -0.031101 1.760520 -0.104154\nv -0.013891 1.745159 -0.107758\nv -0.015613 1.764973 -0.110046\nv -0.019188 1.684644 -0.089803\nv -0.024552 1.722875 -0.098107\nv -0.036455 1.717302 -0.089902\nv -0.030664 1.680693 -0.085473\nv -0.032471 1.700086 -0.086016\nv -0.021599 1.704793 -0.093737\nv -0.012196 1.727068 -0.103302\nv -0.009080 1.687851 -0.092276\nv -0.010639 1.708551 -0.098348\nv -0.011570 1.522348 0.044505\nv -0.005727 1.521526 0.043516\nv -0.088857 1.575770 -0.132210\nv -0.052404 1.610813 -0.118401\nv -0.054463 1.628022 -0.092308\nv -0.066330 1.628566 -0.066877\nv -0.117314 1.585153 -0.077249\nv -0.110949 1.567968 -0.026859\nv -0.071158 1.595545 -0.124303\nv -0.090391 1.605099 -0.072884\nv -0.086155 1.583089 -0.028796\nv -0.056832 1.653671 -0.063934\nv -0.044114 1.655068 -0.079535\nv -0.082679 1.604229 -0.100343\nv -0.107153 1.583321 -0.107287\nv -0.008852 1.661546 -0.092446\nv -0.032181 1.658535 -0.091053\nv -0.019406 1.660454 -0.093737\nv -0.009337 1.634341 -0.100294\nv -0.034691 1.635475 -0.101899\nv -0.020427 1.635603 -0.102955\nv -0.009945 1.609310 -0.111180\nv -0.035441 1.615047 -0.117918\nv -0.021077 1.612992 -0.115948\nv -0.012228 1.541756 -0.147605\nv -0.026976 1.548670 -0.151627\nv -0.011215 1.564107 -0.134853\nv -0.024177 1.569839 -0.139188\nv -0.039728 1.577489 -0.141734\nv -0.045785 1.557310 -0.152380\nv -0.056405 1.585571 -0.137865\nv -0.067307 1.566757 -0.146987\nv -0.047846 1.598169 -0.130298\nv -0.035734 1.596246 -0.130847\nv -0.021972 1.591440 -0.127763\nv -0.010815 1.587211 -0.122010\nv -0.010547 1.843437 0.057247\nv -0.023314 1.840075 0.053884\nv -0.038393 1.833530 0.047842\nv -0.054313 1.822208 0.039422\nv -0.067212 1.803566 0.028582\nv -0.072967 1.779848 0.016483\nv -0.074424 1.761748 0.008789\nv -0.076870 1.747116 0.001483\nv -0.070455 1.697214 0.012293\nv -0.067097 1.685391 0.028486\nv -0.057857 1.672101 0.047424\nv -0.048770 1.663781 0.064639\nv -0.042186 1.661730 0.077786\nv -0.035897 1.662841 0.084875\nv -0.031608 1.664481 0.088914\nv -0.028966 1.665901 0.090707\nv -0.027342 1.666702 0.091190\nv -0.076454 1.750925 -0.011798\nv -0.004418 1.582246 0.029320\nv -0.009036 1.582513 0.026868\nv -0.014137 1.582812 0.023708\nv -0.021082 1.582801 0.020904\nv -0.030821 1.582183 0.020995\nv -0.042688 1.580210 0.019778\nv -0.053214 1.574617 0.012935\nv -0.060551 1.565681 0.004096\nv -0.072050 1.554122 0.000164\nv -0.088520 1.542491 0.005471\nv -0.005053 1.550807 0.024670\nv -0.002954 1.556098 0.023814\nv -0.012683 1.621893 0.090585\nv -0.023416 1.625304 0.082758\nv -0.033722 1.631166 0.069837\nv -0.043494 1.640605 0.053099\nv -0.055321 1.654711 0.033052\nv -0.066106 1.672330 0.015393\nv -0.075517 1.724859 -0.016819\nv -0.078670 1.733459 -0.008283\nv -0.080172 1.744300 -0.010265\nv -0.084499 1.746982 -0.020146\nv -0.085683 1.751063 -0.016501\nv -0.084054 1.745605 -0.005176\nv -0.081515 1.737245 -0.002214\nv -0.079660 1.737020 -0.006208\nv -0.084852 1.745111 -0.027748\nv -0.084122 1.741521 -0.031275\nv -0.083280 1.735492 -0.033575\nv -0.083984 1.736294 -0.037333\nv -0.085079 1.743047 -0.034405\nv -0.085706 1.749057 -0.028962\nv -0.078916 1.730537 -0.002625\nv -0.076132 1.721060 -0.010974\nv -0.080004 1.754382 -0.014894\nv -0.079147 1.735682 -0.036988\nv -0.080027 1.743198 -0.034047\nv -0.080160 1.751023 -0.027795\nv -0.079774 1.752202 -0.006074\nv -0.081502 1.728201 -0.034217\nv -0.080237 1.719759 -0.031680\nv -0.078697 1.711049 -0.027874\nv -0.076225 1.703248 -0.022558\nv -0.077965 1.701381 -0.027078\nv -0.079663 1.709587 -0.032315\nv -0.080938 1.718514 -0.036284\nv -0.082085 1.727799 -0.038394\nv -0.073533 1.701293 -0.026844\nv -0.075165 1.709569 -0.030627\nv -0.076325 1.718310 -0.034170\nv -0.077493 1.727205 -0.036750\nv -0.076222 1.696404 -0.015892\nv -0.076092 1.688368 -0.007369\nv -0.074101 1.685568 -0.014038\nv -0.076624 1.693936 -0.021143\nv -0.072325 1.694338 -0.021955\nv -0.070498 1.688837 -0.014849\nv -0.070426 1.703779 0.001171\nv -0.066925 1.705056 -0.005257\nv -0.072508 1.697161 -0.002220\nv -0.071798 1.702803 -0.009550\nv -0.065924 1.680465 -0.012559\nv -0.061767 1.661888 -0.003208\nv -0.066277 1.658645 -0.012145\nv -0.068091 1.679207 -0.019108\nv -0.067062 1.690576 -0.021785\nv -0.066676 1.697872 -0.022940\nv -0.067325 1.704370 -0.024367\nv -0.068812 1.711066 -0.026890\nv -0.070837 1.718289 -0.031645\nv -0.072137 1.727283 -0.034617\nv -0.073310 1.735921 -0.033303\nv -0.074192 1.743699 -0.027957\nv -0.074250 1.750684 -0.020016\nv -0.074842 1.753666 -0.010693\nv -0.075370 1.753619 -0.002392\nv -0.075238 1.751125 0.004691\nv -0.074252 1.744084 0.011073\nv -0.074165 1.733387 0.008455\nv -0.072430 1.741721 0.023884\nv -0.069200 1.738042 0.039761\nv -0.063621 1.734822 0.057789\nv -0.056749 1.734630 0.071748\nv -0.050472 1.736369 0.078056\nv -0.045665 1.738340 0.080686\nv -0.043123 1.739510 0.082231\nv -0.042468 1.739267 0.081454\nv -0.040950 1.738749 0.080182\nv -0.037273 1.737952 0.079761\nv -0.031394 1.737208 0.080290\nv -0.025052 1.737210 0.081703\nv -0.022254 1.738082 0.083969\nv -0.020320 1.738237 0.085922\nv -0.017866 1.737850 0.088142\nv -0.013983 1.737746 0.093533\nv -0.008616 1.737475 0.099515\nv -0.004702 1.738221 0.105789\nv -0.020766 1.738982 0.084846\nv -0.021019 1.738169 0.084825\nv -0.084278 1.728020 -0.027854\nv -0.082458 1.720284 -0.025477\nv -0.078181 1.713315 -0.021514\nv -0.074896 1.707891 -0.016325\nv -0.066973 1.712496 -0.009964\nv -0.072887 1.714231 -0.014278\nv -0.076042 1.719061 -0.018970\nv -0.081428 1.727332 -0.020669\nv -0.079601 1.722572 -0.021521\nv -0.080443 1.739083 -0.015822\nv -0.085414 1.742389 -0.024470\nv -0.085530 1.734919 -0.027400\nv -0.083075 1.732831 -0.018878\nv -0.076001 1.729014 -0.012865\nv -0.077444 1.726111 -0.005953\nv -0.074448 1.726965 -0.002171\nv -0.074107 1.726852 0.002416\nv -0.072884 1.724916 0.012616\nv -0.070101 1.720304 0.029020\nv -0.065569 1.712886 0.050301\nv -0.057647 1.703466 0.068903\nv -0.048119 1.695440 0.081280\nv -0.036626 1.690315 0.087982\nv -0.027440 1.687463 0.094481\nv -0.018351 1.687018 0.100441\nv -0.009482 1.687585 0.106700\nv -0.090933 1.597029 -0.047136\nv -0.117637 1.578977 -0.049122\nv -0.006319 1.608667 0.044813\nv -0.012645 1.610453 0.041146\nv -0.019239 1.612790 0.034423\nv -0.027059 1.616058 0.024984\nv -0.038452 1.617996 0.013932\nv -0.053223 1.615282 0.006285\nv -0.061665 1.608403 -0.006838\nv -0.065607 1.597407 -0.023120\nv -0.026169 1.666994 0.090746\nv -0.023154 1.667386 0.092814\nv -0.025367 1.667083 0.091259\nv -0.023140 1.667651 0.093041\nv -0.025494 1.667201 0.091265\nv -0.010447 1.669980 0.101243\nv -0.018226 1.668650 0.096556\nv -0.018683 1.668534 0.095726\nv -0.010665 1.669179 0.099465\nv 0.240419 0.028025 -0.064598\nv 0.214742 0.028524 -0.135022\nv 0.170136 0.028524 -0.153646\nv 0.141940 0.028524 -0.131052\nv 0.153248 0.027295 -0.055831\nv 0.255085 0.016737 0.039452\nv 0.238718 0.016737 0.097813\nv 0.145661 0.016737 0.050411\nv 0.164969 0.016737 0.113922\nv 0.202729 0.016737 0.120323\nv 0.242952 0.028025 -0.066538\nv 0.215666 0.028524 -0.141348\nv 0.168821 0.028524 -0.160906\nv 0.139209 0.028524 -0.137178\nv 0.150897 0.027295 -0.057712\nv 0.258034 0.016737 0.042949\nv 0.240914 0.016737 0.103692\nv 0.143524 0.016737 0.054426\nv 0.164210 0.016737 0.120066\nv 0.203423 0.016737 0.126789\nv 0.242952 0.013555 -0.070778\nv 0.242952 0.013555 -0.070778\nv 0.242952 0.013555 -0.070778\nv 0.215666 0.014054 -0.141348\nv 0.168821 0.014054 -0.160906\nv 0.139209 0.014054 -0.137178\nv 0.150897 0.012826 -0.061952\nv 0.150897 0.012826 -0.061952\nv 0.150897 0.012826 -0.061952\nv 0.258034 -0.001088 0.025696\nv 0.240914 -0.000249 0.103692\nv 0.143524 -0.001088 0.037173\nv 0.164210 -0.000249 0.120066\nv 0.203423 -0.000249 0.126789\nv 0.242952 -0.000311 -0.070778\nv 0.242952 -0.000311 -0.070778\nv 0.215666 0.000188 -0.141348\nv 0.168821 0.000188 -0.160906\nv 0.139209 0.000188 -0.137178\nv 0.150897 -0.001041 -0.061952\nv 0.150897 -0.001041 -0.061952\nv 0.250941 0.022381 -0.013800\nv 0.154169 0.022381 -0.007738\nv 0.253682 0.022381 -0.013022\nv 0.151925 0.022381 -0.006671\nv 0.253682 0.005814 -0.021702\nv 0.151925 0.005814 -0.015351\nv 0.141647 0.491328 -0.114189\nv 0.205701 0.490563 -0.094984\nv 0.204864 0.482881 -0.001814\nv 0.219844 0.485703 -0.041760\nv 0.095093 0.485847 -0.085518\nv 0.097055 0.480678 -0.033262\nv 0.129203 0.478420 0.018134\nv 0.172100 0.480332 0.031533\nv 0.134275 1.530679 -0.176386\nv 0.172047 1.568379 -0.134707\nv 0.183198 1.581694 -0.087722\nv 0.183899 1.582353 -0.037207\nv 0.175968 1.570682 0.009387\nv 0.154211 1.545088 0.050300\nv 0.127586 1.516320 0.085776\nv 0.090502 1.480065 0.118043\nv 0.048199 1.439660 0.139210\nv 0.004523 1.397720 0.152934\nv -0.046749 1.346165 0.168389\nv -0.100793 1.286092 0.157968\nv -0.142447 1.245674 0.120062\nv -0.172509 1.215475 0.065591\nv -0.184084 1.200966 0.010595\nv -0.189708 1.193441 -0.037206\nv -0.183778 1.199644 -0.083800\nv -0.161577 1.220932 -0.127256\nv -0.125529 1.256296 -0.164504\nv -0.082149 1.299133 -0.193113\nv -0.040545 1.343281 -0.203115\nv 0.004932 1.390315 -0.209998\nv 0.049917 1.437272 -0.211098\nv 0.091361 1.483013 -0.199357\nv 0.085005 1.564355 -0.174013\nv 0.115650 1.595672 -0.131694\nv 0.128002 1.611723 -0.087199\nv 0.130140 1.610316 -0.037207\nv 0.123674 1.601299 0.009387\nv 0.103645 1.579924 0.048363\nv 0.078677 1.552097 0.085776\nv 0.047546 1.519726 0.111321\nv 0.006099 1.478006 0.130594\nv -0.039131 1.432221 0.142984\nv -0.086835 1.384427 0.149293\nv -0.134336 1.337502 0.138490\nv -0.172510 1.300595 0.106263\nv -0.197874 1.272699 0.058544\nv -0.209579 1.258838 0.009387\nv -0.212954 1.250003 -0.037206\nv -0.207024 1.256206 -0.083800\nv -0.189637 1.274392 -0.127219\nv -0.162299 1.301960 -0.167083\nv -0.125744 1.339687 -0.198121\nv -0.084020 1.384709 -0.206740\nv -0.038853 1.432207 -0.209998\nv 0.006131 1.479163 -0.211098\nv 0.048350 1.524587 -0.199694\nv -0.240419 0.028025 -0.064598\nv -0.214742 0.028524 -0.135022\nv -0.170136 0.028524 -0.153646\nv -0.141940 0.028524 -0.131052\nv -0.153248 0.027295 -0.055831\nv -0.255085 0.016737 0.039452\nv -0.238718 0.016737 0.097813\nv -0.145661 0.016737 0.050411\nv -0.164969 0.016737 0.113922\nv -0.202729 0.016737 0.120323\nv -0.242952 0.028025 -0.066538\nv -0.215666 0.028524 -0.141348\nv -0.168821 0.028524 -0.160906\nv -0.139209 0.028524 -0.137178\nv -0.150897 0.027295 -0.057712\nv -0.258034 0.016737 0.042949\nv -0.240914 0.016737 0.103692\nv -0.143524 0.016737 0.054426\nv -0.164210 0.016737 0.120066\nv -0.203423 0.016737 0.126789\nv -0.242952 0.013555 -0.070778\nv -0.242952 0.013555 -0.070778\nv -0.242952 0.013555 -0.070778\nv -0.215666 0.014054 -0.141348\nv -0.168821 0.014054 -0.160906\nv -0.139209 0.014054 -0.137178\nv -0.150897 0.012826 -0.061952\nv -0.150897 0.012826 -0.061952\nv -0.150897 0.012826 -0.061952\nv -0.258034 -0.001088 0.025696\nv -0.240914 -0.000249 0.103692\nv -0.143524 -0.001088 0.037173\nv -0.164210 -0.000249 0.120066\nv -0.203423 -0.000249 0.126789\nv -0.242952 -0.000311 -0.070778\nv -0.242952 -0.000311 -0.070778\nv -0.215666 0.000188 -0.141348\nv -0.168821 0.000188 -0.160906\nv -0.139209 0.000188 -0.137178\nv -0.150897 -0.001041 -0.061952\nv -0.150897 -0.001041 -0.061952\nv -0.250941 0.022381 -0.013800\nv -0.154169 0.022381 -0.007738\nv -0.253682 0.022381 -0.013022\nv -0.151925 0.022381 -0.006671\nv -0.253682 0.005814 -0.021702\nv -0.151925 0.005814 -0.015351\nv -0.141647 0.491328 -0.114189\nv -0.205701 0.490563 -0.094984\nv -0.204864 0.482881 -0.001814\nv -0.219844 0.485703 -0.041760\nv -0.095093 0.485847 -0.085518\nv -0.097055 0.480678 -0.033262\nv -0.129203 0.478420 0.018134\nv -0.172100 0.480332 0.031533\nv -0.121152 1.119257 0.137785\nv -0.172670 1.119257 0.018168\nv -0.149779 1.119257 -0.074245\nv -0.077684 1.119257 -0.119280\nv 0.000000 1.119257 -0.127489\nv -0.023899 1.119257 0.159816\nv -0.120262 1.183441 0.143953\nv -0.173867 1.183441 0.019491\nv -0.162576 1.183441 -0.083776\nv -0.083964 1.183441 -0.137626\nv -0.023899 1.183441 0.163047\nv 0.000000 1.183441 -0.137360\nv -0.096686 1.597695 -0.035012\nv -0.091318 1.612576 -0.091144\nv -0.079937 1.584289 0.006058\nv -0.052429 1.559129 0.039906\nv -0.059511 1.620940 -0.125205\nv -0.621947 1.243404 -0.024871\nv -0.606588 1.231470 0.001990\nv -0.584973 1.207811 0.013254\nv -0.574175 1.185285 -0.008347\nv -0.577637 1.175574 -0.050069\nv -0.592578 1.182565 -0.088309\nv -0.614130 1.213647 -0.085271\nv -0.624485 1.236964 -0.058985\nv 0.121152 1.119257 0.137785\nv 0.172670 1.119257 0.018168\nv 0.149779 1.119257 -0.074245\nv 0.077684 1.119257 -0.119280\nv 0.023899 1.119257 0.159816\nv 0.120262 1.183441 0.143953\nv 0.173867 1.183441 0.019491\nv 0.162576 1.183441 -0.083776\nv 0.083964 1.183441 -0.137626\nv 0.023899 1.183441 0.163047\nv 0.096686 1.597695 -0.035012\nv 0.091318 1.612576 -0.091144\nv 0.079937 1.584289 0.006058\nv 0.052429 1.559129 0.039906\nv 0.059511 1.620940 -0.125205\nv 0.000000 1.628451 -0.141560\nv 0.621947 1.243404 -0.024871\nv 0.606588 1.231470 0.001990\nv 0.584973 1.207811 0.013254\nv 0.574175 1.185285 -0.008347\nv 0.577637 1.175574 -0.050069\nv 0.592578 1.182565 -0.088309\nv 0.614130 1.213647 -0.085271\nv 0.624485 1.236964 -0.058985\nv 0.081559 1.119257 -0.128551\nv -0.000000 1.119257 -0.134141\nv 0.088152 1.183441 -0.144985\nv -0.000000 1.183441 -0.144198\nv 0.126261 1.183441 0.150700\nv 0.025091 1.183441 0.170750\nv 0.170685 1.183441 -0.088437\nv 0.182540 1.183441 0.020003\nv 0.127195 1.119257 0.144223\nv 0.025091 1.119257 0.167358\nv 0.157250 1.119257 -0.078428\nv 0.181283 1.119257 0.018614\nv -0.088152 1.183441 -0.144985\nv -0.126261 1.183441 0.150700\nv -0.025091 1.183441 0.170750\nv -0.170685 1.183441 -0.088437\nv -0.182540 1.183441 0.020003\nv -0.127195 1.119257 0.144223\nv -0.025091 1.119257 0.167358\nv -0.081559 1.119257 -0.128551\nv -0.157250 1.119257 -0.078428\nv -0.181283 1.119257 0.018614\nvt 0.449580 0.858975\nvt 0.446811 0.893259\nvt 0.433140 0.885831\nvt 0.433670 0.864937\nvt 0.423531 0.880869\nvt 0.423529 0.868245\nvt 0.415835 0.877335\nvt 0.415702 0.871436\nvt 0.414515 0.889573\nvt 0.411628 0.881619\nvt 0.417651 0.899717\nvt 0.421685 0.914018\nvt 0.390458 0.911080\nvt 0.397272 0.898573\nvt 0.402067 0.889322\nvt 0.405591 0.881662\nvt 0.393393 0.880542\nvt 0.401227 0.877410\nvt 0.383359 0.883769\nvt 0.370935 0.888385\nvt 0.370935 0.858506\nvt 0.384257 0.863504\nvt 0.393621 0.868148\nvt 0.401411 0.871385\nvt 0.402268 0.859233\nvt 0.405556 0.867002\nvt 0.398851 0.849239\nvt 0.392501 0.835844\nvt 0.424993 0.835030\nvt 0.419584 0.849580\nvt 0.414715 0.859212\nvt 0.411644 0.867121\nvt 0.417597 0.956332\nvt 0.417576 0.950144\nvt 0.425355 0.946640\nvt 0.425700 0.959173\nvt 0.435322 0.942917\nvt 0.435382 0.963543\nvt 0.449377 0.937962\nvt 0.448665 0.969820\nvt 0.421195 0.978216\nvt 0.426245 0.991732\nvt 0.416986 0.968612\nvt 0.413252 0.960694\nvt 0.407161 0.960617\nvt 0.404125 0.968616\nvt 0.400574 0.978915\nvt 0.395539 0.991932\nvt 0.385867 0.964205\nvt 0.370935 0.970769\nvt 0.395308 0.959573\nvt 0.403059 0.956313\nvt 0.402858 0.950352\nvt 0.395254 0.947339\nvt 0.385027 0.943389\nvt 0.370935 0.936984\nvt 0.399532 0.928313\nvt 0.394459 0.914018\nvt 0.403739 0.938160\nvt 0.407142 0.946172\nvt 0.413099 0.945981\nvt 0.416326 0.938377\nvt 0.420413 0.928266\nvt 0.426931 0.914702\nvt 0.408574 0.874372\nvt 0.410219 0.953325\nvt 0.226562 0.171042\nvt 0.229618 0.153547\nvt 0.213794 0.153209\nvt 0.216627 0.161433\nvt 0.226410 0.138889\nvt 0.215327 0.145725\nvt 0.269719 0.159499\nvt 0.266410 0.180819\nvt 0.294307 0.186604\nvt 0.294055 0.158409\nvt 0.270713 0.134889\nvt 0.298036 0.132710\nvt 0.258461 0.210978\nvt 0.301739 0.207512\nvt 0.274770 0.242260\nvt 0.305605 0.227598\nvt 0.281959 0.262495\nvt 0.310855 0.250656\nvt 0.315800 0.072344\nvt 0.289002 0.056629\nvt 0.282673 0.071246\nvt 0.311929 0.088777\nvt 0.264747 0.101276\nvt 0.307225 0.106799\nvt 0.211147 0.163031\nvt 0.222831 0.178025\nvt 0.224252 0.130297\nvt 0.210242 0.143682\nvt 0.208454 0.152443\nvt 0.251082 0.216519\nvt 0.263951 0.244894\nvt 0.269636 0.264314\nvt 0.277026 0.053942\nvt 0.272423 0.068261\nvt 0.256811 0.095031\nvt 0.205182 0.168917\nvt 0.220732 0.186309\nvt 0.223728 0.122074\nvt 0.205147 0.138166\nvt 0.199559 0.152688\nvt 0.185011 0.084085\nvt 0.144484 0.073736\nvt 0.144751 0.075541\nvt 0.185416 0.086719\nvt 0.205403 0.102002\nvt 0.204551 0.106150\nvt 0.183202 0.143308\nvt 0.201722 0.135143\nvt 0.201745 0.130825\nvt 0.184485 0.141155\nvt 0.141493 0.143342\nvt 0.142525 0.142035\nvt 0.080099 0.067409\nvt 0.041460 0.079404\nvt 0.041565 0.082754\nvt 0.078547 0.069709\nvt 0.036207 0.141284\nvt 0.078817 0.152441\nvt 0.076744 0.150829\nvt 0.036267 0.138918\nvt 0.020677 0.121887\nvt 0.023290 0.118459\nvt 0.019815 0.100613\nvt 0.021521 0.105052\nvt 0.185416 0.086719\nvt 0.144751 0.075541\nvt 0.144483 0.084078\nvt 0.181939 0.092531\nvt 0.204551 0.106150\nvt 0.198910 0.111854\nvt 0.184485 0.141155\nvt 0.201745 0.130825\nvt 0.197755 0.123798\nvt 0.182580 0.134913\nvt 0.142525 0.142035\nvt 0.143589 0.133666\nvt 0.078547 0.069709\nvt 0.041565 0.082754\nvt 0.047086 0.090558\nvt 0.089323 0.078817\nvt 0.036267 0.138918\nvt 0.076744 0.150829\nvt 0.086438 0.141007\nvt 0.041116 0.132335\nvt 0.023290 0.118459\nvt 0.032271 0.112484\nvt 0.021521 0.105052\nvt 0.181939 0.092531\nvt 0.144483 0.084078\nvt 0.147047 0.088006\nvt 0.179083 0.097193\nvt 0.198910 0.111854\nvt 0.193071 0.117160\nvt 0.182580 0.134913\nvt 0.197755 0.123798\nvt 0.181037 0.130160\nvt 0.143589 0.133666\nvt 0.147328 0.129616\nvt 0.147047 0.088006\nvt 0.147328 0.129616\nvt 0.181037 0.130160\nvt 0.179083 0.097193\nvt 0.193071 0.117160\nvt 0.144483 0.084078\nvt 0.143589 0.133666\nvt 0.147328 0.129616\nvt 0.147047 0.088006\nvt 0.089323 0.078817\nvt 0.047086 0.090558\nvt 0.041116 0.132335\nvt 0.086438 0.141007\nvt 0.032271 0.112484\nvt 0.245305 0.176883\nvt 0.247368 0.156147\nvt 0.245837 0.135881\nvt 0.241382 0.192418\nvt 0.247015 0.117246\nvt 0.237166 0.200209\nvt 0.243328 0.108719\nvt 0.114084 0.067095\nvt 0.113248 0.068587\nvt 0.114585 0.148101\nvt 0.113698 0.146940\nvt 0.113248 0.068587\nvt 0.116087 0.079310\nvt 0.113698 0.146940\nvt 0.116115 0.135892\nvt 0.116115 0.135892\nvt 0.116087 0.079310\nvt 0.143589 0.133666\nvt 0.144483 0.084078\nvt 0.343837 0.181822\nvt 0.345261 0.160962\nvt 0.347096 0.138981\nvt 0.343165 0.201512\nvt 0.343765 0.223779\nvt 0.344299 0.249475\nvt 0.350237 0.094115\nvt 0.349758 0.076974\nvt 0.348735 0.114152\nvt 0.387991 0.183482\nvt 0.389992 0.161674\nvt 0.391106 0.141087\nvt 0.385108 0.203688\nvt 0.383523 0.228087\nvt 0.381637 0.254339\nvt 0.389239 0.091371\nvt 0.385619 0.069439\nvt 0.389769 0.113982\nvt 0.427126 0.183782\nvt 0.429698 0.163320\nvt 0.429738 0.142056\nvt 0.424347 0.204292\nvt 0.422566 0.229611\nvt 0.424840 0.259782\nvt 0.427811 0.091080\nvt 0.427593 0.066817\nvt 0.429128 0.114476\nvt 0.081919 0.177576\nvt 0.080241 0.180500\nvt 0.099685 0.188250\nvt 0.099913 0.184162\nvt 0.067501 0.169108\nvt 0.064900 0.171780\nvt 0.059214 0.153117\nvt 0.054794 0.154109\nvt 0.171154 0.153117\nvt 0.159295 0.172265\nvt 0.162213 0.175238\nvt 0.175881 0.153828\nvt 0.143601 0.181366\nvt 0.144924 0.184881\nvt 0.129579 0.184219\nvt 0.129740 0.188639\nvt 0.113974 0.191097\nvt 0.114198 0.186685\nvt 0.077311 0.185604\nvt 0.098367 0.193721\nvt 0.060508 0.176299\nvt 0.048772 0.156819\nvt 0.165918 0.178906\nvt 0.182692 0.155388\nvt 0.146747 0.189406\nvt 0.130229 0.193924\nvt 0.113448 0.196200\nvt 0.064600 0.205653\nvt 0.090460 0.213946\nvt 0.049088 0.231773\nvt 0.082019 0.244697\nvt 0.044529 0.192489\nvt 0.023834 0.214521\nvt 0.157185 0.209804\nvt 0.180567 0.195484\nvt 0.170991 0.240628\nvt 0.199169 0.220714\nvt 0.136177 0.215367\nvt 0.144044 0.248982\nvt 0.112772 0.216603\nvt 0.112056 0.250004\nvt 0.199159 0.173643\nvt 0.218280 0.197160\nvt 0.030243 0.171420\nvt 0.008089 0.190338\nvt 0.464533 0.182687\nvt 0.467609 0.163910\nvt 0.467580 0.145645\nvt 0.461994 0.199903\nvt 0.460739 0.222485\nvt 0.465593 0.250101\nvt 0.465044 0.099687\nvt 0.467609 0.076893\nvt 0.466213 0.121048\nvt 0.161560 0.402243\nvt 0.155588 0.395509\nvt 0.134626 0.410605\nvt 0.146864 0.425262\nvt 0.127497 0.391275\nvt 0.152830 0.385975\nvt 0.127264 0.371100\nvt 0.153383 0.375493\nvt 0.134716 0.352237\nvt 0.156932 0.365158\nvt 0.145395 0.334369\nvt 0.162411 0.356153\nvt 0.160447 0.323865\nvt 0.169799 0.350020\nvt 0.177475 0.320161\nvt 0.178468 0.347729\nvt 0.194608 0.323042\nvt 0.186891 0.350662\nvt 0.210291 0.333563\nvt 0.193747 0.356991\nvt 0.223414 0.349480\nvt 0.199287 0.365151\nvt 0.230882 0.369006\nvt 0.203745 0.374332\nvt 0.231524 0.389807\nvt 0.205469 0.384522\nvt 0.224638 0.409261\nvt 0.203005 0.394667\nvt 0.212389 0.424654\nvt 0.197149 0.402034\nvt 0.197372 0.433287\nvt 0.188865 0.405252\nvt 0.179699 0.435910\nvt 0.179384 0.405997\nvt 0.162040 0.433331\nvt 0.169888 0.405254\nvt 0.165387 0.397638\nvt 0.160572 0.392283\nvt 0.158418 0.384595\nvt 0.158964 0.376281\nvt 0.161637 0.368049\nvt 0.165965 0.360925\nvt 0.171743 0.356015\nvt 0.178593 0.354261\nvt 0.185319 0.356439\nvt 0.190856 0.361326\nvt 0.195230 0.367767\nvt 0.198608 0.375152\nvt 0.199761 0.383347\nvt 0.197879 0.391455\nvt 0.193207 0.397352\nvt 0.186697 0.400151\nvt 0.179298 0.400776\nvt 0.171898 0.400197\nvt 0.178814 0.379350\nvt 0.179769 0.441150\nvt 0.221479 0.443653\nvt 0.256818 0.438950\nvt 0.289823 0.435602\nvt 0.137780 0.444611\nvt 0.102166 0.444863\nvt 0.077709 0.444123\nvt 0.083017 0.411236\nvt 0.097247 0.382934\nvt 0.118968 0.348422\nvt 0.138683 0.313766\nvt 0.160629 0.287922\nvt 0.180188 0.263330\nvt 0.198518 0.285300\nvt 0.216421 0.311488\nvt 0.239706 0.345595\nvt 0.261990 0.379891\nvt 0.277767 0.406096\nvt 0.258659 0.459445\nvt 0.284835 0.453304\nvt 0.264043 0.483454\nvt 0.221478 0.504250\nvt 0.223490 0.459014\nvt 0.181117 0.514996\nvt 0.180077 0.458023\nvt 0.140539 0.505936\nvt 0.136318 0.460481\nvt 0.097191 0.485804\nvt 0.100655 0.461818\nvt 0.077873 0.458646\nvt 0.144974 0.275250\nvt 0.161388 0.256884\nvt 0.128621 0.256115\nvt 0.126787 0.303834\nvt 0.086241 0.279508\nvt 0.104546 0.338805\nvt 0.055299 0.310343\nvt 0.082787 0.376723\nvt 0.042576 0.351978\nvt 0.068614 0.405298\nvt 0.044158 0.400261\nvt 0.061831 0.429301\nvt 0.208879 0.272663\nvt 0.192460 0.255496\nvt 0.225038 0.253308\nvt 0.268938 0.274082\nvt 0.229092 0.300701\nvt 0.302213 0.302785\nvt 0.254202 0.335934\nvt 0.315656 0.344933\nvt 0.276573 0.372608\nvt 0.317865 0.392224\nvt 0.293549 0.400188\nvt 0.301058 0.427072\nvt 0.198759 0.014828\nvt 0.222350 0.014086\nvt 0.225351 0.035722\nvt 0.202970 0.036062\nvt 0.181802 0.015068\nvt 0.184866 0.037201\nvt 0.165781 0.039107\nvt 0.163527 0.016516\nvt 0.147732 0.040863\nvt 0.146627 0.019020\nvt 0.127089 0.021494\nvt 0.129597 0.044996\nvt 0.109381 0.048515\nvt 0.107302 0.025406\nvt 0.089500 0.053020\nvt 0.084308 0.031079\nvt 0.066071 0.058633\nvt 0.061110 0.037042\nvt 0.042124 0.064867\nvt 0.037762 0.043424\nvt 0.010834 0.050893\nvt 0.017458 0.072394\nvt 0.511165 0.043447\nvt 0.536993 0.053487\nvt 0.527229 0.073598\nvt 0.505665 0.063756\nvt 0.489710 0.035104\nvt 0.484654 0.056823\nvt 0.463246 0.050665\nvt 0.466794 0.027952\nvt 0.443529 0.046206\nvt 0.446550 0.021943\nvt 0.425383 0.041270\nvt 0.428544 0.018957\nvt 0.412223 0.017650\nvt 0.407054 0.038941\nvt 0.387963 0.037877\nvt 0.393758 0.016432\nvt 0.370467 0.015023\nvt 0.366871 0.036829\nvt 0.345593 0.014298\nvt 0.344160 0.035900\nvt 0.321358 0.013160\nvt 0.320863 0.036705\nvt 0.296412 0.036649\nvt 0.296765 0.012973\nvt 0.272244 0.012915\nvt 0.271910 0.035815\nvt 0.247906 0.013068\nvt 0.247801 0.035841\nvt 0.202970 0.036062\nvt 0.225351 0.035722\nvt 0.226515 0.038974\nvt 0.203971 0.039652\nvt 0.184866 0.037201\nvt 0.185261 0.041150\nvt 0.165781 0.039107\nvt 0.165701 0.043024\nvt 0.147732 0.040863\nvt 0.147361 0.044516\nvt 0.129597 0.044996\nvt 0.129124 0.048490\nvt 0.109381 0.048515\nvt 0.109030 0.051791\nvt 0.089500 0.053020\nvt 0.089310 0.056207\nvt 0.066071 0.058633\nvt 0.066429 0.061755\nvt 0.042124 0.064867\nvt 0.043392 0.067828\nvt 0.017458 0.072394\nvt 0.019813 0.075036\nvt 0.505665 0.063756\nvt 0.527229 0.073598\nvt 0.526478 0.076523\nvt 0.505443 0.066939\nvt 0.484654 0.056823\nvt 0.484326 0.060219\nvt 0.463246 0.050665\nvt 0.462739 0.054336\nvt 0.443529 0.046206\nvt 0.442909 0.050073\nvt 0.425383 0.041270\nvt 0.424602 0.045251\nvt 0.407054 0.038941\nvt 0.406065 0.042638\nvt 0.387963 0.037877\nvt 0.386843 0.041329\nvt 0.366871 0.036829\nvt 0.365783 0.040029\nvt 0.344160 0.035900\nvt 0.343103 0.038850\nvt 0.320863 0.036705\nvt 0.320106 0.039720\nvt 0.296412 0.036649\nvt 0.296500 0.039755\nvt 0.271910 0.035815\nvt 0.272773 0.038876\nvt 0.247801 0.035841\nvt 0.248977 0.038910\nvt 0.222350 0.014086\nvt 0.198759 0.014828\nvt 0.199687 0.011221\nvt 0.223818 0.010772\nvt 0.181802 0.015068\nvt 0.181553 0.010779\nvt 0.163527 0.016516\nvt 0.162421 0.012640\nvt 0.146627 0.019020\nvt 0.144887 0.015688\nvt 0.127089 0.021494\nvt 0.125141 0.018434\nvt 0.107302 0.025406\nvt 0.105487 0.022568\nvt 0.084308 0.031079\nvt 0.082771 0.028292\nvt 0.061110 0.037042\nvt 0.060037 0.034148\nvt 0.037762 0.043424\nvt 0.037449 0.040101\nvt 0.010834 0.050893\nvt 0.011806 0.047081\nvt 0.536993 0.053487\nvt 0.511165 0.043447\nvt 0.513311 0.040731\nvt 0.538834 0.050433\nvt 0.489710 0.035104\nvt 0.491532 0.031973\nvt 0.466794 0.027952\nvt 0.468347 0.024422\nvt 0.446550 0.021943\nvt 0.447882 0.017991\nvt 0.428544 0.018957\nvt 0.429133 0.014719\nvt 0.412223 0.017650\nvt 0.411628 0.013830\nvt 0.393758 0.016432\nvt 0.392516 0.012983\nvt 0.370467 0.015023\nvt 0.369188 0.011747\nvt 0.345593 0.014298\nvt 0.344564 0.011199\nvt 0.321358 0.013160\nvt 0.320786 0.010038\nvt 0.296765 0.012973\nvt 0.297047 0.009849\nvt 0.272244 0.012915\nvt 0.273167 0.009848\nvt 0.247906 0.013068\nvt 0.249194 0.009894\nvt 0.226562 0.171042\nvt 0.216627 0.161433\nvt 0.213794 0.153209\nvt 0.229618 0.153547\nvt 0.215327 0.145725\nvt 0.226410 0.138889\nvt 0.294307 0.186604\nvt 0.266410 0.180819\nvt 0.269719 0.159499\nvt 0.294055 0.158409\nvt 0.270713 0.134889\nvt 0.298036 0.132710\nvt 0.301739 0.207512\nvt 0.258461 0.210978\nvt 0.305605 0.227598\nvt 0.274770 0.242260\nvt 0.310855 0.250656\nvt 0.281959 0.262495\nvt 0.315800 0.072344\nvt 0.311929 0.088777\nvt 0.282673 0.071246\nvt 0.289002 0.056629\nvt 0.307225 0.106799\nvt 0.264747 0.101276\nvt 0.222831 0.178025\nvt 0.211147 0.163031\nvt 0.210242 0.143682\nvt 0.224252 0.130297\nvt 0.208454 0.152443\nvt 0.263951 0.244894\nvt 0.251082 0.216519\nvt 0.269636 0.264314\nvt 0.272423 0.068261\nvt 0.277026 0.053942\nvt 0.256811 0.095031\nvt 0.220732 0.186309\nvt 0.205182 0.168917\nvt 0.205147 0.138166\nvt 0.223728 0.122074\nvt 0.199559 0.152688\nvt 0.185011 0.084085\nvt 0.185416 0.086719\nvt 0.144751 0.075541\nvt 0.144484 0.073736\nvt 0.205403 0.102002\nvt 0.204551 0.106150\nvt 0.183202 0.143308\nvt 0.184485 0.141155\nvt 0.201745 0.130825\nvt 0.201722 0.135143\nvt 0.141493 0.143342\nvt 0.142525 0.142035\nvt 0.080099 0.067409\nvt 0.078547 0.069709\nvt 0.041565 0.082754\nvt 0.041460 0.079404\nvt 0.036207 0.141284\nvt 0.036267 0.138918\nvt 0.076744 0.150829\nvt 0.078817 0.152441\nvt 0.020677 0.121887\nvt 0.023290 0.118459\nvt 0.021521 0.105052\nvt 0.019815 0.100613\nvt 0.185416 0.086719\nvt 0.181939 0.092531\nvt 0.144483 0.084078\nvt 0.144751 0.075541\nvt 0.204551 0.106150\nvt 0.198910 0.111854\nvt 0.184485 0.141155\nvt 0.182580 0.134913\nvt 0.197755 0.123798\nvt 0.201745 0.130825\nvt 0.142525 0.142035\nvt 0.143589 0.133666\nvt 0.078547 0.069709\nvt 0.089323 0.078817\nvt 0.047086 0.090558\nvt 0.041565 0.082754\nvt 0.036267 0.138918\nvt 0.041116 0.132335\nvt 0.086438 0.141007\nvt 0.076744 0.150829\nvt 0.023290 0.118459\nvt 0.032271 0.112484\nvt 0.021521 0.105052\nvt 0.181939 0.092531\nvt 0.179083 0.097193\nvt 0.147047 0.088006\nvt 0.144483 0.084078\nvt 0.198910 0.111854\nvt 0.193071 0.117160\nvt 0.182580 0.134913\nvt 0.181037 0.130160\nvt 0.197755 0.123798\nvt 0.143589 0.133666\nvt 0.147328 0.129616\nvt 0.147047 0.088006\nvt 0.179083 0.097193\nvt 0.181037 0.130160\nvt 0.147328 0.129616\nvt 0.193071 0.117160\nvt 0.144483 0.084078\nvt 0.147047 0.088006\nvt 0.147328 0.129616\nvt 0.143589 0.133666\nvt 0.089323 0.078817\nvt 0.086438 0.141007\nvt 0.041116 0.132335\nvt 0.047086 0.090558\nvt 0.032271 0.112484\nvt 0.245305 0.176883\nvt 0.247368 0.156147\nvt 0.245837 0.135881\nvt 0.241382 0.192418\nvt 0.247015 0.117246\nvt 0.237166 0.200209\nvt 0.243328 0.108719\nvt 0.113248 0.068587\nvt 0.114084 0.067095\nvt 0.113698 0.146940\nvt 0.114585 0.148101\nvt 0.116087 0.079310\nvt 0.113248 0.068587\nvt 0.116115 0.135892\nvt 0.113698 0.146940\nvt 0.116087 0.079310\nvt 0.116115 0.135892\nvt 0.144483 0.084078\nvt 0.143589 0.133666\nvt 0.345261 0.160962\nvt 0.343837 0.181822\nvt 0.347096 0.138981\nvt 0.343165 0.201512\nvt 0.343765 0.223779\nvt 0.344299 0.249475\nvt 0.349758 0.076974\nvt 0.350237 0.094115\nvt 0.348735 0.114152\nvt 0.389992 0.161674\nvt 0.387991 0.183482\nvt 0.391106 0.141087\nvt 0.385108 0.203688\nvt 0.383523 0.228087\nvt 0.381637 0.254339\nvt 0.385619 0.069439\nvt 0.389239 0.091371\nvt 0.389769 0.113982\nvt 0.429698 0.163320\nvt 0.427126 0.183782\nvt 0.429738 0.142056\nvt 0.424347 0.204292\nvt 0.422566 0.229611\nvt 0.424840 0.259782\nvt 0.427593 0.066817\nvt 0.427811 0.091080\nvt 0.429128 0.114476\nvt 0.081919 0.177576\nvt 0.099913 0.184162\nvt 0.099685 0.188250\nvt 0.080241 0.180500\nvt 0.064900 0.171780\nvt 0.067501 0.169108\nvt 0.054794 0.154109\nvt 0.059214 0.153117\nvt 0.171154 0.153117\nvt 0.175881 0.153828\nvt 0.162213 0.175238\nvt 0.159295 0.172265\nvt 0.144924 0.184881\nvt 0.143601 0.181366\nvt 0.129740 0.188639\nvt 0.129579 0.184219\nvt 0.114198 0.186685\nvt 0.113974 0.191097\nvt 0.098367 0.193721\nvt 0.077311 0.185604\nvt 0.060508 0.176299\nvt 0.048772 0.156819\nvt 0.182692 0.155388\nvt 0.165918 0.178906\nvt 0.146747 0.189406\nvt 0.130229 0.193924\nvt 0.113448 0.196200\nvt 0.090460 0.213946\nvt 0.064600 0.205653\nvt 0.082019 0.244697\nvt 0.049088 0.231773\nvt 0.044529 0.192489\nvt 0.023834 0.214521\nvt 0.180567 0.195484\nvt 0.157185 0.209804\nvt 0.199169 0.220714\nvt 0.170991 0.240628\nvt 0.136177 0.215367\nvt 0.144044 0.248982\nvt 0.112772 0.216603\nvt 0.112056 0.250004\nvt 0.199159 0.173643\nvt 0.218280 0.197160\nvt 0.030243 0.171420\nvt 0.008089 0.190338\nvt 0.467609 0.163910\nvt 0.464533 0.182687\nvt 0.467580 0.145645\nvt 0.461994 0.199903\nvt 0.460739 0.222485\nvt 0.465593 0.250101\nvt 0.467609 0.076893\nvt 0.465044 0.099687\nvt 0.466213 0.121048\nvt 0.824979 0.045863\nvt 0.793239 0.045819\nvt 0.793312 0.041673\nvt 0.825514 0.041655\nvt 0.793893 0.008820\nvt 0.827890 0.012944\nvt 0.828581 0.017380\nvt 0.793815 0.013267\nvt 0.939153 0.031665\nvt 0.979840 0.032855\nvt 0.979755 0.038216\nvt 0.938105 0.037188\nvt 0.862021 0.016414\nvt 0.861433 0.021442\nvt 0.898925 0.022267\nvt 0.897466 0.027142\nvt 0.971485 0.076154\nvt 0.930825 0.061364\nvt 0.931849 0.055717\nvt 0.973626 0.070853\nvt 0.857410 0.048076\nvt 0.858591 0.043648\nvt 0.892122 0.054446\nvt 0.892962 0.049427\nvt 0.759776 0.011742\nvt 0.758929 0.016149\nvt 0.608761 0.027123\nvt 0.648032 0.026496\nvt 0.648918 0.031986\nvt 0.609242 0.031905\nvt 0.725555 0.014001\nvt 0.725965 0.019044\nvt 0.688488 0.018531\nvt 0.689772 0.023449\nvt 0.655359 0.056552\nvt 0.615849 0.068576\nvt 0.614741 0.063712\nvt 0.654567 0.050945\nvt 0.761519 0.044736\nvt 0.761134 0.040513\nvt 0.729040 0.045799\nvt 0.728016 0.041333\nvt 0.694151 0.050945\nvt 0.693483 0.045909\nvt 0.654567 0.050945\nvt 0.648918 0.031986\nvt 0.689772 0.023449\nvt 0.693483 0.045909\nvt 0.725965 0.019044\nvt 0.728016 0.041333\nvt 0.758929 0.016149\nvt 0.761134 0.040513\nvt 0.609242 0.031905\nvt 0.614741 0.063712\nvt 0.793312 0.041673\nvt 0.793815 0.013267\nvt 0.931849 0.055717\nvt 0.892962 0.049427\nvt 0.897466 0.027142\nvt 0.938105 0.037188\nvt 0.858591 0.043648\nvt 0.861433 0.021442\nvt 0.825514 0.041655\nvt 0.828581 0.017380\nvt 0.979755 0.038216\nvt 0.973626 0.070853\nvt 0.579840 0.033462\nvt 0.580275 0.028828\nvt 0.586823 0.076152\nvt 0.584904 0.071762\nvt 0.584904 0.071762\nvt 0.579840 0.033462\nvt 0.104500 0.536214\nvt 0.105732 0.526505\nvt 0.114426 0.528654\nvt 0.112823 0.537425\nvt 0.123022 0.531811\nvt 0.122042 0.539539\nvt 0.117940 0.538790\nvt 0.119592 0.530467\nvt 0.078999 0.531783\nvt 0.079412 0.521731\nvt 0.085663 0.522844\nvt 0.084893 0.532679\nvt 0.073567 0.511819\nvt 0.073483 0.505967\nvt 0.079489 0.506681\nvt 0.079286 0.513366\nvt 0.097178 0.510132\nvt 0.095841 0.516649\nvt 0.086454 0.514927\nvt 0.087336 0.508697\nvt 0.094721 0.524391\nvt 0.116274 0.521220\nvt 0.117835 0.515207\nvt 0.123610 0.517489\nvt 0.121664 0.523054\nvt 0.127950 0.519596\nvt 0.125248 0.524531\nvt 0.066875 0.504854\nvt 0.067932 0.510440\nvt 0.062507 0.509670\nvt 0.060099 0.504182\nvt 0.068905 0.519309\nvt 0.064061 0.518154\nvt 0.073994 0.520429\nvt 0.068891 0.529347\nvt 0.063634 0.527723\nvt 0.107153 0.519113\nvt 0.108756 0.513016\nvt 0.057551 0.509684\nvt 0.054170 0.504868\nvt 0.059161 0.517300\nvt 0.073903 0.530789\nvt 0.094035 0.534646\nvt 0.059014 0.525280\nvt 0.259060 0.536214\nvt 0.250751 0.537425\nvt 0.249134 0.528654\nvt 0.257842 0.526505\nvt 0.240545 0.531811\nvt 0.243975 0.530467\nvt 0.245620 0.538790\nvt 0.241518 0.539539\nvt 0.284575 0.531783\nvt 0.278681 0.532679\nvt 0.277911 0.522844\nvt 0.284162 0.521731\nvt 0.289993 0.511819\nvt 0.284288 0.513366\nvt 0.284071 0.506681\nvt 0.290077 0.505967\nvt 0.266389 0.510132\nvt 0.276224 0.508697\nvt 0.277113 0.514927\nvt 0.267719 0.516649\nvt 0.268846 0.524391\nvt 0.247300 0.521220\nvt 0.241903 0.523054\nvt 0.239964 0.517489\nvt 0.245732 0.515207\nvt 0.238326 0.524531\nvt 0.235624 0.519596\nvt 0.296692 0.504854\nvt 0.303461 0.504182\nvt 0.301060 0.509670\nvt 0.295628 0.510440\nvt 0.299499 0.518154\nvt 0.294662 0.519309\nvt 0.289573 0.520429\nvt 0.299926 0.527723\nvt 0.294662 0.529347\nvt 0.256400 0.519113\nvt 0.254811 0.513016\nvt 0.309390 0.504868\nvt 0.306016 0.509684\nvt 0.304399 0.517300\nvt 0.289671 0.530789\nvt 0.269539 0.534646\nvt 0.304560 0.525280\nvt 0.390760 0.520514\nvt 0.336134 0.535945\nvt 0.335878 0.477450\nvt 0.390759 0.473676\nvt 0.445641 0.477449\nvt 0.445386 0.535945\nvt 0.343131 0.407459\nvt 0.390759 0.403233\nvt 0.438387 0.407458\nvt 0.351461 0.343819\nvt 0.390758 0.342970\nvt 0.430055 0.343818\nvt 0.354191 0.290140\nvt 0.390758 0.289390\nvt 0.427326 0.290140\nvt 0.061135 0.632763\nvt 0.063655 0.610363\nvt 0.074736 0.604595\nvt 0.074295 0.625763\nvt 0.057369 0.540426\nvt 0.054492 0.558794\nvt 0.042242 0.552452\nvt 0.048822 0.539355\nvt 0.054597 0.585583\nvt 0.042970 0.582979\nvt 0.047058 0.631419\nvt 0.050852 0.610538\nvt 0.131163 0.634639\nvt 0.131289 0.642297\nvt 0.127040 0.641562\nvt 0.130316 0.626757\nvt 0.050124 0.642451\nvt 0.057110 0.643109\nvt 0.048927 0.650662\nvt 0.056221 0.651355\nvt 0.132479 0.652342\nvt 0.127229 0.651796\nvt 0.133949 0.669849\nvt 0.128335 0.670255\nvt 0.036621 0.626190\nvt 0.039169 0.607948\nvt 0.053351 0.669968\nvt 0.046939 0.669324\nvt 0.116911 0.654701\nvt 0.121209 0.653091\nvt 0.122651 0.671501\nvt 0.117541 0.671431\nvt 0.116400 0.645923\nvt 0.119935 0.643452\nvt 0.115805 0.640568\nvt 0.115833 0.633351\nvt 0.041486 0.640001\nvt 0.039323 0.647533\nvt 0.033065 0.643837\nvt 0.034997 0.635591\nvt 0.035396 0.661631\nvt 0.029509 0.658880\nvt 0.133074 0.651670\nvt 0.131856 0.642248\nvt 0.135118 0.639469\nvt 0.137316 0.649507\nvt 0.140382 0.665733\nvt 0.135139 0.670927\nvt 0.143609 0.603412\nvt 0.139311 0.594319\nvt 0.146451 0.595663\nvt 0.144869 0.605106\nvt 0.137239 0.609873\nvt 0.142727 0.607563\nvt 0.127712 0.610888\nvt 0.127341 0.591435\nvt 0.149804 0.580648\nvt 0.140410 0.565570\nvt 0.156902 0.571541\nvt 0.151134 0.553271\nvt 0.157896 0.596188\nvt 0.154214 0.606100\nvt 0.162481 0.589545\nvt 0.127362 0.566830\nvt 0.127726 0.552592\nvt 0.137974 0.552347\nvt 0.032470 0.570638\nvt 0.035130 0.586003\nvt 0.026485 0.596818\nvt 0.023293 0.584953\nvt 0.151071 0.614773\nvt 0.157665 0.612183\nvt 0.154424 0.622046\nvt 0.160913 0.617671\nvt 0.158785 0.626792\nvt 0.165001 0.620695\nvt 0.165932 0.626386\nvt 0.139010 0.625182\nvt 0.140998 0.635612\nvt 0.143273 0.679698\nvt 0.138800 0.685942\nvt 0.145870 0.662072\nvt 0.148292 0.676597\nvt 0.141516 0.692179\nvt 0.144757 0.686026\nvt 0.032274 0.675015\nvt 0.030748 0.681700\nvt 0.025036 0.679628\nvt 0.026443 0.672845\nvt 0.104276 0.587774\nvt 0.104024 0.606135\nvt 0.095127 0.600451\nvt 0.095092 0.583609\nvt 0.092334 0.630229\nvt 0.097136 0.634639\nvt 0.095806 0.645678\nvt 0.090850 0.642066\nvt 0.099901 0.636536\nvt 0.099208 0.647064\nvt 0.094539 0.614724\nvt 0.085950 0.614808\nvt 0.086069 0.599660\nvt 0.093440 0.623019\nvt 0.085733 0.622543\nvt 0.085404 0.629431\nvt 0.085103 0.640974\nvt 0.101056 0.645741\nvt 0.105347 0.647771\nvt 0.105067 0.665502\nvt 0.099845 0.665614\nvt 0.105354 0.637397\nvt 0.101259 0.635451\nvt 0.105179 0.681651\nvt 0.100272 0.684353\nvt 0.110835 0.649801\nvt 0.110604 0.666377\nvt 0.115602 0.654288\nvt 0.115693 0.667847\nvt 0.064194 0.650067\nvt 0.071187 0.647365\nvt 0.070571 0.666342\nvt 0.063858 0.668309\nvt 0.115546 0.645545\nvt 0.111787 0.641156\nvt 0.110107 0.682428\nvt 0.109568 0.689204\nvt 0.105186 0.688441\nvt 0.114055 0.686180\nvt 0.112312 0.693663\nvt 0.069682 0.683618\nvt 0.063508 0.685242\nvt 0.101448 0.628682\nvt 0.102645 0.619771\nvt 0.063284 0.691626\nvt 0.069717 0.690331\nvt 0.063340 0.696659\nvt 0.069220 0.696008\nvt 0.110429 0.698472\nvt 0.108532 0.694573\nvt 0.098662 0.628374\nvt 0.100391 0.632343\nvt 0.077802 0.634716\nvt 0.079020 0.645678\nvt 0.164371 0.608921\nvt 0.161452 0.602992\nvt 0.165939 0.596447\nvt 0.168557 0.604168\nvt 0.167094 0.615970\nvt 0.169978 0.615697\nvt 0.018890 0.590553\nvt 0.022565 0.603237\nvt 0.009825 0.603846\nvt 0.014683 0.596510\nvt 0.018211 0.608592\nvt 0.011855 0.613422\nvt 0.093426 0.661645\nvt 0.091774 0.666300\nvt 0.089205 0.664536\nvt 0.089506 0.658698\nvt 0.092670 0.671522\nvt 0.086748 0.665649\nvt 0.096345 0.668043\nvt 0.080371 0.662765\nvt 0.079923 0.656969\nvt 0.085145 0.652370\nvt 0.085397 0.659265\nvt 0.098641 0.660777\nvt 0.094483 0.655982\nvt 0.081253 0.668995\nvt 0.105781 0.693901\nvt 0.103562 0.697737\nvt 0.129420 0.701776\nvt 0.129350 0.694769\nvt 0.133788 0.697695\nvt 0.131142 0.705381\nvt 0.134488 0.689029\nvt 0.129147 0.687265\nvt 0.124520 0.706935\nvt 0.126228 0.701958\nvt 0.042746 0.700852\nvt 0.043446 0.693642\nvt 0.049739 0.694069\nvt 0.048633 0.701293\nvt 0.121188 0.701209\nvt 0.124800 0.695938\nvt 0.119102 0.693250\nvt 0.123967 0.688602\nvt 0.145499 0.696547\nvt 0.146577 0.691080\nvt 0.028998 0.688861\nvt 0.024077 0.687447\nvt 0.149328 0.690240\nvt 0.151652 0.693257\nvt 0.149202 0.683527\nvt 0.094567 0.553677\nvt 0.094679 0.565171\nvt 0.086615 0.563519\nvt 0.086636 0.551990\nvt 0.066105 0.562595\nvt 0.066924 0.545564\nvt 0.076465 0.548805\nvt 0.076794 0.562609\nvt 0.076094 0.583224\nvt 0.065965 0.585912\nvt 0.115385 0.611665\nvt 0.113712 0.551605\nvt 0.114468 0.566676\nvt 0.103422 0.566830\nvt 0.102302 0.553894\nvt 0.115196 0.590889\nvt 0.086433 0.581922\nvt 0.076976 0.629452\nvt 0.089891 0.652531\nvt 0.050859 0.686894\nvt 0.044328 0.686348\nvt 0.142888 0.645587\nvt 0.064404 0.641961\nvt 0.071285 0.639014\nvt 0.145870 0.623131\nvt 0.147487 0.633050\nvt 0.148999 0.642234\nvt 0.145009 0.543016\nvt 0.151743 0.659720\nvt 0.153647 0.676317\nvt 0.101553 0.692361\nvt 0.153563 0.684745\nvt 0.302124 0.632763\nvt 0.288957 0.625763\nvt 0.288516 0.604595\nvt 0.299597 0.610363\nvt 0.305883 0.540426\nvt 0.314430 0.539355\nvt 0.321010 0.552452\nvt 0.308760 0.558794\nvt 0.320282 0.582979\nvt 0.308655 0.585583\nvt 0.316194 0.631419\nvt 0.312400 0.610538\nvt 0.232089 0.634639\nvt 0.232936 0.626757\nvt 0.236212 0.641562\nvt 0.231963 0.642297\nvt 0.313128 0.642451\nvt 0.306142 0.643109\nvt 0.314325 0.650662\nvt 0.307031 0.651355\nvt 0.236023 0.651796\nvt 0.230773 0.652342\nvt 0.234917 0.670255\nvt 0.229303 0.669849\nvt 0.324083 0.607948\nvt 0.326631 0.626190\nvt 0.316313 0.669324\nvt 0.309901 0.669968\nvt 0.246341 0.654701\nvt 0.245704 0.671431\nvt 0.240601 0.671501\nvt 0.242043 0.653091\nvt 0.243317 0.643452\nvt 0.246852 0.645923\nvt 0.247419 0.633351\nvt 0.247447 0.640568\nvt 0.321766 0.640001\nvt 0.328248 0.635591\nvt 0.330187 0.643837\nvt 0.323929 0.647533\nvt 0.333743 0.658880\nvt 0.327856 0.661631\nvt 0.230178 0.651670\nvt 0.225936 0.649507\nvt 0.228127 0.639469\nvt 0.231396 0.642248\nvt 0.228113 0.670927\nvt 0.222870 0.665733\nvt 0.219643 0.603412\nvt 0.218383 0.605106\nvt 0.216801 0.595663\nvt 0.223941 0.594319\nvt 0.226013 0.609873\nvt 0.220518 0.607563\nvt 0.235911 0.591435\nvt 0.235540 0.610888\nvt 0.213448 0.580648\nvt 0.222842 0.565570\nvt 0.206350 0.571541\nvt 0.212118 0.553271\nvt 0.209038 0.606100\nvt 0.205356 0.596188\nvt 0.200771 0.589545\nvt 0.235890 0.566830\nvt 0.225271 0.552347\nvt 0.235526 0.552592\nvt 0.330782 0.570638\nvt 0.339959 0.584953\nvt 0.336767 0.596818\nvt 0.328122 0.586003\nvt 0.212181 0.614773\nvt 0.208828 0.622046\nvt 0.205587 0.612183\nvt 0.204467 0.626792\nvt 0.202339 0.617671\nvt 0.197320 0.626386\nvt 0.198251 0.620695\nvt 0.222254 0.635612\nvt 0.224235 0.625182\nvt 0.224452 0.685942\nvt 0.219979 0.679698\nvt 0.214953 0.676597\nvt 0.217382 0.662072\nvt 0.221736 0.692179\nvt 0.218495 0.686026\nvt 0.330978 0.675015\nvt 0.336809 0.672845\nvt 0.338216 0.679628\nvt 0.332504 0.681700\nvt 0.258976 0.587774\nvt 0.268160 0.583609\nvt 0.268125 0.600451\nvt 0.259228 0.606135\nvt 0.270918 0.630229\nvt 0.272402 0.642066\nvt 0.267446 0.645678\nvt 0.266116 0.634639\nvt 0.264044 0.647064\nvt 0.263351 0.636536\nvt 0.277197 0.599660\nvt 0.277302 0.614808\nvt 0.268713 0.614724\nvt 0.277519 0.622543\nvt 0.269812 0.623019\nvt 0.277834 0.629431\nvt 0.278149 0.640974\nvt 0.262196 0.645741\nvt 0.263407 0.665614\nvt 0.258185 0.665502\nvt 0.257905 0.647771\nvt 0.257898 0.637397\nvt 0.261993 0.635451\nvt 0.262980 0.684353\nvt 0.258073 0.681651\nvt 0.252648 0.666377\nvt 0.252417 0.649801\nvt 0.247566 0.667847\nvt 0.247650 0.654288\nvt 0.299058 0.650067\nvt 0.299394 0.668309\nvt 0.292681 0.666342\nvt 0.292065 0.647365\nvt 0.251465 0.641156\nvt 0.247706 0.645545\nvt 0.253145 0.682428\nvt 0.258066 0.688441\nvt 0.253684 0.689204\nvt 0.250940 0.693663\nvt 0.249197 0.686180\nvt 0.299744 0.685242\nvt 0.293570 0.683618\nvt 0.260607 0.619771\nvt 0.261804 0.628682\nvt 0.299968 0.691626\nvt 0.293535 0.690331\nvt 0.299912 0.696659\nvt 0.294032 0.696008\nvt 0.254720 0.694573\nvt 0.252823 0.698472\nvt 0.262861 0.632343\nvt 0.264590 0.628374\nvt 0.285450 0.634716\nvt 0.284232 0.645678\nvt 0.198881 0.608921\nvt 0.194695 0.604168\nvt 0.197313 0.596447\nvt 0.201800 0.602992\nvt 0.196158 0.615970\nvt 0.193274 0.615697\nvt 0.344362 0.590553\nvt 0.340687 0.603237\nvt 0.353427 0.603846\nvt 0.351397 0.613422\nvt 0.345041 0.608592\nvt 0.348569 0.596510\nvt 0.269826 0.661645\nvt 0.273746 0.658698\nvt 0.274047 0.664536\nvt 0.271478 0.666300\nvt 0.276504 0.665649\nvt 0.270596 0.671522\nvt 0.266907 0.668043\nvt 0.282881 0.662765\nvt 0.277855 0.659265\nvt 0.278100 0.652370\nvt 0.283329 0.656969\nvt 0.264611 0.660777\nvt 0.268769 0.655982\nvt 0.281999 0.668995\nvt 0.257464 0.693901\nvt 0.259690 0.697737\nvt 0.233832 0.701776\nvt 0.232110 0.705381\nvt 0.229464 0.697695\nvt 0.233902 0.694769\nvt 0.228764 0.689029\nvt 0.234105 0.687265\nvt 0.238732 0.706935\nvt 0.237031 0.701958\nvt 0.320506 0.700852\nvt 0.314619 0.701293\nvt 0.313513 0.694069\nvt 0.319806 0.693642\nvt 0.238452 0.695938\nvt 0.242064 0.701209\nvt 0.239285 0.688602\nvt 0.244150 0.693250\nvt 0.217753 0.696547\nvt 0.216675 0.691080\nvt 0.339175 0.687447\nvt 0.334254 0.688861\nvt 0.211600 0.693257\nvt 0.213917 0.690240\nvt 0.214050 0.683527\nvt 0.268685 0.553677\nvt 0.276616 0.551990\nvt 0.276637 0.563519\nvt 0.268573 0.565171\nvt 0.297147 0.562595\nvt 0.286458 0.562609\nvt 0.286787 0.548805\nvt 0.296328 0.545564\nvt 0.297294 0.585912\nvt 0.287158 0.583224\nvt 0.247853 0.611665\nvt 0.249540 0.551605\nvt 0.260950 0.553894\nvt 0.259830 0.566830\nvt 0.248784 0.566676\nvt 0.248056 0.590889\nvt 0.276819 0.581922\nvt 0.286276 0.629452\nvt 0.273347 0.652531\nvt 0.318924 0.686348\nvt 0.312393 0.686894\nvt 0.220364 0.645587\nvt 0.298848 0.641961\nvt 0.291967 0.639014\nvt 0.217382 0.623131\nvt 0.215765 0.633050\nvt 0.214253 0.642234\nvt 0.218243 0.543016\nvt 0.211509 0.659720\nvt 0.209605 0.676317\nvt 0.261699 0.692361\nvt 0.209689 0.684745\nvt 0.195600 0.890617\nvt 0.192100 0.890778\nvt 0.193563 0.885521\nvt 0.196517 0.887992\nvt 0.198547 0.897778\nvt 0.196720 0.903329\nvt 0.192289 0.899066\nvt 0.195943 0.894593\nvt 0.203405 0.900592\nvt 0.202915 0.905597\nvt 0.209404 0.899759\nvt 0.210279 0.903959\nvt 0.214199 0.895839\nvt 0.217510 0.897960\nvt 0.214997 0.891709\nvt 0.218896 0.892017\nvt 0.210440 0.883442\nvt 0.212687 0.878878\nvt 0.216950 0.883526\nvt 0.213758 0.887138\nvt 0.204196 0.881804\nvt 0.204007 0.876106\nvt 0.198883 0.884933\nvt 0.197224 0.880824\nvt 0.189545 0.890813\nvt 0.189622 0.885451\nvt 0.187879 0.891261\nvt 0.187879 0.886228\nvt 0.189573 0.877310\nvt 0.192205 0.876309\nvt 0.187879 0.878157\nvt 0.189447 0.870485\nvt 0.191456 0.870709\nvt 0.187879 0.870926\nvt 0.201550 0.851074\nvt 0.203335 0.844508\nvt 0.208480 0.847497\nvt 0.206219 0.853811\nvt 0.195152 0.873740\nvt 0.199338 0.867804\nvt 0.194053 0.869407\nvt 0.196818 0.863513\nvt 0.199555 0.856919\nvt 0.203580 0.859747\nvt 0.196860 0.913262\nvt 0.192100 0.911414\nvt 0.204357 0.913360\nvt 0.213576 0.909874\nvt 0.224034 0.901404\nvt 0.189622 0.910119\nvt 0.189671 0.900179\nvt 0.187879 0.909132\nvt 0.187879 0.899920\nvt 0.202222 0.825860\nvt 0.198666 0.824257\nvt 0.197210 0.818545\nvt 0.203909 0.821534\nvt 0.193654 0.826651\nvt 0.187879 0.827281\nvt 0.187879 0.824761\nvt 0.192863 0.823284\nvt 0.205757 0.833259\nvt 0.204847 0.829927\nvt 0.208368 0.827687\nvt 0.210146 0.833007\nvt 0.210286 0.839349\nvt 0.205694 0.837354\nvt 0.191561 0.819308\nvt 0.192555 0.814975\nvt 0.192758 0.811363\nvt 0.197126 0.813820\nvt 0.204959 0.818027\nvt 0.198463 0.799582\nvt 0.206464 0.801899\nvt 0.206422 0.805840\nvt 0.198498 0.802074\nvt 0.193367 0.800191\nvt 0.192933 0.798259\nvt 0.218455 0.887460\nvt 0.214563 0.889357\nvt 0.191764 0.894047\nvt 0.195481 0.892346\nvt 0.189531 0.894677\nvt 0.187879 0.894558\nvt 0.187879 0.833924\nvt 0.192800 0.833392\nvt 0.192674 0.836409\nvt 0.187879 0.836626\nvt 0.201592 0.834393\nvt 0.201347 0.834127\nvt 0.202040 0.833238\nvt 0.202348 0.833987\nvt 0.197910 0.829843\nvt 0.201039 0.830452\nvt 0.193584 0.829738\nvt 0.187879 0.829220\nvt 0.197266 0.832580\nvt 0.197084 0.834862\nvt 0.201403 0.834785\nvt 0.202117 0.835002\nvt 0.200885 0.836871\nvt 0.200528 0.835618\nvt 0.192821 0.841414\nvt 0.192772 0.843612\nvt 0.187879 0.842800\nvt 0.187879 0.840847\nvt 0.197574 0.837984\nvt 0.197707 0.839776\nvt 0.203398 0.833525\nvt 0.202957 0.831978\nvt 0.201284 0.838761\nvt 0.203055 0.835597\nvt 0.200612 0.832461\nvt 0.200269 0.833903\nvt 0.197007 0.851207\nvt 0.192128 0.851704\nvt 0.192555 0.849240\nvt 0.198057 0.846797\nvt 0.187879 0.845138\nvt 0.192814 0.846076\nvt 0.187879 0.848190\nvt 0.189440 0.865403\nvt 0.187879 0.865263\nvt 0.192961 0.865697\nvt 0.194319 0.862365\nvt 0.191064 0.866019\nvt 0.192982 0.858704\nvt 0.194074 0.857528\nvt 0.191351 0.857633\nvt 0.191015 0.856100\nvt 0.189825 0.857290\nvt 0.187879 0.855085\nvt 0.187879 0.850605\nvt 0.197994 0.841771\nvt 0.195558 0.855015\nvt 0.191449 0.854441\nvt 0.187879 0.853853\nvt 0.187879 0.819175\nvt 0.187879 0.813113\nvt 0.187879 0.809732\nvt 0.187879 0.798966\nvt 0.187879 0.797405\nvt 0.250809 0.840952\nvt 0.259678 0.854406\nvt 0.256857 0.857745\nvt 0.247309 0.846769\nvt 0.216642 0.805203\nvt 0.216460 0.811062\nvt 0.233078 0.812294\nvt 0.232644 0.819805\nvt 0.255541 0.831250\nvt 0.253868 0.835779\nvt 0.214815 0.824628\nvt 0.210489 0.865872\nvt 0.217510 0.833931\nvt 0.217517 0.842842\nvt 0.215480 0.852446\nvt 0.213226 0.859299\nvt 0.227401 0.892871\nvt 0.217825 0.872137\nvt 0.226771 0.879571\nvt 0.227989 0.886557\nvt 0.221213 0.865718\nvt 0.224356 0.858648\nvt 0.231552 0.871948\nvt 0.235598 0.865109\nvt 0.228213 0.848834\nvt 0.241212 0.856457\nvt 0.187879 0.858151\nvt 0.189461 0.859593\nvt 0.189811 0.861693\nvt 0.187879 0.861350\nvt 0.190525 0.860230\nvt 0.191113 0.861798\nvt 0.192142 0.862442\nvt 0.192737 0.861056\nvt 0.192254 0.859684\nvt 0.191274 0.860314\nvt 0.191694 0.860167\nvt 0.191799 0.860923\nvt 0.191393 0.860860\nvt 0.190931 0.858844\nvt 0.190518 0.859236\nvt 0.189237 0.858116\nvt 0.191575 0.859096\nvt 0.191456 0.859740\nvt 0.191197 0.859866\nvt 0.191862 0.859985\nvt 0.191680 0.859698\nvt 0.192002 0.860412\nvt 0.187879 0.857178\nvt 0.198687 0.889742\nvt 0.198036 0.890897\nvt 0.198358 0.892444\nvt 0.200269 0.894901\nvt 0.203951 0.896966\nvt 0.208494 0.896399\nvt 0.211000 0.894229\nvt 0.211791 0.892234\nvt 0.211336 0.890162\nvt 0.208907 0.887117\nvt 0.204532 0.885962\nvt 0.200591 0.888090\nvt 0.211707 0.891261\nvt 0.198022 0.891548\nvt 0.204763 0.888944\nvt 0.201088 0.889777\nvt 0.199198 0.890617\nvt 0.198771 0.891107\nvt 0.198750 0.891506\nvt 0.199072 0.892052\nvt 0.200864 0.893900\nvt 0.204049 0.895433\nvt 0.207766 0.894691\nvt 0.209656 0.893340\nvt 0.210181 0.892619\nvt 0.210097 0.892255\nvt 0.209796 0.891709\nvt 0.207990 0.889840\nvt 0.199562 0.891898\nvt 0.201102 0.893578\nvt 0.204147 0.894936\nvt 0.207640 0.894215\nvt 0.209264 0.893088\nvt 0.209551 0.892605\nvt 0.209229 0.891961\nvt 0.207528 0.890512\nvt 0.204672 0.889763\nvt 0.201403 0.890428\nvt 0.199744 0.891044\nvt 0.209509 0.892346\nvt 0.199982 0.891954\nvt 0.201361 0.893074\nvt 0.204154 0.893984\nvt 0.207262 0.893459\nvt 0.208557 0.892829\nvt 0.208557 0.892465\nvt 0.208396 0.891940\nvt 0.207052 0.891163\nvt 0.204777 0.890568\nvt 0.201830 0.891037\nvt 0.200472 0.891184\nvt 0.208494 0.892255\nvt 0.199926 0.891534\nvt 0.201305 0.891877\nvt 0.207150 0.891786\nvt 0.204560 0.892220\nvt 0.207304 0.892367\nvt 0.204700 0.891366\nvt 0.201508 0.891387\nvt 0.200150 0.891324\nvt 0.199758 0.924448\nvt 0.193605 0.924189\nvt 0.208844 0.922243\nvt 0.220380 0.916566\nvt 0.231755 0.905604\nvt 0.190280 0.924147\nvt 0.187879 0.923797\nvt 0.243368 0.881874\nvt 0.240470 0.888118\nvt 0.237481 0.895433\nvt 0.246623 0.877429\nvt 0.250564 0.871990\nvt 0.254995 0.865123\nvt 0.269471 0.845439\nvt 0.257851 0.825986\nvt 0.262072 0.821415\nvt 0.273293 0.842597\nvt 0.233575 0.798483\nvt 0.256248 0.788781\nvt 0.243914 0.794892\nvt 0.236522 0.784063\nvt 0.248289 0.777539\nvt 0.199758 0.712404\nvt 0.204882 0.712404\nvt 0.209698 0.721952\nvt 0.203846 0.722239\nvt 0.215683 0.732816\nvt 0.209271 0.734202\nvt 0.226498 0.787696\nvt 0.200941 0.767473\nvt 0.206688 0.766598\nvt 0.211665 0.779940\nvt 0.204231 0.780696\nvt 0.216131 0.795984\nvt 0.214108 0.788123\nvt 0.206303 0.795011\nvt 0.205428 0.788144\nvt 0.192534 0.793835\nvt 0.187879 0.793891\nvt 0.192548 0.787878\nvt 0.187879 0.787962\nvt 0.198666 0.794157\nvt 0.198463 0.787794\nvt 0.220709 0.778680\nvt 0.214101 0.765030\nvt 0.222039 0.761642\nvt 0.230145 0.774578\nvt 0.203524 0.735070\nvt 0.199107 0.722498\nvt 0.196167 0.712411\nvt 0.196027 0.768012\nvt 0.198036 0.780752\nvt 0.215011 0.712390\nvt 0.218203 0.721686\nvt 0.222165 0.730723\nvt 0.273391 0.782376\nvt 0.260042 0.770357\nvt 0.282554 0.810957\nvt 0.270647 0.815521\nvt 0.280902 0.838929\nvt 0.291472 0.835912\nvt 0.191743 0.768600\nvt 0.192513 0.781172\nvt 0.187879 0.782026\nvt 0.187879 0.769132\nvt 0.240890 0.767718\nvt 0.231881 0.755811\nvt 0.238132 0.749560\nvt 0.248037 0.760011\nvt 0.191456 0.743680\nvt 0.187879 0.740054\nvt 0.187879 0.735623\nvt 0.193325 0.736358\nvt 0.198113 0.735819\nvt 0.202418 0.752276\nvt 0.198155 0.753102\nvt 0.208725 0.750771\nvt 0.215774 0.747810\nvt 0.223642 0.743659\nvt 0.229382 0.739459\nvt 0.252951 0.884443\nvt 0.250088 0.891002\nvt 0.246392 0.898597\nvt 0.240078 0.909083\nvt 0.259965 0.875567\nvt 0.259776 0.869211\nvt 0.262681 0.871178\nvt 0.263584 0.877268\nvt 0.255968 0.880607\nvt 0.229095 0.922523\nvt 0.192121 0.945273\nvt 0.187879 0.948787\nvt 0.216502 0.932008\nvt 0.205855 0.937937\nvt 0.197574 0.942067\nvt 0.248394 0.911631\nvt 0.238069 0.926485\nvt 0.225882 0.937958\nvt 0.214087 0.946855\nvt 0.203685 0.953659\nvt 0.195432 0.959168\nvt 0.187879 0.964404\nvt 0.260630 0.898947\nvt 0.261645 0.897764\nvt 0.266153 0.900564\nvt 0.265572 0.901642\nvt 0.260189 0.881944\nvt 0.265467 0.878199\nvt 0.262079 0.882735\nvt 0.257690 0.885395\nvt 0.259804 0.885983\nvt 0.259216 0.892864\nvt 0.256654 0.893585\nvt 0.271725 0.900774\nvt 0.271515 0.901810\nvt 0.255170 0.913073\nvt 0.258684 0.903938\nvt 0.264417 0.905751\nvt 0.262093 0.913997\nvt 0.246035 0.928662\nvt 0.255317 0.930055\nvt 0.235227 0.941661\nvt 0.247043 0.944251\nvt 0.223817 0.952441\nvt 0.237726 0.956627\nvt 0.212764 0.961051\nvt 0.227898 0.967386\nvt 0.203055 0.968086\nvt 0.218483 0.977102\nvt 0.193724 0.973504\nvt 0.209047 0.986671\nvt 0.253119 0.901523\nvt 0.255639 0.861000\nvt 0.244635 0.851375\nvt 0.230439 0.841162\nvt 0.231342 0.834309\nvt 0.232063 0.826791\nvt 0.215746 0.817642\nvt 0.205456 0.811811\nvt 0.197364 0.807870\nvt 0.192835 0.805476\nvt 0.187879 0.804307\nvt 0.266727 0.849408\nvt 0.263850 0.851907\nvt 0.227716 0.721672\nvt 0.226701 0.712390\nvt 0.241037 0.712383\nvt 0.239840 0.722960\nvt 0.228213 0.728343\nvt 0.236074 0.732620\nvt 0.251089 0.724955\nvt 0.255674 0.712383\nvt 0.267658 0.712362\nvt 0.262030 0.726418\nvt 0.245804 0.738402\nvt 0.256395 0.743904\nvt 0.278270 0.860552\nvt 0.275505 0.856716\nvt 0.279180 0.855925\nvt 0.281336 0.860349\nvt 0.281497 0.893067\nvt 0.284038 0.885878\nvt 0.284983 0.886270\nvt 0.281966 0.893970\nvt 0.280839 0.864997\nvt 0.283240 0.865158\nvt 0.284423 0.878752\nvt 0.283114 0.870947\nvt 0.284801 0.871150\nvt 0.285683 0.879137\nvt 0.276947 0.898387\nvt 0.276800 0.899549\nvt 0.272761 0.852264\nvt 0.276821 0.850619\nvt 0.270871 0.905821\nvt 0.269667 0.914011\nvt 0.266181 0.930216\nvt 0.261624 0.944951\nvt 0.256591 0.958167\nvt 0.251047 0.970011\nvt 0.245097 0.980952\nvt 0.238251 0.990689\nvt 0.352071 0.852467\nvt 0.359512 0.855491\nvt 0.359512 0.870135\nvt 0.351553 0.868490\nvt 0.347059 0.925799\nvt 0.359512 0.929593\nvt 0.355494 0.950166\nvt 0.342103 0.942970\nvt 0.305486 0.895384\nvt 0.315496 0.905401\nvt 0.310533 0.917462\nvt 0.301706 0.905821\nvt 0.287265 0.923097\nvt 0.291899 0.938217\nvt 0.276065 0.943327\nvt 0.276541 0.928151\nvt 0.276583 0.903770\nvt 0.282575 0.898821\nvt 0.284108 0.908467\nvt 0.276807 0.912387\nvt 0.288847 0.884135\nvt 0.289169 0.875644\nvt 0.296001 0.884457\nvt 0.294188 0.893578\nvt 0.286880 0.891611\nvt 0.290303 0.901887\nvt 0.302959 0.928837\nvt 0.295595 0.915236\nvt 0.325205 0.984802\nvt 0.283954 0.990528\nvt 0.280762 0.980028\nvt 0.313403 0.972958\nvt 0.276681 0.956382\nvt 0.297170 0.950656\nvt 0.304219 0.961702\nvt 0.278270 0.968205\nvt 0.325282 0.913437\nvt 0.335768 0.920304\nvt 0.330056 0.935340\nvt 0.319724 0.926989\nvt 0.320312 0.950019\nvt 0.310820 0.939911\nvt 0.346373 0.970900\nvt 0.332114 0.960113\nvt 0.306137 0.835485\nvt 0.321369 0.837823\nvt 0.321614 0.853951\nvt 0.307817 0.849478\nvt 0.308426 0.873327\nvt 0.320151 0.880502\nvt 0.318625 0.892997\nvt 0.307488 0.884380\nvt 0.288700 0.868497\nvt 0.287643 0.862134\nvt 0.296484 0.866243\nvt 0.296701 0.875315\nvt 0.284311 0.848778\nvt 0.294209 0.847497\nvt 0.286264 0.855862\nvt 0.295707 0.857178\nvt 0.321005 0.867748\nvt 0.308524 0.861917\nvt 0.359512 0.897932\nvt 0.359512 0.913703\nvt 0.349152 0.910553\nvt 0.350384 0.896266\nvt 0.328775 0.899997\nvt 0.330770 0.886795\nvt 0.340759 0.892234\nvt 0.338911 0.905926\nvt 0.333528 0.841911\nvt 0.343769 0.847742\nvt 0.342957 0.864192\nvt 0.332821 0.858690\nvt 0.341998 0.878689\nvt 0.331869 0.873250\nvt 0.359512 0.885024\nvt 0.350839 0.883043\nvt 0.195187 0.722603\nvt 0.193122 0.712411\nvt 0.191169 0.722477\nvt 0.190077 0.712411\nvt 0.187879 0.721728\nvt 0.187879 0.712411\nvt 0.270178 0.752094\nvt 0.281189 0.756238\nvt 0.322174 0.726446\nvt 0.308972 0.729911\nvt 0.307103 0.712341\nvt 0.319577 0.712348\nvt 0.278816 0.712348\nvt 0.275078 0.729183\nvt 0.292669 0.712341\nvt 0.292886 0.731458\nvt 0.283835 0.730520\nvt 0.285739 0.712348\nvt 0.325422 0.748846\nvt 0.311856 0.757169\nvt 0.293376 0.759269\nvt 0.317855 0.789894\nvt 0.330420 0.766906\nvt 0.296155 0.788809\nvt 0.301153 0.811685\nvt 0.319479 0.815381\nvt 0.353219 0.829759\nvt 0.359512 0.832223\nvt 0.334039 0.820603\nvt 0.344399 0.825475\nvt 0.353716 0.806386\nvt 0.359512 0.809487\nvt 0.334928 0.797265\nvt 0.345330 0.802214\nvt 0.354437 0.785470\nvt 0.359512 0.788795\nvt 0.338687 0.776034\nvt 0.346835 0.781305\nvt 0.355564 0.723436\nvt 0.355445 0.712390\nvt 0.359512 0.712390\nvt 0.359512 0.722169\nvt 0.348781 0.723254\nvt 0.348151 0.712383\nvt 0.341151 0.724353\nvt 0.340724 0.712362\nvt 0.354878 0.766906\nvt 0.359512 0.771351\nvt 0.348242 0.762895\nvt 0.355543 0.746431\nvt 0.348893 0.743848\nvt 0.359512 0.748097\nvt 0.341228 0.759325\nvt 0.341599 0.742525\nvt 0.335628 0.756238\nvt 0.333892 0.742875\nvt 0.332331 0.724269\nvt 0.331505 0.712348\nvt 0.187879 0.839776\nvt 0.192891 0.839923\nvt 0.197490 0.837193\nvt 0.200395 0.835303\nvt 0.192779 0.837354\nvt 0.187879 0.837445\nvt 0.197161 0.835611\nvt 0.200815 0.834358\nvt 0.201004 0.834561\nvt 0.187879 0.755790\nvt 0.190826 0.755482\nvt 0.194375 0.754516\nvt 0.190154 0.748377\nvt 0.187879 0.745871\nvt 0.264186 0.883603\nvt 0.267413 0.879711\nvt 0.269618 0.882077\nvt 0.267056 0.884786\nvt 0.264123 0.898072\nvt 0.267189 0.895251\nvt 0.271641 0.897302\nvt 0.270871 0.900928\nvt 0.261799 0.892325\nvt 0.264662 0.891044\nvt 0.262156 0.886389\nvt 0.265369 0.886893\nvt 0.275624 0.896217\nvt 0.276443 0.898681\nvt 0.278046 0.892465\nvt 0.280006 0.894047\nvt 0.279726 0.887642\nvt 0.281672 0.888825\nvt 0.267525 0.900207\nvt 0.282260 0.888111\nvt 0.280314 0.893837\nvt 0.276583 0.898478\nvt 0.271641 0.900795\nvt 0.279607 0.881692\nvt 0.281777 0.881622\nvt 0.278221 0.875749\nvt 0.280440 0.875294\nvt 0.275785 0.870527\nvt 0.278347 0.869512\nvt 0.273349 0.866684\nvt 0.275806 0.864885\nvt 0.277206 0.864045\nvt 0.279544 0.868532\nvt 0.281455 0.874223\nvt 0.282631 0.880782\nvt 0.270325 0.863191\nvt 0.273265 0.861098\nvt 0.269387 0.857486\nvt 0.265425 0.859656\nvt 0.274217 0.859663\nvt 0.270745 0.855596\nvt 0.260413 0.865242\nvt 0.263157 0.867839\nvt 0.262219 0.862449\nvt 0.266545 0.866754\nvt 0.199464 0.891485\nvt 0.199534 0.891268\nvt 0.266104 0.871948\nvt 0.268988 0.874664\nvt 0.270045 0.869344\nvt 0.272719 0.872886\nvt 0.275932 0.877289\nvt 0.276758 0.882959\nvt 0.271459 0.878164\nvt 0.273762 0.880278\nvt 0.273188 0.884254\nvt 0.269758 0.891751\nvt 0.271760 0.887817\nvt 0.274063 0.892542\nvt 0.275750 0.887915\nvt 0.200213 0.834351\nvt 0.200906 0.834876\nvt 0.201025 0.834652\nvt 0.180158 0.890617\nvt 0.179248 0.887992\nvt 0.182209 0.885521\nvt 0.183672 0.890778\nvt 0.177218 0.897778\nvt 0.179822 0.894593\nvt 0.183476 0.899066\nvt 0.179031 0.903329\nvt 0.172346 0.900592\nvt 0.172836 0.905597\nvt 0.166354 0.899759\nvt 0.165493 0.903959\nvt 0.161573 0.895839\nvt 0.158262 0.897960\nvt 0.160768 0.891709\nvt 0.156869 0.892017\nvt 0.165332 0.883442\nvt 0.162007 0.887138\nvt 0.158815 0.883526\nvt 0.163064 0.878878\nvt 0.171569 0.881804\nvt 0.171758 0.876106\nvt 0.176875 0.884933\nvt 0.178541 0.880824\nvt 0.186150 0.885451\nvt 0.186213 0.890813\nvt 0.183560 0.876309\nvt 0.186192 0.877310\nvt 0.184302 0.870709\nvt 0.186318 0.870485\nvt 0.174215 0.851074\nvt 0.169539 0.853811\nvt 0.167285 0.847497\nvt 0.172430 0.844508\nvt 0.180606 0.873740\nvt 0.176427 0.867804\nvt 0.181712 0.869407\nvt 0.178947 0.863513\nvt 0.172178 0.859747\nvt 0.176217 0.856919\nvt 0.183672 0.911414\nvt 0.178898 0.913262\nvt 0.171408 0.913360\nvt 0.162189 0.909874\nvt 0.151731 0.901404\nvt 0.186087 0.900179\nvt 0.186150 0.910119\nvt 0.173536 0.825860\nvt 0.171856 0.821534\nvt 0.178541 0.818545\nvt 0.177099 0.824257\nvt 0.182097 0.826651\nvt 0.182902 0.823284\nvt 0.170008 0.833259\nvt 0.165605 0.833007\nvt 0.167390 0.827687\nvt 0.170918 0.829927\nvt 0.165479 0.839349\nvt 0.170071 0.837354\nvt 0.183203 0.814975\nvt 0.184204 0.819308\nvt 0.178639 0.813820\nvt 0.183000 0.811363\nvt 0.170813 0.818027\nvt 0.177302 0.799582\nvt 0.177274 0.802074\nvt 0.169343 0.805840\nvt 0.169301 0.801899\nvt 0.182398 0.800191\nvt 0.182839 0.798259\nvt 0.161209 0.889357\nvt 0.157310 0.887460\nvt 0.180270 0.892346\nvt 0.183987 0.894047\nvt 0.186234 0.894677\nvt 0.183091 0.836409\nvt 0.182965 0.833392\nvt 0.174173 0.834393\nvt 0.173417 0.833987\nvt 0.173718 0.833238\nvt 0.174418 0.834127\nvt 0.177862 0.829843\nvt 0.174733 0.830452\nvt 0.182174 0.829738\nvt 0.178681 0.834862\nvt 0.178478 0.832580\nvt 0.174369 0.834785\nvt 0.175244 0.835618\nvt 0.174866 0.836871\nvt 0.173641 0.835002\nvt 0.182930 0.841414\nvt 0.183000 0.843612\nvt 0.178177 0.837984\nvt 0.178051 0.839776\nvt 0.172367 0.833525\nvt 0.172794 0.831978\nvt 0.172703 0.835597\nvt 0.174474 0.838761\nvt 0.175496 0.833903\nvt 0.175160 0.832461\nvt 0.178744 0.851207\nvt 0.177708 0.846797\nvt 0.183224 0.849240\nvt 0.183637 0.851704\nvt 0.182951 0.846076\nvt 0.186332 0.865403\nvt 0.181446 0.862365\nvt 0.182797 0.865697\nvt 0.184708 0.866019\nvt 0.181691 0.857528\nvt 0.182769 0.858704\nvt 0.184750 0.856100\nvt 0.184407 0.857633\nvt 0.185947 0.857290\nvt 0.177764 0.841771\nvt 0.180207 0.855015\nvt 0.184309 0.854441\nvt 0.124956 0.840952\nvt 0.128456 0.846769\nvt 0.118908 0.857745\nvt 0.116080 0.854406\nvt 0.159298 0.811062\nvt 0.159116 0.805203\nvt 0.143121 0.819805\nvt 0.142673 0.812294\nvt 0.121890 0.835779\nvt 0.120210 0.831250\nvt 0.160943 0.824628\nvt 0.165276 0.865872\nvt 0.158262 0.833931\nvt 0.160292 0.852446\nvt 0.158248 0.842842\nvt 0.162532 0.859299\nvt 0.148371 0.892871\nvt 0.157940 0.872137\nvt 0.149001 0.879571\nvt 0.147783 0.886557\nvt 0.154552 0.865718\nvt 0.151416 0.858648\nvt 0.144206 0.871948\nvt 0.140167 0.865109\nvt 0.147559 0.848834\nvt 0.134546 0.856457\nvt 0.185954 0.861693\nvt 0.186311 0.859593\nvt 0.184652 0.861798\nvt 0.185240 0.860230\nvt 0.183616 0.862442\nvt 0.183021 0.861056\nvt 0.183511 0.859684\nvt 0.184484 0.860314\nvt 0.184365 0.860860\nvt 0.183959 0.860923\nvt 0.184078 0.860167\nvt 0.186535 0.858116\nvt 0.185254 0.859236\nvt 0.184820 0.858844\nvt 0.184190 0.859096\nvt 0.184554 0.859866\nvt 0.184302 0.859740\nvt 0.184085 0.859698\nvt 0.183903 0.859985\nvt 0.183756 0.860412\nvt 0.177722 0.890897\nvt 0.177071 0.889742\nvt 0.175510 0.894901\nvt 0.177400 0.892444\nvt 0.171814 0.896966\nvt 0.167271 0.896399\nvt 0.164751 0.894229\nvt 0.163953 0.892234\nvt 0.166858 0.887117\nvt 0.164436 0.890162\nvt 0.171240 0.885962\nvt 0.175181 0.888090\nvt 0.164058 0.891261\nvt 0.177729 0.891548\nvt 0.174677 0.889777\nvt 0.171002 0.888944\nvt 0.176567 0.890617\nvt 0.176994 0.891107\nvt 0.176693 0.892052\nvt 0.177008 0.891506\nvt 0.174901 0.893900\nvt 0.171709 0.895433\nvt 0.167999 0.894691\nvt 0.166102 0.893340\nvt 0.165584 0.892619\nvt 0.165969 0.891709\nvt 0.165675 0.892255\nvt 0.167775 0.889840\nvt 0.174656 0.893578\nvt 0.176196 0.891898\nvt 0.171618 0.894936\nvt 0.168132 0.894215\nvt 0.166508 0.893088\nvt 0.166228 0.892605\nvt 0.168237 0.890512\nvt 0.166536 0.891961\nvt 0.171086 0.889763\nvt 0.174355 0.890428\nvt 0.176021 0.891044\nvt 0.166256 0.892346\nvt 0.174411 0.893074\nvt 0.175790 0.891954\nvt 0.171611 0.893984\nvt 0.168503 0.893459\nvt 0.167208 0.892829\nvt 0.167208 0.892465\nvt 0.168706 0.891163\nvt 0.167369 0.891940\nvt 0.170988 0.890568\nvt 0.173942 0.891037\nvt 0.175293 0.891184\nvt 0.167271 0.892255\nvt 0.174453 0.891877\nvt 0.175839 0.891534\nvt 0.168622 0.891786\nvt 0.168461 0.892367\nvt 0.171205 0.892220\nvt 0.174257 0.891387\nvt 0.171065 0.891366\nvt 0.175615 0.891324\nvt 0.182153 0.924189\nvt 0.176014 0.924448\nvt 0.166921 0.922243\nvt 0.155378 0.916566\nvt 0.144010 0.905604\nvt 0.185485 0.924147\nvt 0.135295 0.888118\nvt 0.132397 0.881874\nvt 0.138284 0.895433\nvt 0.129142 0.877429\nvt 0.125201 0.871990\nvt 0.120770 0.865123\nvt 0.106294 0.845439\nvt 0.102458 0.842597\nvt 0.113700 0.821415\nvt 0.117914 0.825986\nvt 0.142183 0.798483\nvt 0.119517 0.788781\nvt 0.127476 0.777539\nvt 0.139250 0.784063\nvt 0.131858 0.794892\nvt 0.166053 0.721952\nvt 0.170883 0.712404\nvt 0.176014 0.712404\nvt 0.171912 0.722239\nvt 0.160075 0.732816\nvt 0.166494 0.734202\nvt 0.149267 0.787696\nvt 0.174824 0.767473\nvt 0.171527 0.780696\nvt 0.164093 0.779940\nvt 0.169077 0.766598\nvt 0.159627 0.795984\nvt 0.161657 0.788123\nvt 0.169455 0.795011\nvt 0.170337 0.788144\nvt 0.183224 0.793835\nvt 0.183224 0.787878\nvt 0.177092 0.794157\nvt 0.177295 0.787794\nvt 0.155056 0.778680\nvt 0.145620 0.774578\nvt 0.153733 0.761642\nvt 0.161657 0.765030\nvt 0.176665 0.722498\nvt 0.172241 0.735070\nvt 0.179598 0.712411\nvt 0.179738 0.768012\nvt 0.177722 0.780752\nvt 0.157555 0.721686\nvt 0.160747 0.712390\nvt 0.153593 0.730723\nvt 0.102374 0.782376\nvt 0.115716 0.770357\nvt 0.105118 0.815521\nvt 0.093211 0.810957\nvt 0.094870 0.838929\nvt 0.084279 0.835912\nvt 0.184022 0.768600\nvt 0.183252 0.781172\nvt 0.134875 0.767718\nvt 0.143884 0.755811\nvt 0.137633 0.749560\nvt 0.127714 0.760011\nvt 0.184302 0.743680\nvt 0.182440 0.736358\nvt 0.177610 0.753102\nvt 0.173354 0.752276\nvt 0.177645 0.735819\nvt 0.167040 0.750771\nvt 0.159991 0.747810\nvt 0.152130 0.743659\nvt 0.146383 0.739459\nvt 0.125684 0.891002\nvt 0.122807 0.884443\nvt 0.135680 0.909083\nvt 0.129380 0.898597\nvt 0.115800 0.875567\nvt 0.112160 0.877268\nvt 0.113091 0.871178\nvt 0.115996 0.869211\nvt 0.119797 0.880607\nvt 0.146663 0.922523\nvt 0.183651 0.945273\nvt 0.159256 0.932008\nvt 0.169903 0.937937\nvt 0.178177 0.942067\nvt 0.137696 0.926485\nvt 0.127357 0.911631\nvt 0.149883 0.937958\nvt 0.161664 0.946855\nvt 0.172073 0.953659\nvt 0.180340 0.959168\nvt 0.115142 0.898947\nvt 0.110193 0.901642\nvt 0.109619 0.900564\nvt 0.114127 0.897764\nvt 0.115569 0.881944\nvt 0.113679 0.882735\nvt 0.110291 0.878199\nvt 0.118075 0.885395\nvt 0.119111 0.893585\nvt 0.116549 0.892864\nvt 0.115961 0.885983\nvt 0.104257 0.901810\nvt 0.104033 0.900774\nvt 0.120595 0.913073\nvt 0.113679 0.913997\nvt 0.111334 0.905751\nvt 0.117074 0.903938\nvt 0.129716 0.928662\nvt 0.120448 0.930055\nvt 0.140538 0.941661\nvt 0.128729 0.944251\nvt 0.151948 0.952441\nvt 0.138039 0.956627\nvt 0.162994 0.961051\nvt 0.147867 0.967386\nvt 0.172710 0.968086\nvt 0.157289 0.977102\nvt 0.182048 0.973504\nvt 0.166711 0.986671\nvt 0.122646 0.901523\nvt 0.120119 0.861000\nvt 0.131116 0.851375\nvt 0.144430 0.834309\nvt 0.145312 0.841162\nvt 0.143702 0.826791\nvt 0.160019 0.817642\nvt 0.170309 0.811811\nvt 0.178394 0.807870\nvt 0.182930 0.805476\nvt 0.111915 0.851907\nvt 0.109038 0.849408\nvt 0.148035 0.721672\nvt 0.135925 0.722960\nvt 0.134728 0.712383\nvt 0.149050 0.712390\nvt 0.139684 0.732620\nvt 0.147559 0.728343\nvt 0.124676 0.724955\nvt 0.113742 0.726418\nvt 0.108114 0.712362\nvt 0.120098 0.712383\nvt 0.119370 0.743904\nvt 0.129968 0.738402\nvt 0.097495 0.860552\nvt 0.094429 0.860349\nvt 0.096578 0.855925\nvt 0.100253 0.856716\nvt 0.094268 0.893067\nvt 0.093785 0.893970\nvt 0.090789 0.886270\nvt 0.091727 0.885878\nvt 0.094933 0.864997\nvt 0.092525 0.865158\nvt 0.091342 0.878752\nvt 0.090082 0.879137\nvt 0.090978 0.871150\nvt 0.092658 0.870947\nvt 0.098818 0.898387\nvt 0.098965 0.899549\nvt 0.098944 0.850619\nvt 0.102997 0.852264\nvt 0.106105 0.914011\nvt 0.104894 0.905821\nvt 0.109584 0.930216\nvt 0.114148 0.944951\nvt 0.119167 0.958167\nvt 0.124718 0.970011\nvt 0.130668 0.980952\nvt 0.137507 0.990689\nvt 0.023687 0.852467\nvt 0.024198 0.868490\nvt 0.016253 0.870135\nvt 0.016253 0.855491\nvt 0.028706 0.925799\nvt 0.033655 0.942970\nvt 0.020271 0.950166\nvt 0.016253 0.929593\nvt 0.070265 0.895384\nvt 0.074059 0.905821\nvt 0.065225 0.917462\nvt 0.060269 0.905401\nvt 0.088500 0.923097\nvt 0.099224 0.928151\nvt 0.099693 0.943327\nvt 0.083852 0.938217\nvt 0.099168 0.903770\nvt 0.098944 0.912387\nvt 0.091650 0.908467\nvt 0.093176 0.898821\nvt 0.086911 0.884135\nvt 0.081577 0.893578\nvt 0.079764 0.884457\nvt 0.086596 0.875644\nvt 0.085455 0.901887\nvt 0.088871 0.891611\nvt 0.072813 0.928837\nvt 0.080156 0.915236\nvt 0.050560 0.984802\nvt 0.062362 0.972958\nvt 0.094996 0.980028\nvt 0.091811 0.990528\nvt 0.099084 0.956382\nvt 0.097481 0.968205\nvt 0.071553 0.961702\nvt 0.078602 0.950656\nvt 0.050483 0.913437\nvt 0.056041 0.926989\nvt 0.045709 0.935340\nvt 0.039997 0.920304\nvt 0.055453 0.950019\nvt 0.064952 0.939911\nvt 0.029392 0.970900\nvt 0.043637 0.960113\nvt 0.069628 0.835485\nvt 0.067948 0.849478\nvt 0.054151 0.853951\nvt 0.054396 0.837823\nvt 0.067339 0.873327\nvt 0.068284 0.884380\nvt 0.057140 0.892997\nvt 0.055614 0.880502\nvt 0.087058 0.868497\nvt 0.079057 0.875315\nvt 0.079274 0.866243\nvt 0.088129 0.862134\nvt 0.091461 0.848778\nvt 0.081549 0.847497\nvt 0.080051 0.857178\nvt 0.089508 0.855862\nvt 0.054760 0.867748\nvt 0.067248 0.861917\nvt 0.016253 0.897932\nvt 0.025388 0.896266\nvt 0.026613 0.910553\nvt 0.016253 0.913703\nvt 0.046997 0.899997\nvt 0.036854 0.905926\nvt 0.035006 0.892234\nvt 0.044988 0.886795\nvt 0.042244 0.841911\nvt 0.042944 0.858690\nvt 0.032808 0.864192\nvt 0.031996 0.847742\nvt 0.033767 0.878689\nvt 0.043896 0.873250\nvt 0.016253 0.885024\nvt 0.024919 0.883043\nvt 0.182636 0.712411\nvt 0.180571 0.722603\nvt 0.184596 0.722477\nvt 0.185681 0.712411\nvt 0.094569 0.756238\nvt 0.105580 0.752094\nvt 0.053584 0.726446\nvt 0.056181 0.712348\nvt 0.068655 0.712341\nvt 0.066800 0.729911\nvt 0.096949 0.712348\nvt 0.100680 0.729183\nvt 0.083096 0.712341\nvt 0.090019 0.712348\nvt 0.091930 0.730520\nvt 0.082893 0.731458\nvt 0.050329 0.748846\nvt 0.063916 0.757169\nvt 0.082389 0.759269\nvt 0.057896 0.789894\nvt 0.045352 0.766906\nvt 0.079617 0.788809\nvt 0.074612 0.811685\nvt 0.056286 0.815381\nvt 0.016253 0.832223\nvt 0.022532 0.829759\nvt 0.031366 0.825475\nvt 0.041726 0.820603\nvt 0.016253 0.809487\nvt 0.022042 0.806386\nvt 0.030428 0.802214\nvt 0.040830 0.797265\nvt 0.016253 0.788795\nvt 0.021314 0.785470\nvt 0.028923 0.781305\nvt 0.037085 0.776034\nvt 0.020194 0.723436\nvt 0.016253 0.722169\nvt 0.016253 0.712390\nvt 0.020313 0.712390\nvt 0.026991 0.723254\nvt 0.027621 0.712383\nvt 0.034614 0.724353\nvt 0.035041 0.712362\nvt 0.020887 0.766906\nvt 0.016253 0.771351\nvt 0.027509 0.762895\nvt 0.026872 0.743848\nvt 0.020215 0.746431\nvt 0.016253 0.748097\nvt 0.034537 0.759325\nvt 0.034152 0.742525\nvt 0.040123 0.756238\nvt 0.043434 0.724269\nvt 0.041880 0.742875\nvt 0.044246 0.712348\nvt 0.182881 0.839923\nvt 0.178268 0.837193\nvt 0.175370 0.835303\nvt 0.182986 0.837354\nvt 0.178604 0.835611\nvt 0.174957 0.834358\nvt 0.174761 0.834561\nvt 0.184932 0.755482\nvt 0.181397 0.754516\nvt 0.185611 0.748377\nvt 0.111586 0.883603\nvt 0.108709 0.884786\nvt 0.106147 0.882077\nvt 0.108359 0.879711\nvt 0.111649 0.898072\nvt 0.104887 0.900928\nvt 0.104117 0.897302\nvt 0.108569 0.895251\nvt 0.113966 0.892325\nvt 0.111103 0.891044\nvt 0.113609 0.886389\nvt 0.110396 0.886893\nvt 0.099308 0.898681\nvt 0.100134 0.896217\nvt 0.095766 0.894047\nvt 0.097705 0.892465\nvt 0.094100 0.888825\nvt 0.096039 0.887642\nvt 0.108240 0.900207\nvt 0.095451 0.893837\nvt 0.093512 0.888111\nvt 0.099182 0.898478\nvt 0.104124 0.900795\nvt 0.093988 0.881622\nvt 0.096158 0.881692\nvt 0.095318 0.875294\nvt 0.097551 0.875749\nvt 0.097411 0.869512\nvt 0.099980 0.870527\nvt 0.099959 0.864885\nvt 0.102416 0.866684\nvt 0.096214 0.868532\nvt 0.098559 0.864045\nvt 0.094310 0.874223\nvt 0.093134 0.880782\nvt 0.102493 0.861098\nvt 0.105440 0.863191\nvt 0.106371 0.857486\nvt 0.110333 0.859656\nvt 0.101555 0.859663\nvt 0.105027 0.855596\nvt 0.115352 0.865242\nvt 0.112608 0.867839\nvt 0.109220 0.866754\nvt 0.113539 0.862449\nvt 0.176301 0.891485\nvt 0.176238 0.891268\nvt 0.106763 0.874664\nvt 0.109647 0.871948\nvt 0.105720 0.869344\nvt 0.103039 0.872886\nvt 0.099833 0.877289\nvt 0.099014 0.882959\nvt 0.104306 0.878164\nvt 0.102577 0.884254\nvt 0.102003 0.880278\nvt 0.106007 0.891751\nvt 0.104012 0.887817\nvt 0.101702 0.892542\nvt 0.100015 0.887915\nvt 0.175552 0.834351\nvt 0.174859 0.834876\nvt 0.174740 0.834652\nvt 0.544030 0.162262\nvt 0.549261 0.132031\nvt 0.569552 0.133746\nvt 0.565128 0.165297\nvt 0.590469 0.136962\nvt 0.586584 0.168012\nvt 0.611995 0.141081\nvt 0.611089 0.170459\nvt 0.632136 0.143478\nvt 0.634614 0.171874\nvt 0.650241 0.143111\nvt 0.654723 0.170813\nvt 0.667083 0.138634\nvt 0.673887 0.165795\nvt 0.683617 0.131504\nvt 0.694459 0.156871\nvt 0.520951 0.157129\nvt 0.530036 0.130503\nvt 0.554788 0.226461\nvt 0.550990 0.263839\nvt 0.511265 0.260442\nvt 0.527872 0.222247\nvt 0.581740 0.228715\nvt 0.580187 0.264878\nvt 0.610895 0.229989\nvt 0.610391 0.264821\nvt 0.639498 0.230168\nvt 0.640565 0.265681\nvt 0.665604 0.228549\nvt 0.670115 0.264962\nvt 0.691528 0.222947\nvt 0.709501 0.262002\nvt 0.718014 0.211348\nvt 0.734826 0.243567\nvt 0.485372 0.246806\nvt 0.499683 0.212835\nvt 0.553745 0.116510\nvt 0.571592 0.115986\nvt 0.557692 0.094239\nvt 0.574239 0.091166\nvt 0.589810 0.118299\nvt 0.591387 0.092237\nvt 0.611278 0.122282\nvt 0.609938 0.096224\nvt 0.631865 0.125001\nvt 0.628498 0.100191\nvt 0.648231 0.125525\nvt 0.645169 0.102324\nvt 0.663709 0.122857\nvt 0.662400 0.100846\nvt 0.680989 0.117072\nvt 0.681835 0.096041\nvt 0.534742 0.117212\nvt 0.538892 0.097462\nvt 0.572984 0.292452\nvt 0.545412 0.294571\nvt 0.608602 0.292211\nvt 0.645576 0.294116\nvt 0.676301 0.295490\nvt 0.572429 0.325163\nvt 0.543957 0.325757\nvt 0.610055 0.326859\nvt 0.649327 0.328919\nvt 0.679923 0.329711\nvt 0.579387 0.354689\nvt 0.549942 0.357964\nvt 0.614842 0.355946\nvt 0.650225 0.359149\nvt 0.679677 0.361865\nvt 0.704166 0.330605\nvt 0.701904 0.363393\nvt 0.706795 0.293105\nvt 0.515855 0.297093\nvt 0.521022 0.330080\nvt 0.528421 0.363393\nvt 0.560342 0.195144\nvt 0.536553 0.190862\nvt 0.584337 0.197363\nvt 0.610719 0.199143\nvt 0.636650 0.199747\nvt 0.659661 0.198110\nvt 0.681989 0.192639\nvt 0.705440 0.182902\nvt 0.511601 0.183989\nvt 0.909788 0.132516\nvt 0.929971 0.130807\nvt 0.935178 0.160878\nvt 0.914192 0.163899\nvt 0.888982 0.135717\nvt 0.892850 0.166601\nvt 0.867572 0.139817\nvt 0.868476 0.169038\nvt 0.847539 0.142203\nvt 0.845077 0.170447\nvt 0.829532 0.141840\nvt 0.825076 0.169393\nvt 0.812781 0.137389\nvt 0.806016 0.164404\nvt 0.796336 0.130300\nvt 0.785555 0.155530\nvt 0.949094 0.129286\nvt 0.958134 0.155769\nvt 0.924482 0.224736\nvt 0.951254 0.220543\nvt 0.967777 0.258534\nvt 0.928263 0.261916\nvt 0.897674 0.226981\nvt 0.899220 0.262952\nvt 0.868673 0.228249\nvt 0.869176 0.262896\nvt 0.840222 0.228429\nvt 0.839163 0.263753\nvt 0.814256 0.226820\nvt 0.809770 0.263038\nvt 0.788471 0.221248\nvt 0.770595 0.260093\nvt 0.762128 0.209711\nvt 0.745406 0.241756\nvt 0.979293 0.211178\nvt 0.993532 0.244968\nvt 0.925508 0.115370\nvt 0.907756 0.114851\nvt 0.905120 0.090163\nvt 0.921579 0.093217\nvt 0.889636 0.117154\nvt 0.888063 0.091231\nvt 0.868282 0.121119\nvt 0.869611 0.095200\nvt 0.847806 0.123826\nvt 0.851151 0.099149\nvt 0.831528 0.124349\nvt 0.834570 0.101274\nvt 0.816135 0.121698\nvt 0.817433 0.099807\nvt 0.798948 0.115947\nvt 0.798104 0.095031\nvt 0.944411 0.116066\nvt 0.940280 0.096421\nvt 0.933813 0.292486\nvt 0.906387 0.290379\nvt 0.870957 0.290141\nvt 0.834178 0.292036\nvt 0.803617 0.293403\nvt 0.935262 0.323507\nvt 0.906940 0.322918\nvt 0.869512 0.324606\nvt 0.830447 0.326655\nvt 0.800013 0.327442\nvt 0.929311 0.355546\nvt 0.900019 0.352289\nvt 0.864751 0.353540\nvt 0.829554 0.356726\nvt 0.800257 0.359426\nvt 0.778148 0.360946\nvt 0.775899 0.328332\nvt 0.773285 0.291031\nvt 0.963215 0.294992\nvt 0.958077 0.327807\nvt 0.950719 0.360946\nvt 0.942617 0.189325\nvt 0.918955 0.193586\nvt 0.895088 0.195796\nvt 0.868846 0.197568\nvt 0.843054 0.198171\nvt 0.820167 0.196544\nvt 0.797958 0.191103\nvt 0.774634 0.181419\nvt 0.967437 0.182487\nvt 0.887064 0.375332\nvt 0.940035 0.391815\nvt 0.917995 0.455716\nvt 0.870247 0.432190\nvt 0.836073 0.367751\nvt 0.824546 0.423475\nvt 0.788655 0.371611\nvt 0.778784 0.421763\nvt 0.955353 0.484000\nvt 0.986675 0.426237\nvt 0.732314 0.422220\nvt 0.741108 0.381004\nvt 0.900859 0.500210\nvt 0.852406 0.476768\nvt 0.811575 0.466545\nvt 0.770041 0.462344\nvt 0.937386 0.525591\nvt 0.725651 0.461548\nvt 0.880775 0.551075\nvt 0.836701 0.527336\nvt 0.797792 0.514127\nvt 0.759377 0.508439\nvt 0.913857 0.575039\nvt 0.725651 0.506149\nvt 0.865684 0.587482\nvt 0.821281 0.564934\nvt 0.787202 0.552815\nvt 0.755325 0.547510\nvt 0.725651 0.546264\nvt 0.898001 0.607830\nvt 0.569988 0.700590\nvt 0.585328 0.719905\nvt 0.546321 0.754766\nvt 0.535520 0.732184\nvt 0.509563 0.768667\nvt 0.504323 0.747817\nvt 0.675711 0.994379\nvt 0.668356 0.975055\nvt 0.698759 0.964984\nvt 0.705521 0.987438\nvt 0.610127 0.696548\nvt 0.595169 0.678228\nvt 0.732795 0.987196\nvt 0.732794 0.962797\nvt 0.599583 0.738801\nvt 0.555640 0.774850\nvt 0.515662 0.787440\nvt 0.657888 0.959183\nvt 0.691394 0.943641\nvt 0.624971 0.715188\nvt 0.732794 0.941074\nvt 0.603384 0.833329\nvt 0.617210 0.851804\nvt 0.597434 0.867055\nvt 0.585874 0.851314\nvt 0.584980 0.813744\nvt 0.569211 0.833435\nvt 0.556947 0.800928\nvt 0.546762 0.823712\nvt 0.521444 0.800093\nvt 0.521927 0.816784\nvt 0.645904 0.929888\nvt 0.644990 0.953827\nvt 0.628911 0.953150\nvt 0.627877 0.930700\nvt 0.639473 0.899216\nvt 0.620190 0.907346\nvt 0.631625 0.876874\nvt 0.611086 0.888231\nvt 0.637719 0.731981\nvt 0.614475 0.763949\nvt 0.593459 0.800158\nvt 0.652546 0.931978\nvt 0.688612 0.924506\nvt 0.732795 0.920494\nvt 0.575018 0.888069\nvt 0.562870 0.873723\nvt 0.548625 0.858496\nvt 0.532725 0.848360\nvt 0.514132 0.839286\nvt 0.606113 0.955636\nvt 0.602662 0.937567\nvt 0.595448 0.919093\nvt 0.585940 0.902616\nvt 0.611576 0.807529\nvt 0.653300 0.901743\nvt 0.688758 0.898706\nvt 0.732795 0.895839\nvt 0.649974 0.751148\nvt 0.629721 0.779176\nvt 0.640006 0.834554\nvt 0.653952 0.861751\nvt 0.662656 0.764809\nvt 0.644615 0.797575\nvt 0.692152 0.870965\nvt 0.732796 0.872146\nvt 0.463719 0.617262\nvt 0.444181 0.640306\nvt 0.411954 0.624176\nvt 0.435328 0.598532\nvt 0.507715 0.593502\nvt 0.483111 0.602546\nvt 0.457466 0.583360\nvt 0.482990 0.572211\nvt 0.435205 0.663171\nvt 0.430337 0.695020\nvt 0.397018 0.695020\nvt 0.400633 0.650102\nvt 0.387055 0.598617\nvt 0.420846 0.565611\nvt 0.454881 0.547866\nvt 0.489614 0.537081\nvt 0.366470 0.695021\nvt 0.372441 0.644080\nvt 0.661317 0.826152\nvt 0.675408 0.846937\nvt 0.686811 0.815629\nvt 0.698153 0.830461\nvt 0.672320 0.776433\nvt 0.662649 0.800206\nvt 0.681635 0.784822\nvt 0.683389 0.801078\nvt 0.700561 0.852932\nvt 0.732797 0.851748\nvt 0.712587 0.835739\nvt 0.732798 0.835786\nvt 0.559205 0.902622\nvt 0.549001 0.890834\nvt 0.534429 0.876148\nvt 0.519219 0.865725\nvt 0.503900 0.856835\nvt 0.587167 0.960003\nvt 0.582948 0.944963\nvt 0.575561 0.927326\nvt 0.567531 0.913018\nvt 0.540077 0.915350\nvt 0.529826 0.906995\nvt 0.517398 0.897218\nvt 0.505832 0.887784\nvt 0.493477 0.877807\nvt 0.566086 0.967743\nvt 0.562902 0.953735\nvt 0.556264 0.938127\nvt 0.548642 0.924913\nvt 0.519152 0.931261\nvt 0.510448 0.923146\nvt 0.501167 0.914442\nvt 0.492575 0.906612\nvt 0.482102 0.897502\nvt 0.545274 0.975605\nvt 0.540655 0.962839\nvt 0.534209 0.950746\nvt 0.527138 0.940113\nvt 0.503485 0.942839\nvt 0.496518 0.934822\nvt 0.489492 0.926408\nvt 0.482232 0.918688\nvt 0.471761 0.909890\nvt 0.530689 0.981736\nvt 0.524518 0.970370\nvt 0.517646 0.960553\nvt 0.510690 0.951233\nvt 0.796059 0.669944\nvt 0.797327 0.684804\nvt 0.761766 0.691146\nvt 0.759393 0.670512\nvt 0.800064 0.699862\nvt 0.768023 0.713991\nvt 0.807137 0.712911\nvt 0.779774 0.732015\nvt 0.819904 0.727583\nvt 0.797581 0.749343\nvt 0.819904 0.608781\nvt 0.808127 0.623008\nvt 0.782016 0.605592\nvt 0.798364 0.586888\nvt 0.801463 0.638516\nvt 0.769822 0.628971\nvt 0.797261 0.654344\nvt 0.761848 0.650543\nvt 0.727152 0.698963\nvt 0.723401 0.671819\nvt 0.736196 0.728778\nvt 0.753272 0.751032\nvt 0.777947 0.772442\nvt 0.756732 0.586802\nvt 0.779379 0.563621\nvt 0.738826 0.617150\nvt 0.727058 0.645545\nvt 0.564234 0.375332\nvt 0.581052 0.432191\nvt 0.533304 0.455717\nvt 0.511263 0.391816\nvt 0.615225 0.367750\nvt 0.626753 0.423474\nvt 0.662644 0.371609\nvt 0.672517 0.421762\nvt 0.495946 0.484003\nvt 0.464623 0.426240\nvt 0.710193 0.381002\nvt 0.718988 0.422219\nvt 0.598894 0.476769\nvt 0.550441 0.500212\nvt 0.639725 0.466546\nvt 0.681259 0.462344\nvt 0.513914 0.525594\nvt 0.614600 0.527338\nvt 0.570526 0.551077\nvt 0.653509 0.514128\nvt 0.691924 0.508440\nvt 0.537444 0.575042\nvt 0.630020 0.564936\nvt 0.585617 0.587484\nvt 0.664100 0.552816\nvt 0.695977 0.547510\nvt 0.553300 0.607833\nvt 0.895598 0.700619\nvt 0.930060 0.732210\nvt 0.919260 0.754787\nvt 0.880260 0.719930\nvt 0.961252 0.747841\nvt 0.956012 0.768688\nvt 0.789877 0.994378\nvt 0.760068 0.987438\nvt 0.766829 0.964984\nvt 0.797232 0.975055\nvt 0.855465 0.696575\nvt 0.870421 0.678258\nvt 0.909942 0.774869\nvt 0.866006 0.738823\nvt 0.949914 0.787459\nvt 0.774194 0.943641\nvt 0.807700 0.959183\nvt 0.840622 0.715212\nvt 0.862202 0.833339\nvt 0.879709 0.851322\nvt 0.868150 0.867062\nvt 0.848377 0.851812\nvt 0.880604 0.813757\nvt 0.896371 0.833446\nvt 0.908634 0.800943\nvt 0.918817 0.823724\nvt 0.944132 0.800109\nvt 0.943648 0.816798\nvt 0.819683 0.929889\nvt 0.837709 0.930701\nvt 0.836675 0.953150\nvt 0.820596 0.953827\nvt 0.826114 0.899219\nvt 0.845396 0.907349\nvt 0.833962 0.876879\nvt 0.854498 0.888236\nvt 0.827875 0.732002\nvt 0.851115 0.763967\nvt 0.872127 0.800172\nvt 0.813041 0.931979\nvt 0.776976 0.924507\nvt 0.902711 0.873730\nvt 0.890564 0.888074\nvt 0.916954 0.858504\nvt 0.932852 0.848369\nvt 0.951442 0.839296\nvt 0.862922 0.937568\nvt 0.859470 0.955636\nvt 0.870136 0.919095\nvt 0.879642 0.902620\nvt 0.854012 0.807541\nvt 0.812287 0.901746\nvt 0.776831 0.898707\nvt 0.815620 0.751167\nvt 0.835870 0.779192\nvt 0.811637 0.861756\nvt 0.825584 0.834563\nvt 0.802940 0.764824\nvt 0.820977 0.797588\nvt 0.773439 0.870968\nvt 0.463721 0.772779\nvt 0.435329 0.791508\nvt 0.411956 0.765864\nvt 0.444183 0.749734\nvt 0.507716 0.796541\nvt 0.482990 0.817830\nvt 0.457466 0.806681\nvt 0.483112 0.787496\nvt 0.435207 0.726868\nvt 0.400635 0.739939\nvt 0.420846 0.824427\nvt 0.387057 0.791421\nvt 0.489612 0.852961\nvt 0.454880 0.842174\nvt 0.372443 0.745961\nvt 0.790184 0.846942\nvt 0.804275 0.826160\nvt 0.767441 0.830465\nvt 0.778785 0.815636\nvt 0.793277 0.776446\nvt 0.802945 0.800217\nvt 0.782207 0.801087\nvt 0.783963 0.784834\nvt 0.765031 0.852934\nvt 0.753008 0.835742\nvt 0.916579 0.890839\nvt 0.906375 0.902626\nvt 0.931149 0.876153\nvt 0.946357 0.865731\nvt 0.961674 0.856842\nvt 0.882635 0.944965\nvt 0.878415 0.960004\nvt 0.890021 0.927329\nvt 0.898051 0.913022\nvt 0.935753 0.906998\nvt 0.925503 0.915352\nvt 0.948179 0.897221\nvt 0.959743 0.887787\nvt 0.972096 0.877811\nvt 0.902679 0.953737\nvt 0.899496 0.967744\nvt 0.909318 0.938128\nvt 0.916938 0.924915\nvt 0.955130 0.923146\nvt 0.946427 0.931262\nvt 0.964409 0.914442\nvt 0.973000 0.906613\nvt 0.983471 0.897503\nvt 0.924927 0.962840\nvt 0.920307 0.975606\nvt 0.931372 0.950747\nvt 0.938443 0.940114\nvt 0.969060 0.934820\nvt 0.962095 0.942838\nvt 0.976085 0.926406\nvt 0.983343 0.918686\nvt 0.993812 0.909888\nvt 0.941063 0.970371\nvt 0.934893 0.981737\nvt 0.947936 0.960553\nvt 0.954891 0.951233\nvt 0.703019 0.666118\nvt 0.666353 0.665551\nvt 0.668726 0.644916\nvt 0.704287 0.651259\nvt 0.674984 0.622071\nvt 0.707024 0.636201\nvt 0.686735 0.604048\nvt 0.714097 0.623153\nvt 0.704543 0.586720\nvt 0.726865 0.608481\nvt 0.726866 0.727280\nvt 0.705327 0.749173\nvt 0.688977 0.730470\nvt 0.715088 0.713053\nvt 0.676782 0.707091\nvt 0.708424 0.697545\nvt 0.668807 0.685520\nvt 0.704220 0.681718\nvt 0.630360 0.664243\nvt 0.634112 0.637099\nvt 0.643157 0.607284\nvt 0.660233 0.585031\nvt 0.684909 0.563621\nvt 0.686342 0.772442\nvt 0.663693 0.749262\nvt 0.645786 0.718913\nvt 0.634017 0.690518\nvn 0.9808 0.0000 0.1951\nvn 0.6935 0.6935 0.1951\nvn 0.6578 0.6578 0.3668\nvn 0.9303 0.0000 0.3668\nvn 0.5159 0.5159 0.6838\nvn 0.7296 0.0000 0.6838\nvn 0.2988 0.2988 0.9063\nvn 0.4226 0.0000 0.9063\nvn 0.0000 0.7296 0.6838\nvn 0.0000 0.4226 0.9063\nvn 0.0000 0.9303 0.3668\nvn 0.0000 0.9808 0.1951\nvn -0.6935 0.6935 0.1951\nvn -0.6578 0.6578 0.3668\nvn -0.5159 0.5159 0.6838\nvn -0.2988 0.2988 0.9063\nvn -0.7296 0.0000 0.6838\nvn -0.4226 0.0000 0.9063\nvn -0.9303 0.0000 0.3668\nvn -0.9808 0.0000 0.1951\nvn -0.6935 -0.6935 0.1951\nvn -0.6578 -0.6578 0.3668\nvn -0.5159 -0.5159 0.6838\nvn -0.2988 -0.2988 0.9063\nvn 0.0000 -0.7296 0.6839\nvn 0.0000 -0.4226 0.9063\nvn 0.0000 -0.9303 0.3668\nvn 0.0000 -0.9808 0.1951\nvn 0.6935 -0.6935 0.1951\nvn 0.6578 -0.6578 0.3668\nvn 0.5159 -0.5159 0.6839\nvn 0.2988 -0.2988 0.9063\nvn 0.0000 0.7296 0.6839\nvn 0.0000 0.0000 1.0000\nvn 0.5643 0.8040 0.1874\nvn 0.0349 0.9721 0.2318\nvn 0.0947 0.8904 0.4452\nvn 0.4281 0.8664 0.2569\nvn -0.5387 0.8154 0.2117\nvn -0.3772 0.8679 0.3231\nvn 0.1450 0.7340 0.6634\nvn 0.7758 0.5399 0.3265\nvn 0.8703 0.2401 0.4299\nvn 0.1390 0.3490 0.9267\nvn -0.6682 0.5266 0.5255\nvn -0.7534 0.1115 0.6481\nvn 0.9774 0.0400 -0.2074\nvn 0.9857 0.0764 -0.1498\nvn 0.6959 -0.0005 -0.7181\nvn 0.7255 0.0813 -0.6833\nvn -0.1401 0.0042 -0.9901\nvn -0.1604 0.1024 -0.9817\nvn -0.8955 -0.0806 -0.4376\nvn -0.9261 -0.0122 -0.3771\nvn -0.9898 -0.0967 0.1042\nvn -0.9834 -0.0490 0.1744\nvn 0.7592 0.1627 0.6302\nvn 0.9763 0.1522 0.1538\nvn -0.9769 0.1285 0.1706\nvn -0.5997 0.1582 0.7844\nvn 0.1708 0.1190 0.9781\nvn 0.9530 -0.1198 -0.2783\nvn 0.7327 -0.1711 -0.6586\nvn -0.1230 -0.2498 -0.9604\nvn -0.9050 -0.2604 -0.3363\nvn -0.9612 -0.2711 0.0510\nvn 0.7664 -0.4016 0.5014\nvn 0.9768 -0.2119 0.0309\nvn -0.9783 -0.2069 0.0067\nvn -0.6627 -0.4289 0.6138\nvn 0.1814 -0.5018 0.8457\nvn 0.0058 1.0000 0.0024\nvn 0.0173 0.9977 0.0647\nvn 0.0140 0.9991 0.0408\nvn 0.0019 1.0000 0.0008\nvn 0.0000 1.0000 0.0000\nvn -0.0224 0.9996 0.0142\nvn -0.0042 1.0000 0.0027\nvn -0.0213 0.9966 0.0796\nvn -0.0286 0.9987 0.0418\nvn -0.0590 0.9951 0.0785\nvn -0.0184 0.9995 0.0245\nvn 0.1013 0.9910 0.0873\nvn 0.0174 0.9997 0.0150\nvn 0.7189 0.0279 -0.6945\nvn 0.9626 0.0684 -0.2620\nvn 0.9571 0.0645 -0.2824\nvn 0.7201 0.0280 -0.6933\nvn -0.1395 0.0000 -0.9902\nvn -0.9302 -0.0125 -0.3669\nvn -0.9322 -0.0126 -0.3615\nvn -0.9971 -0.0141 0.0751\nvn -0.9955 -0.0159 0.0926\nvn 0.9860 -0.0695 0.1517\nvn 0.8051 -0.0640 0.5896\nvn 0.8059 -0.0642 0.5885\nvn 0.9999 0.0103 0.0087\nvn -0.6658 -0.0783 0.7420\nvn -0.9853 -0.0678 0.1570\nvn -0.9989 0.0388 -0.0261\nvn -0.6672 -0.0785 0.7407\nvn 0.1900 0.0000 0.9817\nvn 0.7158 0.0000 -0.6983\nvn 0.9327 0.0000 -0.3606\nvn 0.7170 0.0000 -0.6970\nvn -0.9310 0.0000 -0.3649\nvn -0.9332 0.0000 -0.3594\nvn -0.9881 0.0000 0.1535\nvn 0.0033 -0.9999 -0.0127\nvn 0.0023 -0.9999 -0.0089\nvn 0.0027 -0.9999 -0.0103\nvn 0.0000 -1.0000 0.0000\nvn 0.0954 0.0000 0.9954\nvn -0.0056 -0.9974 -0.0721\nvn 0.0013 -0.9999 0.0089\nvn 0.0012 -0.9999 0.0084\nvn -0.0046 -0.9981 -0.0606\nvn 0.6696 0.6973 0.2557\nvn 0.1028 0.8632 0.4942\nvn -0.6143 0.7079 0.3484\nvn 0.9873 0.1398 -0.0755\nvn -0.9995 0.0267 0.0124\nvn 0.9779 -0.1336 -0.1604\nvn -0.9660 -0.2525 -0.0552\nvn -0.0163 0.9943 0.1053\nvn -0.0438 0.9933 0.1068\nvn 0.0311 0.9941 0.1034\nvn 0.0784 0.9911 0.1075\nvn 0.9903 0.0667 -0.1216\nvn 0.9864 0.0718 -0.1479\nvn -0.9952 0.0603 -0.0766\nvn -0.9986 0.0348 -0.0406\nvn -0.0098 -0.9895 -0.1438\nvn -0.0098 -0.9895 -0.1439\nvn -0.0081 -0.9884 -0.1514\nvn 0.8285 -0.0534 0.5575\nvn 0.1751 -0.1238 0.9767\nvn -0.6633 -0.1615 0.7307\nvn 0.9980 -0.0279 -0.0564\nvn 0.7054 -0.0600 -0.7062\nvn -0.1476 -0.1158 -0.9822\nvn -0.8880 -0.1676 -0.4282\nvn -0.9642 -0.1540 0.2156\nvn 0.8321 -0.0872 0.5477\nvn 0.1824 -0.1765 0.9672\nvn -0.6357 -0.2123 0.7422\nvn 0.9988 -0.0410 -0.0260\nvn 0.7005 -0.0492 -0.7119\nvn -0.0960 -0.1009 -0.9902\nvn -0.8561 -0.1889 -0.4810\nvn -0.9500 -0.2058 0.2347\nvn 0.8354 -0.0230 0.5491\nvn 0.2129 -0.1662 0.9628\nvn -0.6209 -0.2031 0.7571\nvn 0.9959 0.0898 0.0090\nvn 0.6932 0.2000 -0.6924\nvn -0.1296 0.2023 -0.9707\nvn -0.8672 0.0488 -0.4956\nvn -0.9589 -0.1257 0.2542\nvn 0.5578 0.8200 -0.1283\nvn -0.3067 0.9382 0.1602\nvn -0.2646 0.9275 0.2640\nvn 0.3538 0.8414 -0.4085\nvn 0.4636 0.8056 0.3688\nvn -0.3696 0.9282 -0.0433\nvn 0.0360 0.7070 0.7062\nvn -0.0649 0.9775 -0.2006\nvn -0.4514 0.7279 0.5161\nvn 0.2260 0.9671 -0.1168\nvn -0.6267 0.7781 0.0426\nvn 0.2509 0.9651 0.0752\nvn -0.5250 0.8071 -0.2700\nvn 0.1836 0.9705 0.1564\nvn 0.0480 0.9255 0.3755\nvn -0.1718 0.8630 -0.4750\nvn -0.9321 0.2105 0.2948\nvn -0.6008 0.1924 0.7758\nvn -0.8687 0.3474 -0.3530\nvn -0.1809 0.5031 -0.8451\nvn 0.6545 0.4714 -0.5910\nvn 0.9164 0.3983 0.0378\nvn 0.7583 0.3540 0.5473\nvn 0.1593 0.2625 0.9517\nvn -0.9604 -0.0017 0.2784\nvn -0.6481 -0.0459 0.7602\nvn -0.9610 0.0396 0.2738\nvn -0.6532 0.0267 0.7566\nvn -0.9165 0.1694 -0.3624\nvn -0.9272 0.1982 -0.3177\nvn 0.9709 0.2383 -0.0220\nvn 0.7237 0.3410 -0.6000\nvn 0.9615 0.2732 -0.0290\nvn 0.7596 0.3717 -0.5336\nvn 0.8061 0.1566 0.5707\nvn 0.8005 0.2079 0.5620\nvn 0.1455 0.0126 0.9893\nvn 0.1321 0.1030 0.9858\nvn 0.4210 0.3782 -0.8244\nvn 0.5070 0.4330 -0.7453\nvn -0.6329 0.2678 -0.7264\nvn -0.6678 0.3027 -0.6799\nvn 0.8194 0.0042 0.5731\nvn 0.2042 -0.1556 0.9665\nvn -0.5818 -0.1815 0.7927\nvn 0.9828 0.1584 0.0945\nvn 0.7138 0.3758 -0.5909\nvn -0.1448 0.4327 -0.8898\nvn -0.9073 0.2036 -0.3680\nvn -0.9447 -0.0626 0.3218\nvn 0.5684 0.4212 -0.7067\nvn 0.8292 0.3811 -0.4088\nvn 0.6229 0.7278 -0.2867\nvn 0.4379 0.7944 -0.4209\nvn 0.6725 0.7303 -0.1198\nvn 0.9261 0.3677 -0.0848\nvn 0.6326 0.7729 0.0491\nvn 0.9099 0.3658 0.1957\nvn 0.6224 0.7252 0.2945\nvn 0.8397 0.3773 0.3904\nvn 0.4227 0.8023 0.4215\nvn 0.7024 0.4112 0.5809\nvn 0.2416 0.8125 0.5304\nvn 0.4131 0.4433 0.7955\nvn -0.0042 0.8036 0.5950\nvn 0.0177 0.4490 0.8933\nvn -0.1995 0.7997 0.5662\nvn -0.3616 0.4600 0.8110\nvn -0.4445 0.7648 0.4664\nvn -0.6326 0.4611 0.6222\nvn -0.5921 0.7521 0.2894\nvn -0.8083 0.4348 0.3969\nvn -0.6529 0.7562 0.0428\nvn -0.9061 0.3968 0.1467\nvn -0.6632 0.7307 -0.1617\nvn -0.9214 0.3656 -0.1315\nvn -0.6329 0.7220 -0.2795\nvn -0.8258 0.3753 -0.4209\nvn -0.4337 0.8022 -0.4103\nvn -0.5758 0.4194 -0.7018\nvn -0.1841 0.8154 -0.5488\nvn -0.2334 0.4557 -0.8590\nvn 0.0078 0.7958 -0.6054\nvn 0.0132 0.4587 -0.8885\nvn 0.1951 0.8080 -0.5560\nvn 0.2450 0.4549 -0.8561\nvn 0.2894 0.9051 -0.3114\nvn 0.3992 0.8986 -0.1818\nvn 0.4342 0.8998 -0.0419\nvn 0.4264 0.9007 0.0834\nvn 0.3884 0.9030 0.1838\nvn 0.3197 0.9057 0.2783\nvn 0.2018 0.9081 0.3669\nvn 0.0685 0.9092 0.4106\nvn -0.0517 0.9243 0.3781\nvn -0.1844 0.9428 0.2777\nvn -0.3140 0.9413 0.1234\nvn -0.3878 0.9213 -0.0288\nvn -0.4024 0.9057 -0.1333\nvn -0.3835 0.8993 -0.2100\nvn -0.2905 0.9044 -0.3124\nvn -0.1374 0.9128 -0.3845\nvn 0.0021 0.9184 -0.3956\nvn 0.1396 0.9140 -0.3809\nvn 0.0322 0.9994 -0.0120\nvn 0.0033 0.5454 -0.8382\nvn 0.0412 0.1898 -0.9809\nvn -0.0090 0.3287 -0.9444\nvn -0.4245 -0.9052 0.0175\nvn -0.0551 0.3208 -0.9455\nvn -0.0438 0.2972 -0.9538\nvn 0.2574 -0.6565 -0.7090\nvn 0.8623 0.0269 0.5057\nvn 0.7951 0.3225 0.5136\nvn 0.7586 0.5118 0.4032\nvn 0.8786 0.1567 0.4510\nvn 0.8159 0.2487 0.5219\nvn -0.2534 -0.8652 0.4326\nvn -0.8412 0.2313 0.4887\nvn -0.8099 0.3698 0.4553\nvn -0.7885 0.4400 0.4297\nvn -0.7905 0.3883 0.4736\nvn -0.8086 0.2466 0.5341\nvn -0.0430 -0.1536 -0.9872\nvn 0.1329 -0.6703 -0.7301\nvn -0.1098 0.4881 -0.8658\nvn 0.0657 0.5343 -0.8427\nvn 0.0789 -0.1132 -0.9904\nvn 0.0039 0.5423 -0.8402\nvn 0.0079 -0.0543 -0.9985\nvn -0.0684 0.5347 -0.8422\nvn -0.1110 -0.0825 -0.9904\nvn 0.0688 0.5040 -0.8610\nvn -0.0165 -0.1200 -0.9926\nvn -0.0827 -0.5025 -0.8606\nvn 0.8057 -0.2191 0.5503\nvn 0.6322 -0.6802 0.3709\nvn 0.7025 0.4908 0.5154\nvn 0.8809 -0.1507 0.4487\nvn 0.7418 0.5519 0.3808\nvn 0.8707 -0.0538 0.4888\nvn 0.7161 0.5662 0.4081\nvn 0.8087 -0.0862 0.5819\nvn 0.7042 0.5523 0.4462\nvn 0.8716 -0.1670 0.4609\nvn 0.7978 0.4907 0.3503\nvn 0.6875 -0.6447 0.3342\nvn -0.8472 -0.1510 0.5094\nvn -0.7342 -0.5202 0.4363\nvn -0.7088 0.5038 0.4936\nvn -0.7257 0.5610 0.3982\nvn -0.8927 -0.0851 0.4425\nvn -0.7082 0.5753 0.4092\nvn -0.8581 -0.0227 0.5130\nvn -0.7039 0.5624 0.4337\nvn -0.8097 -0.0734 0.5822\nvn -0.7851 0.5027 0.3617\nvn -0.8606 -0.0916 0.5010\nvn -0.6582 -0.5762 0.4845\nvn 0.3752 0.7432 -0.5540\nvn 0.2752 0.4698 -0.8388\nvn 0.2696 0.4489 -0.8520\nvn 0.3661 0.7237 -0.5849\nvn 0.4506 0.8723 -0.1897\nvn 0.4492 0.8709 -0.1991\nvn 0.4726 0.8720 0.1269\nvn 0.4727 0.8721 0.1265\nvn 0.4491 0.7697 0.4536\nvn 0.4515 0.7777 0.4374\nvn 0.4055 0.6255 0.6665\nvn 0.4045 0.6214 0.6710\nvn 0.3404 0.4999 0.7964\nvn 0.3471 0.5092 0.7875\nvn 0.2132 0.3815 0.8994\nvn 0.2123 0.3808 0.8999\nvn 0.0750 0.2979 0.9516\nvn 0.0760 0.2984 0.9514\nvn -0.0333 0.2791 0.9597\nvn -0.0280 0.2780 0.9601\nvn -0.2097 0.2106 0.9548\nvn -0.2110 0.2097 0.9547\nvn -0.4812 0.0034 0.8766\nvn -0.4667 0.0164 0.8842\nvn -0.6958 -0.1962 0.6909\nvn -0.6890 -0.1898 0.6994\nvn -0.8328 -0.3222 0.4501\nvn -0.8396 -0.3279 0.4331\nvn -0.8966 -0.3772 0.2318\nvn -0.8987 -0.3785 0.2212\nvn -0.9227 -0.3854 -0.0028\nvn -0.9228 -0.3852 -0.0077\nvn -0.8657 -0.3755 -0.3309\nvn -0.8532 -0.3742 -0.3632\nvn -0.6545 -0.3737 -0.6572\nvn -0.6833 -0.3721 -0.6281\nvn -0.4339 -0.3700 -0.8215\nvn -0.4162 -0.3687 -0.8311\nvn -0.1782 -0.2616 -0.9486\nvn -0.1764 -0.2602 -0.9493\nvn -0.0415 -0.1176 -0.9922\nvn -0.0414 -0.1171 -0.9922\nvn -0.0217 -0.0444 -0.9988\nvn -0.0217 -0.0443 -0.9988\nvn 0.0586 0.0589 -0.9965\nvn 0.0595 0.0598 -0.9964\nvn 0.1792 0.2177 -0.9594\nvn 0.1808 0.2205 -0.9585\nvn -0.7294 0.6838 -0.0192\nvn -0.7253 0.6884 0.0018\nvn -0.7275 0.6861 -0.0025\nvn -0.7275 0.6860 -0.0121\nvn -0.7291 0.6843 0.0056\nvn -0.7293 0.6842 0.0047\nvn -0.7222 0.6903 0.0425\nvn -0.7223 0.6904 0.0413\nvn -0.7214 0.6920 0.0241\nvn -0.7210 0.6926 0.0205\nvn -0.7217 0.6919 0.0177\nvn -0.7227 0.6908 0.0214\nvn -0.7215 0.6920 0.0214\nvn -0.7200 0.6937 0.0179\nvn -0.7136 0.7005 0.0067\nvn -0.7122 0.7019 0.0050\nvn -0.7099 0.7043 0.0022\nvn -0.7100 0.7041 0.0024\nvn -0.7091 0.7050 0.0031\nvn -0.7094 0.7048 0.0031\nvn -0.7049 0.7092 0.0029\nvn -0.7058 0.7084 0.0028\nvn -0.7004 0.7137 0.0064\nvn -0.7013 0.7128 0.0054\nvn -0.7098 0.7043 -0.0104\nvn -0.7074 0.7067 -0.0063\nvn -0.7180 0.6955 -0.0250\nvn -0.7210 0.6909 -0.0529\nvn -0.7207 0.6914 -0.0502\nvn -0.7248 0.6878 -0.0391\nvn -0.7248 0.6878 -0.0393\nvn -0.7253 0.6884 -0.0013\nvn -0.7253 0.6884 -0.0014\nvn -0.7234 0.6904 -0.0075\nvn -0.7227 0.6911 -0.0096\nvn -0.7214 0.6924 -0.0132\nvn -0.7217 0.6921 -0.0124\nvn -0.7266 0.6870 -0.0059\nvn -0.7289 0.6846 -0.0034\nvn -0.7299 0.6836 -0.0013\nvn -0.7284 0.6850 -0.0023\nvn -0.7234 0.6903 -0.0050\nvn -0.7235 0.6902 -0.0050\nvn -0.7275 0.6860 -0.0081\nvn -0.7259 0.6877 -0.0072\nvn -0.7315 0.6817 -0.0106\nvn -0.7314 0.6818 -0.0104\nvn 0.7236 -0.6902 -0.0032\nvn 0.7229 -0.6909 0.0057\nvn 0.7211 -0.6928 -0.0006\nvn 0.7295 -0.6839 0.0085\nvn 0.7258 -0.6878 0.0105\nvn 0.7257 -0.6879 0.0103\nvn 0.7237 -0.6897 -0.0227\nvn 0.7236 -0.6897 -0.0275\nvn 0.7226 -0.6895 -0.0487\nvn 0.7226 -0.6895 -0.0482\nvn 0.7207 -0.6921 -0.0394\nvn 0.7194 -0.6937 -0.0340\nvn 0.7120 -0.7020 -0.0128\nvn 0.7087 -0.7054 -0.0054\nvn 0.6996 -0.7144 0.0119\nvn 0.6983 -0.7156 0.0134\nvn 0.6951 -0.7187 0.0167\nvn 0.6951 -0.7187 0.0166\nvn 0.7041 -0.7099 0.0157\nvn 0.7024 -0.7115 0.0157\nvn 0.7296 -0.6834 0.0246\nvn 0.7233 -0.6902 0.0214\nvn 0.7175 -0.6965 0.0045\nvn 0.7247 -0.6889 0.0124\nvn 0.7061 -0.7080 -0.0058\nvn 0.7053 -0.7089 -0.0071\nvn 0.7143 -0.6996 0.0170\nvn 0.7137 -0.7003 0.0146\nvn 0.7179 -0.6955 0.0284\nvn 0.7180 -0.6954 0.0288\nvn 0.7180 -0.6959 0.0117\nvn 0.7180 -0.6959 0.0121\nvn 0.7168 -0.6972 0.0089\nvn 0.7163 -0.6977 0.0121\nvn 0.7144 -0.6993 0.0240\nvn 0.7141 -0.6995 0.0248\nvn 0.7131 -0.7005 0.0273\nvn 0.7127 -0.7009 0.0281\nvn 0.7186 -0.6951 0.0226\nvn 0.7224 -0.6912 0.0186\nvn 0.7251 -0.6885 0.0136\nvn 0.7239 -0.6897 0.0142\nvn 0.7211 -0.6926 0.0160\nvn 0.7210 -0.6927 0.0160\nvn 0.7315 -0.6815 0.0212\nvn 0.7286 -0.6846 0.0195\nvn 0.7373 -0.6751 0.0244\nvn 0.7374 -0.6749 0.0246\nvn -0.5643 0.8040 0.1874\nvn -0.4281 0.8664 0.2569\nvn -0.0947 0.8904 0.4452\nvn -0.0349 0.9721 0.2318\nvn 0.3772 0.8679 0.3231\nvn 0.5387 0.8154 0.2117\nvn -0.8703 0.2401 0.4299\nvn -0.7758 0.5399 0.3265\nvn -0.1450 0.7340 0.6634\nvn -0.1390 0.3490 0.9267\nvn 0.6682 0.5266 0.5255\nvn 0.7534 0.1115 0.6481\nvn -0.9857 0.0764 -0.1498\nvn -0.9774 0.0400 -0.2074\nvn -0.7255 0.0813 -0.6833\nvn -0.6959 -0.0005 -0.7181\nvn 0.1604 0.1024 -0.9817\nvn 0.1401 0.0042 -0.9901\nvn 0.9261 -0.0122 -0.3771\nvn 0.8955 -0.0806 -0.4376\nvn 0.9834 -0.0490 0.1744\nvn 0.9898 -0.0967 0.1042\nvn -0.9763 0.1522 0.1538\nvn -0.7592 0.1627 0.6302\nvn 0.5997 0.1582 0.7844\nvn 0.9769 0.1285 0.1706\nvn -0.1708 0.1190 0.9781\nvn -0.7327 -0.1711 -0.6586\nvn -0.9530 -0.1198 -0.2783\nvn 0.1230 -0.2498 -0.9604\nvn 0.9050 -0.2604 -0.3363\nvn 0.9612 -0.2711 0.0510\nvn -0.9768 -0.2119 0.0309\nvn -0.7664 -0.4016 0.5014\nvn 0.6627 -0.4289 0.6138\nvn 0.9783 -0.2069 0.0067\nvn -0.1814 -0.5018 0.8457\nvn -0.0058 1.0000 0.0024\nvn -0.0019 1.0000 0.0008\nvn -0.0140 0.9991 0.0408\nvn -0.0173 0.9977 0.0647\nvn 0.0224 0.9996 0.0142\nvn 0.0042 1.0000 0.0027\nvn 0.0213 0.9966 0.0796\nvn 0.0286 0.9987 0.0418\nvn 0.0590 0.9951 0.0785\nvn 0.0184 0.9995 0.0245\nvn -0.0174 0.9997 0.0150\nvn -0.1013 0.9910 0.0873\nvn -0.7189 0.0279 -0.6945\nvn -0.7201 0.0280 -0.6933\nvn -0.9571 0.0645 -0.2824\nvn -0.9626 0.0684 -0.2620\nvn 0.1395 0.0000 -0.9902\nvn 0.9302 -0.0125 -0.3669\nvn 0.9322 -0.0126 -0.3615\nvn 0.9971 -0.0141 0.0751\nvn 0.9955 -0.0159 0.0926\nvn -0.9860 -0.0695 0.1517\nvn -0.9999 0.0103 0.0087\nvn -0.8059 -0.0642 0.5885\nvn -0.8051 -0.0640 0.5896\nvn 0.6658 -0.0783 0.7420\nvn 0.6672 -0.0785 0.7407\nvn 0.9989 0.0388 -0.0261\nvn 0.9853 -0.0678 0.1570\nvn -0.1900 0.0000 0.9817\nvn -0.7158 0.0000 -0.6983\nvn -0.7170 0.0000 -0.6970\nvn -0.9327 0.0000 -0.3606\nvn 0.9310 0.0000 -0.3649\nvn 0.9332 0.0000 -0.3594\nvn 0.9881 0.0000 0.1535\nvn -0.0033 -0.9999 -0.0127\nvn -0.0027 -0.9999 -0.0103\nvn -0.0023 -0.9999 -0.0089\nvn -0.0954 0.0000 0.9954\nvn 0.0056 -0.9974 -0.0721\nvn 0.0046 -0.9981 -0.0606\nvn -0.0012 -0.9999 0.0084\nvn -0.0013 -0.9999 0.0089\nvn -0.6696 0.6973 0.2557\nvn -0.1028 0.8632 0.4942\nvn 0.6143 0.7079 0.3484\nvn -0.9873 0.1398 -0.0755\nvn 0.9995 0.0267 0.0124\nvn -0.9779 -0.1336 -0.1604\nvn 0.9660 -0.2525 -0.0552\nvn 0.0438 0.9933 0.1068\nvn 0.0163 0.9943 0.1053\nvn -0.0784 0.9911 0.1075\nvn -0.0311 0.9941 0.1034\nvn -0.9864 0.0718 -0.1479\nvn -0.9903 0.0667 -0.1216\nvn 0.9986 0.0348 -0.0406\nvn 0.9952 0.0603 -0.0766\nvn 0.0098 -0.9895 -0.1439\nvn 0.0098 -0.9895 -0.1438\nvn 0.0081 -0.9884 -0.1514\nvn -0.1751 -0.1238 0.9767\nvn -0.8285 -0.0534 0.5575\nvn 0.6633 -0.1615 0.7307\nvn -0.9980 -0.0279 -0.0564\nvn -0.7054 -0.0600 -0.7062\nvn 0.1476 -0.1158 -0.9822\nvn 0.8880 -0.1676 -0.4282\nvn 0.9642 -0.1540 0.2156\nvn -0.1824 -0.1765 0.9672\nvn -0.8321 -0.0872 0.5477\nvn 0.6357 -0.2123 0.7422\nvn -0.9988 -0.0410 -0.0260\nvn -0.7005 -0.0492 -0.7119\nvn 0.0960 -0.1009 -0.9902\nvn 0.8561 -0.1889 -0.4810\nvn 0.9500 -0.2058 0.2347\nvn -0.2129 -0.1662 0.9628\nvn -0.8354 -0.0230 0.5491\nvn 0.6209 -0.2031 0.7571\nvn -0.9959 0.0898 0.0090\nvn -0.6932 0.2000 -0.6924\nvn 0.1296 0.2023 -0.9707\nvn 0.8672 0.0488 -0.4956\nvn 0.9589 -0.1257 0.2542\nvn -0.5578 0.8200 -0.1283\nvn -0.3538 0.8414 -0.4085\nvn 0.2646 0.9275 0.2640\nvn 0.3067 0.9382 0.1602\nvn 0.3696 0.9282 -0.0433\nvn -0.4636 0.8056 0.3688\nvn 0.0649 0.9775 -0.2006\nvn -0.0360 0.7070 0.7062\nvn -0.2260 0.9671 -0.1168\nvn 0.4514 0.7279 0.5161\nvn -0.2509 0.9651 0.0752\nvn 0.6267 0.7781 0.0426\nvn -0.1836 0.9705 0.1564\nvn 0.5250 0.8071 -0.2700\nvn 0.1718 0.8630 -0.4750\nvn -0.0480 0.9255 0.3755\nvn 0.6008 0.1924 0.7758\nvn 0.9321 0.2105 0.2948\nvn 0.8687 0.3474 -0.3530\nvn 0.1809 0.5031 -0.8451\nvn -0.6545 0.4714 -0.5910\nvn -0.9164 0.3983 0.0378\nvn -0.7583 0.3540 0.5473\nvn -0.1593 0.2625 0.9517\nvn 0.6481 -0.0459 0.7602\nvn 0.9604 -0.0017 0.2784\nvn 0.6532 0.0267 0.7566\nvn 0.9610 0.0396 0.2738\nvn 0.9165 0.1694 -0.3624\nvn 0.9272 0.1982 -0.3177\nvn -0.7237 0.3410 -0.6000\nvn -0.9709 0.2383 -0.0220\nvn -0.7596 0.3717 -0.5336\nvn -0.9615 0.2732 -0.0290\nvn -0.8061 0.1566 0.5707\nvn -0.8005 0.2079 0.5620\nvn -0.1455 0.0126 0.9893\nvn -0.1321 0.1030 0.9858\nvn -0.4210 0.3782 -0.8244\nvn -0.5070 0.4330 -0.7453\nvn 0.6329 0.2678 -0.7264\nvn 0.6678 0.3027 -0.6799\nvn -0.2042 -0.1556 0.9665\nvn -0.8194 0.0042 0.5731\nvn 0.5818 -0.1815 0.7927\nvn -0.9828 0.1584 0.0945\nvn -0.7138 0.3758 -0.5909\nvn 0.1448 0.4327 -0.8898\nvn 0.9073 0.2036 -0.3680\nvn 0.9447 -0.0626 0.3218\nvn -0.6501 -0.0552 0.7578\nvn -0.6162 -0.0574 0.7855\nvn -0.9896 -0.0777 0.1207\nvn -0.9904 -0.0789 0.1137\nvn -0.8206 -0.2037 -0.5339\nvn -0.8253 -0.2023 -0.5272\nvn -0.2995 -0.2358 -0.9244\nvn -0.2933 -0.2353 -0.9266\nvn -0.1039 -0.0634 0.9926\nvn -0.1046 -0.0635 0.9925\nvn 0.0000 -0.2035 -0.9791\nvn 0.6501 -0.0552 0.7578\nvn 0.9904 -0.0789 0.1137\nvn 0.9896 -0.0777 0.1207\nvn 0.6162 -0.0574 0.7855\nvn 0.8253 -0.2023 -0.5272\nvn 0.8206 -0.2037 -0.5339\nvn 0.2933 -0.2353 -0.9266\nvn 0.2995 -0.2358 -0.9244\nvn 0.1039 -0.0634 0.9926\nvn 0.1046 -0.0635 0.9925\nvn 0.5867 0.4925 -0.6428\nvn 0.6344 0.5441 -0.5489\nvn 0.3189 0.0218 -0.9475\nvn 0.2481 -0.0747 -0.9658\nvn -0.5290 -0.8139 -0.2402\nvn -0.5352 -0.8351 -0.1272\nvn -0.2644 -0.6963 -0.6672\nvn -0.2163 -0.6168 -0.7568\nvn -0.4068 0.0869 0.9093\nvn -0.5131 -0.0359 0.8575\nvn 0.1113 0.6099 0.7845\nvn 0.2069 0.6956 0.6880\nvn -0.8217 -0.4729 0.3181\nvn -0.8436 -0.5214 0.1282\nvn -0.7674 -0.0267 0.6405\nvn -0.6554 -0.0763 0.7514\nvn 0.6539 0.7285 0.2041\nvn 0.6378 0.7485 0.1815\nvn 0.0350 0.5455 0.8374\nvn 0.0019 0.6154 0.7882\nvn 0.6195 0.7823 0.0655\nvn 0.4573 0.1525 -0.8761\nvn 0.5605 0.2968 -0.7731\nvn -0.0270 -0.1694 -0.9852\nvn -0.1560 -0.4219 -0.8931\nvn -0.6250 -0.5332 -0.5701\nvn -0.6080 -0.6690 -0.4275\nvn -0.7595 -0.6503 0.0143\nvn -0.7697 -0.6314 0.0938\nvn -0.7246 -0.6873 -0.0500\nvn -0.7741 -0.6307 -0.0551\nvn -0.6974 -0.6462 0.3099\nvn -0.6403 -0.7607 0.1061\nvn -0.7103 -0.4686 0.5253\nvn -0.6522 -0.6209 0.4348\nvn -0.6191 -0.7507 0.2304\nvn 0.7121 0.5679 -0.4128\nvn 0.7577 0.5658 -0.3251\nvn -0.6536 -0.4130 0.6343\nvn 0.6214 0.7812 -0.0602\nvn -0.5867 0.4925 -0.6428\nvn -0.2481 -0.0747 -0.9658\nvn -0.3189 0.0218 -0.9475\nvn -0.6344 0.5441 -0.5489\nvn 0.5291 -0.8140 -0.2398\nvn 0.2163 -0.6169 -0.7567\nvn 0.2644 -0.6964 -0.6671\nvn 0.5351 -0.8352 -0.1268\nvn 0.4069 0.0867 0.9093\nvn -0.2069 0.6956 0.6880\nvn -0.1113 0.6099 0.7846\nvn 0.5132 -0.0362 0.8574\nvn 0.8217 -0.4729 0.3179\nvn 0.6556 -0.0766 0.7512\nvn 0.7675 -0.0268 0.6404\nvn 0.8437 -0.5213 0.1281\nvn -0.6539 0.7285 0.2041\nvn -0.0018 0.6154 0.7882\nvn -0.0350 0.5455 0.8374\nvn -0.6378 0.7485 0.1815\nvn -0.6195 0.7823 0.0655\nvn -0.4573 0.1525 -0.8761\nvn 0.1560 -0.4221 -0.8930\nvn 0.0272 -0.1697 -0.9851\nvn -0.5604 0.2969 -0.7731\nvn 0.6079 -0.6692 -0.4272\nvn 0.6247 -0.5335 -0.5702\nvn 0.7595 -0.6503 0.0144\nvn 0.7739 -0.6308 -0.0553\nvn 0.7247 -0.6873 -0.0499\nvn 0.7699 -0.6312 0.0938\nvn 0.6403 -0.7607 0.1062\nvn 0.6973 -0.6463 0.3099\nvn 0.7102 -0.4689 0.5251\nvn 0.6188 -0.7509 0.2306\nvn 0.6519 -0.6212 0.4348\nvn -0.7121 0.5679 -0.4128\nvn -0.7577 0.5658 -0.3251\nvn 0.6534 -0.4133 0.6342\nvn -0.6214 0.7812 -0.0602\nvn 0.0000 0.5914 0.8064\nvn 0.0316 0.5911 0.8060\nvn 0.0308 0.4554 0.8897\nvn 0.0000 0.4364 0.8997\nvn -0.0308 0.4554 0.8897\nvn -0.0316 0.5911 0.8060\nvn 0.0053 0.2001 0.9797\nvn 0.0000 0.1821 0.9832\nvn -0.0053 0.2001 0.9797\nvn -0.0293 0.0845 0.9960\nvn 0.0000 0.0840 0.9965\nvn 0.0293 0.0845 0.9960\nvn -0.0337 0.0768 0.9965\nvn 0.0000 0.0768 0.9970\nvn 0.0337 0.0768 0.9965\nvn -0.2639 -0.9229 0.2804\nvn -0.5502 -0.7853 0.2838\nvn -0.5543 -0.8025 0.2206\nvn -0.2597 -0.9390 0.2256\nvn -0.6224 -0.7773 0.0913\nvn -0.8156 -0.2854 0.5033\nvn -0.4764 -0.8671 0.1454\nvn -0.2137 -0.9079 0.3605\nvn -0.2650 -0.9136 0.3082\nvn -0.5307 -0.8193 0.2169\nvn -0.3614 -0.4355 0.8245\nvn 0.6115 0.6158 0.4967\nvn 0.9253 0.3726 0.0707\nvn -0.3077 -0.8602 -0.4066\nvn -0.5429 -0.2364 0.8058\nvn -0.4774 -0.7141 -0.5120\nvn 0.5385 0.6378 0.5506\nvn -0.5263 -0.2101 0.8239\nvn 0.5362 0.6596 0.5267\nvn -0.5197 -0.3168 0.7934\nvn -0.3943 -0.4563 0.7976\nvn -0.4687 -0.7263 -0.5027\nvn 0.6151 0.1681 -0.7703\nvn 0.6056 0.1588 -0.7797\nvn 0.6847 0.2040 -0.6996\nvn 0.9241 0.3363 -0.1812\nvn -0.2064 -0.9190 -0.3359\nvn -0.3505 -0.8126 -0.4655\nvn -0.6384 -0.1511 0.7546\nvn -0.6010 -0.2153 0.7696\nvn -0.3800 -0.8005 -0.4635\nvn -0.6182 -0.1377 0.7738\nvn 0.8422 0.0462 -0.5371\nvn 0.7189 0.0910 -0.6891\nvn 0.6671 0.1120 -0.7365\nvn 0.6074 0.4649 0.6440\nvn 0.9569 0.1108 0.2682\nvn 0.1561 -0.9810 -0.1152\nvn 0.4081 0.5417 0.7348\nvn 0.7953 0.6057 0.0260\nvn 0.7091 0.6996 0.0873\nvn -0.1219 0.7312 0.6711\nvn 0.0861 0.7418 0.6650\nvn -0.8705 -0.2709 0.4108\nvn -0.1397 0.7839 0.6049\nvn 0.9502 0.2931 -0.1055\nvn -0.8876 -0.3498 0.2996\nvn 0.6767 0.7203 0.1526\nvn 0.2139 -0.8716 -0.4410\nvn 0.8944 0.3965 -0.2070\nvn 0.2764 -0.8589 -0.4311\nvn 0.8778 0.4236 -0.2235\nvn 0.3988 -0.8937 -0.2055\nvn 0.8715 0.3520 0.3414\nvn 0.5563 -0.7154 0.4228\nvn 0.4584 0.6602 0.5950\nvn 0.4427 0.7237 0.5294\nvn 0.6466 0.1100 -0.7548\nvn -0.3852 -0.8051 -0.4510\nvn 0.4280 0.7653 0.4806\nvn 0.4386 0.7680 0.4667\nvn -0.3182 -0.8583 -0.4025\nvn 0.7022 0.0591 -0.7095\nvn -0.5268 -0.1932 0.8277\nvn -0.5936 -0.1400 0.7924\nvn 0.6956 0.4361 -0.5709\nvn 0.7708 0.4104 -0.4872\nvn 0.3933 0.0489 -0.9181\nvn 0.3017 0.0102 -0.9533\nvn 0.3482 0.2615 -0.9002\nvn 0.7546 0.4969 0.4285\nvn 0.7400 0.4853 0.4657\nvn 0.3758 0.2801 -0.8833\nvn -0.3462 -0.2911 0.8918\nvn -0.3428 -0.3327 0.8785\nvn 0.4376 0.1417 -0.8879\nvn -0.4476 -0.7287 -0.5182\nvn -0.3621 -0.7150 -0.5980\nvn 0.3710 0.2472 -0.8951\nvn -0.6496 -0.6094 -0.4545\nvn -0.7330 -0.5146 -0.4448\nvn -0.6915 -0.5415 -0.4781\nvn -0.5542 -0.6909 -0.4642\nvn 0.4561 0.2024 -0.8666\nvn 0.4927 0.2234 -0.8410\nvn -0.5438 -0.6721 -0.5025\nvn 0.4856 0.2306 -0.8432\nvn -0.3897 -0.8233 -0.4127\nvn 0.5302 0.2436 -0.8121\nvn -0.5614 -0.6305 -0.5359\nvn 0.6357 0.6197 0.4602\nvn 0.6257 0.5990 0.4997\nvn -0.3981 -0.2807 0.8733\nvn -0.4138 -0.2854 0.8644\nvn -0.2250 -0.4896 0.8424\nvn 0.7202 0.5854 0.3723\nvn 0.6359 0.5551 0.5361\nvn 0.6458 0.5111 0.5672\nvn 0.5900 0.1849 -0.7859\nvn -0.4275 -0.2898 0.8563\nvn -0.4019 -0.3358 0.8519\nvn 0.8694 0.2119 -0.4463\nvn -0.5067 -0.6875 -0.5202\nvn 0.0260 -0.6685 0.7432\nvn -0.0347 -0.9581 -0.2843\nvn 0.8344 -0.0024 0.5511\nvn 0.9019 0.3363 0.2710\nvn -0.2236 -0.5208 0.8238\nvn -0.2998 0.8627 0.4071\nvn -0.2232 0.8304 0.5104\nvn -0.8625 -0.4081 0.2992\nvn -0.7765 -0.4543 0.4365\nvn -0.0961 0.6693 0.7367\nvn -0.3810 -0.3719 0.8465\nvn 0.7649 0.4133 0.4940\nvn 0.9079 -0.2552 0.3326\nvn 0.6127 -0.2835 -0.7377\nvn 0.4411 0.2565 -0.8600\nvn 0.0121 -0.7999 0.6000\nvn -0.2419 -0.8693 -0.4310\nvn -0.3346 -0.3858 0.8597\nvn -0.3572 -0.3238 0.8761\nvn -0.6850 -0.5326 -0.4971\nvn -0.6569 -0.5612 -0.5034\nvn 0.7223 0.4910 0.4869\nvn 0.8177 -0.2963 -0.4935\nvn 0.7985 0.0944 0.5945\nvn 0.5864 0.6405 0.4959\nvn -0.5138 -0.1709 0.8407\nvn -0.0509 -0.5529 0.8317\nvn -0.5367 -0.1576 0.8289\nvn 0.5527 0.6792 0.4830\nvn -0.0105 -0.9829 -0.1836\nvn 0.8447 -0.3419 -0.4117\nvn -0.4639 -0.7522 -0.4679\nvn 0.6364 0.0752 -0.7677\nvn -0.4995 -0.7101 -0.4963\nvn 0.5879 0.1422 -0.7963\nvn 0.1442 -0.9864 -0.0787\nvn 0.9043 -0.3073 -0.2962\nvn -0.0049 -0.5033 0.8641\nvn 0.7508 0.2203 0.6226\nvn 0.4887 0.7214 0.4907\nvn 0.1530 -0.0386 -0.9875\nvn -0.5243 -0.5828 -0.6207\nvn -0.6027 -0.7866 0.1343\nvn -0.6928 -0.7179 0.0685\nvn -0.6119 -0.7715 0.1744\nvn -0.5510 -0.7897 0.2697\nvn 0.8391 0.4838 -0.2485\nvn 0.7673 0.5777 -0.2782\nvn 0.6330 0.3985 -0.6636\nvn 0.7683 0.5821 -0.2662\nvn -0.4094 -0.6659 -0.6236\nvn 0.4010 0.2994 -0.8657\nvn 0.4268 0.7456 0.5117\nvn 0.2639 -0.9228 0.2804\nvn 0.2598 -0.9389 0.2255\nvn 0.5544 -0.8024 0.2207\nvn 0.5506 -0.7848 0.2842\nvn 0.8152 -0.2862 0.5034\nvn 0.6218 -0.7778 0.0916\nvn 0.2129 -0.9077 0.3616\nvn 0.4767 -0.8668 0.1462\nvn 0.2655 -0.9135 0.3081\nvn 0.5317 -0.8186 0.2171\nvn -0.9253 0.3724 0.0711\nvn -0.6123 0.6149 0.4969\nvn 0.3612 -0.4361 0.8242\nvn 0.3077 -0.8603 -0.4064\nvn 0.5428 -0.2366 0.8058\nvn 0.4769 -0.7141 -0.5123\nvn -0.5385 0.6377 0.5507\nvn -0.5362 0.6595 0.5267\nvn 0.5264 -0.2105 0.8237\nvn 0.3954 -0.4557 0.7974\nvn 0.5199 -0.3167 0.7933\nvn 0.4686 -0.7263 -0.5028\nvn -0.6057 0.1588 -0.7797\nvn -0.6152 0.1679 -0.7703\nvn -0.6851 0.2033 -0.6995\nvn -0.9241 0.3362 -0.1818\nvn 0.2062 -0.9192 -0.3355\nvn 0.6011 -0.2154 0.7696\nvn 0.6386 -0.1517 0.7544\nvn 0.3498 -0.8126 -0.4661\nvn 0.6185 -0.1382 0.7735\nvn 0.3794 -0.8003 -0.4642\nvn -0.7190 0.0909 -0.6890\nvn -0.8430 0.0452 -0.5359\nvn -0.6671 0.1121 -0.7365\nvn -0.1583 -0.9808 -0.1137\nvn -0.9562 0.1124 0.2702\nvn -0.6069 0.4659 0.6439\nvn -0.4079 0.5414 0.7351\nvn -0.7093 0.6995 0.0865\nvn -0.7952 0.6059 0.0254\nvn 0.1229 0.7307 0.6715\nvn -0.0854 0.7421 0.6648\nvn 0.8695 -0.2719 0.4122\nvn -0.9511 0.2907 -0.1040\nvn 0.1302 0.7873 0.6027\nvn 0.8883 -0.3482 0.2993\nvn -0.6765 0.7206 0.1521\nvn -0.2160 -0.8713 -0.4405\nvn -0.2756 -0.8593 -0.4307\nvn -0.8955 0.3946 -0.2054\nvn -0.3988 -0.8936 -0.2057\nvn -0.8779 0.4227 -0.2249\nvn -0.5557 -0.7164 0.4218\nvn -0.8726 0.3504 0.3404\nvn -0.4432 0.7231 0.5298\nvn -0.4579 0.6601 0.5954\nvn 0.3848 -0.8050 -0.4515\nvn -0.6464 0.1101 -0.7550\nvn -0.4385 0.7681 0.4666\nvn -0.4281 0.7654 0.4805\nvn 0.3176 -0.8581 -0.4034\nvn -0.7007 0.0593 -0.7109\nvn 0.5939 -0.1405 0.7922\nvn 0.5270 -0.1937 0.8274\nvn -0.6956 0.4361 -0.5708\nvn -0.3016 0.0102 -0.9533\nvn -0.3933 0.0490 -0.9181\nvn -0.7708 0.4105 -0.4872\nvn -0.3479 0.2611 -0.9004\nvn -0.3755 0.2799 -0.8835\nvn -0.7402 0.4855 0.4652\nvn -0.7548 0.4970 0.4281\nvn 0.3421 -0.3322 0.8789\nvn 0.3449 -0.2912 0.8923\nvn 0.3622 -0.7150 -0.5979\nvn 0.4474 -0.7287 -0.5184\nvn -0.4377 0.1419 -0.8878\nvn 0.6492 -0.6101 -0.4541\nvn -0.3709 0.2469 -0.8952\nvn 0.7325 -0.5158 -0.4442\nvn 0.6916 -0.5416 -0.4778\nvn 0.5539 -0.6911 -0.4642\nvn 0.5438 -0.6719 -0.5027\nvn -0.4926 0.2233 -0.8411\nvn -0.4561 0.2022 -0.8666\nvn -0.4856 0.2307 -0.8432\nvn 0.3897 -0.8233 -0.4126\nvn 0.5614 -0.6305 -0.5360\nvn -0.5299 0.2437 -0.8123\nvn -0.6257 0.5990 0.4997\nvn -0.6358 0.6196 0.4602\nvn 0.4139 -0.2854 0.8644\nvn 0.3979 -0.2809 0.8733\nvn -0.7202 0.5854 0.3722\nvn 0.2251 -0.4899 0.8422\nvn -0.6357 0.5553 0.5361\nvn -0.5889 0.1855 -0.7866\nvn -0.6452 0.5120 0.5670\nvn 0.4025 -0.3360 0.8515\nvn 0.4278 -0.2901 0.8560\nvn -0.8694 0.2122 -0.4462\nvn 0.5063 -0.6872 -0.5209\nvn -0.0243 -0.6686 0.7432\nvn 0.0365 -0.9579 -0.2848\nvn -0.8351 -0.0030 0.5500\nvn 0.2230 -0.5209 0.8239\nvn -0.9019 0.3365 0.2708\nvn 0.2965 0.8639 0.4070\nvn 0.7761 -0.4523 0.4394\nvn 0.8639 -0.4058 0.2982\nvn 0.2248 0.8299 0.5106\nvn 0.0866 0.6696 0.7376\nvn 0.3829 -0.3690 0.8468\nvn -0.7647 0.4137 0.4939\nvn -0.4406 0.2567 -0.8602\nvn -0.6118 -0.2834 -0.7385\nvn -0.9082 -0.2551 0.3317\nvn 0.2425 -0.8691 -0.4310\nvn -0.0120 -0.7999 0.6000\nvn 0.3356 -0.3854 0.8596\nvn 0.6579 -0.5605 -0.5029\nvn 0.6861 -0.5315 -0.4967\nvn 0.3569 -0.3221 0.8768\nvn -0.7226 0.4910 0.4865\nvn -0.8172 -0.2966 -0.4942\nvn -0.7996 0.0939 0.5932\nvn 0.0532 -0.5525 0.8318\nvn 0.5142 -0.1712 0.8404\nvn -0.5850 0.6419 0.4957\nvn 0.5370 -0.1578 0.8287\nvn -0.5525 0.6792 0.4831\nvn 0.0115 -0.9828 -0.1844\nvn -0.8449 -0.3413 -0.4118\nvn 0.4641 -0.7520 -0.4680\nvn -0.6347 0.0760 -0.7690\nvn -0.5879 0.1423 -0.7963\nvn 0.4989 -0.7098 -0.4972\nvn -0.1428 -0.9865 -0.0794\nvn -0.9038 -0.3078 -0.2973\nvn 0.0068 -0.5033 0.8640\nvn -0.7525 0.2197 0.6209\nvn -0.4883 0.7223 0.4897\nvn 0.5241 -0.5827 -0.6211\nvn -0.1529 -0.0387 -0.9875\nvn 0.6023 -0.7867 0.1349\nvn 0.6928 -0.7179 0.0681\nvn 0.5510 -0.7895 0.2705\nvn 0.6120 -0.7714 0.1743\nvn -0.8389 0.4840 -0.2489\nvn -0.6330 0.3985 -0.6636\nvn -0.7673 0.5777 -0.2782\nvn -0.7683 0.5821 -0.2662\nvn 0.4094 -0.6656 -0.6239\nvn -0.4008 0.2993 -0.8659\nvn -0.4268 0.7455 0.5118\nvn 0.7582 0.2197 0.6139\nvn 0.7683 0.2270 0.5985\nvn 0.7289 0.3504 0.5882\nvn 0.6271 0.4322 0.6480\nvn 0.2808 -0.7982 0.5330\nvn 0.2296 -0.3707 0.8999\nvn 0.3005 -0.3598 0.8833\nvn 0.5432 -0.6083 0.5787\nvn 0.2564 -0.7570 0.6009\nvn 0.4443 -0.2793 0.8512\nvn 0.3707 -0.6150 0.6959\nvn 0.6432 -0.2588 0.7206\nvn 0.4973 -0.4428 0.7460\nvn 0.7764 -0.2221 0.5898\nvn 0.6035 -0.1940 0.7734\nvn 0.8323 -0.1104 0.5431\nvn 0.4249 0.2239 0.8771\nvn 0.6032 0.1015 0.7911\nvn 0.7829 0.1171 0.6110\nvn 0.5765 0.2389 0.7814\nvn 0.3123 0.2911 0.9043\nvn 0.4449 0.1368 0.8850\nvn 0.4106 0.4375 0.8000\nvn 0.5586 0.3086 0.7699\nvn 0.5984 0.3789 0.7059\nvn 0.6377 0.4165 0.6480\nvn -0.0012 0.4888 0.8724\nvn -0.0018 0.5252 0.8509\nvn 0.6285 0.4140 0.6585\nvn 0.7646 0.3588 0.5354\nvn -0.0019 0.5069 0.8620\nvn 0.5952 0.4053 0.6939\nvn 0.7822 0.3161 0.5369\nvn -0.0000 0.4783 0.8782\nvn 0.5764 -0.0305 0.8166\nvn 0.6335 -0.0656 0.7709\nvn 0.6856 -0.1623 0.7096\nvn 0.5863 -0.1444 0.7971\nvn 0.7228 0.2496 0.6443\nvn 0.5743 0.0604 0.8164\nvn 0.8457 0.1133 0.5214\nvn 0.6987 -0.1054 0.7076\nvn 0.5543 -0.0240 0.8320\nvn 0.5129 -0.0626 0.8561\nvn 0.2505 0.2151 0.9439\nvn 0.1112 0.0934 0.9894\nvn 0.5249 0.2817 0.8032\nvn 0.7906 0.2004 0.5786\nvn 0.9182 0.0382 0.3941\nvn 0.1079 0.0482 0.9930\nvn 0.1982 -0.1280 0.9717\nvn -0.0016 0.0508 0.9987\nvn -0.0029 -0.0709 0.9975\nvn 0.6447 0.0479 0.7629\nvn 0.5792 0.0915 0.8100\nvn 0.6718 0.0253 0.7403\nvn 0.7821 -0.0792 0.6181\nvn 0.4368 -0.0073 0.8995\nvn 0.0018 -0.0942 0.9955\nvn -0.0003 0.3992 0.9168\nvn 0.4355 0.3392 0.8338\nvn 0.6934 -0.1085 0.7123\nvn 0.6929 -0.0406 0.7198\nvn 0.8389 -0.1663 0.5182\nvn 0.8260 -0.1741 0.5361\nvn 0.7719 -0.1557 0.6163\nvn 0.6703 -0.1058 0.7344\nvn 0.3185 0.2238 0.9211\nvn 0.4400 -0.0919 0.8932\nvn 0.4340 -0.5919 0.6791\nvn 0.7140 -0.4843 0.5055\nvn 0.8408 -0.4059 0.3582\nvn 0.4530 -0.8890 0.0665\nvn 0.5708 -0.8210 0.0130\nvn 0.5992 -0.8000 0.0306\nvn 0.4465 -0.8902 0.0908\nvn 0.2340 -0.9614 0.1449\nvn 0.2551 -0.9553 0.1496\nvn 0.8333 0.0587 0.5496\nvn 0.6356 0.1060 0.7647\nvn 0.6740 -0.1415 0.7250\nvn 0.7595 -0.1903 0.6220\nvn 0.4297 0.0634 0.9008\nvn -0.0018 0.1940 0.9810\nvn 0.0011 0.1267 0.9919\nvn 0.4440 0.1291 0.8867\nvn 0.2810 0.8336 0.4756\nvn 0.0007 0.8745 0.4850\nvn 0.2239 -0.1651 0.9605\nvn 0.4341 0.5425 0.7192\nvn 0.5389 0.0443 0.8412\nvn 0.4574 -0.1100 0.8824\nvn 0.5737 -0.1514 0.8049\nvn 0.6222 -0.0359 0.7820\nvn 0.4025 -0.3061 0.8627\nvn 0.0014 -0.3820 0.9242\nvn 0.6379 0.1158 0.7613\nvn 0.5120 0.6898 0.5118\nvn 0.1267 -0.7389 0.6618\nvn 0.4449 -0.2964 0.8451\nvn 0.5599 -0.3430 0.7542\nvn 0.2820 -0.7669 0.5764\nvn 0.1836 -0.8027 0.5674\nvn 0.3493 -0.4081 0.8435\nvn 0.0005 -0.4352 0.9003\nvn 0.0002 -0.8286 0.5598\nvn 0.2763 -0.7842 0.5555\nvn 0.5560 -0.3713 0.7436\nvn 0.5989 -0.1033 0.7941\nvn 0.6156 -0.0436 0.7869\nvn 0.6388 -0.0931 0.7637\nvn 0.6008 -0.1194 0.7904\nvn 0.6345 0.1051 0.7657\nvn 0.5628 0.5589 0.6089\nvn 0.5777 0.0794 0.8123\nvn 0.3768 0.0939 0.9215\nvn 0.4074 0.2368 0.8820\nvn 0.6372 0.0876 0.7656\nvn 0.0008 0.0973 0.9952\nvn 0.4223 0.0628 0.9042\nvn -0.0011 0.2424 0.9702\nvn 0.6432 0.0412 0.7646\nvn -0.0016 0.0902 0.9959\nvn 0.8080 -0.2907 0.5124\nvn 0.6423 -0.4734 0.6028\nvn 0.8296 -0.0756 0.5532\nvn 0.4301 -0.2327 0.8723\nvn 0.5863 -0.1966 0.7859\nvn 0.5485 0.0652 0.8336\nvn 0.4421 -0.2856 0.8502\nvn 0.7363 -0.3884 0.5541\nvn -0.0065 -0.6837 0.7297\nvn -0.0013 0.0307 0.9995\nvn 0.6607 -0.0385 0.7496\nvn 0.5651 -0.0099 0.8250\nvn 0.3843 -0.1113 0.9164\nvn 0.0025 -0.2507 0.9680\nvn 0.0001 0.2142 0.9768\nvn 0.0003 -0.1929 0.9812\nvn 0.0005 -0.6541 0.7564\nvn 0.0002 -0.9863 0.1646\nvn -0.0004 -0.9821 0.1881\nvn 0.9288 -0.3647 0.0655\nvn 0.9420 -0.3108 0.1264\nvn 0.9825 -0.0652 0.1742\nvn 0.9540 -0.2391 0.1807\nvn 0.6856 -0.7273 0.0310\nvn 0.6975 -0.7165 -0.0093\nvn 0.7934 -0.6047 0.0697\nvn 0.7778 -0.6280 -0.0254\nvn 0.8989 -0.4382 -0.0031\nvn 0.8698 -0.4851 -0.0900\nvn 0.8912 -0.3417 0.2982\nvn 0.5824 -0.0899 0.8079\nvn 0.8924 -0.2492 0.3761\nvn 0.8615 -0.2203 0.4574\nvn 0.8052 -0.2293 0.5468\nvn 0.7174 -0.2016 0.6669\nvn 0.9355 -0.0226 0.3527\nvn 0.8004 -0.0605 0.5963\nvn 0.9296 -0.0194 0.3680\nvn 0.9355 0.0184 0.3527\nvn 0.8829 -0.1617 0.4407\nvn 0.9070 -0.2055 0.3676\nvn 0.9589 -0.1118 0.2609\nvn 0.9661 -0.1306 0.2226\nvn 0.9134 -0.2235 0.3400\nvn 0.9673 -0.1345 0.2147\nvn -0.0077 -0.9073 0.4205\nvn 0.5738 -0.7885 0.2210\nvn 0.6131 -0.5511 0.5659\nvn -0.0053 -0.5700 0.8216\nvn 0.6828 -0.7227 -0.1070\nvn 0.6583 -0.7222 0.2121\nvn 0.3005 -0.9102 0.2849\nvn -0.0864 -0.8240 0.5599\nvn 0.0508 -0.1701 0.9841\nvn 0.4794 -0.8242 -0.3014\nvn 0.3572 -0.9297 -0.0893\nvn -0.1660 -0.9697 -0.1791\nvn 0.1188 -0.9566 -0.2658\nvn 0.9348 0.0031 0.3550\nvn 0.9108 -0.4120 -0.0250\nvn 0.6838 -0.7113 0.1628\nvn 0.6259 0.2243 0.7469\nvn 0.8694 -0.4674 0.1600\nvn 0.8431 -0.5220 -0.1293\nvn 0.1321 -0.7737 0.6196\nvn 0.7475 -0.3038 0.5907\nvn -0.4339 -0.8863 0.1620\nvn -0.0095 -0.9611 0.2760\nvn 0.4239 0.5181 0.7428\nvn 0.6842 0.2856 0.6710\nvn 0.4343 -0.5007 0.7488\nvn 0.1718 -0.6532 0.7374\nvn 0.1506 -0.6232 0.7674\nvn 0.3354 -0.4684 0.8173\nvn 0.4507 -0.3902 0.8028\nvn 0.4709 -0.2305 0.8515\nvn 0.4901 0.2327 0.8400\nvn 0.3445 0.2478 0.9055\nvn 0.2073 0.3290 0.9213\nvn 0.2049 0.4727 0.8570\nvn 0.5107 0.0732 0.8566\nvn 0.6915 -0.1222 0.7119\nvn 0.0887 0.8676 0.4893\nvn 0.1953 0.8434 0.5004\nvn 0.2855 0.7825 0.5534\nvn 0.6216 0.4313 0.6539\nvn 0.6761 -0.2232 0.7021\nvn 0.4150 -0.6373 0.6492\nvn 0.2639 -0.7886 0.5554\nvn 0.1015 -0.8883 0.4479\nvn 0.0315 -0.8718 0.4888\nvn -0.0269 -0.7701 0.6373\nvn -0.0586 -0.4636 0.8841\nvn -0.0179 0.1928 0.9810\nvn -0.0081 0.7203 0.6935\nvn -0.0016 0.8168 0.5769\nvn 0.5870 -0.7756 0.2319\nvn 0.4189 -0.9076 -0.0290\nvn 0.0971 -0.9887 -0.1137\nvn -0.2621 -0.9584 -0.1130\nvn -0.5486 -0.8301 0.0992\nvn -0.7003 -0.4384 0.5633\nvn -0.5495 0.8342 0.0453\nvn -0.3517 0.9142 -0.2010\nvn -0.0664 0.9420 -0.3288\nvn 0.2329 0.9147 -0.3301\nvn 0.4197 0.9076 0.0074\nvn -0.7428 0.3618 0.5633\nvn 0.6023 -0.6748 0.4264\nvn 0.4478 -0.8006 0.3981\nvn 0.1610 -0.8795 0.4477\nvn -0.1231 -0.8754 0.4674\nvn -0.3728 -0.7714 0.5156\nvn -0.4050 -0.3814 0.8310\nvn -0.4878 0.7882 0.3751\nvn -0.2984 0.8819 0.3650\nvn -0.0045 0.9598 0.2805\nvn 0.3976 0.9027 0.1640\nvn 0.6170 0.7787 0.1132\nvn -0.4851 0.2970 0.8224\nvn 0.5795 -0.3055 0.7555\nvn 0.4514 -0.3508 0.8205\nvn -0.0685 0.2510 0.9655\nvn 0.1788 -0.3938 0.9016\nvn -0.0384 -0.3904 0.9198\nvn 0.1355 0.2574 0.9567\nvn 0.4353 0.2475 0.8656\nvn 0.6648 0.3251 0.6726\nvn 0.4090 0.3750 0.8319\nvn 0.2337 0.3243 0.9166\nvn 0.6208 0.3724 0.6898\nvn 0.8311 0.2465 0.4984\nvn 0.9360 0.0803 0.3425\nvn 0.1045 0.2871 0.9522\nvn -0.0005 0.2781 0.9605\nvn 0.9784 -0.0448 0.2019\nvn 0.9702 -0.0006 0.2425\nvn 0.9619 0.0069 0.2731\nvn 0.9825 -0.0949 0.1599\nvn 0.9854 -0.1102 0.1298\nvn 0.9915 -0.0888 0.0943\nvn 0.9880 -0.1543 -0.0030\nvn 0.9150 -0.1765 0.3627\nvn 0.9299 -0.0402 0.3656\nvn 0.9738 0.0487 0.2221\nvn 0.8012 -0.2927 0.5219\nvn 0.9819 -0.0320 0.1866\nvn 0.8314 -0.0290 0.5549\nvn 0.6979 0.0679 0.7129\nvn 0.9319 0.0355 0.3608\nvn -0.2330 0.7887 0.5688\nvn -0.1733 0.8108 0.5591\nvn -0.0406 0.7854 0.6177\nvn -0.1187 0.7190 0.6848\nvn 0.2815 0.5347 0.7967\nvn 0.1209 0.4666 0.8761\nvn 0.6155 -0.0837 0.7837\nvn 0.4122 -0.1155 0.9037\nvn 0.2281 0.0598 0.9718\nvn 0.5482 -0.1726 0.8183\nvn 0.6457 -0.3360 0.6856\nvn 0.7675 -0.5440 0.3392\nvn 0.7195 -0.3168 0.6179\nvn 0.6938 -0.6651 0.2762\nvn 0.7239 -0.4323 0.5376\nvn 0.3470 -0.8618 0.3699\nvn -0.0003 -0.9059 0.4235\nvn 0.3786 -0.6455 0.6633\nvn -0.0023 -0.7054 0.7088\nvn 0.5796 -0.7589 0.2967\nvn 0.6210 -0.5266 0.5805\nvn 0.3775 0.0646 0.9237\nvn 0.1235 0.2131 0.9692\nvn 0.3949 0.2199 0.8920\nvn 0.5367 0.1600 0.8285\nvn -0.1141 0.4584 0.8814\nvn -0.1984 0.6807 0.7052\nvn -0.2395 0.7730 0.5874\nvn 0.4125 -0.2410 0.8785\nvn 0.5680 -0.4289 0.7024\nvn -0.0469 0.8608 0.5068\nvn 0.0623 0.8558 0.5135\nvn 0.3302 0.6493 0.6851\nvn 0.9360 0.3086 0.1692\nvn 0.8394 0.2729 0.4700\nvn 0.9763 0.0412 -0.2122\nvn 0.9982 -0.0604 -0.0012\nvn 0.9929 -0.0176 -0.1176\nvn 0.9183 -0.0115 -0.3956\nvn 0.2800 -0.3257 0.9030\nvn 0.3546 -0.4979 0.7914\nvn -0.0019 -0.5312 0.8472\nvn -0.0009 -0.3603 0.9328\nvn 0.8511 0.0940 0.5165\nvn 0.7298 0.1426 0.6686\nvn 0.7349 0.2952 0.6105\nvn 0.7986 0.2608 0.5423\nvn -0.1494 0.1932 0.9697\nvn -0.0002 0.3187 0.9478\nvn -0.0002 0.5611 0.8277\nvn -0.2356 0.4376 0.8677\nvn -0.2796 0.4599 0.8427\nvn -0.1245 0.2579 0.9581\nvn -0.0468 0.1417 0.9888\nvn -0.0344 0.2994 0.9535\nvn 0.2831 0.2507 0.9257\nvn 0.5556 0.2141 0.8034\nvn 0.5726 0.3702 0.7314\nvn 0.9876 -0.0668 0.1421\nvn 0.9851 -0.0138 0.1712\nvn 0.9778 0.0101 0.2091\nvn 0.9519 0.0619 0.2999\nvn 0.9716 -0.2317 0.0472\nvn 0.9615 -0.2031 -0.1852\nvn 0.9527 -0.2558 -0.1638\nvn 0.9453 -0.1385 -0.2951\nvn 0.9861 -0.0878 0.1412\nvn 0.8603 0.2256 0.4572\nvn 0.1733 0.4817 0.8590\nvn -0.0003 0.4824 0.8759\nvn 0.6938 0.3904 0.6052\nvn 0.5276 0.4577 0.7156\nvn 0.3629 0.4737 0.8024\nvn 0.9733 0.0722 0.2177\nvn 0.8965 0.2665 0.3538\nvn 0.7335 0.4720 0.4891\nvn 0.5754 0.5840 0.5726\nvn 0.4160 0.6532 0.6326\nvn 0.2203 0.6996 0.6797\nvn -0.0003 0.7155 0.6986\nvn 0.9843 0.0758 0.1589\nvn 0.8727 0.2265 0.4324\nvn 0.7198 0.6345 0.2814\nvn 0.9867 0.1558 0.0455\nvn 0.9953 -0.0320 0.0910\nvn 0.8187 -0.5516 0.1594\nvn 0.9470 -0.2358 0.2180\nvn 0.9819 -0.0986 0.1617\nvn 0.8855 -0.2559 0.3879\nvn 0.8912 -0.0589 0.4497\nvn 0.9817 -0.0146 0.1897\nvn 0.1102 0.9903 -0.0840\nvn 0.9721 0.2278 -0.0561\nvn 0.9825 0.0748 0.1702\nvn 0.9916 0.0168 0.1278\nvn 0.9982 0.0088 0.0586\nvn 0.9908 0.0696 0.1161\nvn 0.9223 0.2758 0.2707\nvn 0.9426 0.2722 0.1932\nvn 0.7584 0.5297 0.3799\nvn 0.7790 0.5678 0.2660\nvn 0.5808 0.6882 0.4348\nvn 0.5733 0.7639 0.2962\nvn 0.4173 0.7829 0.4613\nvn 0.3999 0.8648 0.3036\nvn 0.2281 0.8453 0.4831\nvn 0.2131 0.9275 0.3070\nvn -0.0004 0.8702 0.4926\nvn -0.0004 0.9512 0.3085\nvn 0.9862 0.0124 0.1651\nvn 0.9927 -0.0243 0.1179\nvn 0.9630 -0.1671 0.2115\nvn 0.9147 -0.2635 0.3062\nvn 0.9081 -0.3343 0.2522\nvn 0.8651 -0.4870 0.1198\nvn 0.8138 -0.5675 0.1252\nvn 0.7233 -0.6749 0.1458\nvn 0.5530 -0.8056 0.2127\nvn 0.3056 -0.9041 0.2986\nvn 0.0005 -0.9414 0.3372\nvn 0.7523 -0.5602 -0.3468\nvn 0.7585 -0.6347 -0.1476\nvn 0.1063 0.8930 0.4373\nvn 0.0349 0.9186 0.3937\nvn 0.0775 0.9284 0.3633\nvn 0.1617 0.8659 0.4733\nvn 0.2545 0.7494 0.6111\nvn 0.3673 0.6561 0.6592\nvn 0.2130 0.7909 0.5737\nvn 0.1453 0.8597 0.4896\nvn 0.1990 0.7491 0.6318\nvn 0.2266 0.6973 0.6800\nvn 0.4455 0.5990 0.6653\nvn 0.4367 0.5471 0.7141\nvn -0.1425 -0.4490 -0.8821\nvn 0.2102 -0.5287 -0.8223\nvn 0.9877 -0.1237 -0.0952\nvn 0.8433 -0.3157 -0.4349\nvn -0.5966 0.5023 -0.6258\nvn -0.4835 0.2249 -0.8459\nvn 0.1132 0.2753 -0.9546\nvn 0.4564 0.5646 -0.6877\nvn -0.4101 -0.3906 -0.8242\nvn 0.5776 -0.4773 -0.6622\nvn -0.4126 -0.1463 -0.8991\nvn -0.4539 -0.3581 -0.8159\nvn 0.2999 -0.4301 -0.8515\nvn 0.0530 -0.1151 -0.9919\nvn -0.4859 0.7632 -0.4259\nvn 0.9001 0.3909 -0.1921\nvn 0.7945 -0.3614 -0.4880\nvn 0.9912 0.0295 0.1287\nvn 0.9997 -0.0249 -0.0060\nvn 0.9976 0.0560 0.0413\nvn 0.9604 0.2673 0.0779\nvn 0.8026 0.5873 0.1041\nvn 0.5779 0.8076 0.1174\nvn 0.3890 0.9136 0.1177\nvn 0.2006 0.9730 0.1140\nvn -0.0004 0.9937 0.1122\nvn 0.1099 -0.1335 -0.9849\nvn -0.0013 -0.1688 -0.9856\nvn -0.0021 -0.2815 -0.9595\nvn 0.1777 -0.2741 -0.9451\nvn 0.2228 0.2255 -0.9484\nvn -0.0019 0.2469 -0.9690\nvn -0.0015 0.4761 -0.8794\nvn 0.2279 0.4626 -0.8567\nvn 0.9266 -0.1050 -0.3609\nvn 0.8503 -0.0323 -0.5253\nvn 0.8820 0.1174 -0.4563\nvn 0.9517 -0.0046 -0.3069\nvn 0.9702 0.1510 -0.1891\nvn 0.8631 0.4317 -0.2619\nvn 0.8317 0.5497 -0.0783\nvn 0.9714 0.2313 -0.0539\nvn 0.9973 -0.0433 -0.0592\nvn 0.9921 -0.0302 -0.1218\nvn 0.9905 -0.0083 -0.1372\nvn 0.9984 0.0272 -0.0489\nvn 0.8845 -0.0619 -0.4624\nvn 0.8536 -0.2029 -0.4797\nvn 0.9547 -0.1487 -0.2577\nvn 0.9726 -0.0816 -0.2176\nvn 0.9643 -0.0119 -0.2644\nvn 0.9814 -0.0383 -0.1882\nvn 0.8812 0.2624 -0.3932\nvn 0.9607 0.0628 -0.2703\nvn -0.0005 0.9091 -0.4166\nvn -0.0005 0.9922 -0.1247\nvn 0.2080 0.9708 -0.1196\nvn 0.2274 0.8871 -0.4016\nvn 0.6017 0.7930 -0.0956\nvn 0.6460 0.6899 -0.3267\nvn 0.4249 0.8261 -0.3701\nvn 0.3981 0.9109 -0.1085\nvn 0.6695 0.0681 -0.7397\nvn 0.4472 0.1622 -0.8795\nvn 0.4550 0.4081 -0.7915\nvn 0.6993 0.2840 -0.6559\nvn 0.4480 0.6412 -0.6230\nvn 0.6867 0.4964 -0.5310\nvn -0.0008 0.7150 -0.6991\nvn 0.2353 0.6986 -0.6756\nvn 0.8272 0.0580 -0.5589\nvn 0.7219 0.0827 -0.6870\nvn 0.7139 -0.1442 -0.6852\nvn 0.8134 -0.0699 -0.5775\nvn 0.8559 -0.2378 -0.4592\nvn 0.7453 -0.2657 -0.6114\nvn 0.7913 -0.1794 -0.5846\nvn 0.8858 -0.2047 -0.4165\nvn 0.9355 -0.2147 -0.2804\nvn 0.9838 -0.0923 -0.1534\nvn 0.9246 -0.1564 -0.3473\nvn 0.9341 -0.1876 -0.3038\nvn 0.9921 0.0409 -0.1186\nvn 0.8987 -0.0174 -0.4381\nvn 0.9939 0.0239 -0.1077\nvn 0.9127 -0.0683 -0.4027\nvn 0.7162 -0.2773 -0.6404\nvn 0.8311 -0.1960 -0.5204\nvn -0.0023 -0.1747 -0.9846\nvn -0.0024 0.0132 -0.9999\nvn 0.2215 -0.0114 -0.9751\nvn 0.2142 -0.1877 -0.9586\nvn 0.6084 -0.1230 -0.7840\nvn 0.5613 -0.2423 -0.7913\nvn 0.3898 -0.2101 -0.8966\nvn 0.4230 -0.0648 -0.9038\nvn 0.5422 0.0598 -0.8381\nvn 0.2660 -0.0318 -0.9634\nvn 0.3533 -0.2477 -0.9021\nvn 0.5500 -0.2034 -0.8100\nvn 0.3727 -0.2710 -0.8875\nvn 0.5468 -0.2907 -0.7851\nvn -0.0020 -0.2572 -0.9663\nvn 0.2035 -0.2591 -0.9442\nvn -0.2736 0.6673 0.6927\nvn -0.2621 0.7474 0.6105\nvn -0.2578 0.6690 0.6971\nvn -0.2536 0.7208 0.6451\nvn -0.0009 0.6898 0.7240\nvn -0.0017 0.7283 0.6852\nvn 0.4770 0.5701 0.6688\nvn 0.6020 0.6883 0.4046\nvn 0.4076 0.6126 -0.6771\nvn 0.5396 0.7326 -0.4147\nvn 0.5414 0.7185 -0.4365\nvn 0.4115 0.5860 -0.6980\nvn 0.3188 0.7439 0.5872\nvn 0.3443 0.7071 0.6176\nvn 0.5740 0.8174 -0.0478\nvn 0.5846 0.8101 -0.0441\nvn 0.5126 0.7874 0.3422\nvn 0.4878 0.8095 0.3265\nvn 0.3878 0.6632 -0.6401\nvn 0.5404 0.7517 -0.3780\nvn 0.6359 0.7716 -0.0160\nvn 0.5855 0.6347 -0.5043\nvn 0.3090 0.6850 -0.6597\nvn 0.8221 0.5492 -0.1501\nvn 0.8868 0.2250 -0.4037\nvn 0.7142 0.3279 -0.6184\nvn -0.0360 0.1549 -0.9872\nvn 0.0003 0.1149 -0.9933\nvn 0.4610 0.3333 -0.8224\nvn 0.0914 0.2518 -0.9634\nvn -0.2069 0.3382 -0.9181\nvn 0.0006 0.3193 -0.9476\nvn 0.2899 0.5476 -0.7849\nvn -0.1165 0.4249 -0.8977\nvn -0.3315 0.3966 -0.8560\nvn 0.0005 0.4023 -0.9155\nvn 0.0374 0.6140 -0.7884\nvn -0.3061 0.4682 -0.8289\nvn -0.3375 0.4068 -0.8489\nvn -0.3328 0.3706 -0.8671\nvn 0.0027 0.3914 -0.9202\nvn 0.0033 0.4318 -0.9019\nvn -0.3330 0.4060 -0.8510\nvn -0.3170 0.3846 -0.8669\nvn -0.1143 0.4408 -0.8903\nvn -0.0972 0.4361 -0.8946\nvn -0.3764 0.4190 -0.8263\nvn 0.0024 0.4577 -0.8891\nvn -0.4048 0.4227 -0.8108\nvn -0.3542 0.4439 -0.8231\nvn -0.3871 0.4146 -0.8236\nvn 0.0028 0.4855 -0.8742\nvn -0.1590 0.5109 -0.8448\nvn -0.1673 0.4494 -0.8775\nvn 0.1023 0.5842 -0.8051\nvn 0.1390 0.5389 -0.8308\nvn 0.1746 0.5078 -0.8436\nvn 0.1820 0.4980 -0.8478\nvn 0.0002 -0.9609 0.2768\nvn 0.0384 -0.9514 0.3056\nvn 0.0774 -0.9317 0.3548\nvn 0.0820 -0.9211 0.3806\nvn 0.0826 0.9966 0.0069\nvn -0.0004 0.9938 -0.1114\nvn 0.3482 0.9025 0.2535\nvn 0.3241 0.8325 0.4493\nvn -0.0356 0.0069 0.9993\nvn -0.0001 -0.2038 0.9790\nvn 0.1049 -0.1682 0.9801\nvn 0.0651 -0.0324 0.9973\nvn -0.0440 0.0190 0.9988\nvn -0.0001 0.0779 0.9969\nvn 0.8638 -0.4530 0.2206\nvn 0.8901 -0.3815 0.2491\nvn 0.8909 -0.2893 0.3501\nvn 0.9586 -0.2387 0.1552\nvn 0.9630 0.0624 0.2621\nvn 0.8904 -0.4506 -0.0646\nvn 0.9232 -0.3580 0.1397\nvn 0.9494 0.3134 0.0179\nvn 0.9470 -0.2390 0.2148\nvn 0.9239 -0.2732 -0.2678\nvn 0.8668 -0.4187 0.2706\nvn 0.9751 -0.2151 -0.0533\nvn 0.9903 -0.1325 0.0421\nvn 0.7928 0.4661 -0.3926\nvn 0.9936 -0.1132 -0.0003\nvn 0.7425 0.2701 -0.6129\nvn 0.9850 -0.1650 -0.0515\nvn 0.6906 0.0520 -0.7214\nvn 0.2201 0.8912 0.3966\nvn -0.3141 0.2509 -0.9156\nvn -0.3352 0.5180 -0.7870\nvn -0.2610 0.8384 -0.4785\nvn -0.0594 0.9981 0.0172\nvn 0.9791 -0.1948 -0.0591\nvn 0.5885 -0.1664 -0.7911\nvn 0.9810 -0.1935 0.0129\nvn 0.5948 -0.3266 -0.7345\nvn 0.9660 -0.1584 0.2042\nvn 0.6543 -0.4228 -0.6270\nvn 0.9621 0.0010 0.2727\nvn 0.7318 -0.4447 -0.5164\nvn -0.4756 -0.3942 -0.7864\nvn -0.4958 -0.3291 -0.8036\nvn -0.4092 -0.2751 -0.8700\nvn -0.3319 -0.0656 -0.9410\nvn 0.9833 0.0629 0.1704\nvn 0.6975 -0.5161 -0.4971\nvn 0.5649 -0.7472 -0.3500\nvn 0.9426 -0.2803 0.1816\nvn -0.3965 -0.5141 -0.7606\nvn -0.0161 -0.6634 -0.7480\nvn 0.9752 0.2024 -0.0894\nvn 0.9228 0.3839 0.0322\nvn 0.9515 0.2336 0.2002\nvn 0.8798 0.3264 0.3454\nvn 0.7342 -0.3280 0.5944\nvn 0.6364 0.5125 0.5764\nvn 0.8796 -0.1671 0.4454\nvn 0.8158 -0.1409 0.5609\nvn 0.8726 0.0760 0.4825\nvn 0.9072 -0.1517 0.3924\nvn 0.9424 -0.2628 0.2069\nvn 0.9701 -0.2418 -0.0198\nvn 0.8577 -0.1611 0.4883\nvn 0.7598 -0.2012 0.6183\nvn 0.8122 -0.3142 0.4915\nvn 0.9293 -0.1915 0.3159\nvn 0.8831 -0.2182 0.4154\nvn 0.9926 -0.0184 0.1202\nvn 0.9953 -0.0960 0.0104\nvn 0.4295 0.8123 0.3944\nvn -0.0378 -0.9143 0.4032\nvn -0.7576 0.2227 0.6135\nvn -0.6259 0.4340 0.6479\nvn -0.7276 0.3501 0.5899\nvn -0.7667 0.2268 0.6006\nvn -0.2808 -0.7982 0.5328\nvn -0.5447 -0.6069 0.5788\nvn -0.3009 -0.3595 0.8833\nvn -0.2294 -0.3709 0.8999\nvn -0.2562 -0.7573 0.6007\nvn -0.4440 -0.2799 0.8512\nvn -0.3703 -0.6155 0.6957\nvn -0.6433 -0.2595 0.7202\nvn -0.4968 -0.4434 0.7460\nvn -0.7762 -0.2223 0.5900\nvn -0.6029 -0.1942 0.7738\nvn -0.8313 -0.1086 0.5451\nvn -0.4238 0.2262 0.8770\nvn -0.5754 0.2423 0.7811\nvn -0.7813 0.1209 0.6123\nvn -0.6026 0.1031 0.7914\nvn -0.3123 0.2925 0.9038\nvn -0.4446 0.1367 0.8852\nvn -0.4102 0.4376 0.8001\nvn -0.5571 0.3085 0.7710\nvn -0.6391 0.4162 0.6467\nvn -0.5991 0.3782 0.7056\nvn -0.7627 0.3644 0.5343\nvn -0.6281 0.4156 0.6578\nvn -0.7784 0.3213 0.5393\nvn -0.5921 0.4064 0.6959\nvn -0.5761 -0.0316 0.8167\nvn -0.5876 -0.1444 0.7961\nvn -0.6862 -0.1626 0.7089\nvn -0.6333 -0.0679 0.7709\nvn -0.7230 0.2553 0.6419\nvn -0.5759 0.0615 0.8152\nvn -0.8471 0.1152 0.5187\nvn -0.6992 -0.1080 0.7067\nvn -0.5145 -0.0621 0.8552\nvn -0.5533 -0.0246 0.8326\nvn -0.1113 0.0935 0.9893\nvn -0.2495 0.2148 0.9442\nvn -0.5246 0.2798 0.8040\nvn -0.7914 0.1979 0.5784\nvn -0.9181 0.0380 0.3945\nvn -0.2009 -0.1287 0.9711\nvn -0.1096 0.0485 0.9928\nvn -0.6452 0.0475 0.7625\nvn -0.7821 -0.0792 0.6181\nvn -0.6719 0.0252 0.7402\nvn -0.5797 0.0903 0.8098\nvn -0.4357 -0.0081 0.9000\nvn -0.4352 0.3398 0.8337\nvn -0.6922 -0.1092 0.7133\nvn -0.8258 -0.1750 0.5360\nvn -0.8386 -0.1669 0.5184\nvn -0.6926 -0.0408 0.7202\nvn -0.7721 -0.1559 0.6161\nvn -0.6697 -0.1072 0.7349\nvn -0.4397 -0.0924 0.8933\nvn -0.3182 0.2236 0.9212\nvn -0.7140 -0.4844 0.5054\nvn -0.4335 -0.5922 0.6792\nvn -0.8407 -0.4060 0.3581\nvn -0.4533 -0.8888 0.0670\nvn -0.4465 -0.8902 0.0908\nvn -0.5992 -0.8000 0.0306\nvn -0.5711 -0.8207 0.0134\nvn -0.2340 -0.9613 0.1451\nvn -0.2555 -0.9551 0.1501\nvn -0.6349 0.1084 0.7649\nvn -0.8309 0.0635 0.5527\nvn -0.7607 -0.1873 0.6215\nvn -0.6735 -0.1411 0.7256\nvn -0.4311 0.0622 0.9001\nvn -0.2803 0.8336 0.4758\nvn -0.4420 0.1303 0.8875\nvn -0.2239 -0.1650 0.9605\nvn -0.4573 -0.1100 0.8824\nvn -0.5394 0.0449 0.8408\nvn -0.4342 0.5429 0.7189\nvn -0.5744 -0.1513 0.8045\nvn -0.6238 -0.0356 0.7808\nvn -0.4010 -0.3059 0.8635\nvn -0.5114 0.6908 0.5111\nvn -0.6380 0.1183 0.7609\nvn -0.1267 -0.7389 0.6618\nvn -0.2820 -0.7669 0.5764\nvn -0.5599 -0.3430 0.7542\nvn -0.4447 -0.2965 0.8451\nvn -0.1833 -0.8026 0.5676\nvn -0.3489 -0.4081 0.8436\nvn -0.2763 -0.7842 0.5555\nvn -0.5560 -0.3713 0.7436\nvn -0.5987 -0.1036 0.7942\nvn -0.6160 -0.0434 0.7865\nvn -0.5998 -0.1215 0.7908\nvn -0.6375 -0.0953 0.7645\nvn -0.5628 0.5601 0.6079\nvn -0.6357 0.1074 0.7644\nvn -0.5751 0.0773 0.8144\nvn -0.6353 0.0852 0.7675\nvn -0.4077 0.2362 0.8820\nvn -0.3772 0.0945 0.9213\nvn -0.4221 0.0623 0.9044\nvn -0.6418 0.0403 0.7658\nvn -0.6374 -0.4831 0.6002\nvn -0.8033 -0.3002 0.5144\nvn -0.8241 -0.0822 0.5604\nvn -0.5837 -0.2004 0.7869\nvn -0.4278 -0.2356 0.8726\nvn -0.4436 -0.2859 0.8494\nvn -0.5491 0.0667 0.8331\nvn -0.7407 -0.3831 0.5518\nvn -0.6600 -0.0395 0.7502\nvn -0.5623 -0.0121 0.8268\nvn -0.3849 -0.1120 0.9161\nvn -0.9272 -0.3687 0.0660\nvn -0.9529 -0.2410 0.1841\nvn -0.9825 -0.0625 0.1751\nvn -0.9412 -0.3139 0.1247\nvn -0.6973 -0.7166 -0.0093\nvn -0.6857 -0.7272 0.0312\nvn -0.7763 -0.6298 -0.0259\nvn -0.7927 -0.6057 0.0688\nvn -0.8666 -0.4901 -0.0936\nvn -0.8968 -0.4424 -0.0073\nvn -0.8911 -0.3421 0.2980\nvn -0.5821 -0.0915 0.8079\nvn -0.8926 -0.2496 0.3754\nvn -0.8054 -0.2300 0.5463\nvn -0.8619 -0.2203 0.4567\nvn -0.7175 -0.2029 0.6664\nvn -0.9344 -0.0171 0.3559\nvn -0.8003 -0.0625 0.5964\nvn -0.9286 -0.0215 0.3704\nvn -0.9335 0.0249 0.3577\nvn -0.8825 -0.1645 0.4405\nvn -0.9063 -0.2065 0.3686\nvn -0.9579 -0.1181 0.2618\nvn -0.9657 -0.1322 0.2236\nvn -0.9125 -0.2247 0.3418\nvn -0.9670 -0.1332 0.2170\nvn -0.6157 -0.5504 0.5638\nvn -0.5809 -0.7841 0.2182\nvn -0.6524 -0.7273 0.2130\nvn -0.6828 -0.7227 -0.1070\nvn -0.2906 -0.9139 0.2834\nvn 0.0897 -0.8253 0.5575\nvn -0.0505 -0.1701 0.9841\nvn -0.4794 -0.8242 -0.3013\nvn -0.1189 -0.9566 -0.2659\nvn 0.1654 -0.9698 -0.1793\nvn -0.3571 -0.9297 -0.0894\nvn -0.6914 -0.7044 0.1605\nvn -0.9109 -0.4119 -0.0250\nvn -0.9349 0.0031 0.3550\nvn -0.6256 0.2243 0.7472\nvn -0.8431 -0.5220 -0.1293\nvn -0.8694 -0.4675 0.1600\nvn -0.7473 -0.3038 0.5910\nvn -0.1331 -0.7736 0.6195\nvn 0.4332 -0.8865 0.1625\nvn -0.6856 0.2907 0.6674\nvn -0.4248 0.5212 0.7402\nvn -0.1718 -0.6532 0.7374\nvn -0.4356 -0.4997 0.7486\nvn -0.1506 -0.6232 0.7674\nvn -0.3353 -0.4684 0.8173\nvn -0.4508 -0.3901 0.8029\nvn -0.4712 -0.2302 0.8514\nvn -0.3449 0.2480 0.9053\nvn -0.4907 0.2334 0.8395\nvn -0.2075 0.3292 0.9212\nvn -0.2059 0.4741 0.8560\nvn -0.5112 0.0741 0.8562\nvn -0.6939 -0.1185 0.7102\nvn -0.1951 0.8438 0.4999\nvn -0.0885 0.8677 0.4892\nvn -0.2856 0.7831 0.5524\nvn -0.6218 0.4335 0.6522\nvn -0.4152 -0.6372 0.6493\nvn -0.6769 -0.2214 0.7019\nvn -0.2639 -0.7885 0.5555\nvn -0.1015 -0.8883 0.4479\nvn -0.0316 -0.8718 0.4888\nvn 0.0266 -0.7700 0.6374\nvn 0.0582 -0.4640 0.8839\nvn 0.0081 0.7206 0.6932\nvn 0.0166 0.1924 0.9812\nvn 0.0016 0.8167 0.5770\nvn -0.4190 -0.9075 -0.0291\nvn -0.5872 -0.7755 0.2318\nvn -0.0971 -0.9887 -0.1138\nvn 0.2620 -0.9584 -0.1130\nvn 0.5482 -0.8304 0.0992\nvn 0.6994 -0.4400 0.5632\nvn 0.3520 0.9144 -0.1998\nvn 0.5500 0.8339 0.0464\nvn 0.0668 0.9422 -0.3284\nvn -0.2325 0.9147 -0.3306\nvn -0.4192 0.9078 0.0064\nvn 0.7430 0.3601 0.5641\nvn -0.4480 -0.8005 0.3980\nvn -0.6027 -0.6745 0.4264\nvn -0.1610 -0.8795 0.4477\nvn 0.1231 -0.8754 0.4674\nvn 0.3726 -0.7715 0.5156\nvn 0.4044 -0.3821 0.8309\nvn 0.2980 0.8817 0.3658\nvn 0.4878 0.7878 0.3760\nvn 0.0038 0.9598 0.2806\nvn -0.3977 0.9028 0.1635\nvn -0.6171 0.7788 0.1122\nvn 0.4848 0.2959 0.8230\nvn -0.4516 -0.3506 0.8204\nvn -0.5798 -0.3049 0.7555\nvn 0.0682 0.2508 0.9656\nvn 0.0383 -0.3904 0.9198\nvn -0.1788 -0.3938 0.9016\nvn -0.4357 0.2477 0.8653\nvn -0.1355 0.2574 0.9567\nvn -0.6651 0.3258 0.6718\nvn -0.2333 0.3243 0.9167\nvn -0.4069 0.3753 0.8328\nvn -0.6210 0.3710 0.6904\nvn -0.8324 0.2431 0.4979\nvn -0.9360 0.0790 0.3430\nvn -0.1051 0.2871 0.9521\nvn -0.9695 0.0083 0.2450\nvn -0.9781 -0.0479 0.2024\nvn -0.9613 0.0141 0.2751\nvn -0.9819 -0.1034 0.1583\nvn -0.9851 -0.1146 0.1282\nvn -0.9919 -0.0842 0.0950\nvn -0.9874 -0.1581 -0.0065\nvn -0.9742 0.0472 0.2204\nvn -0.9300 -0.0407 0.3653\nvn -0.9152 -0.1782 0.3615\nvn -0.8013 -0.2928 0.5217\nvn -0.9819 -0.0320 0.1866\nvn -0.9319 0.0355 0.3608\nvn -0.6979 0.0680 0.7129\nvn -0.8314 -0.0290 0.5549\nvn 0.0406 0.7854 0.6176\nvn 0.1733 0.8108 0.5591\nvn 0.2330 0.7887 0.5688\nvn 0.1187 0.7190 0.6848\nvn -0.2815 0.5347 0.7967\nvn -0.1209 0.4666 0.8761\nvn -0.6158 -0.0837 0.7834\nvn -0.4135 -0.1163 0.9030\nvn -0.6478 -0.3359 0.6836\nvn -0.5490 -0.1726 0.8178\nvn -0.2286 0.0595 0.9717\nvn -0.7679 -0.5433 0.3392\nvn -0.7202 -0.3164 0.6174\nvn -0.6950 -0.6635 0.2768\nvn -0.7258 -0.4304 0.5366\nvn -0.3485 -0.8603 0.3721\nvn -0.3828 -0.6423 0.6640\nvn -0.5817 -0.7566 0.2985\nvn -0.6244 -0.5230 0.5802\nvn -0.3778 0.0647 0.9236\nvn -0.5367 0.1600 0.8284\nvn -0.3951 0.2198 0.8919\nvn -0.1236 0.2130 0.9692\nvn 0.1984 0.6807 0.7052\nvn 0.1141 0.4584 0.8814\nvn 0.2395 0.7730 0.5874\nvn -0.4141 -0.2423 0.8774\nvn -0.5713 -0.4290 0.6997\nvn -0.0623 0.8558 0.5135\nvn 0.0469 0.8608 0.5067\nvn -0.3302 0.6492 0.6851\nvn -0.9360 0.3086 0.1692\nvn -0.8394 0.2729 0.4700\nvn -0.9981 -0.0605 -0.0013\nvn -0.9763 0.0412 -0.2122\nvn -0.9928 -0.0180 -0.1181\nvn -0.9183 -0.0115 -0.3957\nvn -0.2816 -0.3268 0.9022\nvn -0.3581 -0.4982 0.7896\nvn -0.8511 0.0940 0.5164\nvn -0.7298 0.1426 0.6686\nvn -0.7349 0.2952 0.6105\nvn -0.7986 0.2608 0.5423\nvn 0.1492 0.1933 0.9697\nvn 0.2354 0.4377 0.8677\nvn 0.0467 0.1416 0.9888\nvn 0.1244 0.2579 0.9581\nvn 0.2796 0.4599 0.8427\nvn 0.0344 0.2994 0.9535\nvn -0.2831 0.2506 0.9257\nvn -0.5556 0.2141 0.8034\nvn -0.5726 0.3702 0.7314\nvn -0.9857 -0.0034 0.1686\nvn -0.9883 -0.0652 0.1375\nvn -0.9523 0.0604 0.2990\nvn -0.9779 0.0164 0.2083\nvn -0.9706 -0.2356 0.0478\nvn -0.9482 -0.1438 -0.2831\nvn -0.9536 -0.2590 -0.1536\nvn -0.9630 -0.2023 -0.1777\nvn -0.9860 -0.0949 0.1371\nvn -0.8612 0.2244 0.4560\nvn -0.1736 0.4817 0.8589\nvn -0.6938 0.3915 0.6045\nvn -0.5256 0.4593 0.7161\nvn -0.3620 0.4740 0.8026\nvn -0.8971 0.2673 0.3518\nvn -0.9738 0.0715 0.2159\nvn -0.7332 0.4739 0.4876\nvn -0.5746 0.5854 0.5720\nvn -0.4152 0.6537 0.6326\nvn -0.2206 0.6995 0.6796\nvn -0.9844 0.0777 0.1578\nvn -0.9867 0.1562 0.0455\nvn -0.7184 0.6361 0.2815\nvn -0.8713 0.2311 0.4328\nvn -0.9951 -0.0367 0.0910\nvn -0.9434 -0.2402 0.2287\nvn -0.8160 -0.5533 0.1673\nvn -0.9835 -0.0923 0.1558\nvn -0.9827 -0.0066 0.1852\nvn -0.8921 -0.0506 0.4490\nvn -0.8859 -0.2499 0.3907\nvn -0.9720 0.2281 -0.0559\nvn -0.1099 0.9904 -0.0840\nvn -0.9828 0.0746 0.1689\nvn -0.9908 0.0696 0.1156\nvn -0.9982 0.0089 0.0585\nvn -0.9917 0.0175 0.1272\nvn -0.9225 0.2767 0.2691\nvn -0.9427 0.2725 0.1926\nvn -0.7580 0.5308 0.3789\nvn -0.7789 0.5680 0.2658\nvn -0.5806 0.6887 0.4343\nvn -0.5733 0.7639 0.2962\nvn -0.4171 0.7831 0.4612\nvn -0.3999 0.8648 0.3036\nvn -0.2285 0.8452 0.4831\nvn -0.2135 0.9274 0.3070\nvn -0.9864 0.0155 0.1635\nvn -0.9926 -0.0165 0.1201\nvn -0.9622 -0.1663 0.2156\nvn -0.9070 -0.3356 0.2542\nvn -0.9137 -0.2647 0.3083\nvn -0.8639 -0.4888 0.1209\nvn -0.8137 -0.5675 0.1254\nvn -0.7233 -0.6749 0.1458\nvn -0.5529 -0.8056 0.2127\nvn -0.3053 -0.9042 0.2987\nvn -0.7539 -0.6388 -0.1535\nvn -0.7487 -0.5625 -0.3507\nvn -0.1063 0.8930 0.4373\nvn -0.1617 0.8660 0.4733\nvn -0.0775 0.9284 0.3633\nvn -0.0349 0.9185 0.3937\nvn -0.3673 0.6561 0.6592\nvn -0.2545 0.7494 0.6111\nvn -0.2130 0.7909 0.5737\nvn -0.2266 0.6973 0.6800\nvn -0.1990 0.7491 0.6318\nvn -0.1453 0.8597 0.4896\nvn -0.4367 0.5471 0.7141\nvn -0.4455 0.5990 0.6653\nvn 0.1419 -0.4497 -0.8818\nvn -0.8428 -0.3155 -0.4361\nvn -0.9874 -0.1232 -0.0991\nvn -0.2070 -0.5288 -0.8231\nvn 0.5967 0.5022 -0.6258\nvn -0.4561 0.5647 -0.6877\nvn -0.1131 0.2753 -0.9546\nvn 0.4833 0.2247 -0.8461\nvn 0.4110 -0.3905 -0.8238\nvn -0.5776 -0.4773 -0.6622\nvn 0.4122 -0.1464 -0.8992\nvn -0.0529 -0.1151 -0.9919\nvn -0.3002 -0.4301 -0.8514\nvn 0.4535 -0.3587 -0.8159\nvn 0.4860 0.7631 -0.4259\nvn -0.9001 0.3911 -0.1919\nvn -0.9918 0.0285 0.1247\nvn -0.7909 -0.3644 -0.4917\nvn -0.9976 0.0561 0.0413\nvn -0.9997 -0.0248 -0.0059\nvn -0.9604 0.2673 0.0779\nvn -0.8026 0.5873 0.1041\nvn -0.5779 0.8076 0.1174\nvn -0.3890 0.9136 0.1177\nvn -0.2010 0.9729 0.1140\nvn -0.1113 -0.1333 -0.9848\nvn -0.1800 -0.2740 -0.9447\nvn -0.2247 0.2254 -0.9480\nvn -0.2294 0.4626 -0.8564\nvn -0.9266 -0.1050 -0.3610\nvn -0.9517 -0.0045 -0.3069\nvn -0.8820 0.1174 -0.4563\nvn -0.8503 -0.0323 -0.5253\nvn -0.9702 0.1511 -0.1891\nvn -0.9714 0.2313 -0.0539\nvn -0.8317 0.5497 -0.0783\nvn -0.8631 0.4317 -0.2619\nvn -0.9973 -0.0431 -0.0591\nvn -0.9984 0.0273 -0.0489\nvn -0.9905 -0.0082 -0.1372\nvn -0.9921 -0.0300 -0.1218\nvn -0.8844 -0.0618 -0.4625\nvn -0.9726 -0.0815 -0.2177\nvn -0.9546 -0.1487 -0.2579\nvn -0.8536 -0.2029 -0.4798\nvn -0.9814 -0.0382 -0.1883\nvn -0.9643 -0.0117 -0.2645\nvn -0.8812 0.2624 -0.3932\nvn -0.9607 0.0629 -0.2703\nvn -0.2280 0.8871 -0.4014\nvn -0.2085 0.9707 -0.1195\nvn -0.6017 0.7930 -0.0956\nvn -0.3981 0.9109 -0.1085\nvn -0.4249 0.8261 -0.3701\nvn -0.6460 0.6899 -0.3267\nvn -0.6695 0.0681 -0.7397\nvn -0.6993 0.2840 -0.6559\nvn -0.4550 0.4081 -0.7915\nvn -0.4472 0.1622 -0.8795\nvn -0.4480 0.6412 -0.6230\nvn -0.6867 0.4964 -0.5310\nvn -0.2363 0.6986 -0.6753\nvn -0.8272 0.0580 -0.5589\nvn -0.8133 -0.0700 -0.5775\nvn -0.7139 -0.1443 -0.6852\nvn -0.7219 0.0827 -0.6870\nvn -0.8558 -0.2378 -0.4593\nvn -0.8857 -0.2047 -0.4166\nvn -0.7912 -0.1794 -0.5846\nvn -0.7453 -0.2657 -0.6115\nvn -0.9355 -0.2145 -0.2806\nvn -0.9340 -0.1875 -0.3040\nvn -0.9245 -0.1565 -0.3476\nvn -0.9838 -0.0924 -0.1538\nvn -0.9920 0.0407 -0.1194\nvn -0.8986 -0.0175 -0.4383\nvn -0.9126 -0.0684 -0.4030\nvn -0.9938 0.0238 -0.1083\nvn -0.7162 -0.2773 -0.6404\nvn -0.8310 -0.1960 -0.5205\nvn -0.2164 -0.1880 -0.9580\nvn -0.2238 -0.0116 -0.9745\nvn -0.6084 -0.1230 -0.7840\nvn -0.4230 -0.0648 -0.9038\nvn -0.3898 -0.2101 -0.8966\nvn -0.5613 -0.2423 -0.7913\nvn -0.5422 0.0598 -0.8381\nvn -0.5500 -0.2034 -0.8100\nvn -0.3536 -0.2476 -0.9020\nvn -0.2663 -0.0316 -0.9633\nvn -0.3728 -0.2709 -0.8874\nvn -0.5468 -0.2907 -0.7851\nvn -0.2057 -0.2591 -0.9437\nvn 0.2621 0.7474 0.6105\nvn 0.2736 0.6673 0.6927\nvn 0.2569 0.6692 0.6972\nvn 0.2520 0.7212 0.6453\nvn -0.6020 0.6883 0.4046\nvn -0.4770 0.5701 0.6688\nvn -0.4076 0.6126 -0.6771\nvn -0.4115 0.5860 -0.6980\nvn -0.5414 0.7186 -0.4365\nvn -0.5396 0.7326 -0.4147\nvn -0.3188 0.7439 0.5872\nvn -0.3443 0.7071 0.6176\nvn -0.5740 0.8174 -0.0478\nvn -0.4878 0.8095 0.3265\nvn -0.5126 0.7874 0.3422\nvn -0.5846 0.8101 -0.0441\nvn -0.3878 0.6632 -0.6401\nvn -0.5404 0.7517 -0.3780\nvn -0.6359 0.7716 -0.0160\nvn -0.5855 0.6347 -0.5043\nvn -0.3090 0.6850 -0.6597\nvn -0.8221 0.5492 -0.1501\nvn -0.8868 0.2250 -0.4037\nvn -0.7142 0.3279 -0.6184\nvn 0.0362 0.1551 -0.9872\nvn -0.0914 0.2519 -0.9634\nvn -0.4610 0.3333 -0.8224\nvn 0.2075 0.3383 -0.9178\nvn 0.1165 0.4249 -0.8977\nvn -0.2899 0.5476 -0.7849\nvn 0.3323 0.3963 -0.8558\nvn 0.3061 0.4682 -0.8289\nvn -0.0374 0.6140 -0.7884\nvn 0.3404 0.4063 -0.8480\nvn 0.3359 0.3703 -0.8660\nvn 0.3330 0.4060 -0.8510\nvn 0.3170 0.3846 -0.8669\nvn 0.1143 0.4408 -0.8903\nvn 0.0972 0.4361 -0.8946\nvn 0.3783 0.4183 -0.8257\nvn 0.4048 0.4227 -0.8108\nvn 0.3870 0.4146 -0.8236\nvn 0.3569 0.4433 -0.8223\nvn 0.1590 0.5109 -0.8448\nvn 0.1673 0.4494 -0.8775\nvn -0.1023 0.5842 -0.8051\nvn -0.1746 0.5078 -0.8436\nvn -0.1390 0.5389 -0.8308\nvn -0.1820 0.4980 -0.8478\nvn -0.0383 -0.9514 0.3056\nvn -0.0774 -0.9317 0.3549\nvn -0.0820 -0.9210 0.3807\nvn -0.0828 0.9965 0.0068\nvn -0.3481 0.9026 0.2534\nvn -0.3241 0.8326 0.4491\nvn 0.0356 0.0068 0.9993\nvn -0.1052 -0.1686 0.9800\nvn -0.0653 -0.0327 0.9973\nvn 0.0440 0.0191 0.9988\nvn -0.8591 -0.4635 0.2170\nvn -0.9543 -0.2606 0.1462\nvn -0.8896 -0.2932 0.3502\nvn -0.8952 -0.3780 0.2360\nvn -0.9590 0.0819 0.2710\nvn -0.9445 0.3278 0.0207\nvn -0.9386 -0.3172 0.1358\nvn -0.9080 -0.4163 -0.0473\nvn -0.9430 -0.2246 0.2454\nvn -0.9404 -0.2538 -0.2262\nvn -0.8572 -0.4245 0.2914\nvn -0.9717 -0.2338 -0.0339\nvn -0.7827 0.4797 -0.3965\nvn -0.9940 -0.1072 0.0193\nvn -0.7390 0.2726 -0.6161\nvn -0.9946 -0.1015 -0.0223\nvn -0.6915 0.0512 -0.7205\nvn -0.9845 -0.1605 -0.0706\nvn -0.2169 0.8922 0.3960\nvn 0.3349 0.5178 -0.7872\nvn 0.3130 0.2507 -0.9160\nvn 0.2616 0.8382 -0.4785\nvn 0.0611 0.9980 0.0168\nvn -0.5894 -0.1664 -0.7905\nvn -0.9776 -0.1956 -0.0776\nvn -0.5957 -0.3257 -0.7342\nvn -0.9797 -0.2000 -0.0104\nvn -0.6506 -0.4241 -0.6300\nvn -0.9680 -0.1726 0.1818\nvn -0.7290 -0.4457 -0.5195\nvn -0.9651 -0.0133 0.2614\nvn 0.4953 -0.3296 -0.8038\nvn 0.4760 -0.3949 -0.7857\nvn 0.4075 -0.2755 -0.8706\nvn 0.3300 -0.0660 -0.9416\nvn -0.6968 -0.5148 -0.4994\nvn -0.9844 0.0598 0.1653\nvn -0.5655 -0.7459 -0.3518\nvn -0.9438 -0.2784 0.1778\nvn 0.3953 -0.5165 -0.7596\nvn 0.0176 -0.6652 -0.7465\nvn -0.9749 0.2061 -0.0837\nvn -0.9253 0.3772 0.0386\nvn -0.8803 0.3233 0.3471\nvn -0.9504 0.2384 0.1997\nvn -0.7347 -0.3269 0.5944\nvn -0.6359 0.5136 0.5759\nvn -0.8123 -0.1348 0.5673\nvn -0.8770 -0.1678 0.4501\nvn -0.8743 0.0653 0.4809\nvn -0.9090 -0.1593 0.3851\nvn -0.9442 -0.2705 0.1879\nvn -0.9682 -0.2455 -0.0472\nvn -0.8538 -0.1513 0.4981\nvn -0.8040 -0.3254 0.4976\nvn -0.7456 -0.2026 0.6348\nvn -0.9333 -0.1788 0.3115\nvn -0.8843 -0.2327 0.4047\nvn -0.9953 0.0024 0.0963\nvn -0.9958 -0.0881 -0.0215\nvn -0.4295 0.8125 0.3942\nvn 0.0378 -0.9143 0.4032\nvn 0.7009 -0.2227 -0.6775\nvn 0.7507 -0.1354 -0.6466\nvn 0.0158 0.0911 -0.9957\nvn -0.0139 -0.1133 -0.9934\nvn -0.7542 0.1018 -0.6487\nvn -0.7330 -0.0163 -0.6800\nvn -0.9999 -0.0004 0.0144\nvn -1.0000 -0.0049 0.0031\nvn -0.7617 -0.1222 0.6362\nvn -0.7435 -0.0915 0.6624\nvn -0.0387 -0.2729 0.9612\nvn -0.0289 -0.2206 0.9749\nvn 0.6930 -0.3455 0.6327\nvn 0.6909 -0.2953 0.6599\nvn 0.9523 -0.3048 0.0131\nvn 0.9585 -0.2850 0.0026\nvn -0.0241 -0.1111 -0.9935\nvn -0.1103 -0.2661 -0.9576\nvn 0.0000 -0.4858 -0.8740\nvn 0.7055 -0.2544 -0.6614\nvn -0.7425 -0.0121 -0.6697\nvn -0.7738 -0.0472 -0.6316\nvn -1.0000 0.0053 0.0044\nvn -0.9987 0.0507 0.0060\nvn -0.7434 -0.0461 0.6672\nvn -0.7721 -0.0116 0.6354\nvn -0.0388 -0.1682 0.9850\nvn -0.1471 -0.2321 0.9615\nvn 0.6964 -0.2921 0.6555\nvn 0.0000 -0.4816 0.8764\nvn 0.9490 -0.3152 0.0058\nvn 0.0000 -0.9998 0.0206\nvn 0.7694 0.1193 -0.6275\nvn 0.0522 0.4025 -0.9139\nvn 0.7053 0.2607 -0.6592\nvn -0.0031 0.5076 -0.8616\nvn -0.6920 0.4010 -0.6003\nvn -0.6443 0.5223 -0.5585\nvn -0.9798 0.1981 0.0274\nvn -0.9314 0.3637 -0.0124\nvn -0.7489 -0.0432 0.6612\nvn -0.7943 0.0840 0.6016\nvn -0.0193 -0.2656 0.9639\nvn -0.0644 -0.2408 0.9684\nvn 0.7075 -0.3245 0.6278\nvn 0.7505 -0.2868 0.5954\nvn 0.9817 -0.1902 0.0023\nvn 0.9959 -0.0618 -0.0661\nvn -0.7490 0.0587 -0.6599\nvn -0.1558 -0.1548 -0.9756\nvn -0.9841 0.1770 -0.0132\nvn -0.7669 0.0595 0.6390\nvn -0.2355 -0.1804 0.9550\nvn -0.7317 0.2497 -0.6342\nvn -0.1797 0.2153 -0.9598\nvn -0.9853 0.1702 -0.0129\nvn -0.7740 0.0499 0.6312\nvn -0.2623 -0.0431 0.9640\nvn -0.7886 0.2376 -0.5671\nvn -0.2038 0.3414 -0.9175\nvn -0.9967 0.0685 0.0432\nvn -0.7622 -0.0278 0.6467\nvn -0.2533 -0.0563 0.9657\nvn 0.0000 -0.0675 0.9977\nvn 0.0000 -0.0549 0.9985\nvn 0.0000 -0.3066 0.9518\nvn 0.0000 -0.2510 -0.9680\nvn 0.0000 0.1825 -0.9832\nvn 0.0000 0.3395 -0.9406\nvn -0.0137 -0.1017 -0.9947\nvn 0.7148 -0.2085 -0.6674\nvn -0.7429 0.0031 -0.6694\nvn -0.9997 0.0232 -0.0000\nvn -0.7467 -0.0530 0.6630\nvn -0.0230 -0.1830 0.9828\nvn 0.7042 -0.2631 0.6594\nvn 0.9651 -0.2619 -0.0003\nvn -0.0158 0.0911 -0.9957\nvn -0.7507 -0.1354 -0.6466\nvn -0.7009 -0.2227 -0.6775\nvn 0.0139 -0.1133 -0.9934\nvn 0.7542 0.1018 -0.6487\nvn 0.7330 -0.0163 -0.6800\nvn 0.9999 -0.0004 0.0144\nvn 1.0000 -0.0049 0.0031\nvn 0.7617 -0.1222 0.6362\nvn 0.7435 -0.0915 0.6624\nvn 0.0387 -0.2729 0.9612\nvn 0.0289 -0.2206 0.9749\nvn -0.6930 -0.3455 0.6327\nvn -0.6909 -0.2953 0.6599\nvn -0.9523 -0.3048 0.0131\nvn -0.9585 -0.2850 0.0026\nvn 0.0241 -0.1111 -0.9935\nvn -0.7055 -0.2544 -0.6614\nvn 0.1103 -0.2661 -0.9576\nvn 0.7425 -0.0121 -0.6697\nvn 0.7738 -0.0472 -0.6316\nvn 1.0000 0.0053 0.0044\nvn 0.9987 0.0507 0.0060\nvn 0.7434 -0.0461 0.6672\nvn 0.7721 -0.0116 0.6354\nvn 0.0388 -0.1682 0.9850\nvn 0.1471 -0.2321 0.9615\nvn -0.6964 -0.2921 0.6555\nvn -0.9490 -0.3152 0.0058\nvn -0.7694 0.1193 -0.6275\nvn -0.0522 0.4025 -0.9139\nvn 0.0031 0.5076 -0.8616\nvn -0.7053 0.2607 -0.6592\nvn 0.6920 0.4010 -0.6003\nvn 0.6443 0.5223 -0.5585\nvn 0.9798 0.1981 0.0274\nvn 0.9314 0.3637 -0.0124\nvn 0.7489 -0.0432 0.6612\nvn 0.7943 0.0840 0.6016\nvn 0.0193 -0.2656 0.9639\nvn 0.0644 -0.2408 0.9684\nvn -0.7075 -0.3245 0.6278\nvn -0.7505 -0.2868 0.5954\nvn -0.9817 -0.1902 0.0023\nvn -0.9959 -0.0618 -0.0661\nvn 0.1558 -0.1548 -0.9756\nvn 0.7490 0.0587 -0.6599\nvn 0.9841 0.1770 -0.0132\nvn 0.7669 0.0595 0.6390\nvn 0.2355 -0.1804 0.9550\nvn 0.1797 0.2153 -0.9598\nvn 0.7317 0.2497 -0.6342\nvn 0.9853 0.1702 -0.0129\nvn 0.7740 0.0499 0.6312\nvn 0.2623 -0.0431 0.9640\nvn 0.2038 0.3414 -0.9175\nvn 0.7886 0.2376 -0.5671\nvn 0.9967 0.0685 0.0432\nvn 0.7622 -0.0278 0.6467\nvn 0.2533 -0.0563 0.9657\nvn -0.7148 -0.2085 -0.6674\nvn 0.0137 -0.1017 -0.9947\nvn 0.7429 0.0031 -0.6694\nvn 0.9997 0.0232 -0.0000\nvn 0.7467 -0.0530 0.6630\nvn 0.0230 -0.1830 0.9828\nvn -0.7042 -0.2631 0.6594\nvn -0.9651 -0.2619 -0.0003\nvn -0.9305 0.3442 0.1249\nvn -0.7269 0.2687 0.6320\nvn -0.6836 0.2431 0.6882\nvn -0.9388 0.3246 0.1150\nvn -0.8229 0.4189 -0.3837\nvn -0.8218 0.3643 -0.4380\nvn -0.4623 0.3872 -0.7977\nvn -0.4250 0.3146 -0.8487\nvn -0.3871 0.1594 0.9081\nvn -0.4411 0.2060 0.8734\nvn -0.1795 0.2589 -0.9490\nvn -0.1585 0.2797 -0.9469\nvn -0.6629 0.1725 0.7285\nvn -0.9602 0.2648 0.0883\nvn -0.8278 0.2795 -0.4864\nvn -0.4296 0.2507 -0.8675\nvn -0.3178 0.0812 0.9447\nvn 0.0000 0.2257 -0.9742\nvn -0.6545 0.1083 0.7482\nvn -0.9778 0.1840 0.1006\nvn -0.8214 0.2268 -0.5233\nvn -0.3881 0.2433 -0.8889\nvn -0.2732 0.0443 0.9609\nvn 0.0000 0.2517 -0.9678\nvn -0.6199 0.0893 0.7796\nvn -0.9856 0.1431 0.0903\nvn -0.8148 0.1839 -0.5498\nvn -0.3740 0.2535 -0.8921\nvn 0.0000 0.3010 -0.9536\nvn -0.2541 0.0410 0.9663\nvn -0.6076 0.0420 0.7931\nvn -0.5980 -0.0347 0.8007\nvn -0.9375 -0.2623 0.2285\nvn -0.9684 -0.1241 0.2161\nvn -0.7920 -0.4096 -0.4527\nvn -0.8297 -0.3137 -0.4616\nvn -0.2858 -0.3760 -0.8814\nvn -0.2590 -0.2981 -0.9187\nvn -0.2287 0.0641 0.9714\nvn -0.2261 0.0876 0.9701\nvn 0.0000 -0.2554 -0.9668\nvn 0.0000 -0.3327 -0.9430\nvn -0.5301 -0.0882 0.8433\nvn -0.8493 -0.4104 0.3321\nvn -0.7116 -0.6099 -0.3486\nvn -0.2332 -0.4742 -0.8489\nvn -0.2270 0.0645 0.9717\nvn 0.0000 -0.3504 -0.9366\nvn -0.5909 0.2845 0.7549\nvn -0.7041 0.6731 0.2262\nvn -0.6750 0.7067 0.2120\nvn -0.5221 0.3539 0.7760\nvn -0.4721 -0.3063 0.8266\nvn -0.1414 -0.2192 0.9654\nvn -0.4331 -0.7302 0.5285\nvn 0.2356 -0.7827 0.5761\nvn -0.3519 -0.9301 -0.1050\nvn 0.3481 -0.9354 -0.0623\nvn -0.2890 -0.5904 -0.7536\nvn 0.1628 -0.5295 -0.8325\nvn -0.4027 0.2159 -0.8895\nvn -0.2563 0.2367 -0.9371\nvn -0.6033 0.6231 -0.4978\nvn -0.5584 0.6612 -0.5009\nvn -0.2101 0.1086 0.9716\nvn -0.4954 -0.0439 0.8675\nvn -0.6827 -0.2557 0.6844\nvn -0.5014 -0.5441 -0.6727\nvn -0.1398 -0.2956 -0.9450\nvn 0.0000 -0.1463 -0.9892\nvn -0.5453 0.8124 0.2065\nvn -0.4171 0.4386 0.7960\nvn -0.1028 -0.1814 0.9780\nvn 0.3036 -0.7578 0.5774\nvn 0.4383 -0.8970 -0.0575\nvn 0.1599 -0.5565 -0.8153\nvn -0.2808 0.2088 -0.9368\nvn -0.4873 0.6958 -0.5276\nvn -0.6286 0.1609 0.7609\nvn -0.3999 0.0846 -0.9127\nvn -0.1346 0.1483 -0.9797\nvn 0.0000 0.1400 -0.9901\nvn -0.1455 0.3738 0.9160\nvn -0.3840 0.3333 0.8610\nvn -0.5499 0.7781 0.3034\nvn -0.4931 0.7170 -0.4927\nvn -0.0883 0.6438 0.7600\nvn -0.3134 0.6461 0.6959\nvn -0.1995 0.4476 -0.8717\nvn 0.0000 0.3229 -0.9464\nvn 0.8813 0.4724 -0.0088\nvn 0.7941 0.4290 0.4305\nvn -0.0864 0.9856 0.1451\nvn 0.0280 0.9633 0.2670\nvn 0.8173 0.5099 -0.2683\nvn 0.8346 0.5002 -0.2308\nvn 0.1703 0.9413 0.2913\nvn 0.3046 0.8919 0.3341\nvn 0.4281 0.3246 0.8434\nvn 0.0000 0.2474 0.9689\nvn 0.0000 0.9969 0.0780\nvn -0.0873 0.9961 0.0095\nvn -0.7519 0.6446 -0.1379\nvn -0.7108 0.6391 0.2936\nvn -0.4981 0.6461 0.5783\nvn -0.3810 0.6395 0.6677\nvn 0.0000 0.5987 -0.8009\nvn -0.4424 0.6083 -0.6590\nvn -0.3468 0.9035 0.2518\nvn -0.3959 0.8547 -0.3358\nvn -0.3311 0.9086 0.2546\nvn -0.4184 0.8819 -0.2170\nvn -0.1143 0.6544 0.7474\nvn -0.2011 0.7588 0.6194\nvn -0.0993 0.7308 0.6753\nvn -0.1946 0.8158 0.5446\nvn -0.2835 0.6131 -0.7374\nvn 0.0000 0.4652 -0.8852\nvn -0.2913 0.6623 -0.6902\nvn 0.0000 0.5503 -0.8350\nvn -0.5202 0.8349 0.1797\nvn -0.2936 0.5365 0.7912\nvn 0.0479 -0.0916 0.9946\nvn 0.4032 -0.7122 0.5746\nvn 0.4545 -0.8882 -0.0677\nvn 0.1287 -0.5681 -0.8128\nvn -0.3250 0.1772 -0.9290\nvn -0.5206 0.6599 -0.5417\nvn -0.5974 0.7709 0.2210\nvn -0.2542 0.5503 0.7953\nvn 0.1685 -0.0062 0.9857\nvn 0.4398 -0.6721 0.5956\nvn 0.3967 -0.9172 -0.0357\nvn 0.0496 -0.6219 -0.7815\nvn -0.4032 0.1236 -0.9067\nvn -0.6385 0.5749 -0.5116\nvn -0.5685 0.7945 0.2133\nvn -0.2400 0.5868 0.7733\nvn 0.2135 -0.0171 0.9768\nvn 0.4756 -0.6876 0.5486\nvn 0.4152 -0.9088 -0.0402\nvn 0.0716 -0.6156 -0.7848\nvn -0.3941 0.1769 -0.9019\nvn -0.6174 0.6261 -0.4762\nvn -0.4967 0.8361 0.2327\nvn -0.1930 0.6250 0.7564\nvn 0.2468 0.0142 0.9689\nvn 0.5123 -0.6868 0.5157\nvn 0.4545 -0.8877 -0.0728\nvn 0.1032 -0.5467 -0.8309\nvn -0.3741 0.2477 -0.8937\nvn -0.5754 0.6948 -0.4315\nvn -0.6893 0.6571 0.3049\nvn -0.3672 0.4369 0.8211\nvn -0.3961 0.3886 0.8319\nvn -0.7006 0.6366 0.3222\nvn 0.0527 -0.1498 0.9873\nvn -0.0166 -0.2036 0.9789\nvn 0.3432 -0.7831 0.5185\nvn 0.2679 -0.8050 0.5293\nvn 0.3268 -0.9419 -0.0771\nvn 0.2604 -0.9629 -0.0707\nvn -0.0390 -0.6161 -0.7867\nvn -0.1134 -0.6437 -0.7568\nvn -0.5172 0.1429 -0.8438\nvn -0.5643 0.1326 -0.8148\nvn -0.7468 0.5476 -0.3773\nvn -0.7632 0.5405 -0.3542\nvn -0.4188 0.3402 0.8419\nvn -0.7050 0.6152 0.3528\nvn -0.0831 -0.2227 0.9713\nvn 0.1923 -0.7967 0.5729\nvn 0.2002 -0.9780 -0.0580\nvn -0.1858 -0.6495 -0.7373\nvn -0.6026 0.1249 -0.7882\nvn -0.7770 0.5413 -0.3211\nvn 0.9305 0.3442 0.1249\nvn 0.9388 0.3246 0.1150\nvn 0.6836 0.2431 0.6882\nvn 0.7269 0.2687 0.6320\nvn 0.8229 0.4189 -0.3837\nvn 0.8218 0.3643 -0.4380\nvn 0.4623 0.3872 -0.7977\nvn 0.4250 0.3146 -0.8487\nvn 0.3871 0.1594 0.9081\nvn 0.4411 0.2060 0.8734\nvn 0.1585 0.2797 -0.9469\nvn 0.1795 0.2589 -0.9490\nvn 0.9602 0.2648 0.0883\nvn 0.6629 0.1725 0.7285\nvn 0.8278 0.2795 -0.4864\nvn 0.4296 0.2507 -0.8675\nvn 0.3178 0.0812 0.9447\nvn 0.9778 0.1840 0.1006\nvn 0.6545 0.1083 0.7482\nvn 0.8214 0.2268 -0.5233\nvn 0.3881 0.2433 -0.8889\nvn 0.2732 0.0443 0.9609\nvn 0.9856 0.1431 0.0903\nvn 0.6199 0.0893 0.7796\nvn 0.8148 0.1839 -0.5498\nvn 0.3740 0.2535 -0.8921\nvn 0.2541 0.0410 0.9663\nvn 0.6076 0.0420 0.7931\nvn 0.9684 -0.1241 0.2161\nvn 0.9375 -0.2623 0.2285\nvn 0.5980 -0.0347 0.8007\nvn 0.8297 -0.3137 -0.4616\nvn 0.7920 -0.4096 -0.4527\nvn 0.2590 -0.2981 -0.9187\nvn 0.2858 -0.3760 -0.8814\nvn 0.2287 0.0641 0.9714\nvn 0.2261 0.0876 0.9701\nvn 0.8493 -0.4104 0.3321\nvn 0.5301 -0.0882 0.8433\nvn 0.7116 -0.6099 -0.3486\nvn 0.2332 -0.4742 -0.8489\nvn 0.2270 0.0645 0.9717\nvn 0.5909 0.2845 0.7549\nvn 0.5221 0.3539 0.7760\nvn 0.6750 0.7067 0.2120\nvn 0.7041 0.6731 0.2262\nvn 0.4721 -0.3063 0.8266\nvn 0.1414 -0.2192 0.9654\nvn 0.4331 -0.7302 0.5285\nvn -0.2356 -0.7827 0.5761\nvn 0.3519 -0.9301 -0.1050\nvn -0.3481 -0.9354 -0.0623\nvn 0.2890 -0.5904 -0.7536\nvn -0.1628 -0.5295 -0.8325\nvn 0.4027 0.2159 -0.8895\nvn 0.2563 0.2367 -0.9371\nvn 0.6033 0.6231 -0.4978\nvn 0.5584 0.6612 -0.5009\nvn 0.2101 0.1086 0.9716\nvn 0.4954 -0.0439 0.8675\nvn 0.6827 -0.2557 0.6844\nvn 0.5014 -0.5441 -0.6727\nvn 0.1398 -0.2956 -0.9450\nvn 0.4171 0.4386 0.7960\nvn 0.5453 0.8124 0.2065\nvn 0.1028 -0.1814 0.9780\nvn -0.3036 -0.7578 0.5774\nvn -0.4383 -0.8970 -0.0575\nvn -0.1599 -0.5565 -0.8153\nvn 0.2808 0.2088 -0.9368\nvn 0.4873 0.6958 -0.5276\nvn 0.6286 0.1609 0.7609\nvn 0.3999 0.0846 -0.9127\nvn 0.1346 0.1483 -0.9797\nvn 0.1455 0.3738 0.9160\nvn 0.3840 0.3333 0.8610\nvn 0.4931 0.7170 -0.4927\nvn 0.5499 0.7781 0.3034\nvn 0.0883 0.6438 0.7600\nvn 0.3134 0.6461 0.6959\nvn 0.1995 0.4476 -0.8717\nvn -0.8813 0.4724 -0.0088\nvn -0.0280 0.9633 0.2670\nvn 0.0864 0.9856 0.1451\nvn -0.7941 0.4290 0.4305\nvn -0.8173 0.5099 -0.2683\nvn -0.3046 0.8919 0.3341\nvn -0.1703 0.9413 0.2913\nvn -0.8346 0.5002 -0.2308\nvn -0.4281 0.3246 0.8434\nvn 0.0873 0.9961 0.0095\nvn 0.7108 0.6391 0.2936\nvn 0.7519 0.6446 -0.1379\nvn 0.3810 0.6395 0.6677\nvn 0.4981 0.6461 0.5783\nvn 0.4424 0.6083 -0.6590\nvn 0.3959 0.8547 -0.3358\nvn 0.3468 0.9035 0.2518\nvn 0.4184 0.8819 -0.2170\nvn 0.3311 0.9086 0.2546\nvn 0.1143 0.6544 0.7474\nvn 0.2011 0.7588 0.6194\nvn 0.1946 0.8158 0.5446\nvn 0.0993 0.7308 0.6753\nvn 0.2835 0.6131 -0.7374\nvn 0.2913 0.6623 -0.6902\nvn 0.2936 0.5365 0.7912\nvn 0.5202 0.8349 0.1797\nvn -0.0479 -0.0916 0.9946\nvn -0.4032 -0.7122 0.5746\nvn -0.4545 -0.8882 -0.0677\nvn -0.1287 -0.5681 -0.8128\nvn 0.3250 0.1772 -0.9290\nvn 0.5206 0.6599 -0.5417\nvn 0.2542 0.5503 0.7953\nvn 0.5974 0.7709 0.2210\nvn -0.1685 -0.0062 0.9857\nvn -0.4398 -0.6721 0.5956\nvn -0.3967 -0.9172 -0.0357\nvn -0.0496 -0.6219 -0.7815\nvn 0.4032 0.1236 -0.9067\nvn 0.6385 0.5749 -0.5116\nvn 0.2400 0.5868 0.7733\nvn 0.5685 0.7945 0.2133\nvn -0.2135 -0.0171 0.9768\nvn -0.4756 -0.6876 0.5486\nvn -0.4152 -0.9088 -0.0402\nvn -0.0716 -0.6156 -0.7848\nvn 0.3941 0.1769 -0.9019\nvn 0.6174 0.6261 -0.4762\nvn 0.1930 0.6250 0.7564\nvn 0.4967 0.8361 0.2327\nvn -0.2468 0.0142 0.9689\nvn -0.5123 -0.6868 0.5157\nvn -0.4545 -0.8877 -0.0728\nvn -0.1032 -0.5467 -0.8309\nvn 0.3741 0.2477 -0.8937\nvn 0.5754 0.6948 -0.4315\nvn 0.6893 0.6571 0.3049\nvn 0.7006 0.6366 0.3222\nvn 0.3961 0.3886 0.8319\nvn 0.3672 0.4369 0.8211\nvn 0.0166 -0.2036 0.9789\nvn -0.0527 -0.1498 0.9873\nvn -0.2679 -0.8050 0.5293\nvn -0.3432 -0.7831 0.5185\nvn -0.2604 -0.9629 -0.0707\nvn -0.3268 -0.9419 -0.0771\nvn 0.1134 -0.6437 -0.7568\nvn 0.0390 -0.6161 -0.7867\nvn 0.5643 0.1326 -0.8148\nvn 0.5172 0.1429 -0.8438\nvn 0.7632 0.5405 -0.3542\nvn 0.7468 0.5476 -0.3773\nvn 0.7050 0.6152 0.3528\nvn 0.4188 0.3402 0.8419\nvn 0.0831 -0.2227 0.9713\nvn -0.1923 -0.7967 0.5729\nvn -0.2002 -0.9780 -0.0580\nvn 0.1858 -0.6495 -0.7373\nvn 0.6026 0.1249 -0.7882\nvn 0.7770 0.5413 -0.3211\nusemtl Cornea\ns 1\nf 268/1/1 269/2/2 270/3/3 267/4/4\nf 267/4/4 270/3/3 271/5/5 266/6/6\nf 266/6/6 271/5/5 272/7/7 265/8/8\nf 271/5/5 274/9/9 273/10/10 272/7/7\nf 270/3/3 275/11/11 274/9/9 271/5/5\nf 269/2/2 276/12/12 275/11/11 270/3/3\nf 276/12/12 277/13/13 278/14/14 275/11/11\nf 275/11/11 278/14/14 279/15/15 274/9/9\nf 274/9/9 279/15/15 280/16/16 273/10/10\nf 279/15/15 282/17/17 281/18/18 280/16/16\nf 278/14/14 283/19/19 282/17/17 279/15/15\nf 277/13/13 284/20/20 283/19/19 278/14/14\nf 284/20/20 285/21/21 286/22/22 283/19/19\nf 283/19/19 286/22/22 287/23/23 282/17/17\nf 282/17/17 287/23/23 288/24/24 281/18/18\nf 287/23/23 290/25/25 289/26/26 288/24/24\nf 286/22/22 291/27/27 290/25/25 287/23/23\nf 285/21/21 292/28/28 291/27/27 286/22/22\nf 292/28/28 293/29/29 294/30/30 291/27/27\nf 291/27/27 294/30/30 295/31/31 290/25/25\nf 290/25/25 295/31/31 296/32/32 289/26/26\nf 295/31/31 266/6/6 265/8/8 296/32/32\nf 294/30/30 267/4/4 266/6/6 295/31/31\nf 293/29/29 268/1/1 267/4/4 294/30/30\nf 300/33/7 326/34/8 327/35/6 299/36/5\nf 299/36/5 327/35/6 328/37/4 298/38/3\nf 298/38/3 328/37/4 329/39/1 297/40/2\nf 303/41/11 298/38/3 297/40/2 304/42/12\nf 302/43/33 299/36/5 298/38/3 303/41/11\nf 301/44/10 300/33/7 299/36/5 302/43/33\nf 308/45/16 301/44/10 302/43/33 307/46/15\nf 307/46/15 302/43/33 303/41/11 306/47/14\nf 306/47/14 303/41/11 304/42/12 305/48/13\nf 312/49/19 306/47/14 305/48/13 313/50/20\nf 311/51/17 307/46/15 306/47/14 312/49/19\nf 310/52/18 308/45/16 307/46/15 311/51/17\nf 317/53/24 310/52/18 311/51/17 316/54/23\nf 316/54/23 311/51/17 312/49/19 315/55/22\nf 315/55/22 312/49/19 313/50/20 314/56/21\nf 320/57/27 315/55/22 314/56/21 321/58/28\nf 319/59/25 316/54/23 315/55/22 320/57/27\nf 318/60/26 317/53/24 316/54/23 319/59/25\nf 325/61/32 318/60/26 319/59/25 324/62/31\nf 324/62/31 319/59/25 320/57/27 323/63/30\nf 323/63/30 320/57/27 321/58/28 322/64/29\nf 328/37/4 323/63/30 322/64/29 329/39/1\nf 327/35/6 324/62/31 323/63/30 328/37/4\nf 326/34/8 325/61/32 324/62/31 327/35/6\nusemtl Iris\nf 272/7/7 264/65/34 265/8/8\nf 273/10/10 264/65/34 272/7/7\nf 280/16/16 264/65/34 273/10/10\nf 281/18/18 264/65/34 280/16/16\nf 288/24/24 264/65/34 281/18/18\nf 289/26/26 264/65/34 288/24/24\nf 296/32/32 264/65/34 289/26/26\nf 265/8/8 264/65/34 296/32/32\nf 309/66/34 326/34/8 300/33/7\nf 309/66/34 300/33/7 301/44/10\nf 309/66/34 301/44/10 308/45/16\nf 309/66/34 308/45/16 310/52/18\nf 309/66/34 310/52/18 317/53/24\nf 309/66/34 317/53/24 318/60/26\nf 309/66/34 318/60/26 325/61/32\nf 309/66/34 325/61/32 326/34/8\nusemtl Leather\nf 17/67/35 22/68/36 24/69/37 20/70/38\nf 22/68/36 18/71/39 21/72/40 24/69/37\nf 23/73/41 16/74/42 25/75/43 26/76/44\nf 19/77/45 23/73/41 26/76/44 27/78/46\nf 16/74/42 28/79/47 30/80/48 25/75/43\nf 28/79/47 32/81/49 31/82/50 30/80/48\nf 31/82/50 32/81/49 34/83/51 33/84/52\nf 33/85/52 34/86/51 36/87/53 35/88/54\nf 35/88/54 36/87/53 38/89/55 37/90/56\nf 19/77/45 27/78/46 37/90/56 38/89/55\nf 17/67/35 20/70/38 40/91/57 29/92/58\nf 18/71/39 39/93/59 41/94/60 21/72/40\nf 21/72/40 41/94/60 42/95/61 24/69/37\nf 20/70/38 24/69/37 42/95/61 40/91/57\nf 32/81/49 28/79/47 2661/96/62 2662/97/63\nf 34/83/51 32/81/49 2662/97/63 2663/98/64\nf 36/87/53 34/86/51 2663/99/64 2664/100/65\nf 38/89/55 36/87/53 2664/100/65 2665/101/66\nf 29/92/58 40/91/57 2667/102/67 2666/103/68\nf 41/94/60 39/93/59 2668/104/69 2669/105/70\nf 42/95/61 41/94/60 2669/105/70 2670/106/71\nf 40/91/57 42/95/61 2670/106/71 2667/102/67\nf 45/107/72 43/108/73 2671/109/74 2672/110/75\nf 46/111/76 45/107/72 2672/110/75 2673/112/76\nf 47/113/77 46/114/76 2673/115/76 2674/116/78\nf 48/117/79 47/113/77 2674/116/78 2675/118/80\nf 44/119/81 50/120/76 2677/121/76 2676/122/82\nf 51/123/76 49/124/83 2678/125/84 2679/126/76\nf 52/127/76 51/123/76 2679/126/76 2680/128/76\nf 50/120/76 52/129/76 2680/130/76 2677/121/76\nf 55/131/85 53/132/86 2681/133/87 2684/134/88\nf 56/135/89 55/131/85 2684/134/88 2685/136/89\nf 57/137/90 56/138/89 2685/139/89 2686/140/91\nf 58/141/92 57/137/90 2686/140/91 2687/142/93\nf 54/143/94 60/144/95 2691/145/96 2690/146/97\nf 61/147/98 59/148/99 2692/149/100 2693/150/101\nf 62/151/102 61/147/98 2693/150/101 2694/152/102\nf 60/144/95 62/153/102 2694/152/102 2691/145/96\nf 65/154/103 2682/155/104 2695/156/104 2697/157/105\nf 66/158/89 65/154/103 2697/157/105 2698/159/89\nf 67/160/106 66/161/89 2698/159/89 2699/162/107\nf 2688/163/108 67/160/106 2699/162/107 2700/164/108\nf 2696/165/109 2701/166/109 76/167/110 74/168/111\nf 74/168/111 76/167/110 75/169/112\nf 2683/170/113 2689/171/113 77/172/113 73/173/113\nf 63/174/114 70/175/115 71/176/116 69/177/117\nf 70/175/115 72/178/112 71/176/116\nf 78/179/118 16/74/42 23/73/41 80/180/119\nf 80/180/119 22/68/36 17/67/35 78/179/118\nf 19/77/45 79/181/120 80/180/119 23/73/41\nf 79/181/120 18/71/39 22/68/36 80/180/119\nf 16/74/42 78/179/118 81/182/121 28/79/47\nf 78/179/118 17/67/35 29/92/58 81/182/121\nf 18/71/39 79/181/120 82/183/122 39/93/59\nf 79/181/120 19/77/45 38/89/55 82/183/122\nf 28/79/47 81/182/121 2702/184/123 2661/96/62\nf 81/182/121 29/92/58 2666/103/68 2702/184/123\nf 39/93/59 82/183/122 2703/185/124 2668/104/69\nf 82/183/122 38/89/55 2665/101/66 2703/185/124\nf 43/108/73 83/186/125 2704/187/126 2671/109/74\nf 83/186/125 44/119/81 2676/122/82 2704/187/126\nf 49/124/83 84/188/127 2705/189/128 2678/125/84\nf 84/188/127 48/117/79 2675/118/80 2705/189/128\nf 53/132/86 85/190/129 2706/191/130 2681/133/87\nf 85/190/129 54/143/94 2690/146/97 2706/191/130\nf 59/148/99 86/192/131 2707/193/132 2692/149/100\nf 86/192/131 58/141/92 2687/142/93 2707/193/132\nf 69/177/117 88/194/133 87/195/134 63/174/114\nf 88/194/133 68/196/135 64/197/135 87/195/134\nf 26/76/44 25/75/43 89/198/136 90/199/137\nf 27/78/46 26/76/44 90/199/137 91/200/138\nf 25/75/43 30/80/48 92/201/139 89/198/136\nf 30/80/48 31/82/50 93/202/140 92/201/139\nf 31/82/50 33/84/52 94/203/141 93/202/140\nf 33/85/52 35/88/54 95/204/142 94/205/141\nf 35/88/54 37/90/56 96/206/143 95/204/142\nf 37/90/56 27/78/46 91/200/138 96/206/143\nf 90/199/137 89/198/136 98/207/144 97/208/145\nf 91/200/138 90/199/137 97/208/145 99/209/146\nf 89/198/136 92/201/139 100/210/147 98/207/144\nf 92/201/139 93/202/140 101/211/148 100/210/147\nf 93/202/140 94/203/141 102/212/149 101/211/148\nf 94/205/141 95/204/142 103/213/150 102/214/149\nf 95/204/142 96/206/143 104/215/151 103/213/150\nf 96/206/143 91/200/138 99/209/146 104/215/151\nf 97/208/145 98/207/144 106/216/152 105/217/153\nf 99/209/146 97/208/145 105/217/153 107/218/154\nf 98/207/144 100/210/147 108/219/155 106/216/152\nf 100/210/147 101/211/148 109/220/156 108/219/155\nf 101/211/148 102/212/149 110/221/157 109/220/156\nf 102/214/149 103/213/150 111/222/158 110/223/157\nf 103/213/150 104/215/151 112/224/159 111/222/158\nf 104/215/151 99/209/146 107/218/154 112/224/159\nf 154/225/160 116/226/161 117/227/162 151/228/163\nf 154/225/160 153/229/164 118/230/165 116/226/161\nf 153/229/164 2708/231/166 119/232/167 118/230/165\nf 2708/233/166 2709/234/168 120/235/169 119/236/167\nf 2709/234/168 152/237/170 121/238/171 120/235/169\nf 152/237/170 150/239/172 122/240/173 121/238/171\nf 151/228/163 117/227/162 123/241/174 2715/242/175\nf 150/239/172 2715/242/175 123/241/174 122/240/173\nf 116/226/161 124/243/176 125/244/177 117/227/162\nf 116/226/161 118/230/165 126/245/178 124/243/176\nf 118/230/165 119/232/167 127/246/179 126/245/178\nf 119/236/167 120/235/169 128/247/180 127/248/179\nf 120/235/169 121/238/171 129/249/181 128/247/180\nf 121/238/171 122/240/173 130/250/182 129/249/181\nf 117/227/162 125/244/177 131/251/183 123/241/174\nf 122/240/173 123/241/174 131/251/183 130/250/182\nf 124/243/176 142/252/184 141/253/185 125/244/177\nf 142/252/184 132/254/186 133/255/187 141/253/185\nf 126/245/178 143/256/188 142/252/184 124/243/176\nf 143/256/188 134/257/189 132/254/186 142/252/184\nf 129/249/181 145/258/190 144/259/191 128/247/180\nf 145/258/190 136/260/192 135/261/193 144/259/191\nf 130/250/182 146/262/194 145/258/190 129/249/181\nf 146/262/194 137/263/195 136/260/192 145/258/190\nf 125/244/177 141/253/185 147/264/196 131/251/183\nf 141/253/185 133/255/187 138/265/197 147/264/196\nf 131/251/183 147/264/196 146/262/194 130/250/182\nf 147/264/196 138/265/197 137/263/195 146/262/194\nf 128/247/180 144/259/191 148/266/198 127/248/179\nf 144/259/191 135/261/193 139/267/199 148/266/198\nf 127/246/179 149/268/200 143/256/188 126/245/178\nf 149/268/200 140/269/201 134/257/189 143/256/188\nf 105/217/153 106/216/152 2710/270/202 115/271/203\nf 107/218/154 105/217/153 115/271/203 2714/272/204\nf 106/216/152 108/219/155 2711/273/205 2710/270/202\nf 108/219/155 109/220/156 114/274/206 2711/273/205\nf 109/220/156 110/221/157 113/275/207 114/274/206\nf 110/223/157 111/222/158 2712/276/208 113/277/207\nf 111/222/158 112/224/159 2713/278/209 2712/276/208\nf 112/224/159 107/218/154 2714/272/204 2713/278/209\nf 173/279/210 174/280/211 156/281/212 155/282/213\nf 157/283/214 156/281/212 174/280/211 175/284/215\nf 158/285/216 157/283/214 175/284/215 176/286/217\nf 159/287/218 158/285/216 176/286/217 177/288/219\nf 160/289/220 159/287/218 177/288/219 178/290/221\nf 161/291/222 160/289/220 178/290/221 179/292/223\nf 162/293/224 161/291/222 179/292/223 180/294/225\nf 163/295/226 162/293/224 180/294/225 181/296/227\nf 164/297/228 163/295/226 181/296/227 182/298/229\nf 165/299/230 164/297/228 182/298/229 183/300/231\nf 166/301/232 165/299/230 183/300/231 184/302/233\nf 167/303/234 166/301/232 184/302/233 185/304/235\nf 168/305/236 167/303/234 185/304/235 186/306/237\nf 169/307/238 168/305/236 186/306/237 187/308/239\nf 170/309/240 169/307/238 187/308/239 188/310/241\nf 171/311/242 170/309/240 188/310/241 189/312/243\nf 172/313/244 171/311/242 189/312/243 190/314/245\nf 172/313/244 190/314/245 173/279/210 155/282/213\nf 174/280/211 173/279/210 192/315/246 191/316/247\nf 175/284/215 174/280/211 191/316/247 193/317/248\nf 176/286/217 175/284/215 193/317/248 194/318/249\nf 177/288/219 176/286/217 194/318/249 195/319/250\nf 178/290/221 177/288/219 195/319/250 196/320/251\nf 179/292/223 178/290/221 196/320/251 197/321/252\nf 180/294/225 179/292/223 197/321/252 198/322/253\nf 181/296/227 180/294/225 198/322/253 199/323/254\nf 182/298/229 181/296/227 199/323/254 200/324/255\nf 183/300/231 182/298/229 200/324/255 201/325/256\nf 184/302/233 183/300/231 201/325/256 202/326/257\nf 185/304/235 184/302/233 202/326/257 203/327/258\nf 186/306/237 185/304/235 203/327/258 204/328/259\nf 187/308/239 186/306/237 204/328/259 205/329/260\nf 188/310/241 187/308/239 205/329/260 206/330/261\nf 189/312/243 188/310/241 206/330/261 207/331/262\nf 190/314/245 189/312/243 207/331/262 208/332/263\nf 190/314/245 208/332/263 192/315/246 173/279/210\nf 191/316/247 192/315/246 209/333/264\nf 193/317/248 191/316/247 209/333/264\nf 194/318/249 193/317/248 209/333/264\nf 195/319/250 194/318/249 209/333/264\nf 196/320/251 195/319/250 209/333/264\nf 197/321/252 196/320/251 209/333/264\nf 198/322/253 197/321/252 209/333/264\nf 199/323/254 198/322/253 209/333/264\nf 200/324/255 199/323/254 209/333/264\nf 201/325/256 200/324/255 209/333/264\nf 202/326/257 201/325/256 209/333/264\nf 203/327/258 202/326/257 209/333/264\nf 204/328/259 203/327/258 209/333/264\nf 205/329/260 204/328/259 209/333/264\nf 206/330/261 205/329/260 209/333/264\nf 207/331/262 206/330/261 209/333/264\nf 208/332/263 207/331/262 209/333/264\nf 208/332/263 209/333/264 192/315/246\nf 170/309/240 171/311/242 220/334/265 221/335/266\nf 169/307/238 170/309/240 221/335/266 222/336/267\nf 168/305/236 169/307/238 222/336/267 213/337/268\nf 171/311/242 172/313/244 219/338/269 220/334/265\nf 155/282/213 218/339/270 219/338/269 172/313/244\nf 155/282/213 156/281/212 211/340/271 218/339/270\nf 156/281/212 157/283/214 223/341/272 211/340/271\nf 157/283/214 158/285/216 224/342/273 223/341/272\nf 158/285/216 159/287/218 225/343/274 224/342/273\nf 159/287/218 160/289/220 226/344/275 225/343/274\nf 160/289/220 161/291/222 227/345/276 226/344/275\nf 161/291/222 162/293/224 212/346/277 227/345/276\nf 162/293/224 163/295/226 228/347/278 212/346/277\nf 163/295/226 164/297/228 229/348/279 228/347/278\nf 164/297/228 165/299/230 230/349/280 229/348/279\nf 165/299/230 166/301/232 231/350/281 230/349/280\nf 166/301/232 167/303/234 232/351/282 231/350/281\nf 167/303/234 168/305/236 213/337/268 232/351/282\nf 235/352/283 234/353/284 214/354/285\nf 222/336/267 213/337/268 234/353/284 235/352/283\nf 214/354/285 215/355/286 236/356/287 235/352/283\nf 235/352/283 236/356/287 221/335/266 222/336/267\nf 215/355/286 216/357/288 237/358/289 236/356/287\nf 236/356/287 237/358/289 220/334/265 221/335/266\nf 216/357/288 217/359/290 238/360/291 237/358/289\nf 237/358/289 238/360/291 219/338/269 220/334/265\nf 217/359/290 210/361/292 239/362/293 238/360/291\nf 238/360/291 239/362/293 218/339/270 219/338/269\nf 233/363/294 239/362/293 210/361/292\nf 211/340/271 218/339/270 239/362/293 233/363/294\nf 212/346/277 227/345/276 240/364/295 246/365/296\nf 246/365/296 240/364/295 251/366/297\nf 241/367/298 240/364/295 227/345/276 226/344/275\nf 247/368/299 251/366/297 240/364/295 241/367/298\nf 242/369/300 241/367/298 226/344/275 225/343/274\nf 248/370/301 247/368/299 241/367/298 242/369/300\nf 243/371/302 242/369/300 225/343/274 224/342/273\nf 249/372/303 248/370/301 242/369/300 243/371/302\nf 244/373/304 243/371/302 224/342/273 223/341/272\nf 250/374/305 249/372/303 243/371/302 244/373/304\nf 223/341/272 211/340/271 245/375/306 244/373/304\nf 244/373/304 245/375/306 250/374/305\nf 259/376/307 258/377/308 253/378/309\nf 228/347/278 212/346/277 258/377/308 259/376/307\nf 253/378/309 254/379/310 260/380/311 259/376/307\nf 259/376/307 260/380/311 229/348/279 228/347/278\nf 254/379/310 255/381/312 261/382/313 260/380/311\nf 260/380/311 261/382/313 230/349/280 229/348/279\nf 255/381/312 256/383/314 262/384/315 261/382/313\nf 261/382/313 262/384/315 231/350/281 230/349/280\nf 256/383/314 252/385/316 263/386/317 262/384/315\nf 262/384/315 263/386/317 232/351/282 231/350/281\nf 257/387/318 263/386/317 252/385/316\nf 213/337/268 232/351/282 263/386/317 257/387/318\nf 2717/388/319 2716/389/320 2740/390/321 2741/391/322\nf 2718/392/323 2717/388/319 2741/391/322 2742/393/324\nf 2718/392/323 2742/393/324 2743/394/325 2719/395/326\nf 2719/395/326 2743/394/325 2744/396/327 2720/397/328\nf 2721/398/329 2720/397/328 2744/396/327 2745/399/330\nf 2721/398/329 2745/399/330 2746/400/331 2722/401/332\nf 2722/401/332 2746/400/331 2747/402/333 2723/403/334\nf 2723/403/334 2747/402/333 2748/404/335 2724/405/336\nf 2724/405/336 2748/404/335 2749/406/337 2725/407/338\nf 2726/408/339 2725/407/338 2749/406/337 2750/409/340\nf 2727/410/341 2726/411/339 2750/412/340 2751/413/342\nf 2728/414/343 2727/410/341 2751/413/342 2752/415/344\nf 2728/414/343 2752/415/344 2753/416/345 2729/417/346\nf 2729/417/346 2753/416/345 2754/418/347 2730/419/348\nf 2730/419/348 2754/418/347 2755/420/349 2731/421/350\nf 2732/422/351 2731/421/350 2755/420/349 2756/423/352\nf 2732/422/351 2756/423/352 2757/424/353 2733/425/354\nf 2734/426/355 2733/425/354 2757/424/353 2758/427/356\nf 2735/428/357 2734/426/355 2758/427/356 2759/429/358\nf 2736/430/359 2735/428/357 2759/429/358 2760/431/360\nf 2736/430/359 2760/431/360 2761/432/361 2737/433/362\nf 2738/434/363 2737/433/362 2761/432/361 2762/435/364\nf 2739/436/365 2738/434/363 2762/435/364 2763/437/366\nf 2740/390/321 2716/389/320 2739/436/365 2763/437/366\nf 500/438/367 499/439/368 523/440/369 524/441/370\nf 501/442/371 500/438/367 524/441/370 525/443/372\nf 502/444/373 501/442/371 525/443/372 526/445/374\nf 503/446/375 502/444/373 526/445/374 527/447/376\nf 504/448/377 503/446/375 527/447/376 528/449/378\nf 505/450/379 504/448/377 528/449/378 529/451/380\nf 506/452/381 505/450/379 529/451/380 530/453/382\nf 507/454/383 506/452/381 530/453/382 531/455/384\nf 508/456/385 507/454/383 531/455/384 532/457/386\nf 509/458/387 508/456/385 532/457/386 533/459/388\nf 510/460/389 509/461/387 533/462/388 534/463/390\nf 511/464/391 510/460/389 534/463/390 535/465/392\nf 512/466/393 511/464/391 535/465/392 536/467/393\nf 513/468/394 512/466/393 536/467/393 537/469/395\nf 514/470/396 513/468/394 537/469/395 538/471/397\nf 515/472/398 514/470/396 538/471/397 539/473/399\nf 516/474/400 515/472/398 539/473/399 540/475/401\nf 517/476/402 516/474/400 540/475/401 541/477/403\nf 518/478/404 517/476/402 541/477/403 542/479/405\nf 519/480/406 518/478/404 542/479/405 543/481/407\nf 520/482/408 519/480/406 543/481/407 544/483/409\nf 521/484/410 520/482/408 544/483/409 545/485/411\nf 522/486/412 521/484/410 545/485/411 546/487/413\nf 499/439/368 522/486/412 546/487/413 523/440/369\nf 475/488/414 476/489/415 548/490/416 547/491/417\nf 476/489/415 477/492/418 549/493/419 548/490/416\nf 477/492/418 478/494/420 550/495/421 549/493/419\nf 478/494/420 479/496/422 551/497/423 550/495/421\nf 479/496/422 480/498/424 552/499/425 551/497/423\nf 480/498/424 481/500/426 553/501/427 552/499/425\nf 481/500/426 482/502/428 554/503/429 553/501/427\nf 482/502/428 483/504/430 555/505/431 554/503/429\nf 483/504/430 484/506/432 556/507/433 555/505/431\nf 484/506/432 485/508/434 557/509/435 556/507/433\nf 485/510/434 486/511/436 558/512/437 557/513/435\nf 486/511/436 487/514/438 559/515/439 558/512/437\nf 487/514/438 488/516/440 560/517/441 559/515/439\nf 488/516/440 489/518/442 561/519/443 560/517/441\nf 489/518/442 490/520/444 562/521/445 561/519/443\nf 490/520/444 491/522/446 563/523/447 562/521/445\nf 491/522/446 492/524/448 564/525/449 563/523/447\nf 492/524/448 493/526/450 565/527/451 564/525/449\nf 493/526/450 494/528/452 566/529/453 565/527/451\nf 494/528/452 495/530/454 567/531/455 566/529/453\nf 495/530/454 496/532/456 568/533/457 567/531/455\nf 496/532/456 497/534/458 569/535/459 568/533/457\nf 497/534/458 498/536/460 570/537/461 569/535/459\nf 498/536/460 475/488/414 547/491/417 570/537/461\nf 572/538/462 575/539/463 579/540/464 577/541/465\nf 577/541/465 579/540/464 576/542/466 573/543/467\nf 580/544/468 571/545/469 578/546/470 581/547/471\nf 574/548/472 582/549/473 581/547/471 578/546/470\nf 571/545/469 580/544/468 585/550/474 583/551/475\nf 583/551/475 585/550/474 586/552/476 587/553/477\nf 586/552/476 588/554/478 589/555/479 587/553/477\nf 588/556/478 590/557/480 591/558/481 589/559/479\nf 590/557/480 592/560/482 593/561/483 591/558/481\nf 574/548/472 593/561/483 592/560/482 582/549/473\nf 572/538/462 584/562/484 595/563/485 575/539/463\nf 573/543/467 576/542/466 596/564/486 594/565/487\nf 576/542/466 579/540/464 597/566/488 596/564/486\nf 575/539/463 595/563/485 597/566/488 579/540/464\nf 587/553/477 2765/567/489 2764/568/490 583/551/475\nf 589/555/479 2766/569/491 2765/567/489 587/553/477\nf 591/558/481 2767/570/492 2766/571/491 589/559/479\nf 593/561/483 2768/572/493 2767/570/492 591/558/481\nf 584/562/484 2769/573/494 2770/574/495 595/563/485\nf 596/564/486 2772/575/496 2771/576/497 594/565/487\nf 597/566/488 2773/577/498 2772/575/496 596/564/486\nf 595/563/485 2770/574/495 2773/577/498 597/566/488\nf 600/578/499 2775/579/500 2774/580/501 598/581/502\nf 601/582/76 2776/583/76 2775/579/500 600/578/499\nf 602/584/503 2777/585/504 2776/586/76 601/587/76\nf 603/588/505 2778/589/506 2777/585/504 602/584/503\nf 599/590/507 2779/591/508 2780/592/76 605/593/76\nf 606/594/76 2782/595/76 2781/596/509 604/597/510\nf 607/598/76 2783/599/76 2782/595/76 606/594/76\nf 605/593/76 2780/592/76 2783/600/76 607/601/76\nf 610/602/511 2787/603/512 2784/604/513 608/605/514\nf 611/606/515 2788/607/515 2787/603/512 610/602/511\nf 612/608/516 2789/609/517 2788/610/515 611/611/515\nf 613/612/518 2790/613/519 2789/609/517 612/608/516\nf 609/614/520 2793/615/521 2794/616/522 615/617/523\nf 616/618/524 2796/619/525 2795/620/526 614/621/527\nf 617/622/528 2797/623/528 2796/619/525 616/618/524\nf 615/617/523 2794/616/522 2797/623/528 617/624/528\nf 620/625/529 2800/626/530 2798/627/531 2785/628/531\nf 621/629/515 2801/630/515 2800/626/530 620/625/529\nf 622/631/532 2802/632/533 2801/630/515 621/633/515\nf 2791/634/534 2803/635/534 2802/632/533 622/631/532\nf 2799/636/535 629/637/536 631/638/537 2804/639/535\nf 629/637/536 630/640/112 631/638/537\nf 2786/641/538 628/642/538 632/643/538 2792/644/538\nf 618/645/539 624/646/540 626/647/541 625/648/542\nf 625/648/542 626/647/541 627/649/112\nf 578/546/470 571/545/469 633/650/543 635/651/544\nf 635/651/544 633/650/543 572/538/462 577/541/465\nf 574/548/472 578/546/470 635/651/544 634/652/545\nf 634/652/545 635/651/544 577/541/465 573/543/467\nf 571/545/469 583/551/475 636/653/546 633/650/543\nf 633/650/543 636/653/546 584/562/484 572/538/462\nf 573/543/467 594/565/487 637/654/547 634/652/545\nf 634/652/545 637/654/547 593/561/483 574/548/472\nf 583/551/475 2764/568/490 2805/655/548 636/653/546\nf 636/653/546 2805/655/548 2769/573/494 584/562/484\nf 594/565/487 2771/576/497 2806/656/549 637/654/547\nf 637/654/547 2806/656/549 2768/572/493 593/561/483\nf 598/581/502 2774/580/501 2807/657/550 638/658/551\nf 638/658/551 2807/657/550 2779/591/508 599/590/507\nf 604/597/510 2781/596/509 2808/659/552 639/660/553\nf 639/660/553 2808/659/552 2778/589/506 603/588/505\nf 608/605/514 2784/604/513 2809/661/554 640/662/555\nf 640/662/555 2809/661/554 2793/615/521 609/614/520\nf 614/621/527 2795/620/526 2810/663/556 641/664/557\nf 641/664/557 2810/663/556 2790/613/519 613/612/518\nf 624/646/540 618/645/539 642/665/558 643/666/559\nf 643/666/559 642/665/558 619/667/560 623/668/560\nf 581/547/471 645/669/561 644/670/562 580/544/468\nf 582/549/473 646/671/563 645/669/561 581/547/471\nf 580/544/468 644/670/562 647/672/564 585/550/474\nf 585/550/474 647/672/564 648/673/565 586/552/476\nf 586/552/476 648/673/565 649/674/566 588/554/478\nf 588/556/478 649/675/566 650/676/567 590/557/480\nf 590/557/480 650/676/567 651/677/568 592/560/482\nf 592/560/482 651/677/568 646/671/563 582/549/473\nf 645/669/561 652/678/569 653/679/570 644/670/562\nf 646/671/563 654/680/571 652/678/569 645/669/561\nf 644/670/562 653/679/570 655/681/572 647/672/564\nf 647/672/564 655/681/572 656/682/573 648/673/565\nf 648/673/565 656/682/573 657/683/574 649/674/566\nf 649/675/566 657/684/574 658/685/575 650/676/567\nf 650/676/567 658/685/575 659/686/576 651/677/568\nf 651/677/568 659/686/576 654/680/571 646/671/563\nf 652/678/569 660/687/577 661/688/578 653/679/570\nf 654/680/571 662/689/579 660/687/577 652/678/569\nf 653/679/570 661/688/578 663/690/580 655/681/572\nf 655/681/572 663/690/580 664/691/581 656/682/573\nf 656/682/573 664/691/581 665/692/582 657/683/574\nf 657/684/574 665/693/582 666/694/583 658/685/575\nf 658/685/575 666/694/583 667/695/584 659/686/576\nf 659/686/576 667/695/584 662/689/579 654/680/571\nf 709/696/585 706/697/586 672/698/587 671/699/588\nf 709/696/585 671/699/588 673/700/589 708/701/590\nf 708/701/590 673/700/589 674/702/591 2811/703/592\nf 2811/704/592 674/705/591 675/706/593 2812/707/594\nf 2812/707/594 675/706/593 676/708/595 707/709/596\nf 707/709/596 676/708/595 677/710/597 705/711/598\nf 706/697/586 2818/712/599 678/713/600 672/698/587\nf 705/711/598 677/710/597 678/713/600 2818/712/599\nf 671/699/588 672/698/587 680/714/601 679/715/602\nf 671/699/588 679/715/602 681/716/603 673/700/589\nf 673/700/589 681/716/603 682/717/604 674/702/591\nf 674/705/591 682/718/604 683/719/605 675/706/593\nf 675/706/593 683/719/605 684/720/606 676/708/595\nf 676/708/595 684/720/606 685/721/607 677/710/597\nf 672/698/587 678/713/600 686/722/608 680/714/601\nf 677/710/597 685/721/607 686/722/608 678/713/600\nf 679/715/602 680/714/601 696/723/609 697/724/610\nf 697/724/610 696/723/609 688/725/611 687/726/612\nf 681/716/603 679/715/602 697/724/610 698/727/613\nf 698/727/613 697/724/610 687/726/612 689/728/614\nf 684/720/606 683/719/605 699/729/615 700/730/616\nf 700/730/616 699/729/615 690/731/617 691/732/618\nf 685/721/607 684/720/606 700/730/616 701/733/619\nf 701/733/619 700/730/616 691/732/618 692/734/620\nf 680/714/601 686/722/608 702/735/621 696/723/609\nf 696/723/609 702/735/621 693/736/622 688/725/611\nf 686/722/608 685/721/607 701/733/619 702/735/621\nf 702/735/621 701/733/619 692/734/620 693/736/622\nf 683/719/605 682/718/604 703/737/623 699/729/615\nf 699/729/615 703/737/623 694/738/624 690/731/617\nf 682/717/604 681/716/603 698/727/613 704/739/625\nf 704/739/625 698/727/613 689/728/614 695/740/626\nf 660/687/577 670/741/627 2813/742/628 661/688/578\nf 662/689/579 2817/743/629 670/741/627 660/687/577\nf 661/688/578 2813/742/628 2814/744/630 663/690/580\nf 663/690/580 2814/744/630 669/745/631 664/691/581\nf 664/691/581 669/745/631 668/746/632 665/692/582\nf 665/693/582 668/747/632 2815/748/633 666/694/583\nf 666/694/583 2815/748/633 2816/749/634 667/695/584\nf 667/695/584 2816/749/634 2817/743/629 662/689/579\nf 902/750/112 728/751/112 2869/752/112 2868/753/112\nf 730/754/76 906/755/76 2870/756/76 2871/757/76\nf 903/758/76 873/759/76 2873/760/76 2872/761/76\nf 906/755/76 905/762/76 2874/763/76 2870/756/76\nf 905/762/76 904/764/76 2875/765/76 2874/763/76\nf 904/764/76 903/758/76 2872/761/76 2875/765/76\nf 874/766/112 899/767/112 2876/768/112 2877/769/112\nf 901/770/112 902/750/112 2868/753/112 2878/771/112\nf 900/772/112 901/770/112 2878/771/112 2879/773/112\nf 899/767/112 900/772/112 2879/773/112 2876/768/112\nf 753/774/76 730/754/76 2871/757/76 2880/775/76\nf 715/776/76 750/777/76 2881/778/76 2882/779/76\nf 752/780/76 753/774/76 2880/775/76 2883/781/76\nf 751/782/76 752/780/76 2883/781/76 2884/783/76\nf 750/777/76 751/782/76 2884/783/76 2881/778/76\nf 746/784/112 716/785/112 2886/786/112 2885/787/112\nf 728/751/112 749/788/112 2887/789/112 2869/752/112\nf 749/788/112 748/790/112 2888/791/112 2887/789/112\nf 748/790/112 747/792/112 2889/793/112 2888/791/112\nf 747/792/112 746/784/112 2885/787/112 2889/793/112\nf 1038/794/635 1034/795/636 1033/796/637 1037/797/638\nf 1037/797/638 1033/796/637 1032/798/639 1036/799/640\nf 1036/799/640 1032/798/639 1031/800/641 1035/801/642\nf 1042/802/643 1034/795/636 1038/794/635 1041/803/644\nf 1040/804/645 1035/801/642 1031/800/641 1039/805/645\nf 1028/806/646 1027/807/647 1023/808/648 1024/809/649\nf 1027/807/647 1026/810/650 1022/811/651 1023/808/648\nf 1026/810/650 1025/812/652 1021/813/653 1022/811/651\nf 1030/814/654 1029/815/655 1028/806/646 1024/809/649\nf 1040/804/645 1039/805/645 1021/813/653 1025/812/652\nf 715/776/76 2882/779/76 2873/816/76 873/817/76\nf 716/785/112 874/818/112 2877/819/112 2886/786/112\nf 1029/820/655 1030/821/654 1042/802/643 1041/803/644\nusemtl WhiteCloth\nf 1208/822/656 1207/823/657 1085/824/658 1145/825/659\nf 1165/826/660 1164/827/661 1189/828/662 1188/829/663\nf 1234/830/664 1233/831/665 1210/832/666 1209/833/667\nf 1144/834/668 1059/835/669 1231/836/670 1232/837/671\nf 1096/838/672 1095/839/673 1211/840/674 1212/841/675\nf 1095/839/673 1136/842/676 1210/832/666 1211/840/674\nf 1084/843/677 1163/844/678 1186/845/679 1187/846/680\nf 1085/824/658 1084/843/677 1187/846/680 1188/829/663\nf 1145/825/659 1085/824/658 1188/829/663 1189/828/662\nf 1187/846/680 1186/845/679 1167/847/681 1166/848/682\nf 1192/849/683 1191/850/684 1146/851/685 1147/852/686\nf 1191/850/684 1190/853/687 1140/854/688 1146/851/685\nf 1144/834/668 1142/855/689 1190/853/687 1191/850/684\nf 1059/835/669 1144/834/668 1191/850/684 1192/849/683\nf 1190/853/687 1141/856/690 1086/857/691 1140/854/688\nf 1206/858/692 1205/859/693 1163/844/678 1084/843/677\nf 1207/823/657 1206/858/692 1084/843/677 1085/824/658\nf 1147/852/686 1146/851/685 1166/860/682 1167/861/681\nf 1146/851/685 1140/854/688 1165/862/660 1166/860/682\nf 1142/855/689 1087/863/694 1141/856/690 1190/853/687\nf 1095/839/673 1096/838/672 1205/859/693 1206/858/692\nf 1137/864/695 1136/842/676 1207/823/657 1208/822/656\nf 1136/842/676 1095/839/673 1206/858/692 1207/823/657\nf 1136/842/676 1137/864/695 1209/833/667 1210/832/666\nf 1142/855/689 1144/834/668 1232/837/671 1233/831/665\nf 1087/863/694 1142/855/689 1233/831/665 1234/830/664\nf 1140/854/688 1086/857/691 1164/865/661 1165/862/660\nf 1212/841/675 1211/840/674 1232/837/671 1231/836/670\nf 1211/840/674 1210/832/666 1233/831/665 1232/837/671\nf 1165/826/660 1188/829/663 1187/846/680 1166/848/682\nf 1424/866/696 1361/867/697 1301/868/698 1423/869/699\nf 1381/870/700 1404/871/701 1405/872/702 1380/873/703\nf 1450/874/704 1425/875/705 1426/876/706 1449/877/707\nf 1360/878/708 1448/879/709 1447/880/710 1275/881/711\nf 1312/882/712 1428/883/713 1427/884/714 1311/885/715\nf 1311/885/715 1427/884/714 1426/876/706 1352/886/716\nf 1300/887/717 1403/888/718 1402/889/719 1379/890/720\nf 1301/868/698 1404/871/701 1403/888/718 1300/887/717\nf 1361/867/697 1405/872/702 1404/871/701 1301/868/698\nf 1403/888/718 1382/891/721 1383/892/722 1402/889/719\nf 1408/893/723 1363/894/724 1362/895/725 1407/896/726\nf 1407/896/726 1362/895/725 1356/897/727 1406/898/728\nf 1360/878/708 1407/896/726 1406/898/728 1358/899/729\nf 1275/881/711 1408/893/723 1407/896/726 1360/878/708\nf 1406/898/728 1356/897/727 1302/900/730 1357/901/731\nf 1422/902/732 1300/887/717 1379/890/720 1421/903/733\nf 1423/869/699 1301/868/698 1300/887/717 1422/902/732\nf 1363/894/724 1383/904/722 1382/905/721 1362/895/725\nf 1362/895/725 1382/905/721 1381/906/700 1356/897/727\nf 1358/899/729 1406/898/728 1357/901/731 1303/907/734\nf 1311/885/715 1422/902/732 1421/903/733 1312/882/712\nf 1353/908/735 1424/866/696 1423/869/699 1352/886/716\nf 1352/886/716 1423/869/699 1422/902/732 1311/885/715\nf 1352/886/716 1426/876/706 1425/875/705 1353/908/735\nf 1358/899/729 1449/877/707 1448/879/709 1360/878/708\nf 1303/907/734 1450/874/704 1449/877/707 1358/899/729\nf 1356/897/727 1381/906/700 1380/909/703 1302/900/730\nf 1428/883/713 1447/880/710 1448/879/709 1427/884/714\nf 1427/884/714 1448/879/709 1449/877/707 1426/876/706\nf 1381/870/700 1382/891/721 1403/888/718 1404/871/701\nusemtl WhiteCloth_NONE\nf 2/910/736 1/911/737 4/912/738 5/913/739\nf 5/913/739 6/914/740 3/915/741 2/910/736\nf 7/916/742 8/917/743 5/913/739 4/912/738\nf 8/917/743 9/918/744 6/914/740 5/913/739\nf 10/919/745 11/920/746 8/917/743 7/916/742\nf 11/920/746 12/921/747 9/918/744 8/917/743\nf 15/922/748 13/923/749 11/920/746 10/919/745\nf 13/923/749 14/924/750 12/921/747 11/920/746\nusemtl Skin\nf 1093/925/751 1058/926/752 1088/927/753 1118/928/754\nf 1059/929/669 1089/930/755 1230/931/756 1231/932/670\nf 1230/931/756 1089/930/755 1242/933/757 1243/934/758\nf 1091/935/759 1060/936/760 1058/926/752 1093/925/751\nf 1091/937/759 1061/938/761 1103/939/762 1102/940/763\nf 1061/941/761 1091/935/759 1093/925/751 1193/942/764\nf 1090/943/765 1061/941/761 1193/942/764 1194/944/766\nf 1061/938/761 1090/945/765 1104/946/767 1103/939/762\nf 1104/946/767 1090/945/765 1092/947/768 1106/948/769\nf 1060/936/760 1091/935/759 1228/949/770 1229/950/771\nf 1090/943/765 1194/944/766 1195/951/772 1092/952/768\nf 1194/953/766 1203/954/773 1202/955/774 1195/956/772\nf 1194/953/766 1193/957/764 1204/958/775 1203/954/773\nf 1193/957/764 1093/959/751 1108/960/776 1204/958/775\nf 1094/961/777 1062/962/778 1226/963/779 1227/964/780\nf 1226/963/779 1062/962/778 1068/965/781 1225/966/782\nf 1062/967/778 1094/968/777 1098/969/783 1099/970/784\nf 1062/967/778 1099/970/784 1100/971/785 1068/972/781\nf 1243/973/758 1063/974/786 1253/975/787 1244/976/788\nf 1214/977/789 1063/974/786 1243/973/758 1229/978/771\nf 1063/974/786 1214/977/789 1107/979/790 1235/980/791\nf 1064/981/792 1253/975/787 1063/974/786 1213/982/793\nf 1066/983/794 1064/981/792 1213/982/793 1230/984/756\nf 1253/975/787 1064/981/792 1097/985/795 1252/986/796\nf 1064/981/792 1066/983/794 1073/987/797 1097/985/795\nf 1065/988/798 1213/982/793 1063/974/786 1235/980/791\nf 1065/988/798 1096/989/672 1212/990/675 1213/982/793\nf 1066/991/794 1244/992/788 1245/993/799 1073/994/797\nf 1244/976/788 1253/975/787 1252/986/796 1245/995/799\nf 1252/986/796 1251/996/800 1246/997/801 1245/995/799\nf 1251/996/800 1250/998/802 1247/999/803 1246/997/801\nf 1250/998/802 1249/1000/804 1248/1001/805 1247/999/803\nf 1098/969/783 1102/940/763 1215/1002/806 1216/1003/807\nf 1091/937/759 1102/940/763 1098/969/783 1094/968/777\nf 1102/940/763 1107/979/790 1214/977/789 1215/1002/806\nf 1068/972/781 1100/971/785 1101/1004/808 1067/1005/809\nf 1100/971/785 1218/1006/810 1219/1007/811 1101/1004/808\nf 1134/1008/812 1067/1005/809 1101/1004/808 1135/1009/813\nf 1067/1010/809 1134/1011/812 1223/1012/814 1224/1013/815\nf 1225/966/782 1068/965/781 1067/1010/809 1224/1013/815\nf 1237/1014/816 1162/1015/817 1184/1016/818 1238/1017/819\nf 1181/1018/820 1110/1019/821 1121/1020/822 1180/1021/823\nf 1110/1019/821 1117/1022/824 1120/1023/825 1121/1020/822\nf 1184/1016/818 1183/1024/826 1170/1025/827 1169/1026/828\nf 1183/1024/826 1182/1027/829 1171/1028/830 1170/1025/827\nf 1182/1027/829 1181/1018/820 1172/1029/831 1171/1028/830\nf 1172/1029/831 1181/1018/820 1180/1021/823 1173/1030/832\nf 1150/1031/833 1159/1032/834 1158/1033/835 1151/1034/836\nf 1160/1035/837 1159/1032/834 1150/1031/833 1149/1036/838\nf 1151/1034/836 1158/1033/835 1157/1037/839 1152/1038/840\nf 1159/1032/834 1114/1039/841 1116/1040/842 1158/1033/835\nf 1114/1039/841 1112/1041/843 1070/1042/844 1116/1040/842\nf 1112/1043/843 1150/1044/833 1151/1045/836 1070/1046/844\nf 1093/959/751 1113/1047/845 1115/1048/846 1108/960/776\nf 1113/1047/845 1112/1041/843 1114/1039/841 1115/1048/846\nf 1069/1049/847 1157/1037/839 1158/1033/835 1116/1040/842\nf 1069/1049/847 1128/1050/848 1156/1051/849 1157/1037/839\nf 1128/1050/848 1069/1049/847 1071/1052/850 1072/1053/851\nf 1069/1049/847 1116/1040/842 1070/1042/844 1071/1052/850\nf 1070/1046/844 1151/1045/836 1152/1054/840 1071/1055/850\nf 1160/1035/837 1149/1036/838 1118/1056/754 1161/1057/852\nf 1072/1058/851 1071/1055/850 1152/1054/840 1153/1059/853\nf 1129/1060/854 1072/1058/851 1153/1059/853 1154/1061/855\nf 1128/1050/848 1072/1053/851 1129/1062/854 1080/1063/856\nf 1110/1019/821 1111/1064/857 1119/1065/858 1117/1022/824\nf 1117/1066/824 1172/1029/831 1173/1030/832 1120/1067/825\nf 1123/1068/859 1075/1069/860 1122/1070/861 1125/1071/862\nf 1124/1072/863 1123/1068/859 1125/1071/862 1074/1073/864\nf 1073/987/797 1122/1070/861 1075/1069/860 1097/985/795\nf 1122/1074/861 1073/994/797 1245/993/799 1246/1075/801\nf 1074/1076/864 1125/1077/862 1247/1078/803 1248/1079/805\nf 1075/1069/860 1123/1068/859 1250/998/802 1251/996/800\nf 1097/985/795 1075/1069/860 1251/996/800 1252/986/796\nf 1126/1080/865 1076/1081/866 1177/1082/867 1178/1083/868\nf 1177/1082/867 1076/1081/866 1079/1084/869 1176/1085/870\nf 1076/1081/866 1126/1080/865 1078/1086/871 1079/1084/869\nf 1078/1087/871 1077/1088/872 1174/1089/873 1175/1090/874\nf 1077/1091/872 1078/1086/871 1126/1080/865 1127/1092/875\nf 1079/1093/869 1078/1087/871 1175/1090/874 1176/1085/870\nf 1128/1050/848 1080/1063/856 1155/1094/876 1156/1051/849\nf 1129/1062/854 1154/1095/855 1155/1094/876 1080/1063/856\nf 1130/1096/877 1131/1097/878 1132/1098/879 1081/1099/880\nf 1133/1100/881 1132/1098/879 1131/1097/878 1105/1101/882\nf 1198/1102/883 1199/1103/884 1130/1096/877 1081/1099/880\nf 1081/1104/880 1132/1105/879 1197/1106/885 1198/1107/883\nf 1198/1102/883 1197/1108/885 1200/1109/886 1199/1103/884\nf 1197/1108/885 1196/1110/887 1201/1111/888 1200/1109/886\nf 1195/956/772 1202/955/774 1201/1111/888 1196/1110/887\nf 1082/1112/889 1134/1008/812 1135/1009/813 1083/1113/890\nf 1134/1011/812 1082/1114/889 1222/1115/891 1223/1012/814\nf 1082/1112/889 1083/1113/890 1221/1116/892 1222/1117/891\nf 1083/1113/890 1135/1009/813 1220/1118/893 1221/1116/892\nf 1186/1119/679 1185/1120/894 1168/1121/895 1167/1122/681\nf 1143/1123/896 1192/1124/683 1147/1125/686 1148/1126/897\nf 1143/1123/896 1148/1126/897 1240/1127/898 1241/1128/899\nf 1058/926/752 1241/1128/899 1240/1127/898 1088/927/753\nf 1108/960/776 1115/1048/846 1160/1035/837 1161/1057/852\nf 1115/1048/846 1114/1039/841 1159/1032/834 1160/1035/837\nf 1109/1129/900 1108/960/776 1161/1057/852 1162/1015/817\nf 1205/1130/693 1139/1131/901 1138/1132/902 1163/1133/678\nf 1138/1132/902 1139/1131/901 1236/1134/903 1237/1014/816\nf 1148/1126/897 1147/1125/686 1167/1122/681 1168/1121/895\nf 1148/1126/897 1168/1121/895 1239/1135/904 1240/1127/898\nf 1088/927/753 1240/1127/898 1239/1135/904 1169/1026/828\nf 1117/1066/824 1119/1136/858 1171/1028/830 1172/1029/831\nf 1119/1136/858 1118/928/754 1170/1025/827 1171/1028/830\nf 1127/1092/875 1126/1080/865 1178/1083/868 1179/1137/905\nf 1127/1092/875 1179/1137/905 1180/1021/823 1121/1020/822\nf 1111/1064/857 1110/1019/821 1181/1018/820 1182/1027/829\nf 1161/1057/852 1111/1064/857 1182/1027/829 1183/1024/826\nf 1162/1015/817 1161/1057/852 1183/1024/826 1184/1016/818\nf 1163/1133/678 1138/1132/902 1185/1120/894 1186/1119/679\nf 1089/930/755 1059/929/669 1192/1124/683 1143/1123/896\nf 1089/930/755 1143/1123/896 1241/1128/899 1242/933/757\nf 1060/936/760 1242/933/757 1241/1128/899 1058/926/752\nf 1195/951/772 1196/1138/887 1133/1139/881 1092/952/768\nf 1132/1105/879 1133/1139/881 1196/1138/887 1197/1106/885\nf 1131/1097/878 1130/1096/877 1199/1103/884 1200/1109/886\nf 1105/1101/882 1131/1097/878 1200/1109/886 1201/1111/888\nf 1105/1101/882 1201/1111/888 1202/955/774 1106/948/769\nf 1104/946/767 1106/948/769 1202/955/774 1203/954/773\nf 1103/939/762 1104/946/767 1203/954/773 1204/958/775\nf 1102/940/763 1103/939/762 1204/958/775 1108/960/776\nf 1235/980/791 1107/979/790 1109/1129/900 1236/1134/903\nf 1096/989/672 1065/988/798 1139/1131/901 1205/1130/693\nf 1139/1131/901 1065/988/798 1235/980/791 1236/1134/903\nf 1218/1006/810 1100/971/785 1099/970/784 1217/1140/906\nf 1091/935/759 1094/961/777 1227/964/780 1228/949/770\nf 1185/1120/894 1238/1017/819 1239/1135/904 1168/1121/895\nf 1185/1120/894 1138/1132/902 1237/1014/816 1238/1017/819\nf 1125/1077/862 1122/1074/861 1246/1075/801 1247/1078/803\nf 1249/1000/804 1124/1072/863 1074/1073/864 1248/1001/805\nf 1123/1068/859 1124/1072/863 1249/1000/804 1250/998/802\nf 1242/933/757 1060/936/760 1229/950/771 1243/934/758\nf 1118/928/754 1088/927/753 1169/1026/828 1170/1025/827\nf 1113/1141/845 1093/925/751 1118/928/754 1149/1142/838\nf 1228/1143/770 1227/1144/780 1216/1003/807 1215/1002/806\nf 1227/1144/780 1226/1145/779 1217/1140/906 1216/1003/807\nf 1229/978/771 1228/1143/770 1215/1002/806 1214/977/789\nf 1213/982/793 1212/990/675 1231/1146/670 1230/984/756\nf 1066/991/794 1230/931/756 1243/934/758 1244/992/788\nf 1099/970/784 1098/969/783 1216/1003/807 1217/1140/906\nf 1135/1009/813 1101/1004/808 1219/1007/811 1220/1118/893\nf 1218/1006/810 1217/1140/906 1226/1145/779 1225/1147/782\nf 1218/1006/810 1225/1147/782 1224/1148/815 1219/1007/811\nf 1107/979/790 1102/940/763 1108/960/776 1109/1129/900\nf 1133/1100/881 1105/1101/882 1106/948/769 1092/947/768\nf 1236/1134/903 1109/1129/900 1162/1015/817 1237/1014/816\nf 1111/1064/857 1161/1057/852 1118/1056/754 1119/1065/858\nf 1238/1017/819 1184/1016/818 1169/1026/828 1239/1135/904\nf 1112/1043/843 1113/1141/845 1149/1142/838 1150/1044/833\nf 1153/1149/853 1152/1038/840 1157/1037/839 1156/1051/849\nf 1154/1095/855 1153/1149/853 1156/1051/849 1155/1094/876\nf 1077/1088/872 1120/1067/825 1173/1030/832 1174/1089/873\nf 1127/1092/875 1121/1020/822 1120/1023/825 1077/1091/872\nf 1179/1137/905 1174/1089/873 1173/1030/832 1180/1021/823\nf 1179/1137/905 1178/1083/868 1175/1090/874 1174/1089/873\nf 1178/1083/868 1177/1082/867 1176/1085/870 1175/1090/874\nf 1219/1007/811 1224/1148/815 1223/1150/814 1220/1118/893\nf 1221/1116/892 1220/1118/893 1223/1150/814 1222/1117/891\nf 1309/1151/907 1334/1152/908 1304/1153/909 1274/1154/910\nf 1275/1155/711 1447/1156/710 1446/1157/911 1305/1158/912\nf 1446/1157/911 1459/1159/913 1458/1160/914 1305/1158/912\nf 1307/1161/915 1309/1151/907 1274/1154/910 1276/1162/916\nf 1307/1163/915 1318/1164/917 1319/1165/918 1277/1166/919\nf 1277/1167/919 1409/1168/920 1309/1151/907 1307/1161/915\nf 1306/1169/921 1410/1170/922 1409/1168/920 1277/1167/919\nf 1277/1166/919 1319/1165/918 1320/1171/923 1306/1172/921\nf 1320/1171/923 1322/1173/924 1308/1174/925 1306/1172/921\nf 1276/1162/916 1445/1175/926 1444/1176/927 1307/1161/915\nf 1306/1169/921 1308/1177/925 1411/1178/928 1410/1170/922\nf 1410/1179/922 1411/1180/928 1418/1181/929 1419/1182/930\nf 1410/1179/922 1419/1182/930 1420/1183/931 1409/1184/920\nf 1409/1184/920 1420/1183/931 1324/1185/932 1309/1186/907\nf 1310/1187/933 1443/1188/934 1442/1189/935 1278/1190/936\nf 1442/1189/935 1441/1191/937 1284/1192/938 1278/1190/936\nf 1278/1193/936 1315/1194/939 1314/1195/940 1310/1196/933\nf 1278/1193/936 1284/1197/938 1316/1198/941 1315/1194/939\nf 1459/1199/913 1460/1200/942 1469/1201/943 1279/1202/944\nf 1430/1203/945 1445/1204/926 1459/1199/913 1279/1202/944\nf 1279/1202/944 1451/1205/946 1323/1206/947 1430/1203/945\nf 1280/1207/948 1429/1208/949 1279/1202/944 1469/1201/943\nf 1282/1209/950 1446/1210/911 1429/1208/949 1280/1207/948\nf 1469/1201/943 1468/1211/951 1313/1212/952 1280/1207/948\nf 1280/1207/948 1313/1212/952 1289/1213/953 1282/1209/950\nf 1281/1214/954 1451/1205/946 1279/1202/944 1429/1208/949\nf 1281/1214/954 1429/1208/949 1428/1215/713 1312/1216/712\nf 1282/1217/950 1289/1218/953 1461/1219/955 1460/1220/942\nf 1460/1200/942 1461/1221/955 1468/1211/951 1469/1201/943\nf 1468/1211/951 1461/1221/955 1462/1222/956 1467/1223/957\nf 1467/1223/957 1462/1222/956 1463/1224/958 1466/1225/959\nf 1466/1225/959 1463/1224/958 1464/1226/960 1465/1227/961\nf 1314/1195/940 1432/1228/962 1431/1229/963 1318/1164/917\nf 1307/1163/915 1310/1196/933 1314/1195/940 1318/1164/917\nf 1318/1164/917 1431/1229/963 1430/1203/945 1323/1206/947\nf 1284/1197/938 1283/1230/964 1317/1231/965 1316/1198/941\nf 1316/1198/941 1317/1231/965 1435/1232/966 1434/1233/967\nf 1350/1234/968 1351/1235/969 1317/1231/965 1283/1230/964\nf 1283/1236/964 1440/1237/970 1439/1238/971 1350/1239/968\nf 1441/1191/937 1440/1237/970 1283/1236/964 1284/1192/938\nf 1453/1240/972 1454/1241/973 1400/1242/974 1378/1243/975\nf 1397/1244/976 1396/1245/977 1337/1246/978 1326/1247/979\nf 1326/1247/979 1337/1246/978 1336/1248/980 1333/1249/981\nf 1400/1242/974 1385/1250/982 1386/1251/983 1399/1252/984\nf 1399/1252/984 1386/1251/983 1387/1253/985 1398/1254/986\nf 1398/1254/986 1387/1253/985 1388/1255/987 1397/1244/976\nf 1388/1255/987 1389/1256/988 1396/1245/977 1397/1244/976\nf 1366/1257/989 1367/1258/990 1374/1259/991 1375/1260/992\nf 1376/1261/993 1365/1262/994 1366/1257/989 1375/1260/992\nf 1367/1258/990 1368/1263/995 1373/1264/996 1374/1259/991\nf 1375/1260/992 1374/1259/991 1332/1265/997 1330/1266/998\nf 1330/1266/998 1332/1265/997 1286/1267/999 1328/1268/1000\nf 1328/1269/1000 1286/1270/999 1367/1271/990 1366/1272/989\nf 1309/1186/907 1324/1185/932 1331/1273/1001 1329/1274/1002\nf 1329/1274/1002 1331/1273/1001 1330/1266/998 1328/1268/1000\nf 1285/1275/1003 1332/1265/997 1374/1259/991 1373/1264/996\nf 1285/1275/1003 1373/1264/996 1372/1276/1004 1344/1277/1005\nf 1344/1277/1005 1288/1278/1006 1287/1279/1007 1285/1275/1003\nf 1285/1275/1003 1287/1279/1007 1286/1267/999 1332/1265/997\nf 1286/1270/999 1287/1280/1007 1368/1281/995 1367/1271/990\nf 1376/1261/993 1377/1282/1008 1334/1283/908 1365/1262/994\nf 1288/1284/1006 1369/1285/1009 1368/1281/995 1287/1280/1007\nf 1345/1286/1010 1370/1287/1011 1369/1285/1009 1288/1284/1006\nf 1344/1277/1005 1296/1288/1012 1345/1289/1010 1288/1278/1006\nf 1326/1247/979 1333/1249/981 1335/1290/1013 1327/1291/1014\nf 1333/1292/981 1336/1293/980 1389/1256/988 1388/1255/987\nf 1339/1294/1015 1341/1295/1016 1338/1296/1017 1291/1297/1018\nf 1340/1298/1019 1290/1299/1020 1341/1295/1016 1339/1294/1015\nf 1289/1213/953 1313/1212/952 1291/1297/1018 1338/1296/1017\nf 1338/1300/1017 1462/1301/956 1461/1219/955 1289/1218/953\nf 1290/1302/1020 1464/1303/960 1463/1304/958 1341/1305/1016\nf 1291/1297/1018 1467/1223/957 1466/1225/959 1339/1294/1015\nf 1313/1212/952 1468/1211/951 1467/1223/957 1291/1297/1018\nf 1342/1306/1021 1394/1307/1022 1393/1308/1023 1292/1309/1024\nf 1393/1308/1023 1392/1310/1025 1295/1311/1026 1292/1309/1024\nf 1292/1309/1024 1295/1311/1026 1294/1312/1027 1342/1306/1021\nf 1294/1313/1027 1391/1314/1028 1390/1315/1029 1293/1316/1030\nf 1293/1317/1030 1343/1318/1031 1342/1306/1021 1294/1312/1027\nf 1295/1319/1026 1392/1310/1025 1391/1314/1028 1294/1313/1027\nf 1344/1277/1005 1372/1276/1004 1371/1320/1032 1296/1288/1012\nf 1345/1289/1010 1296/1288/1012 1371/1320/1032 1370/1321/1011\nf 1346/1322/1033 1297/1323/1034 1348/1324/1035 1347/1325/1036\nf 1349/1326/1037 1321/1327/1038 1347/1325/1036 1348/1324/1035\nf 1414/1328/1039 1297/1323/1034 1346/1322/1033 1415/1329/1040\nf 1297/1330/1034 1414/1331/1039 1413/1332/1041 1348/1333/1035\nf 1414/1328/1039 1415/1329/1040 1416/1334/1042 1413/1335/1041\nf 1413/1335/1041 1416/1334/1042 1417/1336/1043 1412/1337/1044\nf 1411/1180/928 1412/1337/1044 1417/1336/1043 1418/1181/929\nf 1298/1338/1045 1299/1339/1046 1351/1235/969 1350/1234/968\nf 1350/1239/968 1439/1238/971 1438/1340/1047 1298/1341/1045\nf 1298/1338/1045 1438/1342/1047 1437/1343/1048 1299/1339/1046\nf 1299/1339/1046 1437/1343/1048 1436/1344/1049 1351/1235/969\nf 1402/1345/719 1383/1346/722 1384/1347/1050 1401/1348/1051\nf 1359/1349/1052 1364/1350/1053 1363/1351/724 1408/1352/723\nf 1359/1349/1052 1457/1353/1054 1456/1354/1055 1364/1350/1053\nf 1274/1154/910 1304/1153/909 1456/1354/1055 1457/1353/1054\nf 1324/1185/932 1377/1282/1008 1376/1261/993 1331/1273/1001\nf 1331/1273/1001 1376/1261/993 1375/1260/992 1330/1266/998\nf 1325/1355/1056 1378/1243/975 1377/1282/1008 1324/1185/932\nf 1421/1356/733 1379/1357/720 1354/1358/1057 1355/1359/1058\nf 1354/1358/1057 1453/1240/972 1452/1360/1059 1355/1359/1058\nf 1364/1350/1053 1384/1347/1050 1383/1346/722 1363/1351/724\nf 1364/1350/1053 1456/1354/1055 1455/1361/1060 1384/1347/1050\nf 1304/1153/909 1385/1250/982 1455/1361/1060 1456/1354/1055\nf 1333/1292/981 1388/1255/987 1387/1253/985 1335/1362/1013\nf 1335/1362/1013 1387/1253/985 1386/1251/983 1334/1152/908\nf 1343/1318/1031 1395/1363/1061 1394/1307/1022 1342/1306/1021\nf 1343/1318/1031 1337/1246/978 1396/1245/977 1395/1363/1061\nf 1327/1291/1014 1398/1254/986 1397/1244/976 1326/1247/979\nf 1377/1282/1008 1399/1252/984 1398/1254/986 1327/1291/1014\nf 1378/1243/975 1400/1242/974 1399/1252/984 1377/1282/1008\nf 1379/1357/720 1402/1345/719 1401/1348/1051 1354/1358/1057\nf 1305/1158/912 1359/1349/1052 1408/1352/723 1275/1155/711\nf 1305/1158/912 1458/1160/914 1457/1353/1054 1359/1349/1052\nf 1276/1162/916 1274/1154/910 1457/1353/1054 1458/1160/914\nf 1411/1178/928 1308/1177/925 1349/1364/1037 1412/1365/1044\nf 1348/1333/1035 1413/1332/1041 1412/1365/1044 1349/1364/1037\nf 1347/1325/1036 1416/1334/1042 1415/1329/1040 1346/1322/1033\nf 1321/1327/1038 1417/1336/1043 1416/1334/1042 1347/1325/1036\nf 1321/1327/1038 1322/1173/924 1418/1181/929 1417/1336/1043\nf 1320/1171/923 1419/1182/930 1418/1181/929 1322/1173/924\nf 1319/1165/918 1420/1183/931 1419/1182/930 1320/1171/923\nf 1318/1164/917 1324/1185/932 1420/1183/931 1319/1165/918\nf 1451/1205/946 1452/1360/1059 1325/1355/1056 1323/1206/947\nf 1312/1216/712 1421/1356/733 1355/1359/1058 1281/1214/954\nf 1355/1359/1058 1452/1360/1059 1451/1205/946 1281/1214/954\nf 1434/1233/967 1433/1366/1062 1315/1194/939 1316/1198/941\nf 1307/1161/915 1444/1176/927 1443/1188/934 1310/1187/933\nf 1401/1348/1051 1384/1347/1050 1455/1361/1060 1454/1241/973\nf 1401/1348/1051 1454/1241/973 1453/1240/972 1354/1358/1057\nf 1341/1305/1016 1463/1304/958 1462/1301/956 1338/1300/1017\nf 1465/1227/961 1464/1226/960 1290/1299/1020 1340/1298/1019\nf 1339/1294/1015 1466/1225/959 1465/1227/961 1340/1298/1019\nf 1458/1160/914 1459/1159/913 1445/1175/926 1276/1162/916\nf 1334/1152/908 1386/1251/983 1385/1250/982 1304/1153/909\nf 1329/1367/1002 1365/1368/994 1334/1152/908 1309/1151/907\nf 1444/1369/927 1431/1229/963 1432/1228/962 1443/1370/934\nf 1443/1370/934 1432/1228/962 1433/1366/1062 1442/1371/935\nf 1445/1204/926 1430/1203/945 1431/1229/963 1444/1369/927\nf 1429/1208/949 1446/1210/911 1447/1372/710 1428/1215/713\nf 1282/1217/950 1460/1220/942 1459/1159/913 1446/1157/911\nf 1315/1194/939 1433/1366/1062 1432/1228/962 1314/1195/940\nf 1351/1235/969 1436/1344/1049 1435/1232/966 1317/1231/965\nf 1434/1233/967 1441/1373/937 1442/1371/935 1433/1366/1062\nf 1434/1233/967 1435/1232/966 1440/1374/970 1441/1373/937\nf 1323/1206/947 1325/1355/1056 1324/1185/932 1318/1164/917\nf 1349/1326/1037 1308/1174/925 1322/1173/924 1321/1327/1038\nf 1452/1360/1059 1453/1240/972 1378/1243/975 1325/1355/1056\nf 1327/1291/1014 1335/1290/1013 1334/1283/908 1377/1282/1008\nf 1454/1241/973 1455/1361/1060 1385/1250/982 1400/1242/974\nf 1328/1269/1000 1366/1272/989 1365/1368/994 1329/1367/1002\nf 1369/1375/1009 1372/1276/1004 1373/1264/996 1368/1263/995\nf 1370/1321/1011 1371/1320/1032 1372/1276/1004 1369/1375/1009\nf 1293/1316/1030 1390/1315/1029 1389/1256/988 1336/1293/980\nf 1343/1318/1031 1293/1317/1030 1336/1248/980 1337/1246/978\nf 1395/1363/1061 1396/1245/977 1389/1256/988 1390/1315/1029\nf 1395/1363/1061 1390/1315/1029 1391/1314/1028 1394/1307/1022\nf 1394/1307/1022 1391/1314/1028 1392/1310/1025 1393/1308/1023\nf 1435/1232/966 1436/1344/1049 1439/1376/971 1440/1374/970\nf 1437/1343/1048 1438/1342/1047 1439/1376/971 1436/1344/1049\nf 2044/1377/1063 2045/1378/1064 1485/1379/1065 1475/1380/1066\nf 1477/1381/1067 1487/1382/1068 1486/1383/1069 1476/1384/1070\nf 1478/1385/1071 1488/1386/1072 1487/1382/1068 1477/1381/1067\nf 1479/1387/1073 1489/1388/1074 1488/1386/1072 1478/1385/1071\nf 1480/1389/1075 1490/1390/1076 1489/1388/1074 1479/1387/1073\nf 1537/1391/1077 1538/1392/1078 1490/1390/1076 1480/1389/1075\nf 1482/1393/1079 1492/1394/1080 1491/1395/1081 1481/1396/1082\nf 1483/1397/1083 1493/1398/1084 1492/1394/1080 1482/1393/1079\nf 1484/1399/1085 1494/1400/1086 1493/1398/1084 1483/1397/1083\nf 1475/1380/1066 1485/1379/1065 1494/1400/1086 1484/1399/1085\nf 2045/1378/1064 2046/1401/1087 1495/1402/1088 1485/1379/1065\nf 2046/1401/1087 2047/1403/1089 1497/1404/1090 1495/1402/1088\nf 1495/1402/1088 1503/1405/1091 1502/1406/1092 1485/1379/1065\nf 1497/1404/1090 1504/1407/1093 1503/1405/1091 1495/1402/1088\nf 1503/1405/1091 1500/1408/1094 1499/1409/1095 1502/1406/1092\nf 1504/1407/1093 1501/1410/1096 1500/1408/1094 1503/1405/1091\nf 2073/1411/1097 1509/1412/1098 1508/1413/1099 2072/1414/1100\nf 1505/1415/1101 1494/1400/1086 1485/1379/1065 1502/1406/1092\nf 1506/1416/1102 1493/1398/1084 1494/1400/1086 1505/1415/1101\nf 1499/1409/1095 1512/1417/1103 1505/1415/1101 1502/1406/1092\nf 1512/1417/1103 1511/1418/1104 1506/1416/1102 1505/1415/1101\nf 1511/1418/1104 1510/1419/1105 1507/1420/1106 1506/1416/1102\nf 1487/1382/1068 1514/1421/1107 1513/1422/1108 1486/1383/1069\nf 1488/1386/1072 1515/1423/1109 1514/1421/1107 1487/1382/1068\nf 1489/1388/1074 1516/1424/1110 1515/1423/1109 1488/1386/1072\nf 1490/1390/1076 1517/1425/1111 1516/1424/1110 1489/1388/1074\nf 1486/1383/1069 1513/1422/1108 1518/1426/1112 1496/1427/1113\nf 1496/1427/1113 1518/1426/1112 1519/1428/1114 1498/1429/1115\nf 1528/1430/1116 1527/1431/1117 1522/1432/1118 1521/1433/1119\nf 1526/1434/1120 1525/1435/1121 1524/1436/1122 1523/1437/1123\nf 1527/1431/1117 1526/1434/1120 1523/1437/1123 1522/1432/1118\nf 1941/1438/1124 1561/1439/1125 1562/1440/1126 1940/1441/1127\nf 1520/1442/1128 1508/1413/1099 1509/1412/1098 1529/1443/1129\nf 1523/1437/1123 1611/1444/1130 1582/1445/1131 1522/1432/1118\nf 1582/1445/1131 1583/1446/1132 1535/1447/1133 1522/1432/1118\nf 1535/1447/1133 1534/1448/1134 1521/1433/1119 1522/1432/1118\nf 1531/1449/1135 1533/1450/1136 1787/1451/1137 1788/1452/1138\nf 1789/1453/1139 1584/1454/1140 1531/1449/1135 1788/1452/1138\nf 1481/1396/1082 1491/1395/1081 2032/1455/1141 2033/1456/1142\nf 1476/1384/1070 1486/1383/1069 1540/1457/1143 1539/1458/1144\nf 1486/1383/1069 1496/1427/1113 1541/1459/1145 1540/1457/1143\nf 1496/1427/1113 1498/1429/1115 1542/1460/1146 1541/1459/1145\nf 1585/1461/1147 1586/1462/1148 1545/1463/1149 1546/1464/1150\nf 1944/1465/1151 1559/1466/1152 1589/1467/1153 1943/1468/1154\nf 1550/1469/1155 1527/1431/1117 1528/1430/1116 1547/1470/1156\nf 1525/1435/1121 1526/1434/1120 1549/1471/1157 1548/1472/1158\nf 1545/1463/1149 1586/1462/1148 1587/1473/1159 1544/1474/1160\nf 1549/1471/1157 1526/1434/1120 1527/1431/1117 1550/1469/1155\nf 1543/1475/1161 1590/1476/1162 1591/1477/1163 1566/1478/1164\nf 1557/1479/1165 1593/1480/1166 1594/1481/1167 1556/1482/1168\nf 1553/1483/1169 1592/1484/1170 1593/1480/1166 1557/1479/1165\nf 1942/1485/1171 1560/1486/1172 1561/1439/1125 1941/1438/1124\nf 1529/1443/1129 1509/1412/1098 1551/1487/1173 1558/1488/1174\nf 1544/1474/1160 1587/1473/1159 1588/1489/1175 1565/1490/1176\nf 1528/1430/1116 1561/1439/1125 1560/1486/1172 1547/1470/1156\nf 1521/1433/1119 1562/1440/1126 1561/1439/1125 1528/1430/1116\nf 2074/1491/1177 2075/1492/1178 1563/1493/1179 1578/1494/1180\nf 1555/1495/1181 1554/1496/1182 1563/1493/1179 1564/1497/1183\nf 1559/1466/1152 1565/1490/1176 1588/1489/1175 1589/1467/1153\nf 1566/1478/1164 1591/1477/1163 1592/1484/1170 1553/1483/1169\nf 1568/1498/1184 1500/1408/1094 1501/1410/1096 1567/1499/1185\nf 1511/1418/1104 1512/1417/1103 1570/1500/1186 1571/1501/1187\nf 1570/1500/1186 1512/1417/1103 1499/1409/1095 1569/1502/1188\nf 1569/1502/1188 1499/1409/1095 1500/1408/1094 1568/1498/1184\nf 1511/1418/1104 1571/1501/1187 1572/1503/1189 1573/1504/1190\nf 1573/1504/1190 1572/1503/1189 1574/1505/1191 1575/1506/1192\nf 1575/1506/1192 1574/1505/1191 1576/1507/1193 1577/1508/1194\nf 2075/1492/1178 2076/1509/1195 1564/1497/1183 1563/1493/1179\nf 1554/1496/1182 1552/1510/1196 1578/1494/1180 1563/1493/1179\nf 1552/1510/1196 1551/1487/1173 1509/1412/1098 1578/1494/1180\nf 2074/1491/1177 1578/1494/1180 1509/1412/1098 2073/1411/1097\nf 1573/1504/1190 1579/1511/1197 1510/1419/1105 1511/1418/1104\nf 1573/1504/1190 1575/1506/1192 1580/1512/1198 1579/1511/1197\nf 1575/1506/1192 1577/1508/1194 1581/1513/1199 1580/1512/1198\nf 1612/1514/1200 1530/1515/1201 1582/1445/1131 1611/1444/1130\nf 1530/1515/1201 1536/1516/1202 1583/1446/1132 1582/1445/1131\nf 1790/1517/1203 1532/1518/1204 1584/1454/1140 1789/1453/1139\nf 1548/1472/1158 1549/1471/1157 1586/1462/1148 1585/1461/1147\nf 1587/1473/1159 1586/1462/1148 1549/1471/1157 1550/1469/1155\nf 1588/1489/1175 1587/1473/1159 1550/1469/1155 1547/1470/1156\nf 1589/1467/1153 1588/1489/1175 1547/1470/1156 1560/1486/1172\nf 1943/1468/1154 1589/1467/1153 1560/1486/1172 1942/1485/1171\nf 1591/1477/1163 1590/1476/1162 1558/1488/1174 1551/1487/1173\nf 1592/1484/1170 1591/1477/1163 1551/1487/1173 1552/1510/1196\nf 1593/1480/1166 1592/1484/1170 1552/1510/1196 1554/1496/1182\nf 1594/1481/1167 1593/1480/1166 1554/1496/1182 1555/1495/1181\nf 1967/1519/1205 1599/1520/1206 1776/1521/1207 1777/1522/1208\nf 1787/1451/1137 1533/1450/1136 1595/1523/1209 1786/1524/1210\nf 1786/1524/1210 1595/1523/1209 1596/1525/1211 1785/1526/1212\nf 1596/1525/1211 1597/1527/1213 1784/1528/1214 1785/1526/1212\nf 1562/1440/1126 1521/1433/1119 1534/1448/1134 1600/1529/1215\nf 1507/1420/1106 1603/1530/1216 1493/1398/1084 1506/1416/1102\nf 1940/1441/1127 1562/1440/1126 1600/1529/1215 1939/1531/1217\nf 1520/1442/1128 1601/1532/1218 1602/1533/1219 1508/1413/1099\nf 1508/1413/1099 1602/1533/1219 2071/1534/1220 2072/1414/1100\nf 1538/1392/1078 1606/1535/1221 1517/1425/1111 1490/1390/1076\nf 1493/1398/1084 1603/1530/1216 1604/1536/1222 1492/1394/1080\nf 1492/1394/1080 1604/1536/1222 1605/1537/1223 1491/1395/1081\nf 1491/1395/1081 1605/1537/1223 2031/1538/1224 2032/1455/1141\nf 2070/1539/1225 2071/1534/1220 1602/1533/1219 1608/1540/1226\nf 2069/1541/1227 2070/1539/1225 1608/1540/1226 1610/1542/1228\nf 1608/1540/1226 1602/1533/1219 1601/1532/1218 1607/1543/1229\nf 1610/1542/1228 1608/1540/1226 1607/1543/1229 1609/1544/1230\nf 1524/1436/1122 1612/1514/1200 1611/1444/1130 1523/1437/1123\nf 1614/1545/1231 1613/1546/1232 1615/1547/1233 1616/1548/1234\nf 1616/1548/1234 1615/1547/1233 1568/1498/1184 1567/1499/1185\nf 1613/1546/1232 1621/1549/1235 1617/1550/1236 1615/1547/1233\nf 1615/1547/1233 1617/1550/1236 1569/1502/1188 1568/1498/1184\nf 1617/1550/1236 1618/1551/1237 1570/1500/1186 1569/1502/1188\nf 1619/1552/1238 1620/1553/1239 1572/1503/1189 1571/1501/1187\nf 1618/1551/1237 1619/1552/1238 1571/1501/1187 1570/1500/1186\nf 1624/1554/1240 1631/1555/1241 1629/1556/1242 1630/1557/1243\nf 1576/1507/1193 1622/1558/1244 1633/1559/1245 1634/1560/1246\nf 1623/1561/1247 1574/1505/1191 1572/1503/1189 1620/1553/1239\nf 1576/1507/1193 1574/1505/1191 1623/1561/1247 1622/1558/1244\nf 1633/1559/1245 1622/1558/1244 1625/1562/1248 1632/1563/1249\nf 1623/1561/1247 1620/1553/1239 1627/1564/1250 1626/1565/1251\nf 1620/1553/1239 1619/1552/1238 1628/1566/1252 1627/1564/1250\nf 1619/1552/1238 1618/1551/1237 1629/1556/1242 1628/1566/1252\nf 1618/1551/1237 1617/1550/1236 1630/1557/1243 1629/1556/1242\nf 1617/1550/1236 1621/1549/1235 1624/1554/1240 1630/1557/1243\nf 1629/1556/1242 1631/1555/1241 1627/1564/1250 1628/1566/1252\nf 1624/1554/1240 1632/1563/1249 1625/1562/1248 1631/1555/1241\nf 1627/1564/1250 1631/1555/1241 1625/1562/1248 1626/1565/1251\nf 1621/1549/1235 1633/1559/1245 1632/1563/1249 1624/1554/1240\nf 1634/1560/1246 1633/1559/1245 1621/1549/1235 1613/1546/1232\nf 1635/1567/1253 1634/1560/1246 1613/1546/1232 1614/1545/1231\nf 2044/1377/1063 1475/1380/1066 1650/1568/1254 2043/1569/1255\nf 1477/1381/1067 1476/1384/1070 1652/1570/1256 1653/1571/1257\nf 1478/1385/1071 1477/1381/1067 1653/1571/1257 1654/1572/1258\nf 1479/1387/1073 1478/1385/1071 1654/1572/1258 1655/1573/1259\nf 1480/1389/1075 1479/1387/1073 1655/1573/1259 1656/1574/1260\nf 1537/1391/1077 1480/1389/1075 1656/1574/1260 1657/1575/1261\nf 1482/1393/1079 1481/1396/1082 1658/1576/1262 1659/1577/1263\nf 1483/1397/1083 1482/1393/1079 1659/1577/1263 1648/1578/1264\nf 1484/1399/1085 1483/1397/1083 1648/1578/1264 1649/1579/1265\nf 1475/1380/1066 1484/1399/1085 1649/1579/1265 1650/1568/1254\nf 1481/1396/1082 2033/1456/1142 2034/1580/1266 1658/1576/1262\nf 1476/1384/1070 1539/1458/1144 1651/1581/1267 1652/1570/1256\nf 1649/1579/1265 1648/1578/1264 1646/1582/1268 1647/1583/1269\nf 1650/1568/1254 1649/1579/1265 1647/1583/1269 1636/1584/1270\nf 2043/1569/1255 1650/1568/1254 1636/1584/1270 2042/1585/1271\nf 1652/1570/1256 1651/1581/1267 1637/1586/1272 1638/1587/1273\nf 1653/1571/1257 1652/1570/1256 1638/1587/1273 1639/1588/1274\nf 1654/1572/1258 1653/1571/1257 1639/1588/1274 1640/1589/1275\nf 1655/1573/1259 1654/1572/1258 1640/1589/1275 1641/1590/1276\nf 1656/1574/1260 1655/1573/1259 1641/1590/1276 1642/1591/1277\nf 1657/1575/1261 1656/1574/1260 1642/1591/1277 1643/1592/1278\nf 1658/1576/1262 2034/1580/1266 2035/1593/1279 1644/1594/1280\nf 1659/1577/1263 1658/1576/1262 1644/1594/1280 1645/1595/1281\nf 1648/1578/1264 1659/1577/1263 1645/1595/1281 1646/1582/1268\nf 1639/1588/1274 1638/1587/1273 1661/1596/1282 1662/1597/1283\nf 1640/1589/1275 1639/1588/1274 1662/1597/1283 1663/1598/1284\nf 1641/1590/1276 1640/1589/1275 1663/1598/1284 1664/1599/1285\nf 1642/1591/1277 1641/1590/1276 1664/1599/1285 1665/1600/1286\nf 1643/1592/1278 1642/1591/1277 1665/1600/1286 1666/1601/1287\nf 1645/1595/1281 1644/1594/1280 1667/1602/1288 1668/1603/1289\nf 1646/1582/1268 1645/1595/1281 1668/1603/1289 1669/1604/1290\nf 1647/1583/1269 1646/1582/1268 1669/1604/1290 1670/1605/1291\nf 1636/1584/1270 1647/1583/1269 1670/1605/1291 1660/1606/1292\nf 1644/1594/1280 2035/1593/1279 2036/1607/1293 1667/1602/1288\nf 1662/1597/1283 1661/1596/1282 1673/1608/1294 1674/1609/1295\nf 1663/1598/1284 1662/1597/1283 1674/1609/1295 1675/1610/1296\nf 1664/1599/1285 1663/1598/1284 1675/1610/1296 1676/1611/1297\nf 1665/1600/1286 1664/1599/1285 1676/1611/1297 1677/1612/1298\nf 1666/1601/1287 1665/1600/1286 1677/1612/1298 1678/1613/1299\nf 1668/1603/1289 1667/1602/1288 1679/1614/1300 1680/1615/1301\nf 1669/1604/1290 1668/1603/1289 1680/1615/1301 1681/1616/1302\nf 1670/1605/1291 1669/1604/1290 1681/1616/1302 1682/1617/1303\nf 1660/1606/1292 1670/1605/1291 1682/1617/1303 1671/1618/1304\nf 1667/1602/1288 2036/1607/1293 2037/1619/1305 1679/1614/1300\nf 1674/1609/1295 1673/1608/1294 1672/1620/1306 1685/1621/1307\nf 2037/1619/1305 2038/1622/1308 1680/1615/1301 1679/1614/1300\nf 1675/1610/1296 1683/1623/1309 1684/1624/1310 1676/1611/1297\nf 1681/1616/1302 2039/1625/1311 2040/1626/1312 1682/1617/1303\nf 2038/1622/1308 2039/1625/1311 1681/1616/1302 1680/1615/1301\nf 1676/1611/1297 1684/1624/1310 1678/1613/1299 1677/1612/1298\nf 1674/1609/1295 1685/1621/1307 1683/1623/1309 1675/1610/1296\nf 1682/1617/1303 2040/1626/1312 2041/1627/1313 1671/1618/1304\nf 1513/1422/1108 1514/1421/1107 1687/1628/1314 1686/1629/1315\nf 1514/1421/1107 1515/1423/1109 1688/1630/1316 1687/1628/1314\nf 1515/1423/1109 1516/1424/1110 1689/1631/1317 1688/1630/1316\nf 1516/1424/1110 1517/1425/1111 1690/1632/1318 1689/1631/1317\nf 1518/1426/1112 1513/1422/1108 1686/1629/1315 1691/1633/1319\nf 1519/1428/1114 1518/1426/1112 1691/1633/1319 1692/1634/1320\nf 2031/1538/1224 1605/1537/1223 1693/1635/1321 2030/1636/1322\nf 1517/1425/1111 1606/1535/1221 1694/1637/1323 1690/1632/1318\nf 2068/1638/1324 2069/1541/1227 1610/1542/1228 1696/1639/1325\nf 1696/1639/1325 1610/1542/1228 1609/1544/1230 1695/1640/1326\nf 2011/1641/1327 2012/1642/1328 2013/1643/1329 2014/1644/1330\nf 1707/1645/1331 2012/1642/1328 1597/1527/1213 1596/1525/1211\nf 1718/1646/1332 1699/1647/1333 2085/1648/1334 2086/1649/1335\nf 1047/1650/1336 1043/1651/1337 1716/1652/1338 1704/1653/1339\nf 1717/1654/1340 1700/1655/1341 1704/1653/1339 1716/1652/1338\nf 1699/1647/1333 1707/1645/1331 2084/1656/1342 2085/1648/1334\nf 1949/1657/1343 1950/1658/1344 1725/1659/1345 1724/1660/1346\nf 1595/1523/1209 1708/1661/1347 1707/1645/1331 1596/1525/1211\nf 2084/1656/1342 1707/1645/1331 1708/1661/1347 2083/1662/1348\nf 1595/1523/1209 1533/1450/1136 1711/1663/1349 1708/1661/1347\nf 2083/1662/1348 1708/1661/1347 1711/1663/1349 2082/1664/1350\nf 1709/1665/1351 1584/1454/1140 1532/1518/1204 1705/1666/1352\nf 2080/1667/1353 1709/1665/1351 1705/1666/1352 2079/1668/1354\nf 1710/1669/1355 1531/1449/1135 1584/1454/1140 1709/1665/1351\nf 2081/1670/1356 1710/1669/1355 1709/1665/1351 2080/1667/1353\nf 1533/1450/1136 1531/1449/1135 1710/1669/1355 1711/1663/1349\nf 2082/1664/1350 1711/1663/1349 1710/1669/1355 2081/1670/1356\nf 1726/1671/1357 1951/1672/1358 1952/1673/1359 1727/1674/1360\nf 1700/1655/1341 1712/1675/1361 1713/1676/1362 1704/1653/1339\nf 1704/1653/1339 1713/1676/1362 1046/1677/1363 1047/1650/1336\nf 1725/1659/1345 1950/1658/1344 1951/1672/1358 1726/1671/1357\nf 1948/1678/1364 1949/1657/1343 1724/1660/1346 1723/1679/1365\nf 1043/1651/1337 1049/1680/1366 1703/1681/1367 1716/1652/1338\nf 1701/1682/1368 1717/1654/1340 1716/1652/1338 1703/1681/1367\nf 1702/1683/1369 1718/1646/1332 2086/1649/1335 2087/1684/1370\nf 1718/1646/1332 1702/1683/1369 1697/1685/1371 1719/1686/1372\nf 1720/1687/1373 1719/1686/1372 1697/1685/1371 1698/1688/1374\nf 1947/1689/1375 1948/1678/1364 1723/1679/1365 1722/1690/1376\nf 1721/1691/1377 1946/1692/1378 1947/1689/1375 1722/1690/1376\nf 1728/1693/1379 1727/1674/1360 1952/1673/1359 1953/1694/1380\nf 1954/1695/1381 1729/1696/1382 1728/1693/1379 1953/1694/1380\nf 1957/1697/1383 1958/1698/1384 1706/1699/1385 1715/1700/1386\nf 1715/1700/1386 1714/1701/1387 1734/1702/1388 1733/1703/1389\nf 1735/1704/1390 1734/1702/1388 1714/1701/1387 1712/1675/1361\nf 1736/1705/1391 1735/1704/1390 1712/1675/1361 1700/1655/1341\nf 1737/1706/1392 1736/1705/1391 1700/1655/1341 1717/1654/1340\nf 1738/1707/1393 1737/1706/1392 1717/1654/1340 1701/1682/1368\nf 2030/1636/1322 1693/1635/1321 1765/1708/1394 2029/1709/1395\nf 1690/1632/1318 1694/1637/1323 1766/1710/1396 1740/1711/1397\nf 1742/1712/1398 1774/1713/1399 1741/1714/1400 1764/1715/1401\nf 2068/1638/1324 1696/1639/1325 1742/1712/1398 2067/1716/1402\nf 1689/1631/1317 1690/1632/1318 1740/1711/1397 1743/1717/1403\nf 1692/1634/1320 1691/1633/1319 1749/1718/1404 1747/1719/1405\nf 1745/1720/1406 1688/1630/1316 1689/1631/1317 1743/1717/1403\nf 1746/1721/1407 1687/1628/1314 1688/1630/1316 1745/1720/1406\nf 1748/1722/1408 1686/1629/1315 1687/1628/1314 1746/1721/1407\nf 1749/1718/1404 1691/1633/1319 1686/1629/1315 1748/1722/1408\nf 1743/1717/1403 1740/1711/1397 1767/1723/1409 1768/1724/1410\nf 1745/1720/1406 1743/1717/1403 1768/1724/1410 1769/1725/1411\nf 1746/1721/1407 1745/1720/1406 1769/1725/1411 1770/1726/1412\nf 1748/1722/1408 1746/1721/1407 1770/1726/1412 1771/1727/1413\nf 1749/1718/1404 1748/1722/1408 1771/1727/1413 1772/1728/1414\nf 1747/1719/1405 1749/1718/1404 1772/1728/1414 1773/1729/1415\nf 2026/1730/1416 1935/1731/1417 1782/1732/1418 2025/1733/1419\nf 2066/1734/1420 1764/1715/1401 1779/1735/1421 2065/1736/1422\nf 2028/1737/1423 1780/1738/1424 1781/1739/1425 2027/1740/1426\nf 2025/1733/1419 1782/1732/1418 1945/1741/1427 2024/1742/1428\nf 1933/1743/1429 1934/1744/1430 1750/1745/1431 1751/1746/1432\nf 1932/1747/1433 1933/1743/1429 1751/1746/1432 1752/1748/1434\nf 1931/1749/1435 1932/1747/1433 1752/1748/1434 1753/1750/1436\nf 1930/1751/1437 1931/1749/1435 1753/1750/1436 1754/1752/1438\nf 1929/1753/1439 1930/1751/1437 1754/1752/1438 1755/1754/1440\nf 1928/1755/1441 1929/1753/1439 1755/1754/1440 1756/1756/1442\nf 1927/1757/1443 1928/1755/1441 1756/1756/1442 1757/1758/1444\nf 1740/1711/1397 1766/1710/1396 1739/1759/1445 1767/1723/1409\nf 1696/1639/1325 1695/1640/1326 1774/1713/1399 1742/1712/1398\nf 1936/1760/1446 1937/1761/1447 1777/1522/1208 1776/1521/1207\nf 1937/1761/1447 1938/1762/1448 1778/1763/1449 1777/1522/1208\nf 1938/1762/1448 1939/1531/1217 1600/1529/1215 1778/1763/1449\nf 1966/1764/1450 1967/1519/1205 1777/1522/1208 1778/1763/1449\nf 1600/1529/1215 1965/1765/1451 1966/1764/1450 1778/1763/1449\nf 1534/1448/1134 1964/1766/1452 1965/1765/1451 1600/1529/1215\nf 1963/1767/1453 1964/1766/1452 1534/1448/1134 1535/1447/1133\nf 1583/1446/1132 1962/1768/1454 1963/1767/1453 1535/1447/1133\nf 1536/1516/1202 1961/1769/1455 1962/1768/1454 1583/1446/1132\nf 1597/1527/1213 1598/1770/1456 1783/1771/1457 1784/1528/1214\nf 1792/1772/1458 1254/1773/1459 1050/1774/1460 1793/1775/1461\nf 1738/1707/1393 1701/1682/1368 1794/1776/1462 1796/1777/1463\nf 1956/1778/1464 1051/1779/1465 1056/1780/1466 1795/1781/1467\nf 1729/1696/1382 1954/1695/1381 1955/1782/1468 1797/1783/1469\nf 1794/1776/1462 1792/1772/1458 1793/1775/1461 1796/1777/1463\nf 1956/1778/1464 1795/1781/1467 1797/1783/1469 1955/1782/1468\nf 1758/1784/1470 1744/1785/1471 2016/1786/1472 2017/1787/1473\nf 1763/1788/1474 1760/1789/1475 2021/1790/1476 2022/1791/1477\nf 1791/1792/1478 1758/1784/1470 2017/1787/1473 2018/1793/1479\nf 1761/1794/1480 1762/1795/1481 2019/1796/1482 2020/1797/1483\nf 1760/1789/1475 1761/1794/1480 2020/1797/1483 2021/1790/1476\nf 1759/1798/1484 1763/1788/1474 2022/1791/1477 2023/1799/1485\nf 1744/1785/1471 1775/1800/1486 2015/1801/1487 2016/1786/1472\nf 1762/1795/1481 1791/1792/1478 2018/1793/1479 2019/1796/1482\nf 2015/1801/1487 1775/1800/1486 2011/1641/1327 2014/1644/1330\nf 1751/1746/1432 1750/1745/1431 1807/1802/1488 1808/1803/1489\nf 1752/1748/1434 1751/1746/1432 1808/1803/1489 1809/1804/1490\nf 1753/1750/1436 1752/1748/1434 1809/1804/1490 1810/1805/1491\nf 1754/1752/1438 1753/1750/1436 1810/1805/1491 1811/1806/1492\nf 1755/1754/1440 1754/1752/1438 1811/1806/1492 1812/1807/1493\nf 1756/1756/1442 1755/1754/1440 1812/1807/1493 1813/1808/1494\nf 1757/1758/1444 1756/1756/1442 1813/1808/1494 1814/1809/1495\nf 1881/1810/1496 1815/1811/1497 1883/1812/1498 1882/1813/1499\nf 1848/1814/1500 1816/1815/1501 1850/1816/1502 1849/1817/1503\nf 1829/1818/1504 1817/1819/1505 1831/1820/1506 1830/1821/1507\nf 1821/1822/1508 1818/1823/1509 1823/1824/1510 1822/1825/1511\nf 1805/1826/1512 1801/1827/1513 1819/1828/1514 1820/1829/1515\nf 1807/1802/1488 1805/1826/1512 1820/1829/1515 1808/1803/1489\nf 1819/1828/1514 1821/1822/1508 1822/1825/1511 1820/1829/1515\nf 1822/1825/1511 1809/1804/1490 1808/1803/1489 1820/1829/1515\nf 1823/1824/1510 1810/1805/1491 1809/1804/1490 1822/1825/1511\nf 1804/1830/1516 1803/1831/1517 1824/1832/1518 1826/1833/1519\nf 1801/1827/1513 1800/1834/1520 1825/1835/1521 1819/1828/1514\nf 1800/1834/1520 1804/1830/1516 1826/1833/1519 1825/1835/1521\nf 1827/1836/1522 1818/1823/1509 1821/1822/1508 1828/1837/1523\nf 1821/1822/1508 1819/1828/1514 1825/1835/1521 1828/1837/1523\nf 1824/1832/1518 1829/1818/1504 1830/1821/1507 1826/1833/1519\nf 1830/1821/1507 1828/1837/1523 1825/1835/1521 1826/1833/1519\nf 1831/1820/1506 1827/1836/1522 1828/1837/1523 1830/1821/1507\nf 1832/1838/1524 1839/1839/1525 1838/1840/1526 1837/1841/1527\nf 1835/1842/1528 1834/1843/1529 1833/1844/1530 1836/1845/1531\nf 1823/1824/1510 1818/1823/1509 1834/1843/1529 1835/1842/1528\nf 1811/1806/1492 1810/1805/1491 1823/1824/1510 1835/1842/1528\nf 1812/1807/1493 1811/1806/1492 1835/1842/1528 1836/1845/1531\nf 1836/1845/1531 1833/1844/1530 1837/1841/1527 1838/1840/1526\nf 1813/1808/1494 1812/1807/1493 1836/1845/1531 1838/1840/1526\nf 1839/1839/1525 1814/1809/1495 1813/1808/1494 1838/1840/1526\nf 1843/1846/1532 1840/1847/1533 1845/1848/1534 1844/1849/1535\nf 1841/1850/1536 1833/1844/1530 1834/1843/1529 1842/1851/1537\nf 1834/1843/1529 1818/1823/1509 1827/1836/1522 1842/1851/1537\nf 1817/1819/1505 1843/1846/1532 1844/1849/1535 1831/1820/1506\nf 1844/1849/1535 1842/1851/1537 1827/1836/1522 1831/1820/1506\nf 1845/1848/1534 1841/1850/1536 1842/1851/1537 1844/1849/1535\nf 1846/1852/1538 1832/1838/1524 1837/1841/1527 1847/1853/1539\nf 1837/1841/1527 1833/1844/1530 1841/1850/1536 1847/1853/1539\nf 1840/1847/1533 1848/1814/1500 1849/1817/1503 1845/1848/1534\nf 1849/1817/1503 1847/1853/1539 1841/1850/1536 1845/1848/1534\nf 1850/1816/1502 1846/1852/1538 1847/1853/1539 1849/1817/1503\nf 1862/1854/1540 1851/1855/1541 1864/1856/1542 1863/1857/1543\nf 1855/1858/1544 1852/1859/1545 1857/1860/1546 1856/1861/1547\nf 1802/1862/1548 1799/1863/1549 1853/1864/1550 1854/1865/1551\nf 1803/1831/1517 1802/1862/1548 1854/1865/1551 1824/1832/1518\nf 1853/1864/1550 1855/1858/1544 1856/1861/1547 1854/1865/1551\nf 1856/1861/1547 1829/1818/1504 1824/1832/1518 1854/1865/1551\nf 1857/1860/1546 1817/1819/1505 1829/1818/1504 1856/1861/1547\nf 1806/1866/1552 1720/1687/1373 1698/1688/1374 1859/1867/1553\nf 1799/1863/1549 1798/1868/1554 1858/1869/1555 1853/1864/1550\nf 1798/1868/1554 1806/1866/1552 1859/1867/1553 1858/1869/1555\nf 1860/1870/1556 1852/1859/1545 1855/1858/1544 1861/1871/1557\nf 1855/1858/1544 1853/1864/1550 1858/1869/1555 1861/1871/1557\nf 1698/1688/1374 1862/1854/1540 1863/1857/1543 1859/1867/1553\nf 1863/1857/1543 1861/1871/1557 1858/1869/1555 1859/1867/1553\nf 1864/1856/1542 1860/1870/1556 1861/1871/1557 1863/1857/1543\nf 1865/1872/1558 1872/1873/1559 1871/1874/1560 1870/1875/1561\nf 1868/1876/1562 1867/1877/1563 1866/1878/1564 1869/1879/1565\nf 1857/1860/1546 1852/1859/1545 1867/1877/1563 1868/1876/1562\nf 1843/1846/1532 1817/1819/1505 1857/1860/1546 1868/1876/1562\nf 1840/1847/1533 1843/1846/1532 1868/1876/1562 1869/1879/1565\nf 1869/1879/1565 1866/1878/1564 1870/1875/1561 1871/1874/1560\nf 1848/1814/1500 1840/1847/1533 1869/1879/1565 1871/1874/1560\nf 1872/1873/1559 1816/1815/1501 1848/1814/1500 1871/1874/1560\nf 1876/1880/1566 1873/1881/1567 1878/1882/1568 1877/1883/1569\nf 1874/1884/1570 1866/1878/1564 1867/1877/1563 1875/1885/1571\nf 1867/1877/1563 1852/1859/1545 1860/1870/1556 1875/1885/1571\nf 1851/1855/1541 1876/1880/1566 1877/1883/1569 1864/1856/1542\nf 1877/1883/1569 1875/1885/1571 1860/1870/1556 1864/1856/1542\nf 1878/1882/1568 1874/1884/1570 1875/1885/1571 1877/1883/1569\nf 1879/1886/1572 1865/1872/1558 1870/1875/1561 1880/1887/1573\nf 1870/1875/1561 1866/1878/1564 1874/1884/1570 1880/1887/1573\nf 1873/1881/1567 1881/1810/1496 1882/1813/1499 1878/1882/1568\nf 1882/1813/1499 1880/1887/1573 1874/1884/1570 1878/1882/1568\nf 1883/1812/1498 1879/1886/1572 1880/1887/1573 1882/1813/1499\nf 1046/1677/1363 1713/1676/1362 1885/1888/1574 1045/1889/1575\nf 1885/1888/1574 1713/1676/1362 1712/1675/1361 1714/1701/1387\nf 1886/1890/1576 1885/1888/1574 1714/1701/1387 1715/1700/1386\nf 1045/1889/1575 1885/1888/1574 1886/1890/1576 1048/1891/1577\nf 1886/1890/1576 1715/1700/1386 1706/1699/1385 1884/1892/1578\nf 1884/1892/1578 1044/1893/1579 1048/1891/1577 1886/1890/1576\nf 1792/1772/1458 1703/1681/1367 1049/1680/1366 1254/1773/1459\nf 1792/1772/1458 1794/1776/1462 1701/1682/1368 1703/1681/1367\nf 1702/1683/1369 2087/1684/1370 1895/1894/1580 2077/1895/1581\nf 1887/1896/1582 1899/1897/1583 1052/1898/1584 1057/1899/1585\nf 1055/1900/1586 1892/1901/1587 1795/1781/1467 1056/1780/1466\nf 1053/1902/1588 1891/1903/1589 2078/1904/1590 1054/1905/1591\nf 1893/1906/1592 1898/1907/1593 1899/1897/1583 1887/1896/1582\nf 1894/1908/1594 2077/1895/1581 2078/1904/1590 1891/1903/1589\nf 1895/1894/1580 1797/1783/1469 1795/1781/1467 1892/1901/1587\nf 1889/1909/1595 1898/1907/1593 1893/1906/1592 1888/1910/1596\nf 1702/1683/1369 2077/1895/1581 1894/1908/1594 1890/1911/1597\nf 1702/1683/1369 1890/1911/1597 1896/1912/1598 1697/1685/1371\nf 1890/1911/1597 1889/1909/1595 1897/1913/1599 1896/1912/1598\nf 1862/1854/1540 1698/1688/1374 1697/1685/1371 1896/1912/1598\nf 1851/1855/1541 1862/1854/1540 1896/1912/1598 1897/1913/1599\nf 1890/1911/1597 1894/1908/1594 1898/1907/1593 1889/1909/1595\nf 1899/1897/1583 1898/1907/1593 1894/1908/1594 1891/1903/1589\nf 1052/1898/1584 1899/1897/1583 1891/1903/1589 1053/1902/1588\nf 1815/1811/1497 1881/1810/1496 1900/1914/1600 1901/1915/1601\nf 1873/1881/1567 1876/1880/1566 1902/1916/1602 1903/1917/1603\nf 1876/1880/1566 1851/1855/1541 1897/1913/1599 1902/1916/1602\nf 1881/1810/1496 1873/1881/1567 1903/1917/1603 1900/1914/1600\nf 1900/1914/1600 1904/1918/1604 1905/1919/1605 1901/1915/1601\nf 1902/1916/1602 1906/1920/1606 1907/1921/1607 1903/1917/1603\nf 1889/1909/1595 1906/1920/1606 1902/1916/1602 1897/1913/1599\nf 1907/1921/1607 1904/1918/1604 1900/1914/1600 1903/1917/1603\nf 1905/1919/1605 1904/1918/1604 1908/1922/1608 1909/1923/1609\nf 1907/1921/1607 1906/1920/1606 1910/1924/1610 1911/1925/1611\nf 1906/1920/1606 1889/1909/1595 1888/1910/1596 1910/1924/1610\nf 1904/1918/1604 1907/1921/1607 1911/1925/1611 1908/1922/1608\nf 1913/1926/1612 1256/1927/1613 1255/1928/1614 1912/1929/1615\nf 1914/1930/1616 1257/1931/1617 1256/1927/1613 1913/1926/1612\nf 1919/1932/1618 1258/1933/1619 1257/1931/1617 1914/1930/1616\nf 1925/1934/1620 1926/1935/1621 1909/1923/1609 1908/1922/1608\nf 1925/1934/1620 1908/1922/1608 1911/1925/1611 1924/1936/1622\nf 1913/1926/1612 1915/1937/1623 1917/1938/1624 1914/1930/1616\nf 1913/1926/1612 1912/1929/1615 1916/1939/1625 1915/1937/1623\nf 1924/1936/1622 1911/1925/1611 1910/1924/1610 1923/1940/1626\nf 1914/1930/1616 1917/1938/1624 1918/1941/1627 1919/1932/1618\nf 1923/1940/1626 1910/1924/1610 1888/1910/1596 1922/1942/1628\nf 1919/1932/1618 1918/1941/1627 1920/1943/1629 1921/1944/1630\nf 1259/1945/1631 1258/1933/1619 1919/1932/1618 1921/1944/1630\nf 1920/1943/1629 1922/1942/1628 1888/1910/1596 1893/1906/1592\nf 1921/1944/1630 1920/1943/1629 1893/1906/1592 1887/1896/1582\nf 1057/1899/1585 1259/1945/1631 1921/1944/1630 1887/1896/1582\nf 1918/1941/1627 1923/1940/1626 1922/1942/1628 1920/1943/1629\nf 1917/1938/1624 1924/1936/1622 1923/1940/1626 1918/1941/1627\nf 1915/1937/1623 1925/1934/1620 1924/1936/1622 1917/1938/1624\nf 1915/1937/1623 1916/1939/1625 1926/1935/1621 1925/1934/1620\nf 1773/1729/1415 1772/1728/1414 1928/1755/1441 1927/1757/1443\nf 1772/1728/1414 1771/1727/1413 1929/1753/1439 1928/1755/1441\nf 1771/1727/1413 1770/1726/1412 1930/1751/1437 1929/1753/1439\nf 1770/1726/1412 1769/1725/1411 1931/1749/1435 1930/1751/1437\nf 1769/1725/1411 1768/1724/1410 1932/1747/1433 1931/1749/1435\nf 1768/1724/1410 1767/1723/1409 1933/1743/1429 1932/1747/1433\nf 1767/1723/1409 1739/1759/1445 1934/1744/1430 1933/1743/1429\nf 2027/1740/1426 1781/1739/1425 1935/1731/1417 2026/1730/1416\nf 1695/1640/1326 1609/1544/1230 1937/1761/1447 1936/1760/1446\nf 1609/1544/1230 1607/1543/1229 1938/1762/1448 1937/1761/1447\nf 1607/1543/1229 1601/1532/1218 1939/1531/1217 1938/1762/1448\nf 1520/1442/1128 1940/1441/1127 1939/1531/1217 1601/1532/1218\nf 1529/1443/1129 1941/1438/1124 1940/1441/1127 1520/1442/1128\nf 1558/1488/1174 1942/1485/1171 1941/1438/1124 1529/1443/1129\nf 1590/1476/1162 1943/1468/1154 1942/1485/1171 1558/1488/1174\nf 1543/1475/1161 1944/1465/1151 1943/1468/1154 1590/1476/1162\nf 1557/1479/1165 1556/1482/1168 2093/1946/1632 2094/1947/1633\nf 1553/1483/1169 1557/1479/1165 2094/1947/1633 2095/1948/1634\nf 1566/1478/1164 1553/1483/1169 2095/1948/1634 2091/1949/1635\nf 1546/1464/1150 1545/1463/1149 2097/1950/1636 2098/1951/1637\nf 1545/1463/1149 1544/1474/1160 2096/1952/1638 2097/1950/1636\nf 2090/1953/1639 1559/1466/1152 1944/1465/1151 2088/1954/1640\nf 2024/1742/1428 1945/1741/1427 1759/1798/1484 2023/1799/1485\nf 1946/1692/1378 1730/1955/1641 1731/1956/1642 1947/1689/1375\nf 1731/1956/1642 1732/1957/1643 1948/1678/1364 1947/1689/1375\nf 1732/1957/1643 1733/1703/1389 1949/1657/1343 1948/1678/1364\nf 1733/1703/1389 1734/1702/1388 1950/1658/1344 1949/1657/1343\nf 1951/1672/1358 1950/1658/1344 1734/1702/1388 1735/1704/1390\nf 1952/1673/1359 1951/1672/1358 1735/1704/1390 1736/1705/1391\nf 1953/1694/1380 1952/1673/1359 1736/1705/1391 1737/1706/1392\nf 1738/1707/1393 1954/1695/1381 1953/1694/1380 1737/1706/1392\nf 1955/1782/1468 1954/1695/1381 1738/1707/1393 1796/1777/1463\nf 1793/1775/1461 1956/1778/1464 1955/1782/1468 1796/1777/1463\nf 1793/1775/1461 1050/1774/1460 1051/1779/1465 1956/1778/1464\nf 1957/1697/1383 1715/1700/1386 1733/1703/1389 1732/1957/1643\nf 1731/1956/1642 1960/1958/1644 1957/1697/1383 1732/1957/1643\nf 1731/1956/1642 1730/1955/1641 1959/1959/1645 1960/1958/1644\nf 1957/1697/1383 1960/1958/1644 1959/1959/1645 1958/1698/1384\nf 1961/1769/1455 1790/1517/1203 1789/1453/1139 1962/1768/1454\nf 1963/1767/1453 1962/1768/1454 1789/1453/1139 1788/1452/1138\nf 1788/1452/1138 1787/1451/1137 1964/1766/1452 1963/1767/1453\nf 1965/1765/1451 1964/1766/1452 1787/1451/1137 1786/1524/1210\nf 1966/1764/1450 1965/1765/1451 1786/1524/1210 1785/1526/1212\nf 1785/1526/1212 1784/1528/1214 1967/1519/1205 1966/1764/1450\nf 1784/1528/1214 1783/1771/1457 1599/1520/1206 1967/1519/1205\nf 2064/1960/1646 1983/1961/1647 1968/1962/1648 2063/1963/1649\nf 1973/1964/1650 1970/1965/1651 1971/1966/1652 1972/1967/1653\nf 1974/1968/1654 1975/1969/1655 1970/1965/1651 1973/1964/1650\nf 1982/1970/1656 1969/1971/1657 1975/1969/1655 1974/1968/1654\nf 1972/1967/1653 1971/1966/1652 1976/1972/1658 1981/1973/1659\nf 1981/1973/1659 1976/1972/1658 1977/1974/1660 1980/1975/1661\nf 1980/1975/1661 1977/1974/1660 1978/1976/1662 1979/1977/1663\nf 2065/1736/1422 1779/1735/1421 1983/1961/1647 2064/1960/1646\nf 1935/1731/1417 1973/1964/1650 1988/1978/1664 1782/1732/1418\nf 1974/1968/1654 1973/1964/1650 1935/1731/1417 1781/1739/1425\nf 1980/1975/1661 1979/1977/1663 1985/1979/1665 1986/1980/1666\nf 1981/1973/1659 1980/1975/1661 1986/1980/1666 1987/1981/1667\nf 1972/1967/1653 1981/1973/1659 1987/1981/1667 1984/1982/1668\nf 1982/1970/1656 1974/1968/1654 1781/1739/1425 1780/1738/1424\nf 1973/1964/1650 1972/1967/1653 1984/1982/1668 1988/1978/1664\nf 1945/1741/1427 1782/1732/1418 1988/1978/1664 1984/1982/1668\nf 1759/1798/1484 1945/1741/1427 1984/1982/1668 1987/1981/1667\nf 1763/1788/1474 1759/1798/1484 1987/1981/1667 1986/1980/1666\nf 1760/1789/1475 1763/1788/1474 1986/1980/1666 1985/1979/1665\nf 1979/1977/1663 1978/1976/1662 1989/1983/1669 1996/1984/1670\nf 1996/1984/1670 1989/1983/1669 1990/1985/1671 1995/1986/1672\nf 1995/1986/1672 1990/1985/1671 1991/1987/1673 1994/1988/1674\nf 1994/1988/1674 1991/1987/1673 1992/1989/1675 1993/1990/1676\nf 1994/1988/1674 1993/1990/1676 1997/1991/1677 1998/1992/1678\nf 1995/1986/1672 1994/1988/1674 1998/1992/1678 1999/1993/1679\nf 1996/1984/1670 1995/1986/1672 1999/1993/1679 2000/1994/1680\nf 1979/1977/1663 1996/1984/1670 2000/1994/1680 1985/1979/1665\nf 1999/1993/1679 1762/1795/1481 1761/1794/1480 2000/1994/1680\nf 2000/1994/1680 1761/1794/1480 1760/1789/1475 1985/1979/1665\nf 1758/1784/1470 1791/1792/1478 1998/1992/1678 1997/1991/1677\nf 1998/1992/1678 1791/1792/1478 1762/1795/1481 1999/1993/1679\nf 1993/1990/1676 1992/1989/1675 2001/1995/1681 2004/1996/1682\nf 2003/1997/1683 2002/1998/1684 1599/1520/1206 1783/1771/1457\nf 2004/1996/1682 2001/1995/1681 2002/1998/1684 2003/1997/1683\nf 1758/1784/1470 1997/1991/1677 2005/1999/1685 1744/1785/1471\nf 2003/1997/1683 2006/2000/1686 2005/1999/1685 2004/1996/1682\nf 1744/1785/1471 2005/1999/1685 2006/2000/1686 1775/1800/1486\nf 1598/1770/1456 2006/2000/1686 2003/1997/1683 1783/1771/1457\nf 2004/1996/1682 2005/1999/1685 1997/1991/1677 1993/1990/1676\nf 2007/2001/1687 1774/1713/1399 1695/1640/1326 1936/1760/1446\nf 1741/1714/1400 1774/1713/1399 2007/2001/1687 2008/2002/1688\nf 2008/2002/1688 2007/2001/1687 2009/2003/1689 2010/2004/1690\nf 2009/2003/1689 2007/2001/1687 1936/1760/1446 1776/1521/1207\nf 2002/1998/1684 2009/2003/1689 1776/1521/1207 1599/1520/1206\nf 2010/2004/1690 2009/2003/1689 2002/1998/1684 2001/1995/1681\nf 1775/1800/1486 2006/2000/1686 1598/1770/1456 2011/1641/1327\nf 1598/1770/1456 1597/1527/1213 2012/1642/1328 2011/1641/1327\nf 2012/1642/1328 1707/1645/1331 1699/1647/1333 2013/1643/1329\nf 2014/1644/1330 2013/1643/1329 1719/1686/1372 1720/1687/1373\nf 1806/1866/1552 2015/1801/1487 2014/1644/1330 1720/1687/1373\nf 2016/1786/1472 2015/1801/1487 1806/1866/1552 1798/1868/1554\nf 2017/1787/1473 2016/1786/1472 1798/1868/1554 1799/1863/1549\nf 2018/1793/1479 2017/1787/1473 1799/1863/1549 1802/1862/1548\nf 2019/1796/1482 2018/1793/1479 1802/1862/1548 1803/1831/1517\nf 2020/1797/1483 2019/1796/1482 1803/1831/1517 1804/1830/1516\nf 2021/1790/1476 2020/1797/1483 1804/1830/1516 1800/1834/1520\nf 2022/1791/1477 2021/1790/1476 1800/1834/1520 1801/1827/1513\nf 2023/1799/1485 2022/1791/1477 1801/1827/1513 1805/1826/1512\nf 1807/1802/1488 2024/1742/1428 2023/1799/1485 1805/1826/1512\nf 1750/1745/1431 2025/1733/1419 2024/1742/1428 1807/1802/1488\nf 1934/1744/1430 2026/1730/1416 2025/1733/1419 1750/1745/1431\nf 1739/1759/1445 2027/1740/1426 2026/1730/1416 1934/1744/1430\nf 2029/1709/1395 2027/1740/1426 1739/1759/1445 1766/1710/1396\nf 1764/1715/1401 2066/1734/1420 2067/1716/1402 1742/1712/1398\nf 2013/1643/1329 1699/1647/1333 1718/1646/1332 1719/1686/1372\nf 1694/1637/1323 2030/1636/1322 2029/1709/1395 1766/1710/1396\nf 1606/1535/1221 2031/1538/1224 2030/1636/1322 1694/1637/1323\nf 2032/1455/1141 2031/1538/1224 1606/1535/1221 1538/1392/1078\nf 2033/1456/1142 2032/1455/1141 1538/1392/1078 1537/1391/1077\nf 2034/1580/1266 2033/1456/1142 1537/1391/1077 1657/1575/1261\nf 2035/1593/1279 2034/1580/1266 1657/1575/1261 1643/1592/1278\nf 2036/1607/1293 2035/1593/1279 1643/1592/1278 1666/1601/1287\nf 2037/1619/1305 2036/1607/1293 1666/1601/1287 1678/1613/1299\nf 1678/1613/1299 1684/1624/1310 2038/1622/1308 2037/1619/1305\nf 1684/1624/1310 1683/1623/1309 2039/1625/1311 2038/1622/1308\nf 2040/1626/1312 2039/1625/1311 1683/1623/1309 1685/1621/1307\nf 2041/1627/1313 2040/1626/1312 1685/1621/1307 1672/1620/1306\nf 1765/1708/1394 2028/1737/1423 2027/1740/1426 2029/1709/1395\nf 1651/1581/1267 2043/1569/1255 2042/1585/1271 1637/1586/1272\nf 1539/1458/1144 2044/1377/1063 2043/1569/1255 1651/1581/1267\nf 1539/1458/1144 1540/1457/1143 2045/1378/1064 2044/1377/1063\nf 1540/1457/1143 1541/1459/1145 2046/1401/1087 2045/1378/1064\nf 1541/1459/1145 1542/1460/1146 2047/1403/1089 2046/1401/1087\nf 1661/1596/1282 2048/2005/1691 1672/1620/1306 1673/1608/1294\nf 1637/1586/1272 2048/2005/1691 1661/1596/1282 1638/1587/1273\nf 2042/1585/1271 2049/2006/1692 2048/2005/1691 1637/1586/1272\nf 1672/1620/1306 2048/2005/1691 2049/2006/1692 2041/1627/1313\nf 1636/1584/1270 1660/1606/1292 2049/2006/1692 2042/1585/1271\nf 2041/1627/1313 2049/2006/1692 1660/1606/1292 1671/1618/1304\nf 1779/1735/1421 2054/2007/1693 2055/2008/1694 1983/1961/1647\nf 2010/2004/1690 2001/1995/1681 1992/1989/1675 2053/2009/1695\nf 2053/2009/1695 1992/1989/1675 1991/1987/1673 2052/2010/1696\nf 2052/2010/1696 1991/1987/1673 1990/1985/1671 2051/2011/1697\nf 2051/2011/1697 1990/1985/1671 1989/1983/1669 2050/2012/1698\nf 2056/2013/1699 2058/2014/1700 2057/2015/1701 1968/1962/1648\nf 2056/2013/1699 1968/1962/1648 1983/1961/1647 2055/2008/1694\nf 1741/1714/1400 2054/2007/1693 1779/1735/1421 1764/1715/1401\nf 1741/1714/1400 2008/2002/1688 2010/2004/1690 2054/2007/1693\nf 2055/2008/1694 2054/2007/1693 2010/2004/1690 2053/2009/1695\nf 2052/2010/1696 2056/2013/1699 2055/2008/1694 2053/2009/1695\nf 2051/2011/1697 2050/2012/1698 2057/2015/1701 2058/2014/1700\nf 2051/2011/1697 2058/2014/1700 2056/2013/1699 2052/2010/1696\nf 1970/1965/1651 1975/1969/1655 1969/1971/1657 2059/2016/1702\nf 2062/2017/1703 2063/1963/1649 1968/1962/1648 2057/2015/1701\nf 1971/1966/1652 1970/1965/1651 2059/2016/1702 2060/2018/1704\nf 2061/2019/1705 2062/2017/1703 2057/2015/1701 2050/2012/1698\nf 1977/1974/1660 1976/1972/1658 1971/1966/1652 2060/2018/1704\nf 2061/2019/1705 1978/1976/1662 1977/1974/1660 2060/2018/1704\nf 2060/2018/1704 2059/2016/1702 2062/2017/1703 2061/2019/1705\nf 2059/2016/1702 1969/1971/1657 2063/1963/1649 2062/2017/1703\nf 1982/1970/1656 2064/1960/1646 2063/1963/1649 1969/1971/1657\nf 1780/1738/1424 2065/1736/1422 2064/1960/1646 1982/1970/1656\nf 2028/1737/1423 2066/1734/1420 2065/1736/1422 1780/1738/1424\nf 2067/1716/1402 2066/1734/1420 2028/1737/1423 1765/1708/1394\nf 1693/1635/1321 2068/1638/1324 2067/1716/1402 1765/1708/1394\nf 1693/1635/1321 1605/1537/1223 2069/1541/1227 2068/1638/1324\nf 1605/1537/1223 1604/1536/1222 2070/1539/1225 2069/1541/1227\nf 1604/1536/1222 1603/1530/1216 2071/1534/1220 2070/1539/1225\nf 2072/1414/1100 2071/1534/1220 1603/1530/1216 1507/1420/1106\nf 1510/1419/1105 2073/1411/1097 2072/1414/1100 1507/1420/1106\nf 1579/1511/1197 2074/1491/1177 2073/1411/1097 1510/1419/1105\nf 1579/1511/1197 1580/1512/1198 2075/1492/1178 2074/1491/1177\nf 1580/1512/1198 1581/1513/1199 2076/1509/1195 2075/1492/1178\nf 1989/1983/1669 1978/1976/1662 2061/2019/1705 2050/2012/1698\nf 1577/1508/1194 1576/1507/1193 1634/1560/1246 1635/1567/1253\nf 1622/1558/1244 1623/1561/1247 1626/1565/1251 1625/1562/1248\nf 2078/1904/1590 2077/1895/1581 1895/1894/1580 1892/1901/1587\nf 1054/1905/1591 2078/1904/1590 1892/1901/1587 1055/1900/1586\nf 1722/1690/1376 2080/1667/1353 2079/1668/1354 1721/1691/1377\nf 1723/1679/1365 2081/1670/1356 2080/1667/1353 1722/1690/1376\nf 1724/1660/1346 2082/1664/1350 2081/1670/1356 1723/1679/1365\nf 1725/1659/1345 2083/1662/1348 2082/1664/1350 1724/1660/1346\nf 1726/1671/1357 2084/1656/1342 2083/1662/1348 1725/1659/1345\nf 2085/1648/1334 2084/1656/1342 1726/1671/1357 1727/1674/1360\nf 2086/1649/1335 2085/1648/1334 1727/1674/1360 1728/1693/1379\nf 2087/1684/1370 2086/1649/1335 1728/1693/1379 1729/1696/1382\nf 1895/1894/1580 2087/1684/1370 1729/1696/1382 1797/1783/1469\nf 2089/2020/1706 1565/1490/1176 1559/1466/1152 2090/1953/1639\nf 2092/2021/1707 1543/1475/1161 1566/1478/1164 2091/1949/1635\nf 1944/1465/1151 1543/1475/1161 2092/2021/1707 2088/2022/1640\nf 1544/1474/1160 1565/1490/1176 2089/2020/1706 2096/1952/1638\nf 2611/2023/1708 2099/2024/1709 2109/2025/1710 2612/2026/1711\nf 2101/2027/1712 2100/2028/1713 2110/2029/1714 2111/2030/1715\nf 2102/2031/1716 2101/2027/1712 2111/2030/1715 2112/2032/1717\nf 2103/2033/1718 2102/2031/1716 2112/2032/1717 2113/2034/1719\nf 2104/2035/1720 2103/2033/1718 2113/2034/1719 2114/2036/1721\nf 2151/2037/1722 2104/2035/1720 2114/2036/1721 2152/2038/1723\nf 2106/2039/1724 2105/2040/1725 2115/2041/1726 2116/2042/1727\nf 2107/2043/1728 2106/2039/1724 2116/2042/1727 2117/2044/1729\nf 2108/2045/1730 2107/2043/1728 2117/2044/1729 2118/2046/1731\nf 2099/2024/1709 2108/2045/1730 2118/2046/1731 2109/2025/1710\nf 2612/2026/1711 2109/2025/1710 2119/2047/1732 2613/2048/1733\nf 2613/2048/1733 2119/2047/1732 1497/1404/1090 2047/1403/1089\nf 2119/2047/1732 2109/2025/1710 2123/2049/1734 2124/2050/1735\nf 1497/1404/1090 2119/2047/1732 2124/2050/1735 1504/1407/1093\nf 2124/2050/1735 2123/2049/1734 2121/2051/1736 2122/2052/1737\nf 1504/1407/1093 2124/2050/1735 2122/2052/1737 1501/1410/1096\nf 2639/2053/1738 2638/2054/1739 2128/2055/1740 2129/2056/1741\nf 2125/2057/1742 2123/2049/1734 2109/2025/1710 2118/2046/1731\nf 2126/2058/1743 2125/2057/1742 2118/2046/1731 2117/2044/1729\nf 2121/2051/1736 2123/2049/1734 2125/2057/1742 2132/2059/1744\nf 2132/2059/1744 2125/2057/1742 2126/2058/1743 2131/2060/1745\nf 2131/2060/1745 2126/2058/1743 2127/2061/1746 2130/2062/1747\nf 2111/2030/1715 2110/2029/1714 2133/2063/1748 2134/2064/1749\nf 2112/2032/1717 2111/2030/1715 2134/2064/1749 2135/2065/1750\nf 2113/2034/1719 2112/2032/1717 2135/2065/1750 2136/2066/1751\nf 2114/2036/1721 2113/2034/1719 2136/2066/1751 2137/2067/1752\nf 2110/2029/1714 2120/2068/1753 2138/2069/1754 2133/2063/1748\nf 2120/2068/1753 1498/1429/1115 1519/1428/1114 2138/2069/1754\nf 2145/2070/1755 2140/2071/1756 2141/2072/1757 2144/2073/1758\nf 2143/2074/1759 2142/2075/1760 1524/1436/1122 1525/1435/1121\nf 2144/2073/1758 2141/2072/1757 2142/2075/1760 2143/2074/1759\nf 2512/2076/1761 2511/2077/1762 2171/2078/1763 2170/2079/1764\nf 2139/2080/1765 2146/2081/1766 2129/2056/1741 2128/2055/1740\nf 2142/2075/1760 2141/2072/1757 2187/2082/1767 2214/2083/1768\nf 2187/2082/1767 2141/2072/1757 2150/2084/1769 2188/2085/1770\nf 2150/2084/1769 2141/2072/1757 2140/2071/1756 2149/2086/1771\nf 2147/2087/1772 2379/2088/1773 2378/2089/1774 2148/2090/1775\nf 2380/2091/1776 2379/2088/1773 2147/2087/1772 2189/2092/1777\nf 2105/2040/1725 2600/2093/1778 2599/2094/1779 2115/2041/1726\nf 2100/2028/1713 2153/2095/1780 2154/2096/1781 2110/2029/1714\nf 2110/2029/1714 2154/2096/1781 2155/2097/1782 2120/2068/1753\nf 2120/2068/1753 2155/2097/1782 1542/1460/1146 1498/1429/1115\nf 1585/1461/1147 1546/1464/1150 2158/2098/1783 2190/2099/1784\nf 2515/2100/1785 2514/2101/1786 2193/2102/1787 2168/2103/1788\nf 2161/2104/1789 2159/2105/1790 2145/2070/1755 2144/2073/1758\nf 1525/1435/1121 1548/1472/1158 2160/2106/1791 2143/2074/1759\nf 2158/2098/1783 2157/2107/1792 2191/2108/1793 2190/2099/1784\nf 2160/2106/1791 2161/2104/1789 2144/2073/1758 2143/2074/1759\nf 2156/2109/1794 2174/2110/1795 2195/2111/1796 2194/2112/1797\nf 2166/2113/1798 1556/1482/1168 1594/1481/1167 2197/2114/1799\nf 2164/2115/1800 2166/2113/1798 2197/2114/1799 2196/2116/1801\nf 2513/2117/1802 2512/2076/1761 2170/2079/1764 2169/2118/1803\nf 2146/2081/1766 2167/2119/1804 2162/2120/1805 2129/2056/1741\nf 2157/2107/1792 2173/2121/1806 2192/2122/1807 2191/2108/1793\nf 2145/2070/1755 2159/2105/1790 2169/2118/1803 2170/2079/1764\nf 2140/2071/1756 2145/2070/1755 2170/2079/1764 2171/2078/1763\nf 2640/2123/1808 2184/2124/1809 2172/2125/1810 2641/2126/1811\nf 1555/1495/1181 1564/1497/1183 2172/2125/1810 2165/2127/1812\nf 2168/2103/1788 2193/2102/1787 2192/2122/1807 2173/2121/1806\nf 2174/2110/1795 2164/2115/1800 2196/2116/1801 2195/2111/1796\nf 2175/2128/1813 1567/1499/1185 1501/1410/1096 2122/2052/1737\nf 2131/2060/1745 2178/2129/1814 2177/2130/1815 2132/2059/1744\nf 2177/2130/1815 2176/2131/1816 2121/2051/1736 2132/2059/1744\nf 2176/2131/1816 2175/2128/1813 2122/2052/1737 2121/2051/1736\nf 2131/2060/1745 2180/2132/1817 2179/2133/1818 2178/2129/1814\nf 2180/2132/1817 2182/2134/1819 2181/2135/1820 2179/2133/1818\nf 2182/2134/1819 1577/1508/1194 2183/2136/1821 2181/2135/1820\nf 2641/2126/1811 2172/2125/1810 1564/1497/1183 2076/1509/1195\nf 2165/2127/1812 2172/2125/1810 2184/2124/1809 2163/2137/1822\nf 2163/2137/1822 2184/2124/1809 2129/2056/1741 2162/2120/1805\nf 2640/2123/1808 2639/2053/1738 2129/2056/1741 2184/2124/1809\nf 2180/2132/1817 2131/2060/1745 2130/2062/1747 2185/2138/1823\nf 2180/2132/1817 2185/2138/1823 2186/2139/1824 2182/2134/1819\nf 2182/2134/1819 2186/2139/1824 1581/1513/1199 1577/1508/1194\nf 1612/1514/1200 2214/2083/1768 2187/2082/1767 1530/1515/1201\nf 1530/1515/1201 2187/2082/1767 2188/2085/1770 1536/1516/1202\nf 1790/1517/1203 2380/2091/1776 2189/2092/1777 1532/1518/1204\nf 1548/1472/1158 1585/1461/1147 2190/2099/1784 2160/2106/1791\nf 2191/2108/1793 2161/2104/1789 2160/2106/1791 2190/2099/1784\nf 2192/2122/1807 2159/2105/1790 2161/2104/1789 2191/2108/1793\nf 2193/2102/1787 2169/2118/1803 2159/2105/1790 2192/2122/1807\nf 2514/2101/1786 2513/2117/1802 2169/2118/1803 2193/2102/1787\nf 2195/2111/1796 2162/2120/1805 2167/2119/1804 2194/2112/1797\nf 2196/2116/1801 2163/2137/1822 2162/2120/1805 2195/2111/1796\nf 2197/2114/1799 2165/2127/1812 2163/2137/1822 2196/2116/1801\nf 1594/1481/1167 1555/1495/1181 2165/2127/1812 2197/2114/1799\nf 2534/2140/1825 2368/2141/1826 2367/2142/1827 2202/2143/1828\nf 2378/2089/1774 2377/2144/1829 2198/2145/1830 2148/2090/1775\nf 2377/2144/1829 2376/2146/1831 2199/2147/1832 2198/2145/1830\nf 2199/2147/1832 2376/2146/1831 2375/2148/1833 2200/2149/1834\nf 2171/2078/1763 2203/2150/1835 2149/2086/1771 2140/2071/1756\nf 2127/2061/1746 2126/2058/1743 2117/2044/1729 2206/2151/1836\nf 2511/2077/1762 2510/2152/1837 2203/2150/1835 2171/2078/1763\nf 2139/2080/1765 2128/2055/1740 2205/2153/1838 2204/2154/1839\nf 2128/2055/1740 2638/2054/1739 2637/2155/1840 2205/2153/1838\nf 2152/2038/1723 2114/2036/1721 2137/2067/1752 2209/2156/1841\nf 2117/2044/1729 2116/2042/1727 2207/2157/1842 2206/2151/1836\nf 2116/2042/1727 2115/2041/1726 2208/2158/1843 2207/2157/1842\nf 2115/2041/1726 2599/2094/1779 2598/2159/1844 2208/2158/1843\nf 2636/2160/1845 2211/2161/1846 2205/2153/1838 2637/2155/1840\nf 2635/2162/1847 2213/2163/1848 2211/2161/1846 2636/2160/1845\nf 2211/2161/1846 2210/2164/1849 2204/2154/1839 2205/2153/1838\nf 2213/2163/1848 2212/2165/1850 2210/2164/1849 2211/2161/1846\nf 1524/1436/1122 2142/2075/1760 2214/2083/1768 1612/1514/1200\nf 1614/1545/1231 1616/1548/1234 2216/2166/1851 2215/2167/1852\nf 1616/1548/1234 1567/1499/1185 2175/2128/1813 2216/2166/1851\nf 2215/2167/1852 2216/2166/1851 2217/2168/1853 2221/2169/1854\nf 2216/2166/1851 2175/2128/1813 2176/2131/1816 2217/2168/1853\nf 2217/2168/1853 2176/2131/1816 2177/2130/1815 2218/2170/1855\nf 2219/2171/1856 2178/2129/1814 2179/2133/1818 2220/2172/1857\nf 2218/2170/1855 2177/2130/1815 2178/2129/1814 2219/2171/1856\nf 2224/2173/1858 2230/2174/1859 2229/2175/1860 2231/2176/1861\nf 2183/2136/1821 2234/2177/1862 2233/2178/1863 2222/2179/1864\nf 2223/2180/1865 2220/2172/1857 2179/2133/1818 2181/2135/1820\nf 2183/2136/1821 2222/2179/1864 2223/2180/1865 2181/2135/1820\nf 2233/2178/1863 2232/2181/1866 2225/2182/1867 2222/2179/1864\nf 2223/2180/1865 2226/2183/1868 2227/2184/1869 2220/2172/1857\nf 2220/2172/1857 2227/2184/1869 2228/2185/1870 2219/2171/1856\nf 2219/2171/1856 2228/2185/1870 2229/2175/1860 2218/2170/1855\nf 2218/2170/1855 2229/2175/1860 2230/2174/1859 2217/2168/1853\nf 2217/2168/1853 2230/2174/1859 2224/2173/1858 2221/2169/1854\nf 2229/2175/1860 2228/2185/1870 2227/2184/1869 2231/2176/1861\nf 2224/2173/1858 2231/2176/1861 2225/2182/1867 2232/2181/1866\nf 2227/2184/1869 2226/2183/1868 2225/2182/1867 2231/2176/1861\nf 2221/2169/1854 2224/2173/1858 2232/2181/1866 2233/2178/1863\nf 2234/2177/1862 2215/2167/1852 2221/2169/1854 2233/2178/1863\nf 1635/1567/1253 1614/1545/1231 2215/2167/1852 2234/2177/1862\nf 2611/2023/1708 2610/2186/1871 2249/2187/1872 2099/2024/1709\nf 2101/2027/1712 2252/2188/1873 2251/2189/1874 2100/2028/1713\nf 2102/2031/1716 2253/2190/1875 2252/2188/1873 2101/2027/1712\nf 2103/2033/1718 2254/2191/1876 2253/2190/1875 2102/2031/1716\nf 2104/2035/1720 2255/2192/1877 2254/2191/1876 2103/2033/1718\nf 2151/2037/1722 2256/2193/1878 2255/2192/1877 2104/2035/1720\nf 2106/2039/1724 2258/2194/1879 2257/2195/1880 2105/2040/1725\nf 2107/2043/1728 2247/2196/1881 2258/2194/1879 2106/2039/1724\nf 2108/2045/1730 2248/2197/1882 2247/2196/1881 2107/2043/1728\nf 2099/2024/1709 2249/2187/1872 2248/2197/1882 2108/2045/1730\nf 2105/2040/1725 2257/2195/1880 2601/2198/1883 2600/2093/1778\nf 2100/2028/1713 2251/2189/1874 2250/2199/1884 2153/2095/1780\nf 2248/2197/1882 2246/2200/1885 2245/2201/1886 2247/2196/1881\nf 2249/2187/1872 2235/2202/1887 2246/2200/1885 2248/2197/1882\nf 2610/2186/1871 2609/2203/1888 2235/2202/1887 2249/2187/1872\nf 2251/2189/1874 2237/2204/1889 2236/2205/1890 2250/2199/1884\nf 2252/2188/1873 2238/2206/1891 2237/2204/1889 2251/2189/1874\nf 2253/2190/1875 2239/2207/1892 2238/2206/1891 2252/2188/1873\nf 2254/2191/1876 2240/2208/1893 2239/2207/1892 2253/2190/1875\nf 2255/2192/1877 2241/2209/1894 2240/2208/1893 2254/2191/1876\nf 2256/2193/1878 2242/2210/1895 2241/2209/1894 2255/2192/1877\nf 2257/2195/1880 2243/2211/1896 2602/2212/1897 2601/2198/1883\nf 2258/2194/1879 2244/2213/1898 2243/2211/1896 2257/2195/1880\nf 2247/2196/1881 2245/2201/1886 2244/2213/1898 2258/2194/1879\nf 2238/2206/1891 2261/2214/1899 2260/2215/1900 2237/2204/1889\nf 2239/2207/1892 2262/2216/1901 2261/2214/1899 2238/2206/1891\nf 2240/2208/1893 2263/2217/1902 2262/2216/1901 2239/2207/1892\nf 2241/2209/1894 2264/2218/1903 2263/2217/1902 2240/2208/1893\nf 2242/2210/1895 2265/2219/1904 2264/2218/1903 2241/2209/1894\nf 2244/2213/1898 2267/2220/1905 2266/2221/1906 2243/2211/1896\nf 2245/2201/1886 2268/2222/1907 2267/2220/1905 2244/2213/1898\nf 2246/2200/1885 2269/2223/1908 2268/2222/1907 2245/2201/1886\nf 2235/2202/1887 2259/2224/1909 2269/2223/1908 2246/2200/1885\nf 2243/2211/1896 2266/2221/1906 2603/2225/1910 2602/2212/1897\nf 2261/2214/1899 2273/2226/1911 2272/2227/1912 2260/2215/1900\nf 2262/2216/1901 2274/2228/1913 2273/2226/1911 2261/2214/1899\nf 2263/2217/1902 2275/2229/1914 2274/2228/1913 2262/2216/1901\nf 2264/2218/1903 2276/2230/1915 2275/2229/1914 2263/2217/1902\nf 2265/2219/1904 2277/2231/1916 2276/2230/1915 2264/2218/1903\nf 2267/2220/1905 2279/2232/1917 2278/2233/1918 2266/2221/1906\nf 2268/2222/1907 2280/2234/1919 2279/2232/1917 2267/2220/1905\nf 2269/2223/1908 2281/2235/1920 2280/2234/1919 2268/2222/1907\nf 2259/2224/1909 2270/2236/1921 2281/2235/1920 2269/2223/1908\nf 2266/2221/1906 2278/2233/1918 2604/2237/1922 2603/2225/1910\nf 2273/2226/1911 2284/2238/1923 2271/2239/1924 2272/2227/1912\nf 2604/2237/1922 2278/2233/1918 2279/2232/1917 2605/2240/1925\nf 2274/2228/1913 2275/2229/1914 2283/2241/1926 2282/2242/1927\nf 2280/2234/1919 2281/2235/1920 2607/2243/1928 2606/2244/1929\nf 2605/2240/1925 2279/2232/1917 2280/2234/1919 2606/2244/1929\nf 2275/2229/1914 2276/2230/1915 2277/2231/1916 2283/2241/1926\nf 2273/2226/1911 2274/2228/1913 2282/2242/1927 2284/2238/1923\nf 2281/2235/1920 2270/2236/1921 2608/2245/1930 2607/2243/1928\nf 2133/2063/1748 2285/2246/1931 2286/2247/1932 2134/2064/1749\nf 2134/2064/1749 2286/2247/1932 2287/2248/1933 2135/2065/1750\nf 2135/2065/1750 2287/2248/1933 2288/2249/1934 2136/2066/1751\nf 2136/2066/1751 2288/2249/1934 2289/2250/1935 2137/2067/1752\nf 2138/2069/1754 2290/2251/1936 2285/2246/1931 2133/2063/1748\nf 1519/1428/1114 1692/1634/1320 2290/2251/1936 2138/2069/1754\nf 2598/2159/1844 2597/2252/1937 2291/2253/1938 2208/2158/1843\nf 2137/2067/1752 2289/2250/1935 2292/2254/1939 2209/2156/1841\nf 2634/2255/1940 2294/2256/1941 2213/2163/1848 2635/2162/1847\nf 2294/2256/1941 2293/2257/1942 2212/2165/1850 2213/2163/1848\nf 2578/2258/1943 2581/2259/1944 2580/2260/1945 2579/2261/1946\nf 2303/2262/1947 2199/2147/1832 2200/2149/1834 2579/2261/1946\nf 2314/2263/1948 2650/2264/1949 2649/2265/1950 2297/2266/1951\nf 2312/2267/1952 1260/2268/1953 1263/2269/1954 2302/2270/1955\nf 2313/2271/1956 2312/2267/1952 2302/2270/1955 2298/2272/1957\nf 2297/2266/1951 2649/2265/1950 2648/2273/1958 2303/2262/1947\nf 2519/2274/1959 2319/2275/1960 2320/2276/1961 2520/2277/1962\nf 2198/2145/1830 2199/2147/1832 2303/2262/1947 2304/2278/1963\nf 2648/2273/1958 2647/2279/1964 2304/2278/1963 2303/2262/1947\nf 2198/2145/1830 2304/2278/1963 2307/2280/1965 2148/2090/1775\nf 2647/2279/1964 2646/2281/1966 2307/2280/1965 2304/2278/1963\nf 2305/2282/1967 1705/1666/1352 1532/1518/1204 2189/2092/1777\nf 2644/2283/1968 2079/1668/1354 1705/1666/1352 2305/2282/1967\nf 2306/2284/1969 2305/2282/1967 2189/2092/1777 2147/2087/1772\nf 2645/2285/1970 2644/2283/1968 2305/2282/1967 2306/2284/1969\nf 2148/2090/1775 2307/2280/1965 2306/2284/1969 2147/2087/1772\nf 2646/2281/1966 2645/2285/1970 2306/2284/1969 2307/2280/1965\nf 2321/2286/1971 2322/2287/1972 2522/2288/1973 2521/2289/1974\nf 2298/2272/1957 2302/2270/1955 2309/2290/1975 2308/2291/1976\nf 2302/2270/1955 1263/2269/1954 1262/2292/1977 2309/2290/1975\nf 2320/2276/1961 2321/2286/1971 2521/2289/1974 2520/2277/1962\nf 2518/2293/1978 2318/2294/1979 2319/2275/1960 2519/2274/1959\nf 2301/2295/1980 1265/2296/1981 1260/2268/1953 2312/2267/1952\nf 2299/2297/1982 2301/2295/1980 2312/2267/1952 2313/2271/1956\nf 2300/2298/1983 2651/2299/1984 2650/2264/1949 2314/2263/1948\nf 2314/2263/1948 2315/2300/1985 2295/2301/1986 2300/2298/1983\nf 2316/2302/1987 2296/2303/1988 2295/2301/1986 2315/2300/1985\nf 2517/2304/1989 2317/2305/1990 2318/2294/1979 2518/2293/1978\nf 1721/1691/1377 2317/2305/1990 2517/2304/1989 1946/1692/1378\nf 2323/2306/1991 2523/2307/1992 2522/2288/1973 2322/2287/1972\nf 2524/2308/1993 2523/2307/1992 2323/2306/1991 2324/2309/1994\nf 2527/2310/1995 2311/2311/1996 1706/1699/1385 1958/1698/1384\nf 2311/2311/1996 2327/2312/1997 2328/2313/1998 2310/2314/1999\nf 2329/2315/2000 2308/2291/1976 2310/2314/1999 2328/2313/1998\nf 2330/2316/2001 2298/2272/1957 2308/2291/1976 2329/2315/2000\nf 2331/2317/2002 2313/2271/1956 2298/2272/1957 2330/2316/2001\nf 2332/2318/2003 2299/2297/1982 2313/2271/1956 2331/2317/2002\nf 2597/2252/1937 2596/2319/2004 2357/2320/2005 2291/2253/1938\nf 2289/2250/1935 2334/2321/2006 2358/2322/2007 2292/2254/1939\nf 2336/2323/2008 2356/2324/2009 2335/2325/2010 2365/2326/2011\nf 2634/2255/1940 2633/2327/2012 2336/2323/2008 2294/2256/1941\nf 2288/2249/1934 2337/2328/2013 2334/2321/2006 2289/2250/1935\nf 1692/1634/1320 1747/1719/1405 2342/2329/2014 2290/2251/1936\nf 2339/2330/2015 2337/2328/2013 2288/2249/1934 2287/2248/1933\nf 2340/2331/2016 2339/2330/2015 2287/2248/1933 2286/2247/1932\nf 2341/2332/2017 2340/2331/2016 2286/2247/1932 2285/2246/1931\nf 2342/2329/2014 2341/2332/2017 2285/2246/1931 2290/2251/1936\nf 2337/2328/2013 2360/2333/2018 2359/2334/2019 2334/2321/2006\nf 2339/2330/2015 2361/2335/2020 2360/2333/2018 2337/2328/2013\nf 2340/2331/2016 2362/2336/2021 2361/2335/2020 2339/2330/2015\nf 2341/2332/2017 2363/2337/2022 2362/2336/2021 2340/2331/2016\nf 2342/2329/2014 2364/2338/2023 2363/2337/2022 2341/2332/2017\nf 1747/1719/1405 1773/1729/1415 2364/2338/2023 2342/2329/2014\nf 2593/2339/2024 2592/2340/2025 2373/2341/2026 2506/2342/2027\nf 2632/2343/2028 2631/2344/2029 2370/2345/2030 2356/2324/2009\nf 2595/2346/2031 2594/2347/2032 2372/2348/2033 2371/2349/2034\nf 2592/2340/2025 2591/2350/2035 2516/2351/2036 2373/2341/2026\nf 2504/2352/2037 2344/2353/2038 2343/2354/2039 2505/2355/2040\nf 2503/2356/2041 2345/2357/2042 2344/2353/2038 2504/2352/2037\nf 2502/2358/2043 2346/2359/2044 2345/2357/2042 2503/2356/2041\nf 2501/2360/2045 2347/2361/2046 2346/2359/2044 2502/2358/2043\nf 2500/2362/2047 2348/2363/2048 2347/2361/2046 2501/2360/2045\nf 2499/2364/2049 2349/2365/2050 2348/2363/2048 2500/2362/2047\nf 1927/2366/1443 1757/2367/1444 2349/2365/2050 2499/2364/2049\nf 2334/2321/2006 2359/2334/2019 2333/2368/2051 2358/2322/2007\nf 2294/2256/1941 2336/2323/2008 2365/2326/2011 2293/2257/1942\nf 2507/2369/2052 2367/2142/1827 2368/2141/1826 2508/2370/2053\nf 2508/2370/2053 2368/2141/1826 2369/2371/2054 2509/2372/2055\nf 2509/2372/2055 2369/2371/2054 2203/2150/1835 2510/2152/1837\nf 2533/2373/2056 2369/2371/2054 2368/2141/1826 2534/2140/1825\nf 2203/2150/1835 2369/2371/2054 2533/2373/2056 2532/2374/2057\nf 2149/2086/1771 2203/2150/1835 2532/2374/2057 2531/2375/2058\nf 2530/2376/2059 2150/2084/1769 2149/2086/1771 2531/2375/2058\nf 2188/2085/1770 2150/2084/1769 2530/2376/2059 2529/2377/2060\nf 1536/1516/1202 2188/2085/1770 2529/2377/2060 1961/1769/1455\nf 2200/2149/1834 2375/2148/1833 2374/2378/2061 2201/2379/2062\nf 2382/2380/2063 2383/2381/2064 1266/2382/2065 1470/2383/2066\nf 2332/2318/2003 2386/2384/2067 2384/2385/2068 2299/2297/1982\nf 2526/2386/2069 2385/2387/2070 1272/2388/2071 1267/2389/2072\nf 2324/2309/1994 2387/2390/2073 2525/2391/2074 2524/2308/1993\nf 2384/2385/2068 2386/2384/2067 2383/2381/2064 2382/2380/2063\nf 2526/2386/2069 2525/2391/2074 2387/2390/2073 2385/2387/2070\nf 2350/2392/2075 2584/2393/2076 2583/2394/2077 2338/2395/2078\nf 2355/2396/2079 2589/2397/2080 2588/2398/2081 2352/2399/2082\nf 2381/2400/2083 2585/2401/2084 2584/2393/2076 2350/2392/2075\nf 2353/2402/2085 2587/2403/2086 2586/2404/2087 2354/2405/2088\nf 2352/2399/2082 2588/2398/2081 2587/2403/2086 2353/2402/2085\nf 2351/2406/2089 2590/2407/2090 2589/2397/2080 2355/2396/2079\nf 2338/2395/2078 2583/2394/2077 2582/2408/2091 2366/2409/2092\nf 2354/2405/2088 2586/2404/2087 2585/2401/2084 2381/2400/2083\nf 2582/2408/2091 2581/2259/1944 2578/2258/1943 2366/2409/2092\nf 2344/2353/2038 2398/2410/2093 2397/2411/2094 2343/2354/2039\nf 2345/2357/2042 2399/2412/2095 2398/2410/2093 2344/2353/2038\nf 2346/2359/2044 2400/2413/2096 2399/2412/2095 2345/2357/2042\nf 2347/2361/2046 2401/2414/2097 2400/2413/2096 2346/2359/2044\nf 2348/2363/2048 2402/2415/2098 2401/2414/2097 2347/2361/2046\nf 2349/2365/2050 2403/2416/2099 2402/2415/2098 2348/2363/2048\nf 1757/2367/1444 1814/2417/1495 2403/2416/2099 2349/2365/2050\nf 2461/2418/2100 2462/2419/2101 1883/2420/1498 1815/2421/1497\nf 2432/2422/2102 2433/2423/2103 1850/2424/1502 1816/2425/1501\nf 2416/2426/2104 2417/2427/2105 2418/2428/2106 2404/2429/2107\nf 2408/2430/2108 2409/2431/2109 2410/2432/2110 2405/2433/2111\nf 2395/2434/2112 2407/2435/2113 2406/2436/2114 2391/2437/2115\nf 2397/2411/2094 2398/2410/2093 2407/2435/2113 2395/2434/2112\nf 2406/2436/2114 2407/2435/2113 2409/2431/2109 2408/2430/2108\nf 2409/2431/2109 2407/2435/2113 2398/2410/2093 2399/2412/2095\nf 2410/2432/2110 2409/2431/2109 2399/2412/2095 2400/2413/2096\nf 2394/2438/2116 2413/2439/2117 2411/2440/2118 2393/2441/2119\nf 2391/2437/2115 2406/2436/2114 2412/2442/2120 2390/2443/2121\nf 2390/2443/2121 2412/2442/2120 2413/2439/2117 2394/2438/2116\nf 2414/2444/2122 2415/2445/2123 2408/2430/2108 2405/2433/2111\nf 2408/2430/2108 2415/2445/2123 2412/2442/2120 2406/2436/2114\nf 2411/2440/2118 2413/2439/2117 2417/2427/2105 2416/2426/2104\nf 2417/2427/2105 2413/2439/2117 2412/2442/2120 2415/2445/2123\nf 2418/2428/2106 2417/2427/2105 2415/2445/2123 2414/2444/2122\nf 1832/2446/1524 2423/2447/2124 2424/2448/2125 1839/2449/1525\nf 2421/2450/2126 2422/2451/2127 2419/2452/2128 2420/2453/2129\nf 2410/2432/2110 2421/2450/2126 2420/2453/2129 2405/2433/2111\nf 2401/2414/2097 2421/2450/2126 2410/2432/2110 2400/2413/2096\nf 2402/2415/2098 2422/2451/2127 2421/2450/2126 2401/2414/2097\nf 2422/2451/2127 2424/2448/2125 2423/2447/2124 2419/2452/2128\nf 2403/2416/2099 2424/2448/2125 2422/2451/2127 2402/2415/2098\nf 1839/2449/1525 2424/2448/2125 2403/2416/2099 1814/2417/1495\nf 2428/2454/2130 2429/2455/2131 2430/2456/2132 2425/2457/2133\nf 2426/2458/2134 2427/2459/2135 2420/2453/2129 2419/2452/2128\nf 2420/2453/2129 2427/2459/2135 2414/2444/2122 2405/2433/2111\nf 2404/2429/2107 2418/2428/2106 2429/2455/2131 2428/2454/2130\nf 2429/2455/2131 2418/2428/2106 2414/2444/2122 2427/2459/2135\nf 2430/2456/2132 2429/2455/2131 2427/2459/2135 2426/2458/2134\nf 1846/2460/1538 2431/2461/2136 2423/2447/2124 1832/2446/1524\nf 2423/2447/2124 2431/2461/2136 2426/2458/2134 2419/2452/2128\nf 2425/2457/2133 2430/2456/2132 2433/2423/2103 2432/2422/2102\nf 2433/2423/2103 2430/2456/2132 2426/2458/2134 2431/2461/2136\nf 1850/2424/1502 2433/2423/2103 2431/2461/2136 1846/2460/1538\nf 2445/2462/2137 2446/2463/2138 2447/2464/2139 2434/2465/2140\nf 2438/2466/2141 2439/2467/2142 2440/2468/2143 2435/2469/2144\nf 2392/2470/2145 2437/2471/2146 2436/2472/2147 2389/2473/2148\nf 2393/2441/2119 2411/2440/2118 2437/2471/2146 2392/2470/2145\nf 2436/2472/2147 2437/2471/2146 2439/2467/2142 2438/2466/2141\nf 2439/2467/2142 2437/2471/2146 2411/2440/2118 2416/2426/2104\nf 2440/2468/2143 2439/2467/2142 2416/2426/2104 2404/2429/2107\nf 2396/2474/2149 2442/2475/2150 2296/2303/1988 2316/2302/1987\nf 2389/2473/2148 2436/2472/2147 2441/2476/2151 2388/2477/2152\nf 2388/2477/2152 2441/2476/2151 2442/2475/2150 2396/2474/2149\nf 2443/2478/2153 2444/2479/2154 2438/2466/2141 2435/2469/2144\nf 2438/2466/2141 2444/2479/2154 2441/2476/2151 2436/2472/2147\nf 2296/2303/1988 2442/2475/2150 2446/2463/2138 2445/2462/2137\nf 2446/2463/2138 2442/2475/2150 2441/2476/2151 2444/2479/2154\nf 2447/2464/2139 2446/2463/2138 2444/2479/2154 2443/2478/2153\nf 1865/2480/1558 2452/2481/2155 2453/2482/2156 1872/2483/1559\nf 2450/2484/2157 2451/2485/2158 2448/2486/2159 2449/2487/2160\nf 2440/2468/2143 2450/2484/2157 2449/2487/2160 2435/2469/2144\nf 2428/2454/2130 2450/2484/2157 2440/2468/2143 2404/2429/2107\nf 2425/2457/2133 2451/2485/2158 2450/2484/2157 2428/2454/2130\nf 2451/2485/2158 2453/2482/2156 2452/2481/2155 2448/2486/2159\nf 2432/2422/2102 2453/2482/2156 2451/2485/2158 2425/2457/2133\nf 1872/2483/1559 2453/2482/2156 2432/2422/2102 1816/2425/1501\nf 2457/2488/2161 2458/2489/2162 2459/2490/2163 2454/2491/2164\nf 2455/2492/2165 2456/2493/2166 2449/2487/2160 2448/2486/2159\nf 2449/2487/2160 2456/2493/2166 2443/2478/2153 2435/2469/2144\nf 2434/2465/2140 2447/2464/2139 2458/2489/2162 2457/2488/2161\nf 2458/2489/2162 2447/2464/2139 2443/2478/2153 2456/2493/2166\nf 2459/2490/2163 2458/2489/2162 2456/2493/2166 2455/2492/2165\nf 1879/2494/1572 2460/2495/2167 2452/2481/2155 1865/2480/1558\nf 2452/2481/2155 2460/2495/2167 2455/2492/2165 2448/2486/2159\nf 2454/2491/2164 2459/2490/2163 2462/2419/2101 2461/2418/2100\nf 2462/2419/2101 2459/2490/2163 2455/2492/2165 2460/2495/2167\nf 1883/2420/1498 2462/2419/2101 2460/2495/2167 1879/2494/1572\nf 1262/2292/1977 1261/2496/2168 2463/2497/2169 2309/2290/1975\nf 2463/2497/2169 2310/2314/1999 2308/2291/1976 2309/2290/1975\nf 2464/2498/2170 2311/2311/1996 2310/2314/1999 2463/2497/2169\nf 1261/2496/2168 1264/2499/2171 2464/2498/2170 2463/2497/2169\nf 2464/2498/2170 1884/1892/1578 1706/1699/1385 2311/2311/1996\nf 1884/1892/1578 2464/2498/2170 1264/2499/2171 1044/1893/1579\nf 2382/2380/2063 1470/2383/2066 1265/2296/1981 2301/2295/1980\nf 2382/2380/2063 2301/2295/1980 2299/2297/1982 2384/2385/2068\nf 2300/2298/1983 2642/2500/2172 2473/2501/2173 2651/2299/1984\nf 2465/2502/2174 1273/2503/2175 1268/2504/2176 2477/2505/2177\nf 1271/2506/2178 1272/2388/2071 2385/2387/2070 2470/2507/2179\nf 1269/2508/2180 1270/2509/2181 2643/2510/2182 2469/2511/2183\nf 2471/2512/2184 2465/2502/2174 2477/2505/2177 2476/2513/2185\nf 2472/2514/2186 2469/2511/2183 2643/2510/2182 2642/2500/2172\nf 2473/2501/2173 2470/2507/2179 2385/2387/2070 2387/2390/2073\nf 2467/2515/2187 2466/2516/2188 2471/2512/2184 2476/2513/2185\nf 2300/2298/1983 2468/2517/2189 2472/2514/2186 2642/2500/2172\nf 2300/2298/1983 2295/2301/1986 2474/2518/2190 2468/2517/2189\nf 2468/2517/2189 2474/2518/2190 2475/2519/2191 2467/2515/2187\nf 2445/2462/2137 2474/2518/2190 2295/2301/1986 2296/2303/1988\nf 2434/2465/2140 2475/2519/2191 2474/2518/2190 2445/2462/2137\nf 2468/2517/2189 2467/2515/2187 2476/2513/2185 2472/2514/2186\nf 2477/2505/2177 2469/2511/2183 2472/2514/2186 2476/2513/2185\nf 1268/2504/2176 1269/2508/2180 2469/2511/2183 2477/2505/2177\nf 1815/2421/1497 1901/2520/1601 2478/2521/2192 2461/2418/2100\nf 2454/2491/2164 2480/2522/2193 2479/2523/2194 2457/2488/2161\nf 2457/2488/2161 2479/2523/2194 2475/2519/2191 2434/2465/2140\nf 2461/2418/2100 2478/2521/2192 2480/2522/2193 2454/2491/2164\nf 2478/2521/2192 1901/2520/1601 1905/2524/1605 2481/2525/2195\nf 2479/2523/2194 2480/2522/2193 2483/2526/2196 2482/2527/2197\nf 2467/2515/2187 2475/2519/2191 2479/2523/2194 2482/2527/2197\nf 2483/2526/2196 2480/2522/2193 2478/2521/2192 2481/2525/2195\nf 1905/2524/1605 1909/2528/1609 2484/2529/2198 2481/2525/2195\nf 2483/2526/2196 2486/2530/2199 2485/2531/2200 2482/2527/2197\nf 2482/2527/2197 2485/2531/2200 2466/2516/2188 2467/2515/2187\nf 2481/2525/2195 2484/2529/2198 2486/2530/2199 2483/2526/2196\nf 2487/2532/2201 1912/2533/1615 1255/2534/1614 1471/2535/2202\nf 2488/2536/2203 2487/2532/2201 1471/2535/2202 1472/2537/2204\nf 2492/2538/2205 2488/2536/2203 1472/2537/2204 1473/2539/2206\nf 2498/2540/2207 2484/2529/2198 1909/2528/1609 1926/2541/1621\nf 2498/2540/2207 2497/2542/2208 2486/2530/2199 2484/2529/2198\nf 2487/2532/2201 2488/2536/2203 2490/2543/2209 2489/2544/2210\nf 2487/2532/2201 2489/2544/2210 1916/2545/1625 1912/2533/1615\nf 2497/2542/2208 2496/2546/2211 2485/2531/2200 2486/2530/2199\nf 2488/2536/2203 2492/2538/2205 2491/2547/2212 2490/2543/2209\nf 2496/2546/2211 2495/2548/2213 2466/2516/2188 2485/2531/2200\nf 2492/2538/2205 2494/2549/2214 2493/2550/2215 2491/2547/2212\nf 1474/2551/2216 2494/2549/2214 2492/2538/2205 1473/2539/2206\nf 2493/2550/2215 2471/2512/2184 2466/2516/2188 2495/2548/2213\nf 2494/2549/2214 2465/2502/2174 2471/2512/2184 2493/2550/2215\nf 1273/2503/2175 2465/2502/2174 2494/2549/2214 1474/2551/2216\nf 2491/2547/2212 2493/2550/2215 2495/2548/2213 2496/2546/2211\nf 2490/2543/2209 2491/2547/2212 2496/2546/2211 2497/2542/2208\nf 2489/2544/2210 2490/2543/2209 2497/2542/2208 2498/2540/2207\nf 2489/2544/2210 2498/2540/2207 1926/2541/1621 1916/2545/1625\nf 1773/1729/1415 1927/2366/1443 2499/2364/2049 2364/2338/2023\nf 2364/2338/2023 2499/2364/2049 2500/2362/2047 2363/2337/2022\nf 2363/2337/2022 2500/2362/2047 2501/2360/2045 2362/2336/2021\nf 2362/2336/2021 2501/2360/2045 2502/2358/2043 2361/2335/2020\nf 2361/2335/2020 2502/2358/2043 2503/2356/2041 2360/2333/2018\nf 2360/2333/2018 2503/2356/2041 2504/2352/2037 2359/2334/2019\nf 2359/2334/2019 2504/2352/2037 2505/2355/2040 2333/2368/2051\nf 2594/2347/2032 2593/2339/2024 2506/2342/2027 2372/2348/2033\nf 2293/2257/1942 2507/2369/2052 2508/2370/2053 2212/2165/1850\nf 2212/2165/1850 2508/2370/2053 2509/2372/2055 2210/2164/1849\nf 2210/2164/1849 2509/2372/2055 2510/2152/1837 2204/2154/1839\nf 2139/2080/1765 2204/2154/1839 2510/2152/1837 2511/2077/1762\nf 2146/2081/1766 2139/2080/1765 2511/2077/1762 2512/2076/1761\nf 2167/2119/1804 2146/2081/1766 2512/2076/1761 2513/2117/1802\nf 2194/2112/1797 2167/2119/1804 2513/2117/1802 2514/2101/1786\nf 2156/2109/1794 2194/2112/1797 2514/2101/1786 2515/2100/1785\nf 2166/2113/1798 2657/2552/2217 2093/1946/1632 1556/1482/1168\nf 2164/2115/1800 2658/2553/2218 2657/2552/2217 2166/2113/1798\nf 2174/2110/1795 2655/2554/2219 2658/2553/2218 2164/2115/1800\nf 1546/1464/1150 2098/1951/1637 2660/2555/2220 2158/2098/1783\nf 2158/2098/1783 2660/2555/2220 2659/2556/2221 2157/2107/1792\nf 2654/2557/2222 2652/2558/2223 2515/2100/1785 2168/2103/1788\nf 2591/2350/2035 2590/2407/2090 2351/2406/2089 2516/2351/2036\nf 1946/1692/1378 2517/2304/1989 2325/2559/2224 1730/1955/1641\nf 2325/2559/2224 2517/2304/1989 2518/2293/1978 2326/2560/2225\nf 2326/2560/2225 2518/2293/1978 2519/2274/1959 2327/2312/1997\nf 2327/2312/1997 2519/2274/1959 2520/2277/1962 2328/2313/1998\nf 2521/2289/1974 2329/2315/2000 2328/2313/1998 2520/2277/1962\nf 2522/2288/1973 2330/2316/2001 2329/2315/2000 2521/2289/1974\nf 2523/2307/1992 2331/2317/2002 2330/2316/2001 2522/2288/1973\nf 2332/2318/2003 2331/2317/2002 2523/2307/1992 2524/2308/1993\nf 2525/2391/2074 2386/2384/2067 2332/2318/2003 2524/2308/1993\nf 2383/2381/2064 2386/2384/2067 2525/2391/2074 2526/2386/2069\nf 2383/2381/2064 2526/2386/2069 1267/2389/2072 1266/2382/2065\nf 2527/2310/1995 2326/2560/2225 2327/2312/1997 2311/2311/1996\nf 2325/2559/2224 2326/2560/2225 2527/2310/1995 2528/2561/2226\nf 2325/2559/2224 2528/2561/2226 1959/1959/1645 1730/1955/1641\nf 2527/2310/1995 1958/1698/1384 1959/1959/1645 2528/2561/2226\nf 1961/1769/1455 2529/2377/2060 2380/2091/1776 1790/1517/1203\nf 2530/2376/2059 2379/2088/1773 2380/2091/1776 2529/2377/2060\nf 2379/2088/1773 2530/2376/2059 2531/2375/2058 2378/2089/1774\nf 2532/2374/2057 2377/2144/1829 2378/2089/1774 2531/2375/2058\nf 2533/2373/2056 2376/2146/1831 2377/2144/1829 2532/2374/2057\nf 2376/2146/1831 2533/2373/2056 2534/2140/1825 2375/2148/1833\nf 2375/2148/1833 2534/2140/1825 2202/2143/1828 2374/2378/2061\nf 2630/2562/2227 2629/2563/2228 2535/2564/2229 2550/2565/2230\nf 2540/2566/2231 2539/2567/2232 2538/2568/2233 2537/2569/2234\nf 2541/2570/2235 2540/2566/2231 2537/2569/2234 2542/2571/2236\nf 2549/2572/2237 2541/2570/2235 2542/2571/2236 2536/2573/2238\nf 2539/2567/2232 2548/2574/2239 2543/2575/2240 2538/2568/2233\nf 2548/2574/2239 2547/2576/2241 2544/2577/2242 2543/2575/2240\nf 2547/2576/2241 2546/2578/2243 2545/2579/2244 2544/2577/2242\nf 2631/2344/2029 2630/2562/2227 2550/2565/2230 2370/2345/2030\nf 2506/2342/2027 2373/2341/2026 2555/2580/2245 2540/2566/2231\nf 2541/2570/2235 2372/2348/2033 2506/2342/2027 2540/2566/2231\nf 2547/2576/2241 2553/2581/2246 2552/2582/2247 2546/2578/2243\nf 2548/2574/2239 2554/2583/2248 2553/2581/2246 2547/2576/2241\nf 2539/2567/2232 2551/2584/2249 2554/2583/2248 2548/2574/2239\nf 2549/2572/2237 2371/2349/2034 2372/2348/2033 2541/2570/2235\nf 2540/2566/2231 2555/2580/2245 2551/2584/2249 2539/2567/2232\nf 2516/2351/2036 2551/2584/2249 2555/2580/2245 2373/2341/2026\nf 2351/2406/2089 2554/2583/2248 2551/2584/2249 2516/2351/2036\nf 2355/2396/2079 2553/2581/2246 2554/2583/2248 2351/2406/2089\nf 2352/2399/2082 2552/2582/2247 2553/2581/2246 2355/2396/2079\nf 2546/2578/2243 2563/2585/2250 2556/2586/2251 2545/2579/2244\nf 2563/2585/2250 2562/2587/2252 2557/2588/2253 2556/2586/2251\nf 2562/2587/2252 2561/2589/2254 2558/2590/2255 2557/2588/2253\nf 2561/2589/2254 2560/2591/2256 2559/2592/2257 2558/2590/2255\nf 2561/2589/2254 2565/2593/2258 2564/2594/2259 2560/2591/2256\nf 2562/2587/2252 2566/2595/2260 2565/2593/2258 2561/2589/2254\nf 2563/2585/2250 2567/2596/2261 2566/2595/2260 2562/2587/2252\nf 2546/2578/2243 2552/2582/2247 2567/2596/2261 2563/2585/2250\nf 2566/2595/2260 2567/2596/2261 2353/2402/2085 2354/2405/2088\nf 2567/2596/2261 2552/2582/2247 2352/2399/2082 2353/2402/2085\nf 2350/2392/2075 2564/2594/2259 2565/2593/2258 2381/2400/2083\nf 2565/2593/2258 2566/2595/2260 2354/2405/2088 2381/2400/2083\nf 2560/2591/2256 2571/2597/2262 2568/2598/2263 2559/2592/2257\nf 2570/2599/2264 2374/2378/2061 2202/2143/1828 2569/2600/2265\nf 2571/2597/2262 2570/2599/2264 2569/2600/2265 2568/2598/2263\nf 2350/2392/2075 2338/2395/2078 2572/2601/2266 2564/2594/2259\nf 2570/2599/2264 2571/2597/2262 2572/2601/2266 2573/2602/2267\nf 2338/2395/2078 2366/2409/2092 2573/2602/2267 2572/2601/2266\nf 2201/2379/2062 2374/2378/2061 2570/2599/2264 2573/2602/2267\nf 2571/2597/2262 2560/2591/2256 2564/2594/2259 2572/2601/2266\nf 2574/2603/2268 2507/2369/2052 2293/2257/1942 2365/2326/2011\nf 2335/2325/2010 2575/2604/2269 2574/2603/2268 2365/2326/2011\nf 2575/2604/2269 2577/2605/2270 2576/2606/2271 2574/2603/2268\nf 2576/2606/2271 2367/2142/1827 2507/2369/2052 2574/2603/2268\nf 2569/2600/2265 2202/2143/1828 2367/2142/1827 2576/2606/2271\nf 2577/2605/2270 2568/2598/2263 2569/2600/2265 2576/2606/2271\nf 2366/2409/2092 2578/2258/1943 2201/2379/2062 2573/2602/2267\nf 2201/2379/2062 2578/2258/1943 2579/2261/1946 2200/2149/1834\nf 2579/2261/1946 2580/2260/1945 2297/2266/1951 2303/2262/1947\nf 2581/2259/1944 2316/2302/1987 2315/2300/1985 2580/2260/1945\nf 2396/2474/2149 2316/2302/1987 2581/2259/1944 2582/2408/2091\nf 2583/2394/2077 2388/2477/2152 2396/2474/2149 2582/2408/2091\nf 2584/2393/2076 2389/2473/2148 2388/2477/2152 2583/2394/2077\nf 2585/2401/2084 2392/2470/2145 2389/2473/2148 2584/2393/2076\nf 2586/2404/2087 2393/2441/2119 2392/2470/2145 2585/2401/2084\nf 2587/2403/2086 2394/2438/2116 2393/2441/2119 2586/2404/2087\nf 2588/2398/2081 2390/2443/2121 2394/2438/2116 2587/2403/2086\nf 2589/2397/2080 2391/2437/2115 2390/2443/2121 2588/2398/2081\nf 2590/2407/2090 2395/2434/2112 2391/2437/2115 2589/2397/2080\nf 2397/2411/2094 2395/2434/2112 2590/2407/2090 2591/2350/2035\nf 2343/2354/2039 2397/2411/2094 2591/2350/2035 2592/2340/2025\nf 2505/2355/2040 2343/2354/2039 2592/2340/2025 2593/2339/2024\nf 2333/2368/2051 2505/2355/2040 2593/2339/2024 2594/2347/2032\nf 2596/2319/2004 2358/2322/2007 2333/2368/2051 2594/2347/2032\nf 2356/2324/2009 2336/2323/2008 2633/2327/2012 2632/2343/2028\nf 2580/2260/1945 2315/2300/1985 2314/2263/1948 2297/2266/1951\nf 2292/2254/1939 2358/2322/2007 2596/2319/2004 2597/2252/1937\nf 2209/2156/1841 2292/2254/1939 2597/2252/1937 2598/2159/1844\nf 2599/2094/1779 2152/2038/1723 2209/2156/1841 2598/2159/1844\nf 2600/2093/1778 2151/2037/1722 2152/2038/1723 2599/2094/1779\nf 2601/2198/1883 2256/2193/1878 2151/2037/1722 2600/2093/1778\nf 2602/2212/1897 2242/2210/1895 2256/2193/1878 2601/2198/1883\nf 2603/2225/1910 2265/2219/1904 2242/2210/1895 2602/2212/1897\nf 2604/2237/1922 2277/2231/1916 2265/2219/1904 2603/2225/1910\nf 2277/2231/1916 2604/2237/1922 2605/2240/1925 2283/2241/1926\nf 2283/2241/1926 2605/2240/1925 2606/2244/1929 2282/2242/1927\nf 2607/2243/1928 2284/2238/1923 2282/2242/1927 2606/2244/1929\nf 2608/2245/1930 2271/2239/1924 2284/2238/1923 2607/2243/1928\nf 2357/2320/2005 2596/2319/2004 2594/2347/2032 2595/2346/2031\nf 2250/2199/1884 2236/2205/1890 2609/2203/1888 2610/2186/1871\nf 2153/2095/1780 2250/2199/1884 2610/2186/1871 2611/2023/1708\nf 2153/2095/1780 2611/2023/1708 2612/2026/1711 2154/2096/1781\nf 2154/2096/1781 2612/2026/1711 2613/2048/1733 2155/2097/1782\nf 2155/2097/1782 2613/2048/1733 2047/1403/1089 1542/1460/1146\nf 2260/2215/1900 2272/2227/1912 2271/2239/1924 2614/2607/2272\nf 2236/2205/1890 2237/2204/1889 2260/2215/1900 2614/2607/2272\nf 2609/2203/1888 2236/2205/1890 2614/2607/2272 2615/2608/2273\nf 2271/2239/1924 2608/2245/1930 2615/2608/2273 2614/2607/2272\nf 2235/2202/1887 2609/2203/1888 2615/2608/2273 2259/2224/1909\nf 2608/2245/1930 2270/2236/1921 2259/2224/1909 2615/2608/2273\nf 2370/2345/2030 2550/2565/2230 2621/2609/2274 2620/2610/2275\nf 2577/2605/2270 2619/2611/2276 2559/2592/2257 2568/2598/2263\nf 2619/2611/2276 2618/2612/2277 2558/2590/2255 2559/2592/2257\nf 2618/2612/2277 2617/2613/2278 2557/2588/2253 2558/2590/2255\nf 2617/2613/2278 2616/2614/2279 2556/2586/2251 2557/2588/2253\nf 2622/2615/2280 2535/2564/2229 2623/2616/2281 2624/2617/2282\nf 2622/2615/2280 2621/2609/2274 2550/2565/2230 2535/2564/2229\nf 2335/2325/2010 2356/2324/2009 2370/2345/2030 2620/2610/2275\nf 2335/2325/2010 2620/2610/2275 2577/2605/2270 2575/2604/2269\nf 2621/2609/2274 2619/2611/2276 2577/2605/2270 2620/2610/2275\nf 2618/2612/2277 2619/2611/2276 2621/2609/2274 2622/2615/2280\nf 2617/2613/2278 2624/2617/2282 2623/2616/2281 2616/2614/2279\nf 2617/2613/2278 2618/2612/2277 2622/2615/2280 2624/2617/2282\nf 2537/2569/2234 2625/2618/2283 2536/2573/2238 2542/2571/2236\nf 2628/2619/2284 2623/2616/2281 2535/2564/2229 2629/2563/2228\nf 2538/2568/2233 2626/2620/2285 2625/2618/2283 2537/2569/2234\nf 2627/2621/2286 2616/2614/2279 2623/2616/2281 2628/2619/2284\nf 2544/2577/2242 2626/2620/2285 2538/2568/2233 2543/2575/2240\nf 2627/2621/2286 2626/2620/2285 2544/2577/2242 2545/2579/2244\nf 2626/2620/2285 2627/2621/2286 2628/2619/2284 2625/2618/2283\nf 2625/2618/2283 2628/2619/2284 2629/2563/2228 2536/2573/2238\nf 2549/2572/2237 2536/2573/2238 2629/2563/2228 2630/2562/2227\nf 2371/2349/2034 2549/2572/2237 2630/2562/2227 2631/2344/2029\nf 2595/2346/2031 2371/2349/2034 2631/2344/2029 2632/2343/2028\nf 2633/2327/2012 2357/2320/2005 2595/2346/2031 2632/2343/2028\nf 2291/2253/1938 2357/2320/2005 2633/2327/2012 2634/2255/1940\nf 2291/2253/1938 2634/2255/1940 2635/2162/1847 2208/2158/1843\nf 2208/2158/1843 2635/2162/1847 2636/2160/1845 2207/2157/1842\nf 2207/2157/1842 2636/2160/1845 2637/2155/1840 2206/2151/1836\nf 2638/2054/1739 2127/2061/1746 2206/2151/1836 2637/2155/1840\nf 2130/2062/1747 2127/2061/1746 2638/2054/1739 2639/2053/1738\nf 2185/2138/1823 2130/2062/1747 2639/2053/1738 2640/2123/1808\nf 2185/2138/1823 2640/2123/1808 2641/2126/1811 2186/2139/1824\nf 2186/2139/1824 2641/2126/1811 2076/1509/1195 1581/1513/1199\nf 2556/2586/2251 2616/2614/2279 2627/2621/2286 2545/2579/2244\nf 1577/1508/1194 1635/1567/1253 2234/2177/1862 2183/2136/1821\nf 2222/2179/1864 2225/2182/1867 2226/2183/1868 2223/2180/1865\nf 2643/2510/2182 2470/2507/2179 2473/2501/2173 2642/2500/2172\nf 1270/2509/2181 1271/2506/2178 2470/2507/2179 2643/2510/2182\nf 2317/2305/1990 1721/1691/1377 2079/1668/1354 2644/2283/1968\nf 2318/2294/1979 2317/2305/1990 2644/2283/1968 2645/2285/1970\nf 2319/2275/1960 2318/2294/1979 2645/2285/1970 2646/2281/1966\nf 2320/2276/1961 2319/2275/1960 2646/2281/1966 2647/2279/1964\nf 2321/2286/1971 2320/2276/1961 2647/2279/1964 2648/2273/1958\nf 2649/2265/1950 2322/2287/1972 2321/2286/1971 2648/2273/1958\nf 2650/2264/1949 2323/2306/1991 2322/2287/1972 2649/2265/1950\nf 2651/2299/1984 2324/2309/1994 2323/2306/1991 2650/2264/1949\nf 2473/2501/2173 2387/2390/2073 2324/2309/1994 2651/2299/1984\nf 2653/2622/2287 2654/2557/2222 2168/2103/1788 2173/2121/1806\nf 2656/2623/2288 2655/2554/2219 2174/2110/1795 2156/2109/1794\nf 2515/2100/1785 2652/2624/2223 2656/2623/2288 2156/2109/1794\nf 2157/2107/1792 2659/2556/2221 2653/2622/2287 2173/2121/1806\nusemtl BlackCloth\nf 338/2625/2289 330/2626/2290 331/2627/2291 339/2628/2292\nf 332/2629/2293 340/2630/2294 339/2628/2292 331/2627/2291\nf 333/2631/2295 341/2632/2296 340/2630/2294 332/2629/2293\nf 334/2633/2297 342/2634/2298 341/2632/2296 333/2631/2295\nf 335/2635/2299 343/2636/2300 342/2634/2298 334/2633/2297\nf 336/2637/2301 344/2638/2302 343/2636/2300 335/2635/2299\nf 337/2639/2303 345/2640/2304 344/2638/2302 336/2637/2301\nf 330/2626/2290 338/2625/2289 345/2641/2304 337/2642/2303\nf 347/2643/2305 354/2644/2306 426/2645/2307 346/2646/2308\nf 348/2647/2309 355/2648/2310 354/2644/2306 347/2643/2305\nf 349/2649/2311 356/2650/2312 355/2648/2310 348/2647/2309\nf 350/2651/2313 357/2652/2314 356/2650/2312 349/2649/2311\nf 351/2653/2315 358/2654/2316 357/2652/2314 350/2651/2313\nf 352/2655/2317 432/2656/2318 358/2654/2316 351/2653/2315\nf 353/2657/2319 359/2658/2320 432/2656/2318 352/2655/2317\nf 346/2646/2308 426/2645/2307 359/2659/2320 353/2660/2319\nf 331/2627/2291 330/2626/2290 369/2661/2321 368/2662/2322\nf 368/2662/2322 369/2661/2321 360/2663/2323 361/2664/2324\nf 332/2629/2293 331/2627/2291 368/2662/2322 370/2665/2325\nf 370/2665/2325 368/2662/2322 361/2664/2324 362/2666/2326\nf 333/2631/2295 332/2629/2293 370/2665/2325 371/2667/2327\nf 371/2667/2327 370/2665/2325 362/2666/2326 363/2668/2328\nf 334/2633/2297 333/2631/2295 371/2667/2327 372/2669/2329\nf 372/2669/2329 371/2667/2327 363/2668/2328 364/2670/2330\nf 335/2635/2299 334/2633/2297 372/2669/2329 373/2671/2331\nf 373/2671/2331 372/2669/2329 364/2670/2330 365/2672/2332\nf 336/2637/2301 335/2635/2299 373/2671/2331 374/2673/2333\nf 374/2673/2333 373/2671/2331 365/2672/2332 366/2674/2334\nf 337/2639/2303 336/2637/2301 374/2673/2333 375/2675/2335\nf 375/2675/2335 374/2673/2333 366/2674/2334 367/2676/2336\nf 330/2626/2290 337/2642/2303 375/2677/2335 369/2661/2321\nf 369/2661/2321 375/2677/2335 367/2678/2336 360/2663/2323\nf 355/2648/2310 377/2679/2337 376/2680/2338 354/2644/2306\nf 356/2650/2312 378/2681/2339 377/2679/2337 355/2648/2310\nf 357/2652/2314 379/2682/2340 378/2681/2339 356/2650/2312\nf 358/2654/2316 380/2683/2341 379/2682/2340 357/2652/2314\nf 377/2679/2337 382/2684/2342 381/2685/2343 376/2680/2338\nf 378/2681/2339 383/2686/2344 382/2684/2342 377/2679/2337\nf 379/2682/2340 384/2687/2345 383/2686/2344 378/2681/2339\nf 380/2683/2341 385/2688/2346 384/2687/2345 379/2682/2340\nf 382/2684/2342 387/2689/2347 386/2690/2348 381/2685/2343\nf 383/2686/2344 388/2691/2349 387/2689/2347 382/2684/2342\nf 384/2687/2345 389/2692/2350 388/2691/2349 383/2686/2344\nf 385/2688/2346 390/2693/2351 389/2692/2350 384/2687/2345\nf 385/2688/2346 465/2694/2352 466/2695/2353 390/2693/2351\nf 380/2683/2341 464/2696/2354 465/2694/2352 385/2688/2346\nf 358/2654/2316 432/2656/2318 464/2696/2354 380/2683/2341\nf 426/2645/2307 354/2644/2306 376/2680/2338 391/2697/2355\nf 376/2680/2338 381/2685/2343 392/2698/2356 391/2697/2355\nf 381/2685/2343 386/2690/2348 393/2699/2357 392/2698/2356\nf 338/2625/2289 339/2628/2292 395/2700/2358 394/2701/2359\nf 394/2701/2359 395/2700/2358 347/2643/2305 346/2646/2308\nf 339/2628/2292 340/2630/2294 396/2702/2360 395/2700/2358\nf 395/2700/2358 396/2702/2360 348/2647/2309 347/2643/2305\nf 340/2630/2294 341/2632/2296 397/2703/2361 396/2702/2360\nf 396/2702/2360 397/2703/2361 349/2649/2311 348/2647/2309\nf 341/2632/2296 342/2634/2298 398/2704/2362 397/2703/2361\nf 397/2703/2361 398/2704/2362 350/2651/2313 349/2649/2311\nf 342/2634/2298 343/2636/2300 399/2705/2363 398/2704/2362\nf 398/2704/2362 399/2705/2363 351/2653/2315 350/2651/2313\nf 343/2636/2300 344/2638/2302 400/2706/2364 399/2705/2363\nf 399/2705/2363 400/2706/2364 352/2655/2317 351/2653/2315\nf 344/2638/2302 345/2640/2304 401/2707/2365 400/2706/2364\nf 400/2706/2364 401/2707/2365 353/2657/2319 352/2655/2317\nf 345/2641/2304 338/2625/2289 394/2701/2359 401/2708/2365\nf 401/2708/2365 394/2701/2359 346/2646/2308 353/2660/2319\nf 403/2709/2366 402/2710/2367 410/2711/2368 411/2712/2369\nf 404/2713/2370 403/2709/2366 411/2712/2369 412/2714/2371\nf 405/2715/2372 404/2713/2370 412/2714/2371 413/2716/2373\nf 406/2717/2374 405/2715/2372 413/2716/2373 414/2718/2375\nf 407/2719/2376 406/2717/2374 414/2718/2375 415/2720/2377\nf 408/2721/2378 407/2719/2376 415/2720/2377 416/2722/2379\nf 409/2723/2380 408/2721/2378 416/2722/2379 417/2724/2381\nf 402/2710/2367 409/2725/2380 417/2726/2381 410/2711/2368\nf 419/2727/2382 418/2728/2383 426/2729/2307 427/2730/2384\nf 420/2731/2385 419/2727/2382 427/2730/2384 428/2732/2386\nf 421/2733/2387 420/2731/2385 428/2732/2386 429/2734/2388\nf 422/2735/2389 421/2733/2387 429/2734/2388 430/2736/2390\nf 423/2737/2391 422/2735/2389 430/2736/2390 431/2738/2392\nf 424/2739/2393 423/2737/2391 431/2738/2392 432/2740/2318\nf 425/2741/2394 424/2739/2393 432/2740/2318 359/2742/2320\nf 418/2728/2383 425/2743/2394 359/2744/2320 426/2729/2307\nf 442/2745/2395 402/2710/2367 403/2709/2366 441/2746/2396\nf 441/2746/2396 434/2747/2397 433/2748/2398 442/2745/2395\nf 404/2713/2370 443/2749/2399 441/2746/2396 403/2709/2366\nf 443/2749/2399 435/2750/2400 434/2747/2397 441/2746/2396\nf 405/2715/2372 444/2751/2401 443/2749/2399 404/2713/2370\nf 444/2751/2401 436/2752/2402 435/2750/2400 443/2749/2399\nf 406/2717/2374 445/2753/2403 444/2751/2401 405/2715/2372\nf 445/2753/2403 437/2754/2404 436/2752/2402 444/2751/2401\nf 407/2719/2376 446/2755/2405 445/2753/2403 406/2717/2374\nf 446/2755/2405 438/2756/2406 437/2754/2404 445/2753/2403\nf 408/2721/2378 447/2757/2407 446/2755/2405 407/2719/2376\nf 447/2757/2407 439/2758/2408 438/2756/2406 446/2755/2405\nf 409/2723/2380 448/2759/2409 447/2757/2407 408/2721/2378\nf 448/2759/2409 440/2760/2410 439/2758/2408 447/2757/2407\nf 402/2710/2367 442/2745/2395 448/2761/2409 409/2725/2380\nf 442/2745/2395 433/2748/2398 440/2762/2410 448/2761/2409\nf 428/2732/2386 427/2730/2384 449/2763/2411 450/2764/2412\nf 429/2734/2388 428/2732/2386 450/2764/2412 451/2765/2413\nf 430/2736/2390 429/2734/2388 451/2765/2413 452/2766/2414\nf 431/2738/2392 430/2736/2390 452/2766/2414 453/2767/2415\nf 450/2764/2412 449/2763/2411 454/2768/2416 455/2769/2417\nf 451/2765/2413 450/2764/2412 455/2769/2417 456/2770/2418\nf 452/2766/2414 451/2765/2413 456/2770/2418 457/2771/2419\nf 453/2767/2415 452/2766/2414 457/2771/2419 458/2772/2420\nf 455/2769/2417 454/2768/2416 459/2773/2421 460/2774/2422\nf 456/2770/2418 455/2769/2417 460/2774/2422 461/2775/2423\nf 457/2771/2419 456/2770/2418 461/2775/2423 462/2776/2424\nf 458/2772/2420 457/2771/2419 462/2776/2424 463/2777/2425\nf 458/2772/2420 463/2777/2425 466/2778/2353 465/2779/2352\nf 453/2767/2415 458/2772/2420 465/2779/2352 464/2780/2354\nf 431/2738/2392 453/2767/2415 464/2780/2354 432/2740/2318\nf 426/2729/2307 391/2781/2355 449/2763/2411 427/2730/2384\nf 449/2763/2411 391/2781/2355 392/2782/2356 454/2768/2416\nf 454/2768/2416 392/2782/2356 393/2783/2357 459/2773/2421\nf 410/2711/2368 467/2784/2426 468/2785/2427 411/2712/2369\nf 467/2784/2426 418/2728/2383 419/2727/2382 468/2785/2427\nf 411/2712/2369 468/2785/2427 469/2786/2428 412/2714/2371\nf 468/2785/2427 419/2727/2382 420/2731/2385 469/2786/2428\nf 412/2714/2371 469/2786/2428 470/2787/2429 413/2716/2373\nf 469/2786/2428 420/2731/2385 421/2733/2387 470/2787/2429\nf 413/2716/2373 470/2787/2429 471/2788/2430 414/2718/2375\nf 470/2787/2429 421/2733/2387 422/2735/2389 471/2788/2430\nf 414/2718/2375 471/2788/2430 472/2789/2431 415/2720/2377\nf 471/2788/2430 422/2735/2389 423/2737/2391 472/2789/2431\nf 415/2720/2377 472/2789/2431 473/2790/2432 416/2722/2379\nf 472/2789/2431 423/2737/2391 424/2739/2393 473/2790/2432\nf 416/2722/2379 473/2790/2432 474/2791/2433 417/2724/2381\nf 473/2790/2432 424/2739/2393 425/2741/2394 474/2791/2433\nf 417/2726/2381 474/2792/2433 467/2784/2426 410/2711/2368\nf 474/2792/2433 425/2743/2394 418/2728/2383 467/2784/2426\nf 722/2793/2434 721/2794/2435 734/2795/2436 735/2796/2437\nf 723/2797/2438 722/2793/2434 735/2796/2437 736/2798/2439\nf 724/2799/2440 723/2797/2438 736/2798/2439 737/2800/2441\nf 719/2801/2442 734/2795/2436 721/2794/2435 720/2802/2443\nf 724/2799/2440 737/2800/2441 729/2803/2444 725/2804/2445\nf 735/2796/2437 734/2795/2436 738/2805/2446 739/2806/2447\nf 736/2798/2439 735/2796/2437 739/2806/2447 740/2807/2448\nf 737/2800/2441 736/2798/2439 740/2807/2448 741/2808/2449\nf 718/2809/2450 738/2805/2446 734/2795/2436 719/2801/2442\nf 726/2810/2451 729/2803/2444 737/2800/2441 741/2808/2449\nf 738/2805/2446 742/2811/2452 743/2812/2453 739/2806/2447\nf 739/2806/2447 743/2812/2453 744/2813/2454 740/2807/2448\nf 740/2807/2448 744/2813/2454 745/2814/2455 741/2808/2449\nf 717/2815/2456 742/2811/2452 738/2805/2446 718/2809/2450\nf 726/2810/2451 741/2808/2449 745/2814/2455 727/2816/2457\nf 742/2811/2452 2819/2817/2458 2820/2818/2459 743/2812/2453\nf 743/2812/2453 2820/2818/2459 2821/2819/2460 744/2813/2454\nf 744/2813/2454 2821/2819/2460 2822/2820/2461 745/2814/2455\nf 727/2816/2457 745/2814/2455 2822/2820/2461 2823/2821/2462\nf 2824/2822/2463 2819/2817/2458 742/2811/2452 717/2815/2456\nf 2825/2823/2464 754/2824/2465 755/2825/2466 2826/2826/2467\nf 2826/2826/2467 755/2825/2466 756/2827/2468 2827/2828/2469\nf 2827/2829/2469 756/2830/2468 757/2831/2470 2828/2832/2471\nf 714/2833/2472 754/2824/2465 2825/2823/2464 2829/2834/2473\nf 2830/2835/2474 2828/2832/2471 757/2831/2470 885/2836/2475\nf 754/2824/2465 758/2837/2476 759/2838/2477 755/2825/2466\nf 755/2825/2466 759/2838/2477 760/2839/2478 756/2827/2468\nf 756/2830/2468 760/2840/2478 761/2841/2479 757/2831/2470\nf 713/2842/2480 758/2837/2476 754/2824/2465 714/2833/2472\nf 885/2836/2475 757/2831/2470 761/2841/2479 886/2843/2481\nf 763/2844/2482 762/2845/2483 770/2846/2484 771/2847/2485\nf 764/2848/2486 763/2844/2482 771/2847/2485 772/2849/2487\nf 765/2850/2488 764/2848/2486 772/2849/2487 773/2851/2489\nf 766/2852/2490 765/2850/2488 773/2851/2489 774/2853/2491\nf 767/2854/2492 766/2855/2490 774/2856/2491 775/2857/2493\nf 768/2858/2494 767/2854/2492 775/2857/2493 776/2859/2495\nf 769/2860/2496 768/2858/2494 776/2859/2495 777/2861/2497\nf 762/2845/2483 769/2860/2496 777/2861/2497 770/2846/2484\nf 759/2838/2477 765/2850/2488 766/2852/2490 760/2839/2478\nf 712/2862/2498 778/2863/2499 758/2837/2476 713/2842/2480\nf 758/2837/2476 778/2863/2499 779/2864/2500 759/2838/2477\nf 759/2838/2477 779/2864/2500 764/2848/2486 765/2850/2488\nf 760/2840/2478 766/2855/2490 767/2854/2492 780/2865/2501\nf 760/2840/2478 780/2865/2501 781/2866/2502 761/2841/2479\nf 886/2843/2481 761/2841/2479 781/2866/2502 731/2867/2503\nf 770/2846/2484 782/2868/2504 783/2869/2505 771/2847/2485\nf 771/2847/2485 783/2869/2505 784/2870/2506 772/2849/2487\nf 772/2849/2487 784/2870/2506 785/2871/2507 773/2851/2489\nf 773/2851/2489 785/2871/2507 786/2872/2508 774/2853/2491\nf 774/2856/2491 786/2873/2508 787/2874/2509 775/2857/2493\nf 775/2857/2493 787/2874/2509 788/2875/2510 776/2859/2495\nf 776/2859/2495 788/2875/2510 789/2876/2511 777/2861/2497\nf 777/2861/2497 789/2876/2511 782/2868/2504 770/2846/2484\nf 763/2844/2482 764/2848/2486 779/2864/2500 790/2877/2512\nf 767/2854/2492 768/2858/2494 791/2878/2513 780/2865/2501\nf 780/2865/2501 791/2878/2513 792/2879/2514 781/2866/2502\nf 731/2867/2503 781/2866/2502 792/2879/2514 732/2880/2515\nf 711/2881/2516 793/2882/2517 778/2863/2499 712/2862/2498\nf 778/2863/2499 793/2882/2517 790/2877/2512 779/2864/2500\nf 762/2845/2483 794/2883/2518 795/2884/2519 769/2860/2496\nf 762/2845/2483 763/2844/2482 790/2877/2512 794/2883/2518\nf 768/2858/2494 769/2860/2496 795/2884/2519 791/2878/2513\nf 710/2885/2520 796/2886/2521 793/2882/2517 711/2881/2516\nf 790/2877/2512 793/2882/2517 796/2886/2521 794/2883/2518\nf 791/2878/2513 795/2884/2519 797/2887/2522 792/2879/2514\nf 732/2880/2515 792/2879/2514 797/2887/2522 733/2888/2523\nf 2831/2889/2524 2832/2890/2525 804/2891/2526 803/2892/2527\nf 2834/2893/2528 2833/2894/2529 805/2895/2530 806/2896/2531\nf 2833/2894/2529 2831/2889/2524 803/2892/2527 805/2895/2530\nf 2835/2897/2532 2859/2898/2533 808/2899/2534 807/2900/2535\nf 2832/2890/2525 2835/2897/2532 807/2900/2535 804/2891/2526\nf 803/2892/2527 804/2891/2526 809/2901/2536 810/2902/2537\nf 806/2896/2531 805/2895/2530 812/2903/2538 811/2904/2539\nf 805/2895/2530 803/2892/2527 810/2902/2537 812/2903/2538\nf 807/2900/2535 808/2899/2534 814/2905/2540 813/2906/2541\nf 804/2891/2526 807/2900/2535 813/2906/2541 809/2901/2536\nf 795/2884/2519 794/2883/2518 816/2907/2542 815/2908/2543\nf 815/2908/2543 816/2907/2542 798/2909/2544 799/2910/2545\nf 796/2886/2521 710/2885/2520 818/2911/2546 817/2912/2547\nf 817/2912/2547 818/2911/2546 801/2913/2548 800/2914/2549\nf 794/2883/2518 796/2886/2521 817/2912/2547 816/2907/2542\nf 816/2907/2542 817/2912/2547 800/2914/2549 798/2909/2544\nf 733/2888/2523 797/2887/2522 819/2915/2550 971/2916/2551\nf 971/2916/2551 819/2915/2550 802/2917/2552 956/2918/2553\nf 797/2887/2522 795/2884/2519 815/2908/2543 819/2915/2550\nf 819/2915/2550 815/2908/2543 799/2910/2545 802/2917/2552\nf 782/2868/2504 820/2919/2554 821/2920/2555 783/2869/2505\nf 783/2869/2505 821/2920/2555 822/2921/2556 784/2870/2506\nf 784/2870/2506 822/2921/2556 823/2922/2557 785/2871/2507\nf 785/2871/2507 823/2922/2557 824/2923/2558 786/2872/2508\nf 786/2873/2508 824/2924/2558 825/2925/2559 787/2874/2509\nf 787/2874/2509 825/2925/2559 826/2926/2560 788/2875/2510\nf 788/2875/2510 826/2926/2560 827/2927/2561 789/2876/2511\nf 789/2876/2511 827/2927/2561 820/2919/2554 782/2868/2504\nf 820/2919/2554 829/2928/2562 828/2929/2563 821/2920/2555\nf 821/2920/2555 828/2929/2563 830/2930/2564 822/2921/2556\nf 822/2921/2556 830/2930/2564 831/2931/2565 823/2922/2557\nf 823/2922/2557 831/2931/2565 832/2932/2566 824/2923/2558\nf 824/2924/2558 832/2933/2566 833/2934/2567 825/2925/2559\nf 825/2925/2559 833/2934/2567 834/2935/2568 826/2926/2560\nf 826/2926/2560 834/2935/2568 835/2936/2569 827/2927/2561\nf 827/2927/2561 835/2936/2569 829/2928/2562 820/2919/2554\nf 829/2928/2562 836/2937/2570 837/2938/2571 828/2929/2563\nf 828/2929/2563 837/2938/2571 838/2939/2572 830/2930/2564\nf 830/2930/2564 838/2939/2572 839/2940/2573 831/2931/2565\nf 831/2931/2565 839/2940/2573 840/2941/2574 832/2932/2566\nf 832/2933/2566 840/2942/2574 841/2943/2575 833/2934/2567\nf 833/2934/2567 841/2943/2575 842/2944/2576 834/2935/2568\nf 834/2935/2568 842/2944/2576 843/2945/2577 835/2936/2569\nf 835/2936/2569 843/2945/2577 836/2937/2570 829/2928/2562\nf 836/2937/2570 2836/2946/2578 2837/2947/2579 837/2938/2571\nf 837/2938/2571 2837/2947/2579 2838/2948/2580 838/2939/2572\nf 838/2939/2572 2838/2948/2580 2839/2949/2581 839/2940/2573\nf 839/2940/2573 2839/2949/2581 2840/2950/2582 840/2941/2574\nf 840/2942/2574 2840/2951/2582 2841/2952/2583 841/2943/2575\nf 841/2943/2575 2841/2952/2583 2842/2953/2584 842/2944/2576\nf 842/2944/2576 2842/2953/2584 2843/2954/2585 843/2945/2577\nf 843/2945/2577 2843/2954/2585 2836/2946/2578 836/2937/2570\nf 844/2955/2586 845/2956/2587 852/2957/2588 853/2958/2589\nf 845/2956/2587 846/2959/2590 854/2960/2591 852/2957/2588\nf 846/2959/2590 847/2961/2592 855/2962/2593 854/2960/2591\nf 847/2961/2592 848/2963/2594 856/2964/2595 855/2962/2593\nf 848/2965/2594 849/2966/2596 857/2967/2597 856/2968/2595\nf 849/2966/2596 850/2969/2598 858/2970/2599 857/2967/2597\nf 850/2969/2598 851/2971/2600 859/2972/2601 858/2970/2599\nf 851/2971/2600 844/2955/2586 853/2958/2589 859/2972/2601\nf 853/2958/2589 852/2957/2588 861/2973/2602 860/2974/2603\nf 852/2957/2588 854/2960/2591 862/2975/2604 861/2973/2602\nf 854/2960/2591 855/2962/2593 863/2976/2605 862/2975/2604\nf 855/2962/2593 856/2964/2595 864/2977/2606 863/2976/2605\nf 856/2968/2595 857/2967/2597 865/2978/2607 864/2979/2606\nf 857/2967/2597 858/2970/2599 866/2980/2608 865/2978/2607\nf 858/2970/2599 859/2972/2601 867/2981/2609 866/2980/2608\nf 859/2972/2601 853/2958/2589 860/2974/2603 867/2981/2609\nf 880/2982/2610 888/2983/2611 887/2984/2612 879/2985/2613\nf 881/2986/2614 889/2987/2615 888/2983/2611 880/2982/2610\nf 882/2988/2616 890/2989/2617 889/2987/2615 881/2986/2614\nf 877/2990/2618 878/2991/2619 879/2985/2613 887/2984/2612\nf 882/2988/2616 883/2992/2620 884/2993/2621 890/2989/2617\nf 888/2983/2611 892/2994/2622 891/2995/2623 887/2984/2612\nf 889/2987/2615 893/2996/2624 892/2994/2622 888/2983/2611\nf 890/2989/2617 894/2997/2625 893/2996/2624 889/2987/2615\nf 876/2998/2626 877/2990/2618 887/2984/2612 891/2995/2623\nf 726/2810/2451 894/2997/2625 890/2989/2617 884/2993/2621\nf 891/2995/2623 892/2994/2622 896/2999/2627 895/3000/2628\nf 892/2994/2622 893/2996/2624 897/3001/2629 896/2999/2627\nf 893/2996/2624 894/2997/2625 898/3002/2630 897/3001/2629\nf 875/3003/2631 876/2998/2626 891/2995/2623 895/3000/2628\nf 726/2810/2451 727/2816/2457 898/3002/2630 894/2997/2625\nf 895/3000/2628 896/2999/2627 2845/3004/2632 2844/3005/2633\nf 896/2999/2627 897/3001/2629 2846/3006/2634 2845/3004/2632\nf 897/3001/2629 898/3002/2630 2847/3007/2635 2846/3006/2634\nf 727/2816/2457 2823/2821/2462 2847/3007/2635 898/3002/2630\nf 2848/3008/2636 875/3003/2631 895/3000/2628 2844/3005/2633\nf 2849/3009/2637 2850/3010/2638 908/3011/2639 907/3012/2640\nf 2850/3010/2638 2851/3013/2641 909/3014/2642 908/3011/2639\nf 2851/3015/2641 2852/3016/2643 910/3017/2644 909/3018/2642\nf 872/3019/2645 2853/3020/2646 2849/3009/2637 907/3012/2640\nf 2830/2835/2474 885/2836/2475 910/3017/2644 2852/3016/2643\nf 907/3012/2640 908/3011/2639 912/3021/2647 911/3022/2648\nf 908/3011/2639 909/3014/2642 913/3023/2649 912/3021/2647\nf 909/3018/2642 910/3017/2644 914/3024/2650 913/3025/2649\nf 871/3026/2651 872/3019/2645 907/3012/2640 911/3022/2648\nf 885/2836/2475 886/2843/2481 914/3024/2650 910/3017/2644\nf 916/3027/2652 924/3028/2653 923/3029/2654 915/3030/2655\nf 917/3031/2656 925/3032/2657 924/3028/2653 916/3027/2652\nf 918/3033/2658 926/3034/2659 925/3032/2657 917/3031/2656\nf 919/3035/2660 927/3036/2661 926/3034/2659 918/3033/2658\nf 920/3037/2662 928/3038/2663 927/3039/2661 919/3040/2660\nf 921/3041/2664 929/3042/2665 928/3038/2663 920/3037/2662\nf 922/3043/2666 930/3044/2667 929/3042/2665 921/3041/2664\nf 915/3030/2655 923/3029/2654 930/3044/2667 922/3043/2666\nf 912/3021/2647 913/3023/2649 919/3035/2660 918/3033/2658\nf 870/3045/2668 871/3026/2651 911/3022/2648 931/3046/2669\nf 911/3022/2648 912/3021/2647 932/3047/2670 931/3046/2669\nf 912/3021/2647 918/3033/2658 917/3031/2656 932/3047/2670\nf 913/3025/2649 933/3048/2671 920/3037/2662 919/3040/2660\nf 913/3025/2649 914/3024/2650 934/3049/2672 933/3048/2671\nf 886/2843/2481 731/2867/2503 934/3049/2672 914/3024/2650\nf 923/3029/2654 924/3028/2653 936/3050/2673 935/3051/2674\nf 924/3028/2653 925/3032/2657 937/3052/2675 936/3050/2673\nf 925/3032/2657 926/3034/2659 938/3053/2676 937/3052/2675\nf 926/3034/2659 927/3036/2661 939/3054/2677 938/3053/2676\nf 927/3039/2661 928/3038/2663 940/3055/2678 939/3056/2677\nf 928/3038/2663 929/3042/2665 941/3057/2679 940/3055/2678\nf 929/3042/2665 930/3044/2667 942/3058/2680 941/3057/2679\nf 930/3044/2667 923/3029/2654 935/3051/2674 942/3058/2680\nf 916/3027/2652 943/3059/2681 932/3047/2670 917/3031/2656\nf 920/3037/2662 933/3048/2671 944/3060/2682 921/3041/2664\nf 933/3048/2671 934/3049/2672 945/3061/2683 944/3060/2682\nf 731/2867/2503 732/2880/2515 945/3061/2683 934/3049/2672\nf 869/3062/2684 870/3045/2668 931/3046/2669 946/3063/2685\nf 931/3046/2669 932/3047/2670 943/3059/2681 946/3063/2685\nf 915/3030/2655 922/3043/2666 948/3064/2686 947/3065/2687\nf 915/3030/2655 947/3065/2687 943/3059/2681 916/3027/2652\nf 921/3041/2664 944/3060/2682 948/3064/2686 922/3043/2666\nf 868/3066/2688 869/3062/2684 946/3063/2685 949/3067/2689\nf 943/3059/2681 947/3065/2687 949/3067/2689 946/3063/2685\nf 944/3060/2682 945/3061/2683 950/3068/2690 948/3064/2686\nf 732/2880/2515 733/2888/2523 950/3068/2690 945/3061/2683\nf 2854/3069/2691 957/3070/2692 958/3071/2693 2855/3072/2694\nf 2857/3073/2695 960/3074/2696 959/3075/2697 2856/3076/2698\nf 2856/3076/2698 959/3075/2697 957/3070/2692 2854/3069/2691\nf 2858/3077/2699 961/3078/2700 808/2899/2534 2859/2898/2533\nf 2855/3072/2694 958/3071/2693 961/3078/2700 2858/3077/2699\nf 957/3070/2692 963/3079/2701 962/3080/2702 958/3071/2693\nf 960/3074/2696 964/3081/2703 965/3082/2704 959/3075/2697\nf 959/3075/2697 965/3082/2704 963/3079/2701 957/3070/2692\nf 961/3078/2700 966/3083/2705 814/2905/2540 808/2899/2534\nf 958/3071/2693 962/3080/2702 966/3083/2705 961/3078/2700\nf 948/3064/2686 967/3084/2706 968/3085/2707 947/3065/2687\nf 967/3084/2706 952/3086/2708 951/3087/2709 968/3085/2707\nf 970/3088/2710 868/3066/2688 949/3067/2689 969/3089/2711\nf 969/3089/2711 953/3090/2712 954/3091/2713 970/3088/2710\nf 947/3065/2687 968/3085/2707 969/3089/2711 949/3067/2689\nf 968/3085/2707 951/3087/2709 953/3090/2712 969/3089/2711\nf 733/2888/2523 971/2916/2551 972/3092/2714 950/3068/2690\nf 971/2916/2551 956/2918/2553 955/3093/2715 972/3092/2714\nf 950/3068/2690 972/3092/2714 967/3084/2706 948/3064/2686\nf 972/3092/2714 955/3093/2715 952/3086/2708 967/3084/2706\nf 935/3051/2674 936/3050/2673 974/3094/2716 973/3095/2717\nf 936/3050/2673 937/3052/2675 975/3096/2718 974/3094/2716\nf 937/3052/2675 938/3053/2676 976/3097/2719 975/3096/2718\nf 938/3053/2676 939/3054/2677 977/3098/2720 976/3097/2719\nf 939/3056/2677 940/3055/2678 978/3099/2721 977/3100/2720\nf 940/3055/2678 941/3057/2679 979/3101/2722 978/3099/2721\nf 941/3057/2679 942/3058/2680 980/3102/2723 979/3101/2722\nf 942/3058/2680 935/3051/2674 973/3095/2717 980/3102/2723\nf 973/3095/2717 974/3094/2716 981/3103/2724 982/3104/2725\nf 974/3094/2716 975/3096/2718 983/3105/2726 981/3103/2724\nf 975/3096/2718 976/3097/2719 984/3106/2727 983/3105/2726\nf 976/3097/2719 977/3098/2720 985/3107/2728 984/3106/2727\nf 977/3100/2720 978/3099/2721 986/3108/2729 985/3109/2728\nf 978/3099/2721 979/3101/2722 987/3110/2730 986/3108/2729\nf 979/3101/2722 980/3102/2723 988/3111/2731 987/3110/2730\nf 980/3102/2723 973/3095/2717 982/3104/2725 988/3111/2731\nf 982/3104/2725 981/3103/2724 990/3112/2732 989/3113/2733\nf 981/3103/2724 983/3105/2726 991/3114/2734 990/3112/2732\nf 983/3105/2726 984/3106/2727 992/3115/2735 991/3114/2734\nf 984/3106/2727 985/3107/2728 993/3116/2736 992/3115/2735\nf 985/3109/2728 986/3108/2729 994/3117/2737 993/3118/2736\nf 986/3108/2729 987/3110/2730 995/3119/2738 994/3117/2737\nf 987/3110/2730 988/3111/2731 996/3120/2739 995/3119/2738\nf 988/3111/2731 982/3104/2725 989/3113/2733 996/3120/2739\nf 989/3113/2733 990/3112/2732 2861/3121/2740 2860/3122/2741\nf 990/3112/2732 991/3114/2734 2862/3123/2742 2861/3121/2740\nf 991/3114/2734 992/3115/2735 2863/3124/2743 2862/3123/2742\nf 992/3115/2735 993/3116/2736 2864/3125/2744 2863/3124/2743\nf 993/3118/2736 994/3117/2737 2865/3126/2745 2864/3127/2744\nf 994/3117/2737 995/3119/2738 2866/3128/2746 2865/3126/2745\nf 995/3119/2738 996/3120/2739 2867/3129/2747 2866/3128/2746\nf 996/3120/2739 989/3113/2733 2860/3122/2741 2867/3129/2747\nf 997/3130/2748 1006/3131/2749 1005/3132/2750 998/3133/2751\nf 998/3133/2751 1005/3132/2750 1007/3134/2752 999/3135/2753\nf 999/3135/2753 1007/3134/2752 1008/3136/2754 1000/3137/2755\nf 1000/3137/2755 1008/3136/2754 1009/3138/2756 1001/3139/2757\nf 1001/3140/2757 1009/3141/2756 1010/3142/2758 1002/3143/2759\nf 1002/3143/2759 1010/3142/2758 1011/3144/2760 1003/3145/2761\nf 1003/3145/2761 1011/3144/2760 1012/3146/2762 1004/3147/2763\nf 1004/3147/2763 1012/3146/2762 1006/3131/2749 997/3130/2748\nf 1006/3131/2749 1013/3148/2764 1014/3149/2765 1005/3132/2750\nf 1005/3132/2750 1014/3149/2765 1015/3150/2766 1007/3134/2752\nf 1007/3134/2752 1015/3150/2766 1016/3151/2767 1008/3136/2754\nf 1008/3136/2754 1016/3151/2767 1017/3152/2768 1009/3138/2756\nf 1009/3141/2756 1017/3153/2768 1018/3154/2769 1010/3142/2758\nf 1010/3142/2758 1018/3154/2769 1019/3155/2770 1011/3144/2760\nf 1011/3144/2760 1019/3155/2770 1020/3156/2771 1012/3146/2762\nf 1012/3146/2762 1020/3156/2771 1013/3148/2764 1006/3131/2749\n"
  },
  {
    "path": "demo/simplify.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<title>meshoptimizer - demo</title>\n\t\t<meta charset=\"utf-8\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0\" />\n\t\t<style>\n\t\t\tbody {\n\t\t\t\tfont-family: Monospace;\n\t\t\t\tbackground-color: #300a24;\n\t\t\t\tcolor: #fff;\n\t\t\t\tmargin: 0px;\n\t\t\t\toverflow: hidden;\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: column;\n\t\t\t\theight: 100vh;\n\t\t\t}\n\t\t\t#info {\n\t\t\t\tcolor: #fff;\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 10px;\n\t\t\t\twidth: 100%;\n\t\t\t\ttext-align: center;\n\t\t\t\tz-index: 100;\n\t\t\t\tdisplay: block;\n\t\t\t}\n\t\t\t#info a,\n\t\t\t.button {\n\t\t\t\tcolor: #f00;\n\t\t\t\tfont-weight: bold;\n\t\t\t\ttext-decoration: underline;\n\t\t\t\tcursor: pointer;\n\t\t\t}\n\t\t\t#grid-container {\n\t\t\t\twidth: 100%;\n\t\t\t\tflex: 1;\n\t\t\t\tdisplay: grid;\n\t\t\t\tgrid-template-columns: repeat(var(--grid-columns, 1), 1fr);\n\t\t\t\tgrid-auto-rows: minmax(300px, 1fr);\n\t\t\t\toverflow-y: auto;\n\t\t\t}\n\t\t\t.renderer-container {\n\t\t\t\tposition: relative;\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t}\n\t\t\t.renderer-container canvas {\n\t\t\t\twidth: 100% !important;\n\t\t\t\theight: 100% !important;\n\t\t\t\tdisplay: block;\n\t\t\t\tuser-select: none;\n\t\t\t}\n\t\t\t.renderer-label {\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 10px;\n\t\t\t\tleft: 10px;\n\t\t\t\tbackground-color: rgba(0, 0, 0, 0.5);\n\t\t\t\tpadding: 5px 10px;\n\t\t\t\tborder-radius: 5px;\n\t\t\t\tpointer-events: auto;\n\t\t\t\tfont-size: 14px;\n\t\t\t\ttransition: background-color 0.2s;\n\t\t\t}\n\t\t\t.renderer-label:hover {\n\t\t\t\tbackground-color: rgba(0, 0, 0, 0.7);\n\t\t\t}\n\t\t\t.renderer-label.focused {\n\t\t\t\tbackground-color: rgba(255, 100, 100, 0.7);\n\t\t\t\tcolor: white;\n\t\t\t}\n\t\t\t.renderer-label.clickable {\n\t\t\t\tcursor: pointer;\n\t\t\t}\n\t\t\t.stats-label {\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 45px;\n\t\t\t\tleft: 10px;\n\t\t\t\tbackground-color: rgba(0, 0, 0, 0.5);\n\t\t\t\tpadding: 5px 10px;\n\t\t\t\tborder-radius: 5px;\n\t\t\t\tpointer-events: none;\n\t\t\t\tfont-size: 12px;\n\t\t\t\tline-height: 1.4;\n\t\t\t}\n\n\t\t\t.renderer-container.hidden {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\n\t\t\t/* Custom scrollbar styling */\n\t\t\t#grid-container::-webkit-scrollbar {\n\t\t\t\twidth: 16px;\n\t\t\t}\n\n\t\t\t#grid-container::-webkit-scrollbar-track {\n\t\t\t\tbackground: rgba(255, 255, 255, 0.1);\n\t\t\t}\n\n\t\t\t#grid-container::-webkit-scrollbar-thumb {\n\t\t\t\tbackground: rgba(255, 255, 255, 0.3);\n\t\t\t\tborder-radius: 8px;\n\t\t\t}\n\n\t\t\t#grid-container::-webkit-scrollbar-thumb:hover {\n\t\t\t\tbackground: rgba(255, 255, 255, 0.5);\n\t\t\t}\n\t\t</style>\n\n\t\t<script type=\"importmap\">\n\t\t\t{\n\t\t\t\t\"imports\": {\n\t\t\t\t\t\"three\": \"https://cdn.jsdelivr.net/npm/three@0.174.0/build/three.module.js\",\n\t\t\t\t\t\"three-examples/\": \"https://cdn.jsdelivr.net/npm/three@0.174.0/examples/jsm/\",\n\t\t\t\t\t\"lil-gui\": \"https://cdn.jsdelivr.net/npm/lil-gui@0.20.0/dist/lil-gui.esm.js\"\n\t\t\t\t}\n\t\t\t}\n\t\t</script>\n\t</head>\n\n\t<body>\n\t\t<div id=\"grid-container\"></div>\n\t\t<input type=\"file\" id=\"fileInput\" style=\"display: none\" accept=\".obj,.glb\" multiple />\n\n\t\t<script type=\"module\">\n\t\t\timport * as THREE from 'three';\n\t\t\timport { GLTFLoader } from 'three-examples/loaders/GLTFLoader.js';\n\t\t\timport { OBJLoader } from 'three-examples/loaders/OBJLoader.js';\n\t\t\timport { OrbitControls } from 'three-examples/controls/OrbitControls.js';\n\t\t\timport { mergeGeometries, mergeVertices } from 'three-examples/utils/BufferGeometryUtils.js';\n\t\t\timport { MeshoptDecoder } from '../js/meshopt_decoder.mjs';\n\t\t\timport { MeshoptSimplifier } from '../js/meshopt_simplifier.js';\n\t\t\timport { GUI } from 'lil-gui';\n\n\t\t\tvar container, gridContainer;\n\t\t\tvar camera, clock;\n\t\t\tvar renderers = [];\n\t\t\tvar scenes = [];\n\t\t\tvar controls = [];\n\t\t\tvar models = [];\n\t\t\tvar mixers = [];\n\t\t\tvar viewStats = [];\n\n\t\t\t// Focus tracking\n\t\t\tvar focusedViewIndex = -1; // -1 means no focus (show all)\n\n\t\t\t// Track camera distance for autoLod\n\t\t\tvar lastCameraDistance = 0;\n\n\t\t\tvar settings = {\n\t\t\t\twireframe: false,\n\t\t\t\twireframeOverlay: false,\n\t\t\t\tanimate: false,\n\t\t\t\tpointSize: 1.0,\n\t\t\t\tratio: 1.0,\n\t\t\t\tdebugOverlay: false,\n\t\t\t\tsloppy: false,\n\t\t\t\tpermissive: false,\n\t\t\t\tpermissiveProtection: 2, // uvs\n\t\t\t\tlockBorder: false,\n\t\t\t\tpreprune: 0.0,\n\t\t\t\tprune: true,\n\t\t\t\tregularize: false,\n\t\t\t\tsolve: false,\n\t\t\t\tuseAttributes: true,\n\t\t\t\tautoTextureWeight: false,\n\t\t\t\terrorThresholdLog10: 1,\n\t\t\t\terrorScaled: true,\n\t\t\t\tnormalWeight: 1.0,\n\t\t\t\tcolorWeight: 1.0,\n\t\t\t\ttextureWeight: 0.0,\n\t\t\t\tautoLod: false,\n\t\t\t\tautoLodFactor: 1.0,\n\t\t\t\tgridColumns: 2,\n\t\t\t\tdebugCheckerboard: false,\n\n\t\t\t\tloadFile: function () {\n\t\t\t\t\tvar input = document.getElementById('fileInput');\n\t\t\t\t\tinput.onchange = function () {\n\t\t\t\t\t\tvar files = Array.from(input.files).sort(function (a, b) {\n\t\t\t\t\t\t\tvar an = a.name.split('.')[0];\n\t\t\t\t\t\t\tvar bn = b.name.split('.')[0];\n\t\t\t\t\t\t\treturn an.localeCompare(bn);\n\t\t\t\t\t\t});\n\t\t\t\t\t\tloadFiles(files);\n\t\t\t\t\t};\n\t\t\t\t\tinput.click();\n\t\t\t\t},\n\n\t\t\t\tupdateModule: function () {\n\t\t\t\t\treload();\n\t\t\t\t\tsimplify();\n\t\t\t\t},\n\t\t\t\tautoUpdate: false,\n\t\t\t\tautoUpdateStatus: '',\n\t\t\t};\n\n\t\t\tvar gui = new GUI({ width: 300 });\n\t\t\tvar guiDisplay = gui.addFolder('Display');\n\t\t\tguiDisplay.add(settings, 'wireframe').onChange(update);\n\t\t\tguiDisplay.add(settings, 'wireframeOverlay').onChange(simplify); // requires debug data rebuild\n\t\t\tguiDisplay.add(settings, 'animate').onChange(update);\n\t\t\tguiDisplay.add(settings, 'pointSize', 1, 16).onChange(update);\n\t\t\tguiDisplay.add(settings, 'debugOverlay').onChange(simplify); // requires debug data rebuild\n\t\t\tguiDisplay.add(settings, 'gridColumns', 1, 6, 1).onChange(updateGridLayout);\n\n\t\t\tvar guiSimplify = gui.addFolder('Simplify');\n\t\t\tguiSimplify.add(settings, 'ratio', 0, 1, 0.01).onChange(simplify);\n\t\t\tguiSimplify.add(settings, 'sloppy').onChange(simplify);\n\t\t\tguiSimplify.add(settings, 'permissive').onChange(simplify);\n\t\t\tguiSimplify.add(settings, 'permissiveProtection', { none: 0, uvs: 2, normals: 1, all: 3 }).onChange(simplify);\n\t\t\tguiSimplify.add(settings, 'lockBorder').onChange(simplify);\n\t\t\tguiSimplify.add(settings, 'prune').onChange(simplify);\n\t\t\tguiSimplify.add(settings, 'regularize').onChange(simplify);\n\t\t\tguiSimplify.add(settings, 'solve').onChange(simplify);\n\t\t\tguiSimplify.add(settings, 'preprune', 0, 0.2, 0.01).onChange(simplify);\n\t\t\tguiSimplify.add(settings, 'useAttributes').onChange(simplify).onFinishChange(updateSettings);\n\n\t\t\tvar guiAttributes = [];\n\t\t\tguiAttributes.push(guiSimplify.add(settings, 'normalWeight', 0, 2, 0.01).onChange(simplify));\n\t\t\tguiAttributes.push(guiSimplify.add(settings, 'colorWeight', 0, 2, 0.01).onChange(simplify));\n\t\t\tguiAttributes.push(guiSimplify.add(settings, 'textureWeight', 0, 100, 0.1).onChange(simplify));\n\n\t\t\tguiSimplify.add(settings, 'autoTextureWeight').onChange(simplify);\n\t\t\tguiSimplify.add(settings, 'errorScaled').onChange(simplify);\n\t\t\tguiSimplify.add(settings, 'errorThresholdLog10', 0, 3, 0.1).onChange(simplify);\n\n\t\t\tvar guiLod = gui.addFolder('LOD');\n\t\t\tguiLod.add(settings, 'autoLod').onChange(simplify);\n\t\t\tguiLod.add(settings, 'autoLodFactor', 0, 10, 0.01).onChange(simplify);\n\n\t\t\tvar guiLoad = gui.addFolder('Load');\n\t\t\tguiLoad.add(settings, 'loadFile');\n\t\t\tguiLoad.add(settings, 'updateModule');\n\t\t\tguiLoad.add(settings, 'debugCheckerboard');\n\t\t\tguiLoad.add(settings, 'autoUpdate').onChange(autoReload);\n\t\t\tguiLoad.add(settings, 'autoUpdateStatus').listen();\n\n\t\t\tupdateSettings();\n\t\t\tinit();\n\t\t\tloadDefault();\n\t\t\tanimate();\n\n\t\t\tfunction loadFiles(files) {\n\t\t\t\t// Clear existing views\n\t\t\t\tclearViews();\n\n\t\t\t\tif (files.length > 0) {\n\t\t\t\t\t// Set grid columns based on the number of files, up to the max setting\n\t\t\t\t\tconst columns = Math.min(files.length, settings.gridColumns);\n\t\t\t\t\tdocument.documentElement.style.setProperty('--grid-columns', columns);\n\n\t\t\t\t\t// Load each file into a separate view\n\t\t\t\t\tfor (var i = 0; i < files.length; i++) {\n\t\t\t\t\t\tvar file = files[i];\n\t\t\t\t\t\tvar uri = URL.createObjectURL(file);\n\t\t\t\t\t\tvar ext = file.name.split('.').pop().toLowerCase();\n\n\t\t\t\t\t\tcreateView(i, file.name);\n\t\t\t\t\t\tloadIntoView(i, uri, ext);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction updateGridLayout() {\n\t\t\t\tif (renderers.length > 0) {\n\t\t\t\t\tvar visibleViews = focusedViewIndex >= 0 ? 1 : renderers.length;\n\t\t\t\t\tvar columns = Math.min(visibleViews, settings.gridColumns);\n\t\t\t\t\tdocument.documentElement.style.setProperty('--grid-columns', columns);\n\n\t\t\t\t\t// Set grid cell height based on whether we're in single view or grid mode\n\t\t\t\t\tif (columns > 1) {\n\t\t\t\t\t\t// In grid mode, make cells square by calculating height based on viewport width\n\t\t\t\t\t\tvar cellWidth = Math.floor(window.innerWidth / columns);\n\t\t\t\t\t\tdocument.documentElement.style.setProperty('--grid-cell-height', cellWidth + 'px');\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Single view mode, use full height\n\t\t\t\t\t\tdocument.documentElement.style.setProperty('--grid-cell-height', '100vh');\n\t\t\t\t\t}\n\n\t\t\t\t\tupdateRendererSizes();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Helper function to handle autoLOD updates - no longer tied to camera rotation\n\t\t\tfunction updateAutoLod() {\n\t\t\t\tif (settings.autoLod) {\n\t\t\t\t\t// Check if distance has changed significantly\n\t\t\t\t\tvar center = new THREE.Vector3();\n\t\t\t\t\tvar currentDistance = camera.position.distanceTo(center);\n\n\t\t\t\t\tif (Math.abs(currentDistance - lastCameraDistance) > 1e-3) {\n\t\t\t\t\t\tlastCameraDistance = currentDistance;\n\t\t\t\t\t\tsimplify();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction updateSettings() {\n\t\t\t\tfor (var i = 0; i < guiAttributes.length; ++i) {\n\t\t\t\t\tguiAttributes[i].enable(settings.useAttributes);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction clearViews() {\n\t\t\t\t// Remove all existing renderers and clear arrays\n\t\t\t\tfor (var i = 0; i < renderers.length; i++) {\n\t\t\t\t\tvar container = renderers[i].domElement.parentElement;\n\t\t\t\t\tif (container) {\n\t\t\t\t\t\tcontainer.parentElement.removeChild(container);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\trenderers = [];\n\t\t\t\tscenes = [];\n\t\t\t\tcontrols = [];\n\t\t\t\tmodels = [];\n\t\t\t\tmixers = [];\n\t\t\t\tviewStats = [];\n\n\t\t\t\t// Reset focus state\n\t\t\t\tfocusedViewIndex = -1;\n\n\t\t\t\t// Clear the grid container\n\t\t\t\tgridContainer.innerHTML = '';\n\t\t\t}\n\n\t\t\tfunction updateLabelsClickability() {\n\t\t\t\tvar isClickable = renderers.length > 1;\n\n\t\t\t\tfor (var i = 0; i < renderers.length; i++) {\n\t\t\t\t\tvar container = renderers[i].domElement.parentElement;\n\t\t\t\t\tvar label = container.querySelector('.renderer-label');\n\n\t\t\t\t\tif (isClickable) {\n\t\t\t\t\t\tlabel.classList.add('clickable');\n\t\t\t\t\t\tlabel.title = 'Click to focus on this view';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlabel.classList.remove('clickable');\n\t\t\t\t\t\tlabel.title = '';\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction toggleFocus(viewIndex) {\n\t\t\t\tif (focusedViewIndex === viewIndex) {\n\t\t\t\t\t// Currently focused on this view, unfocus (show all)\n\t\t\t\t\tfocusedViewIndex = -1;\n\t\t\t\t} else {\n\t\t\t\t\t// Focus on this view\n\t\t\t\t\tfocusedViewIndex = viewIndex;\n\t\t\t\t}\n\t\t\t\tupdateFocusDisplay();\n\t\t\t\tsimplify(); // Re-run simplify with new focus state\n\t\t\t}\n\n\t\t\tfunction updateFocusDisplay() {\n\t\t\t\tfor (var i = 0; i < renderers.length; i++) {\n\t\t\t\t\tvar container = renderers[i].domElement.parentElement;\n\t\t\t\t\tvar label = container.querySelector('.renderer-label');\n\n\t\t\t\t\tif (focusedViewIndex === -1) {\n\t\t\t\t\t\t// No focus - show all containers\n\t\t\t\t\t\tcontainer.classList.remove('hidden');\n\t\t\t\t\t\tlabel.classList.remove('focused');\n\t\t\t\t\t\tif (renderers.length > 1) {\n\t\t\t\t\t\t\tlabel.title = 'Click to focus on this view';\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (focusedViewIndex === i) {\n\t\t\t\t\t\t// This is the focused view\n\t\t\t\t\t\tcontainer.classList.remove('hidden');\n\t\t\t\t\t\tlabel.classList.add('focused');\n\t\t\t\t\t\tlabel.title = 'Click to exit focus mode';\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Hide non-focused views\n\t\t\t\t\t\tcontainer.classList.add('hidden');\n\t\t\t\t\t\tlabel.classList.remove('focused');\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Update grid layout when focus changes\n\t\t\t\tupdateGridLayout();\n\t\t\t}\n\n\t\t\tfunction createView(index, name) {\n\t\t\t\t// Create renderer container\n\t\t\t\tvar rendererContainer = document.createElement('div');\n\t\t\t\trendererContainer.className = 'renderer-container';\n\t\t\t\tgridContainer.appendChild(rendererContainer);\n\n\t\t\t\t// Add label with file name\n\t\t\t\tvar label = document.createElement('div');\n\t\t\t\tlabel.className = 'renderer-label';\n\t\t\t\tlabel.textContent = name;\n\t\t\t\tlabel.onclick = function () {\n\t\t\t\t\tif (renderers.length > 1) {\n\t\t\t\t\t\ttoggleFocus(index);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\trendererContainer.appendChild(label);\n\n\t\t\t\t// Add stats label\n\t\t\t\tvar statsLabel = document.createElement('div');\n\t\t\t\tstatsLabel.className = 'stats-label';\n\t\t\t\trendererContainer.appendChild(statsLabel);\n\n\t\t\t\t// Initialize stats for this view\n\t\t\t\tviewStats.push({\n\t\t\t\t\ttriangles: 0,\n\t\t\t\t\tvertices: 0,\n\t\t\t\t\terror: 0,\n\t\t\t\t\tratio: 0,\n\t\t\t\t\tlabel: statsLabel,\n\t\t\t\t});\n\n\t\t\t\t// Create renderer\n\t\t\t\tvar renderer = new THREE.WebGLRenderer();\n\t\t\t\trenderer.setPixelRatio(window.devicePixelRatio);\n\t\t\t\trendererContainer.appendChild(renderer.domElement);\n\t\t\t\trenderers.push(renderer);\n\n\t\t\t\t// Create scene\n\t\t\t\tvar scene = new THREE.Scene();\n\t\t\t\tscene.background = new THREE.Color(0x300a24);\n\t\t\t\tscenes.push(scene);\n\n\t\t\t\t// Add lighting\n\t\t\t\tvar ambientLight = new THREE.AmbientLight(0xcccccc, 1);\n\t\t\t\tscene.add(ambientLight);\n\n\t\t\t\tvar pointLightR = new THREE.PointLight(0xffffff, 4);\n\t\t\t\tpointLightR.position.set(3, 3, 0);\n\t\t\t\tpointLightR.decay = 0.5;\n\t\t\t\tscene.add(pointLightR);\n\n\t\t\t\tvar pointLightL = new THREE.PointLight(0xffffff, 1);\n\t\t\t\tpointLightL.position.set(-3, 5, 0);\n\t\t\t\tpointLightL.decay = 0.5;\n\t\t\t\tscene.add(pointLightL);\n\n\t\t\t\t// Create controls\n\t\t\t\tvar control = new OrbitControls(camera, renderer.domElement);\n\t\t\t\tcontrol.addEventListener('change', updateAutoLod);\n\t\t\t\tcontrols.push(control);\n\n\t\t\t\t// Set null placeholders\n\t\t\t\tmodels.push(null);\n\t\t\t\tmixers.push(null);\n\n\t\t\t\t// Adjust renderer size\n\t\t\t\tupdateRendererSizes();\n\n\t\t\t\t// Update label clickability for all views\n\t\t\t\tupdateLabelsClickability();\n\t\t\t}\n\n\t\t\tfunction simplifyMesh(geo, threshold, scale) {\n\t\t\t\tvar attributes = 8; // 3 color, 3 normal, 2 uv\n\n\t\t\t\tvar positions = new Float32Array(geo.attributes.position.array).slice(); // clone for solve\n\t\t\t\tvar indices = new Uint32Array(geo.index.array).slice(); // clone for solve, upcast 16-bit indices for _InternalDebug\n\t\t\t\tvar target = settings.autoLod ? 0 : Math.floor((indices.length * settings.ratio) / 3) * 3;\n\n\t\t\t\tvar stride = geo.attributes.position instanceof THREE.InterleavedBufferAttribute ? geo.attributes.position.data.stride : 3;\n\n\t\t\t\tif (settings.useAttributes) {\n\t\t\t\t\tvar attrib = new Float32Array(geo.attributes.position.count * attributes);\n\n\t\t\t\t\tfor (var i = 0, e = geo.attributes.position.count; i < e; ++i) {\n\t\t\t\t\t\tif (geo.attributes.normal) {\n\t\t\t\t\t\t\tattrib[i * attributes + 0] = geo.attributes.normal.getX(i);\n\t\t\t\t\t\t\tattrib[i * attributes + 1] = geo.attributes.normal.getY(i);\n\t\t\t\t\t\t\tattrib[i * attributes + 2] = geo.attributes.normal.getZ(i);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (geo.attributes.color) {\n\t\t\t\t\t\t\tattrib[i * attributes + 3] = geo.attributes.color.getX(i);\n\t\t\t\t\t\t\tattrib[i * attributes + 4] = geo.attributes.color.getY(i);\n\t\t\t\t\t\t\tattrib[i * attributes + 5] = geo.attributes.color.getZ(i);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (geo.attributes.uv) {\n\t\t\t\t\t\t\tattrib[i * attributes + 6] = geo.attributes.uv.getX(i);\n\t\t\t\t\t\t\tattrib[i * attributes + 7] = geo.attributes.uv.getY(i);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tvar autoTextureWeight = 0;\n\n\t\t\t\t\tif (settings.autoTextureWeight && geo.attributes.uv) {\n\t\t\t\t\t\tfor (var i = 0, e = geo.index.count; i < e; i += 3) {\n\t\t\t\t\t\t\tvar a = geo.index.getX(i),\n\t\t\t\t\t\t\t\tb = geo.index.getX(i + 1),\n\t\t\t\t\t\t\t\tc = geo.index.getX(i + 2);\n\n\t\t\t\t\t\t\tvar au = geo.attributes.uv.getX(a),\n\t\t\t\t\t\t\t\tav = geo.attributes.uv.getY(a);\n\n\t\t\t\t\t\t\tvar bu = geo.attributes.uv.getX(b),\n\t\t\t\t\t\t\t\tbv = geo.attributes.uv.getY(b);\n\n\t\t\t\t\t\t\tvar cu = geo.attributes.uv.getX(c),\n\t\t\t\t\t\t\t\tcv = geo.attributes.uv.getY(c);\n\n\t\t\t\t\t\t\tvar uvarea = Math.abs((bu - au) * (cv - av) - (cu - au) * (bv - av)) * 0.5;\n\t\t\t\t\t\t\tautoTextureWeight += uvarea;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tautoTextureWeight /= geo.index.count / 3;\n\t\t\t\t\t\tautoTextureWeight = autoTextureWeight > 0 ? 1 / Math.sqrt(autoTextureWeight) : 0;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar attrib_weights = [\n\t\t\t\t\t\tsettings.normalWeight,\n\t\t\t\t\t\tsettings.normalWeight,\n\t\t\t\t\t\tsettings.normalWeight,\n\t\t\t\t\t\tsettings.colorWeight,\n\t\t\t\t\t\tsettings.colorWeight,\n\t\t\t\t\t\tsettings.colorWeight,\n\t\t\t\t\t\tsettings.autoTextureWeight ? autoTextureWeight : settings.textureWeight,\n\t\t\t\t\t\tsettings.autoTextureWeight ? autoTextureWeight : settings.textureWeight,\n\t\t\t\t\t];\n\t\t\t\t} else {\n\t\t\t\t\tvar attrib = new Float32Array();\n\t\t\t\t\tvar attrib_weights = [];\n\t\t\t\t\tattributes = 0;\n\t\t\t\t}\n\n\t\t\t\tvar flags = [];\n\t\t\t\tif (settings.lockBorder) {\n\t\t\t\t\tflags.push('LockBorder');\n\t\t\t\t}\n\t\t\t\tif (settings.prune) {\n\t\t\t\t\tflags.push('Prune');\n\t\t\t\t}\n\t\t\t\tif (settings.regularize) {\n\t\t\t\t\tflags.push('Regularize');\n\t\t\t\t}\n\t\t\t\tif (settings.permissive) {\n\t\t\t\t\tflags.push('Permissive');\n\t\t\t\t}\n\t\t\t\tif (settings.debugOverlay) {\n\t\t\t\t\tflags.push('_InternalDebug');\n\t\t\t\t}\n\n\t\t\t\tvar locks = new Uint8Array(geo.attributes.position.count);\n\n\t\t\t\tif (settings.permissive) {\n\t\t\t\t\tvar pos_remap = MeshoptSimplifier.generatePositionRemap(positions, stride);\n\t\t\t\t\tvar protect_bit = 2;\n\n\t\t\t\t\tif (geo.attributes.normal && (settings.permissiveProtection & 1) != 0) {\n\t\t\t\t\t\tfor (var i = 0, e = geo.attributes.position.count; i < e; ++i) {\n\t\t\t\t\t\t\tif (pos_remap[i] != i) {\n\t\t\t\t\t\t\t\tvar rx = geo.attributes.normal.getX(pos_remap[i]),\n\t\t\t\t\t\t\t\t\try = geo.attributes.normal.getY(pos_remap[i]),\n\t\t\t\t\t\t\t\t\trz = geo.attributes.normal.getZ(pos_remap[i]);\n\t\t\t\t\t\t\t\tvar nx = geo.attributes.normal.getX(i),\n\t\t\t\t\t\t\t\t\tny = geo.attributes.normal.getY(i),\n\t\t\t\t\t\t\t\t\tnz = geo.attributes.normal.getZ(i);\n\n\t\t\t\t\t\t\t\t// keep sharp edges\n\t\t\t\t\t\t\t\tif (rx * nx + ry * ny + rz * nz < 0.25) {\n\t\t\t\t\t\t\t\t\tlocks[i] |= protect_bit;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (geo.attributes.uv && (settings.permissiveProtection & 2) != 0) {\n\t\t\t\t\t\tfor (var i = 0, e = geo.attributes.position.count; i < e; ++i) {\n\t\t\t\t\t\t\tif (pos_remap[i] != i) {\n\t\t\t\t\t\t\t\tvar ru = geo.attributes.uv.getX(pos_remap[i]),\n\t\t\t\t\t\t\t\t\trv = geo.attributes.uv.getY(pos_remap[i]);\n\t\t\t\t\t\t\t\tvar u = geo.attributes.uv.getX(i),\n\t\t\t\t\t\t\t\t\tv = geo.attributes.uv.getY(i);\n\n\t\t\t\t\t\t\t\t// keep UV seams\n\t\t\t\t\t\t\t\tif (u != ru || v != rv) {\n\t\t\t\t\t\t\t\t\tlocks[i] |= protect_bit;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (settings.preprune > 0) {\n\t\t\t\t\tindices = MeshoptSimplifier.simplifyPrune(indices, positions, stride, settings.preprune / scale);\n\t\t\t\t\ttarget = Math.min(target, indices.length);\n\t\t\t\t}\n\n\t\t\t\tvar S = MeshoptSimplifier; // to avoid line breaks below...\n\t\t\t\tvar res = settings.sloppy\n\t\t\t\t\t? S.simplifySloppy(indices, positions, stride, null, target, threshold)\n\t\t\t\t\t: settings.solve\n\t\t\t\t\t\t? S.simplifyWithUpdate(indices, positions, stride, attrib, attributes, attrib_weights, locks, target, threshold, flags)\n\t\t\t\t\t\t: S.simplifyWithAttributes(indices, positions, stride, attrib, attributes, attrib_weights, locks, target, threshold, flags);\n\n\t\t\t\tif (!settings.sloppy && settings.solve) {\n\t\t\t\t\tres[0] = indices.slice(0, res[0]);\n\t\t\t\t}\n\n\t\t\t\tvar rgeo = geo.clone();\n\n\t\t\t\tvar dind = res[0];\n\t\t\t\tvar dlen = dind.length;\n\n\t\t\t\tif (settings.debugOverlay) {\n\t\t\t\t\t// we need extra indices for debug overlay\n\t\t\t\t\tdind = Array.from(dind);\n\n\t\t\t\t\tvar mask = (1 << 28) - 1;\n\n\t\t\t\t\tfor (var kind = 1; kind <= 6; ++kind) {\n\t\t\t\t\t\tvar offset = dind.length;\n\n\t\t\t\t\t\tfor (var i = 0; i < dlen; i += 3) {\n\t\t\t\t\t\t\tfor (var e = 0; e < 3; ++e) {\n\t\t\t\t\t\t\t\tvar a = dind[i + e],\n\t\t\t\t\t\t\t\t\tb = dind[i + ((e + 1) % 3)];\n\t\t\t\t\t\t\t\tvar ak = (a >> 28) & 7,\n\t\t\t\t\t\t\t\t\tbk = (b >> 28) & 7;\n\n\t\t\t\t\t\t\t\tif (a >> 31 != 0 && (ak == kind || bk == kind) && (ak == kind || ak == 4) && (bk == kind || bk == 4)) {\n\t\t\t\t\t\t\t\t\t// loop edge of current kind (allow one of the vertices to be locked)\n\t\t\t\t\t\t\t\t\t// note: complex edges ignore loop metadata\n\t\t\t\t\t\t\t\t\tdind.push(a & mask);\n\t\t\t\t\t\t\t\t\tdind.push(a & mask);\n\t\t\t\t\t\t\t\t\tdind.push(b & mask);\n\t\t\t\t\t\t\t\t} else if (a >> 31 != 0 && kind == 5 && ak != bk && ak != 4 && bk != 4) {\n\t\t\t\t\t\t\t\t\t// mixed edge (neither vertex is locked, and they are of different kinds)\n\t\t\t\t\t\t\t\t\tdind.push(a & mask);\n\t\t\t\t\t\t\t\t\tdind.push(a & mask);\n\t\t\t\t\t\t\t\t\tdind.push(b & mask);\n\t\t\t\t\t\t\t\t} else if (a >> 31 == 0 && kind == 4 && ak == kind && bk == kind) {\n\t\t\t\t\t\t\t\t\t// locked edge (not marked as a loop)\n\t\t\t\t\t\t\t\t\tdind.push(a & mask);\n\t\t\t\t\t\t\t\t\tdind.push(a & mask);\n\t\t\t\t\t\t\t\t\tdind.push(b & mask);\n\t\t\t\t\t\t\t\t} else if (a >> 31 == 0 && kind == 6 && ak == 3 && bk == 3) {\n\t\t\t\t\t\t\t\t\t// complex edge (not marked as a loop)\n\t\t\t\t\t\t\t\t\tdind.push(a & mask);\n\t\t\t\t\t\t\t\t\tdind.push(a & mask);\n\t\t\t\t\t\t\t\t\tdind.push(b & mask);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (offset != dind.length) {\n\t\t\t\t\t\t\trgeo.addGroup(offset, dind.length - offset, kind + 1); // +1 to skip overlay\n\t\t\t\t\t\t\toffset = dind.length;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// clear debug bits for actual indices\n\t\t\t\t\tfor (var i = 0; i < dlen; i++) {\n\t\t\t\t\t\tdind[i] &= mask;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (settings.wireframeOverlay) {\n\t\t\t\t\t\trgeo.addGroup(0, dlen, 1); // 1=overlay\n\t\t\t\t\t}\n\t\t\t\t} else if (settings.wireframeOverlay) {\n\t\t\t\t\trgeo.addGroup(0, dind.length, 1); // 1=overlay\n\t\t\t\t}\n\n\t\t\t\trgeo.index.array = new Uint32Array(dind);\n\t\t\t\trgeo.index.count = dind.length;\n\t\t\t\trgeo.index.needsUpdate = true;\n\n\t\t\t\tif (settings.solve) {\n\t\t\t\t\trgeo.attributes.position = new THREE.BufferAttribute(positions, stride);\n\n\t\t\t\t\tif (settings.useAttributes) {\n\t\t\t\t\t\tfor (var i = 0, e = geo.attributes.position.count; i < e; ++i) {\n\t\t\t\t\t\t\tvar nx = attrib[i * attributes + 0];\n\t\t\t\t\t\t\tvar ny = attrib[i * attributes + 1];\n\t\t\t\t\t\t\tvar nz = attrib[i * attributes + 2];\n\n\t\t\t\t\t\t\tvar nl = Math.sqrt(nx * nx + ny * ny + nz * nz);\n\n\t\t\t\t\t\t\tif (nl > 0) {\n\t\t\t\t\t\t\t\tattrib[i * attributes + 0] = nx / nl;\n\t\t\t\t\t\t\t\tattrib[i * attributes + 1] = ny / nl;\n\t\t\t\t\t\t\t\tattrib[i * attributes + 2] = nz / nl;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvar attribbuf = new THREE.InterleavedBuffer(attrib, attrib_weights.length);\n\n\t\t\t\t\t\tif (geo.attributes.normal) {\n\t\t\t\t\t\t\trgeo.attributes.normal = new THREE.InterleavedBufferAttribute(attribbuf, 3, 0, false);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (geo.attributes.color) {\n\t\t\t\t\t\t\trgeo.attributes.color = new THREE.InterleavedBufferAttribute(attribbuf, 3, 3, false);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (geo.attributes.uv) {\n\t\t\t\t\t\t\trgeo.attributes.uv = new THREE.InterleavedBufferAttribute(attribbuf, 2, 6, false);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\trgeo.addGroup(0, dlen, 0);\n\n\t\t\t\treturn [rgeo, dlen / 3, res[1]];\n\t\t\t}\n\n\t\t\tfunction simplifyPoints(geo) {\n\t\t\t\tvar positions = new Float32Array(geo.attributes.position.array);\n\t\t\t\tvar colors = undefined;\n\n\t\t\t\tvar target = Math.floor(geo.attributes.position.count * settings.ratio);\n\n\t\t\t\tif (settings.useAttributes && geo.attributes.color) {\n\t\t\t\t\tcolors = new Float32Array(geo.attributes.color.array);\n\t\t\t\t}\n\n\t\t\t\tvar pos_stride = geo.attributes.position instanceof THREE.InterleavedBufferAttribute ? geo.attributes.position.data.stride : 3;\n\t\t\t\tvar col_stride =\n\t\t\t\t\tgeo.attributes.color instanceof THREE.InterleavedBufferAttribute\n\t\t\t\t\t\t? geo.attributes.color.data.stride\n\t\t\t\t\t\t: geo.attributes.color.itemSize;\n\n\t\t\t\t// note: we are converting source color array without normalization; if the color data is quantized, we need to divide by 255 to normalize it\n\t\t\t\tvar colorScale = geo.attributes.color && geo.attributes.color.normalized ? 255 : 1;\n\n\t\t\t\tconsole.time('simplify');\n\n\t\t\t\tvar res = MeshoptSimplifier.simplifyPoints(positions, pos_stride, target, colors, col_stride, settings.colorWeight / colorScale);\n\n\t\t\t\tconsole.timeEnd('simplify');\n\n\t\t\t\tconsole.log('simplified to', res.length);\n\n\t\t\t\tgeo.index = new THREE.BufferAttribute(res, 1);\n\t\t\t}\n\n\t\t\tfunction getRadius(obj) {\n\t\t\t\tvar box = new THREE.Box3().setFromObject(obj);\n\t\t\t\treturn box.max.sub(box.min).length() / 2;\n\t\t\t}\n\n\t\t\tfunction simplify() {\n\t\t\t\tMeshoptSimplifier.ready.then(function () {\n\t\t\t\t\tvar threshold = Math.pow(10, -settings.errorThresholdLog10);\n\n\t\t\t\t\tif (settings.autoLod) {\n\t\t\t\t\t\t// compute distance to model sphere\n\t\t\t\t\t\t// ideally this should actually use the real radius and center :) but we rescale/center the model when loading\n\t\t\t\t\t\tvar center = new THREE.Vector3();\n\t\t\t\t\t\tvar radius = 1;\n\t\t\t\t\t\tvar distance = Math.max(camera.position.distanceTo(center) - radius, 0);\n\t\t\t\t\t\tvar loderrortarget = 1e-3 * (2 * Math.tan(camera.fov * 0.5 * (Math.PI / 180))); // ~1 pixel at 1k x 1k\n\n\t\t\t\t\t\t// note: we are currently cutting corners wrt handling the actual mesh scale\n\t\t\t\t\t\t// since we rescale the entire scene to fit a unit box, we can directly feed the threshold\n\t\t\t\t\t\t// this works correctly if there's just one mesh or scales match; ideally we tweak this to compute\n\t\t\t\t\t\t// threshold per mesh based on relative scale between mesh and scene.\n\t\t\t\t\t\t// this also computes distance to model *center* instead of surface boundary so features on the boundary\n\t\t\t\t\t\t// might be simplified more than they should be.\n\t\t\t\t\t\tthreshold = distance * loderrortarget * settings.autoLodFactor;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Process each scene\n\t\t\t\t\tfor (var sceneIndex = 0; sceneIndex < scenes.length; sceneIndex++) {\n\t\t\t\t\t\tvar scene = scenes[sceneIndex];\n\t\t\t\t\t\tif (focusedViewIndex >= 0 && sceneIndex != focusedViewIndex) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Reset stats for this view\n\t\t\t\t\t\tvar stats = viewStats[sceneIndex];\n\t\t\t\t\t\tstats.triangles = 0;\n\t\t\t\t\t\tstats.vertices = 0;\n\t\t\t\t\t\tstats.error = 0;\n\t\t\t\t\t\tstats.ratio = 0;\n\n\t\t\t\t\t\tvar rnum = 0;\n\t\t\t\t\t\tvar rden = 0;\n\n\t\t\t\t\t\tvar extent = getRadius(scene);\n\n\t\t\t\t\t\tscene.traverse(function (object) {\n\t\t\t\t\t\t\tif (object.isMesh && object.geometry.index) {\n\t\t\t\t\t\t\t\tif (!object.original) {\n\t\t\t\t\t\t\t\t\tobject.original = object.geometry.clone();\n\n\t\t\t\t\t\t\t\t\t// use small depth offset to avoid overlay z-fighting with the original mesh\n\t\t\t\t\t\t\t\t\t// has to be done on the main material as overlays use lines that don't support depth offset\n\t\t\t\t\t\t\t\t\tobject.material.polygonOffset = true;\n\t\t\t\t\t\t\t\t\tobject.material.polygonOffsetFactor = 0.5;\n\t\t\t\t\t\t\t\t\tobject.material.polygonOffsetUnits = 16;\n\n\t\t\t\t\t\t\t\t\tobject.material = [\n\t\t\t\t\t\t\t\t\t\tobject.material,\n\t\t\t\t\t\t\t\t\t\tnew THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true }), // overlay\n\t\t\t\t\t\t\t\t\t\tnew THREE.MeshBasicMaterial({ color: 0x0000ff, wireframe: true }), // border\n\t\t\t\t\t\t\t\t\t\tnew THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true }), // seam\n\t\t\t\t\t\t\t\t\t\tnew THREE.MeshBasicMaterial({ color: 0x009f9f, wireframe: true }), // complex\n\t\t\t\t\t\t\t\t\t\tnew THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true }), // locked\n\t\t\t\t\t\t\t\t\t\tnew THREE.MeshBasicMaterial({ color: 0xff9f00, wireframe: true }), // mixed edge\n\t\t\t\t\t\t\t\t\t\tnew THREE.MeshBasicMaterial({ color: 0x9f5f9f, wireframe: true }), // complex, no loop\n\t\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tvar scale = settings.errorScaled ? getRadius(object) / extent : 1.0;\n\n\t\t\t\t\t\t\t\tvar [geo, tri, err] = simplifyMesh(object.original, threshold / scale, scale);\n\n\t\t\t\t\t\t\t\tobject.geometry = geo;\n\n\t\t\t\t\t\t\t\tstats.error = Math.max(stats.error, err * scale);\n\t\t\t\t\t\t\t\tstats.triangles += tri;\n\t\t\t\t\t\t\t\tstats.vertices += object.geometry.attributes.position.count;\n\n\t\t\t\t\t\t\t\trnum += tri;\n\t\t\t\t\t\t\t\trden += object.original.index.count / 3;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (object.isPoints) {\n\t\t\t\t\t\t\t\tsimplifyPoints(object.geometry);\n\n\t\t\t\t\t\t\t\tstats.vertices += object.geometry.index.count;\n\n\t\t\t\t\t\t\t\trnum += object.geometry.index.count;\n\t\t\t\t\t\t\t\trden += object.geometry.attributes.position.count;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tstats.ratio = rden > 0 ? (rnum / rden) * 100 : 0;\n\n\t\t\t\t\t\t// Update stats display for this view\n\t\t\t\t\t\tupdateStatsDisplay(sceneIndex);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tfunction updateStatsDisplay(viewIndex) {\n\t\t\t\tif (viewIndex >= 0 && viewIndex < viewStats.length) {\n\t\t\t\t\tvar stats = viewStats[viewIndex];\n\n\t\t\t\t\tstats.label.innerHTML =\n\t\t\t\t\t\t'Triangles: ' +\n\t\t\t\t\t\tstats.triangles +\n\t\t\t\t\t\t'<br>' +\n\t\t\t\t\t\t'Vertices: ' +\n\t\t\t\t\t\tstats.vertices +\n\t\t\t\t\t\t'<br>' +\n\t\t\t\t\t\t'Error: ' +\n\t\t\t\t\t\tstats.error.toExponential(3) +\n\t\t\t\t\t\t'<br>' +\n\t\t\t\t\t\t'Ratio: ' +\n\t\t\t\t\t\tstats.ratio.toFixed(1) +\n\t\t\t\t\t\t'%';\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction reload() {\n\t\t\t\tvar simp = import('/js/meshopt_simplifier.js?x=' + Date.now());\n\t\t\t\tMeshoptSimplifier.ready = simp.then(function (s) {\n\t\t\t\t\treturn s.MeshoptSimplifier.ready.then(function () {\n\t\t\t\t\t\tfor (var prop in s.MeshoptSimplifier) {\n\t\t\t\t\t\t\tMeshoptSimplifier[prop] = s.MeshoptSimplifier[prop];\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tvar moduleLastModified = 0;\n\n\t\t\tfunction autoReload() {\n\t\t\t\tif (!settings.autoUpdate) return;\n\n\t\t\t\tfetch('/js/meshopt_simplifier.js?x=' + Date.now(), { method: 'HEAD' })\n\t\t\t\t\t.then(function (r) {\n\t\t\t\t\t\tvar last = r.headers.get('Last-Modified');\n\t\t\t\t\t\tif (last != moduleLastModified) {\n\t\t\t\t\t\t\tmoduleLastModified = last;\n\t\t\t\t\t\t\treload();\n\t\t\t\t\t\t\tsimplify();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tsettings.autoUpdateStatus = new Date(last).toLocaleTimeString();\n\t\t\t\t\t\tsetTimeout(autoReload, 1000);\n\t\t\t\t\t})\n\t\t\t\t\t.catch(function (e) {\n\t\t\t\t\t\tsettings.autoUpdateStatus = 'error';\n\t\t\t\t\t\tsetTimeout(autoReload, 5000);\n\t\t\t\t\t});\n\t\t\t}\n\n\t\t\tfunction update() {\n\t\t\t\tfor (var sceneIndex = 0; sceneIndex < scenes.length; sceneIndex++) {\n\t\t\t\t\tvar scene = scenes[sceneIndex];\n\n\t\t\t\t\tscene.traverse(function (child) {\n\t\t\t\t\t\tif (child.isMesh) {\n\t\t\t\t\t\t\tif (Array.isArray(child.material)) {\n\t\t\t\t\t\t\t\tchild.material[0].wireframe = settings.wireframe;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tchild.material.wireframe = settings.wireframe;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (child.isPoints) {\n\t\t\t\t\t\t\tchild.material.size = settings.pointSize;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction loadIntoView(viewIndex, path, ext) {\n\t\t\t\tif (models[viewIndex]) {\n\t\t\t\t\tscenes[viewIndex].remove(models[viewIndex]);\n\t\t\t\t\tmodels[viewIndex] = undefined;\n\t\t\t\t\tmixers[viewIndex] = undefined;\n\t\t\t\t}\n\n\t\t\t\tvar onProgress = function (xhr) {};\n\t\t\t\tvar onError = function (e) {\n\t\t\t\t\tconsole.log(e);\n\t\t\t\t};\n\n\t\t\t\tfunction center(model) {\n\t\t\t\t\tvar bbox = new THREE.Box3().setFromObject(model);\n\t\t\t\t\tvar scale = 2 / Math.max(bbox.max.x - bbox.min.x, bbox.max.y - bbox.min.y, bbox.max.z - bbox.min.z);\n\t\t\t\t\tvar offset = new THREE.Vector3().addVectors(bbox.max, bbox.min).multiplyScalar(scale / 2);\n\n\t\t\t\t\tmodel.scale.set(scale, scale, scale);\n\t\t\t\t\tmodel.position.set(-offset.x, -offset.y, -offset.z);\n\t\t\t\t}\n\n\t\t\t\tif (ext == 'gltf' || ext == 'glb') {\n\t\t\t\t\tvar loader = new GLTFLoader();\n\t\t\t\t\tloader.setMeshoptDecoder(MeshoptDecoder);\n\t\t\t\t\tloader.load(\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tfunction (gltf) {\n\t\t\t\t\t\t\tmodels[viewIndex] = gltf.scene;\n\t\t\t\t\t\t\tcenter(models[viewIndex]);\n\t\t\t\t\t\t\tscenes[viewIndex].add(models[viewIndex]);\n\n\t\t\t\t\t\t\tmixers[viewIndex] = new THREE.AnimationMixer(models[viewIndex]);\n\n\t\t\t\t\t\t\tif (gltf.animations.length) {\n\t\t\t\t\t\t\t\tmixers[viewIndex].clipAction(gltf.animations[gltf.animations.length - 1]).play();\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Apply simplification\n\t\t\t\t\t\t\tsimplify();\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonProgress,\n\t\t\t\t\t\tonError\n\t\t\t\t\t);\n\t\t\t\t} else if (ext == 'obj') {\n\t\t\t\t\tvar loader = new OBJLoader();\n\t\t\t\t\tloader.load(\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tfunction (obj) {\n\t\t\t\t\t\t\tvar geos = [];\n\n\t\t\t\t\t\t\tobj.traverse(function (node) {\n\t\t\t\t\t\t\t\tif (node.isMesh) {\n\t\t\t\t\t\t\t\t\t// obj loader does not index the geometry, so we need to do it ourselves\n\t\t\t\t\t\t\t\t\tnode.geometry = mergeVertices(node.geometry);\n\n\t\t\t\t\t\t\t\t\t// we use groups and multiple materials for debug visualization, so merge all of the source ones for now\n\t\t\t\t\t\t\t\t\tif (Array.isArray(node.material)) {\n\t\t\t\t\t\t\t\t\t\tnode.material = node.material[0];\n\t\t\t\t\t\t\t\t\t\tnode.geometry.clearGroups();\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tgeos.push(node.geometry);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t// try to merge geometries into a single mesh; this cleanly handles material boundaries and separate objects\n\t\t\t\t\t\t\t// it may fail if the geometries are incompatible (different attributes), in which case we just use the original object\n\t\t\t\t\t\t\tvar merged = mergeGeometries(geos);\n\n\t\t\t\t\t\t\tif (merged) {\n\t\t\t\t\t\t\t\tif (settings.debugCheckerboard) {\n\t\t\t\t\t\t\t\t\tvar cb = new THREE.TextureLoader().load(\n\t\t\t\t\t\t\t\t\t\t'https://threejs.org/examples/textures/floors/FloorsCheckerboard_S_Diffuse.jpg'\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tcb.wrapS = THREE.RepeatWrapping;\n\t\t\t\t\t\t\t\t\tcb.wrapT = THREE.RepeatWrapping;\n\t\t\t\t\t\t\t\t\tcb.repeat.set(8, 8); // Adjust for number of repetitions\n\n\t\t\t\t\t\t\t\t\tvar mat = new THREE.MeshStandardMaterial({ map: cb });\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tvar mat = new THREE.MeshStandardMaterial({ color: 0xdddddd });\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tmodels[viewIndex] = new THREE.Mesh(merged, mat);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tmodels[viewIndex] = obj;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tcenter(models[viewIndex]);\n\t\t\t\t\t\t\tscenes[viewIndex].add(models[viewIndex]);\n\n\t\t\t\t\t\t\t// Apply simplification\n\t\t\t\t\t\t\tsimplify();\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonProgress,\n\t\t\t\t\t\tonError\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error('Unsupported file format');\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction loadDefault() {\n\t\t\t\t// Create a single view with the default model\n\t\t\t\tdocument.documentElement.style.setProperty('--grid-columns', 1);\n\t\t\t\tcreateView(0, 'pirate.glb');\n\t\t\t\tloadIntoView(0, 'pirate.glb', 'glb');\n\t\t\t}\n\n\t\t\tfunction init() {\n\t\t\t\tcontainer = document.createElement('div');\n\t\t\t\tdocument.body.appendChild(container);\n\n\t\t\t\tgridContainer = document.getElementById('grid-container');\n\n\t\t\t\t// Create a single camera shared by all views\n\t\t\t\tcamera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);\n\t\t\t\tcamera.position.y = 1.0;\n\t\t\t\tcamera.position.z = 3.0;\n\n\t\t\t\tclock = new THREE.Clock();\n\n\t\t\t\twindow.addEventListener('resize', updateRendererSizes, false);\n\t\t\t}\n\n\t\t\tfunction updateRendererSizes() {\n\t\t\t\t// Calculate the correct aspect ratio based on a visible view\n\t\t\t\tvar container = renderers[focusedViewIndex >= 0 ? focusedViewIndex : 0].domElement.parentElement;\n\t\t\t\tvar aspectRatio = container.clientWidth / container.clientHeight;\n\n\t\t\t\t// Update camera aspect ratio once\n\t\t\t\tcamera.aspect = aspectRatio;\n\t\t\t\tcamera.updateProjectionMatrix();\n\n\t\t\t\t// Update all renderer sizes\n\t\t\t\tfor (var i = 0; i < renderers.length; i++) {\n\t\t\t\t\tvar domElement = renderers[i].domElement;\n\t\t\t\t\tvar container = domElement.parentElement;\n\n\t\t\t\t\t// Use the container's actual dimensions\n\t\t\t\t\tvar width = container.clientWidth;\n\t\t\t\t\tvar height = container.clientHeight;\n\n\t\t\t\t\trenderers[i].setSize(width, height);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction animate() {\n\t\t\t\trequestAnimationFrame(animate);\n\n\t\t\t\t// Update all controls\n\t\t\t\tfor (var i = 0; i < controls.length; i++) {\n\t\t\t\t\tcontrols[i].update();\n\t\t\t\t}\n\n\t\t\t\t// Update animation mixers\n\t\t\t\tif (settings.animate) {\n\t\t\t\t\tvar delta = clock.getDelta();\n\t\t\t\t\tfor (var i = 0; i < mixers.length; i++) {\n\t\t\t\t\t\tif (mixers[i]) {\n\t\t\t\t\t\t\tmixers[i].update(delta);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Render only visible views\n\t\t\t\tfor (var i = 0; i < renderers.length; i++) {\n\t\t\t\t\t// Only render if view is visible (not hidden by focus mode)\n\t\t\t\t\tif (focusedViewIndex === -1 || focusedViewIndex === i) {\n\t\t\t\t\t\trenderers[i].render(scenes[i], camera);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t</script>\n\t</body>\n</html>\n"
  },
  {
    "path": "demo/tests.cpp",
    "content": "#include \"../src/meshoptimizer.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <vector>\n\n// This file uses assert() to verify algorithm correctness\n#undef NDEBUG\n#include <assert.h>\n\nstruct PV\n{\n\tunsigned short px, py, pz;\n\tunsigned char nu, nv; // octahedron encoded normal, aliases .pw\n\tunsigned short tx, ty;\n};\n\n// note: 4 6 5 triangle here is a combo-breaker:\n// we encode it without rotating, a=next, c=next - this means we do *not* bump next to 6\n// which means that the next triangle can't be encoded via next sequencing!\nstatic const unsigned int kIndexBuffer[] = {0, 1, 2, 2, 1, 3, 4, 6, 5, 7, 8, 9};\n\nstatic const unsigned char kIndexDataV0[] = {\n    0xe0, 0xf0, 0x10, 0xfe, 0xff, 0xf0, 0x0c, 0xff, 0x02, 0x02, 0x02, 0x00, 0x76, 0x87, 0x56, 0x67,\n    0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98, 0x01, 0x69, 0x00, 0x00, // clang-format :-/\n};\n\n// note: this exercises two features of v1 format, restarts (0 1 2) and last\nstatic const unsigned int kIndexBufferTricky[] = {0, 1, 2, 2, 1, 3, 0, 1, 2, 2, 1, 5, 2, 1, 4};\n\nstatic const unsigned char kIndexDataV1[] = {\n    0xe1, 0xf0, 0x10, 0xfe, 0x1f, 0x3d, 0x00, 0x0a, 0x00, 0x76, 0x87, 0x56, 0x67, 0x78, 0xa9, 0x86,\n    0x65, 0x89, 0x68, 0x98, 0x01, 0x69, 0x00, 0x00, // clang-format :-/\n};\n\nstatic const unsigned int kIndexSequence[] = {0, 1, 51, 2, 49, 1000};\n\nstatic const unsigned char kIndexSequenceV1[] = {\n    0xd1, 0x00, 0x04, 0xcd, 0x01, 0x04, 0x07, 0x98, 0x1f, 0x00, 0x00, 0x00, 0x00, // clang-format :-/\n};\n\nstatic const PV kVertexBuffer[] = {\n    {0, 0, 0, 0, 0, 0, 0},\n    {300, 0, 0, 0, 0, 500, 0},\n    {0, 300, 0, 0, 0, 0, 500},\n    {300, 300, 0, 0, 0, 500, 500},\n};\n\nstatic const unsigned char kVertexDataV0[] = {\n    0xa0, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x58, 0x57, 0x58, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01, 0x0c,\n    0x00, 0x00, 0x00, 0x58, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3f, 0x00,\n    0x00, 0x00, 0x17, 0x18, 0x17, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x00, 0x00, 0x00, 0x17,\n    0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, // clang-format :-/\n};\n\nstatic const unsigned char kVertexDataV1[] = {\n    0xa1, 0xee, 0xaa, 0xee, 0x00, 0x4b, 0x4b, 0x4b, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x7d, 0x7d, 0x7d,\n    0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x62, // clang-format :-/\n};\n\n// This binary blob is a valid v1 encoding of vertex buffer but it used a custom version of\n// the encoder that exercised all features of the format; because of this it is much larger\n// and will never be produced by the encoder itself.\nstatic const unsigned char kVertexDataV1Custom[] = {\n    0xa1, 0xd4, 0x94, 0xd4, 0x01, 0x0e, 0x00, 0x58, 0x57, 0x58, 0x02, 0x02, 0x12, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x0e, 0x00, 0x7d, 0x7d, 0x7d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x62, // clang-format :-/\n};\n\nstatic void decodeIndexV0()\n{\n\tconst size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]);\n\n\tstd::vector<unsigned char> buffer(kIndexDataV0, kIndexDataV0 + sizeof(kIndexDataV0));\n\n\tunsigned int decoded[index_count];\n\tassert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0);\n\tassert(memcmp(decoded, kIndexBuffer, sizeof(kIndexBuffer)) == 0);\n}\n\nstatic void decodeIndexV1()\n{\n\tconst size_t index_count = sizeof(kIndexBufferTricky) / sizeof(kIndexBufferTricky[0]);\n\n\tstd::vector<unsigned char> buffer(kIndexDataV1, kIndexDataV1 + sizeof(kIndexDataV1));\n\n\tunsigned int decoded[index_count];\n\tassert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0);\n\tassert(memcmp(decoded, kIndexBufferTricky, sizeof(kIndexBufferTricky)) == 0);\n}\n\nstatic void decodeIndexV1More()\n{\n\tconst unsigned char input[] = {\n\t    0xe1, 0xf0, 0x10, 0xfe, 0xff, 0xf0, 0x0c, 0xff, 0x02, 0x02, 0x02, 0x00, 0x76, 0x87, 0x56, 0x67,\n\t    0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98, 0x01, 0x69, 0x00, 0x00, // clang-format\n\t};\n\n\tconst unsigned int ib[] = {0, 1, 2, 2, 1, 3, 4, 6, 5, 7, 8, 9};\n\tconst size_t index_count = sizeof(ib) / sizeof(ib[0]);\n\n\tstd::vector<unsigned char> buffer(input, input + sizeof(input));\n\n\tunsigned int decoded[index_count];\n\tassert(meshopt_decodeIndexBuffer(decoded, index_count, 4, &buffer[0], buffer.size()) == 0);\n\tassert(memcmp(decoded, ib, sizeof(ib)) == 0);\n}\n\nstatic void decodeIndexV1ThreeEdges()\n{\n\tconst unsigned char input[] = {\n\t    0xe1, 0xf0, 0x20, 0x30, 0x40, 0x00, 0x76, 0x87, 0x56, 0x67, 0x78, 0xa9, 0x86, 0x65, 0x89, 0x68,\n\t    0x98, 0x01, 0x69, 0x00, 0x00, // clang-format\n\t};\n\n\tconst unsigned int ib[] = {0, 1, 2, 1, 0, 3, 2, 1, 4, 0, 2, 5};\n\tconst size_t index_count = sizeof(ib) / sizeof(ib[0]);\n\n\tstd::vector<unsigned char> buffer(input, input + sizeof(input));\n\n\tunsigned int decoded[index_count];\n\tassert(meshopt_decodeIndexBuffer(decoded, index_count, 4, &buffer[0], buffer.size()) == 0);\n\tassert(memcmp(decoded, ib, sizeof(ib)) == 0);\n}\n\nstatic void decodeIndex16()\n{\n\tconst size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]);\n\tconst size_t vertex_count = 10;\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count));\n\tbuffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count));\n\n\tunsigned short decoded[index_count];\n\tassert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0);\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t\tassert(decoded[i] == kIndexBuffer[i]);\n}\n\nstatic void encodeIndexMemorySafe()\n{\n\tconst size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]);\n\tconst size_t vertex_count = 10;\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count));\n\tbuffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count));\n\n\t// check that encode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access\n\tfor (size_t i = 0; i <= buffer.size(); ++i)\n\t{\n\t\tstd::vector<unsigned char> shortbuffer(i);\n\t\tsize_t result = meshopt_encodeIndexBuffer(i == 0 ? NULL : &shortbuffer[0], i, kIndexBuffer, index_count);\n\n\t\tif (i == buffer.size())\n\t\t\tassert(result == buffer.size());\n\t\telse\n\t\t\tassert(result == 0);\n\t}\n}\n\nstatic void decodeIndexMemorySafe()\n{\n\tconst size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]);\n\tconst size_t vertex_count = 10;\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count));\n\tbuffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count));\n\n\t// check that decode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access\n\tunsigned int decoded[index_count];\n\n\tfor (size_t i = 0; i <= buffer.size(); ++i)\n\t{\n\t\tstd::vector<unsigned char> shortbuffer(buffer.begin(), buffer.begin() + i);\n\t\tint result = meshopt_decodeIndexBuffer(decoded, index_count, i == 0 ? NULL : &shortbuffer[0], i);\n\n\t\tif (i == buffer.size())\n\t\t\tassert(result == 0);\n\t\telse\n\t\t\tassert(result < 0);\n\t}\n}\n\nstatic void decodeIndexRejectExtraBytes()\n{\n\tconst size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]);\n\tconst size_t vertex_count = 10;\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count));\n\tbuffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count));\n\n\t// check that decoder doesn't accept extra bytes after a valid stream\n\tstd::vector<unsigned char> largebuffer(buffer);\n\tlargebuffer.push_back(0);\n\n\tunsigned int decoded[index_count];\n\tassert(meshopt_decodeIndexBuffer(decoded, index_count, &largebuffer[0], largebuffer.size()) < 0);\n}\n\nstatic void decodeIndexRejectMalformedHeaders()\n{\n\tconst size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]);\n\tconst size_t vertex_count = 10;\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count));\n\tbuffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count));\n\n\t// check that decoder doesn't accept malformed headers\n\tstd::vector<unsigned char> brokenbuffer(buffer);\n\tbrokenbuffer[0] = 0;\n\n\tunsigned int decoded[index_count];\n\tassert(meshopt_decodeIndexBuffer(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0);\n}\n\nstatic void decodeIndexRejectInvalidVersion()\n{\n\tconst size_t index_count = sizeof(kIndexBuffer) / sizeof(kIndexBuffer[0]);\n\tconst size_t vertex_count = 10;\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count));\n\tbuffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBuffer, index_count));\n\n\t// check that decoder doesn't accept invalid version\n\tstd::vector<unsigned char> brokenbuffer(buffer);\n\tbrokenbuffer[0] |= 0x0f;\n\n\tunsigned int decoded[index_count];\n\tassert(meshopt_decodeIndexBuffer(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0);\n}\n\nstatic void decodeIndexMalformedVByte()\n{\n\tconst unsigned char input[] = {\n\t    0xe1, 0x20, 0x20, 0x20, 0xff, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n\t    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n\t    0xff, 0xff, 0xff, 0xff, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,\n\t    0x20, 0x20, 0x20, // clang-format :-/\n\t};\n\n\tunsigned int decoded[66];\n\tassert(meshopt_decodeIndexBuffer(decoded, 66, input, sizeof(input)) < 0);\n}\n\nstatic void roundtripIndexTricky()\n{\n\tconst size_t index_count = sizeof(kIndexBufferTricky) / sizeof(kIndexBufferTricky[0]);\n\tconst size_t vertex_count = 6;\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexBufferBound(index_count, vertex_count));\n\tbuffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), kIndexBufferTricky, index_count));\n\n\tunsigned int decoded[index_count];\n\tassert(meshopt_decodeIndexBuffer(decoded, index_count, &buffer[0], buffer.size()) == 0);\n\tassert(memcmp(decoded, kIndexBufferTricky, sizeof(kIndexBufferTricky)) == 0);\n}\n\nstatic void encodeIndexEmpty()\n{\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexBufferBound(0, 0));\n\tbuffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), NULL, 0));\n\n\tassert(meshopt_decodeIndexBuffer(static_cast<unsigned int*>(NULL), 0, &buffer[0], buffer.size()) == 0);\n}\n\nstatic void decodeIndexSequence()\n{\n\tconst size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]);\n\n\tstd::vector<unsigned char> buffer(kIndexSequenceV1, kIndexSequenceV1 + sizeof(kIndexSequenceV1));\n\n\tunsigned int decoded[index_count];\n\tassert(meshopt_decodeIndexSequence(decoded, index_count, &buffer[0], buffer.size()) == 0);\n\tassert(memcmp(decoded, kIndexSequence, sizeof(kIndexSequence)) == 0);\n}\n\nstatic void decodeIndexSequence16()\n{\n\tconst size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]);\n\tconst size_t vertex_count = 1001;\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count));\n\tbuffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count));\n\n\tunsigned short decoded[index_count];\n\tassert(meshopt_decodeIndexSequence(decoded, index_count, &buffer[0], buffer.size()) == 0);\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t\tassert(decoded[i] == kIndexSequence[i]);\n}\n\nstatic void encodeIndexSequenceMemorySafe()\n{\n\tconst size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]);\n\tconst size_t vertex_count = 1001;\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count));\n\tbuffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count));\n\n\t// check that encode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access\n\tfor (size_t i = 0; i <= buffer.size(); ++i)\n\t{\n\t\tstd::vector<unsigned char> shortbuffer(i);\n\t\tsize_t result = meshopt_encodeIndexSequence(i == 0 ? NULL : &shortbuffer[0], i, kIndexSequence, index_count);\n\n\t\tif (i == buffer.size())\n\t\t\tassert(result == buffer.size());\n\t\telse\n\t\t\tassert(result == 0);\n\t}\n}\n\nstatic void decodeIndexSequenceMemorySafe()\n{\n\tconst size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]);\n\tconst size_t vertex_count = 1001;\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count));\n\tbuffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count));\n\n\t// check that decode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access\n\tunsigned int decoded[index_count];\n\n\tfor (size_t i = 0; i <= buffer.size(); ++i)\n\t{\n\t\tstd::vector<unsigned char> shortbuffer(buffer.begin(), buffer.begin() + i);\n\t\tint result = meshopt_decodeIndexSequence(decoded, index_count, i == 0 ? NULL : &shortbuffer[0], i);\n\n\t\tif (i == buffer.size())\n\t\t\tassert(result == 0);\n\t\telse\n\t\t\tassert(result < 0);\n\t}\n}\n\nstatic void decodeIndexSequenceRejectExtraBytes()\n{\n\tconst size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]);\n\tconst size_t vertex_count = 1001;\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count));\n\tbuffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count));\n\n\t// check that decoder doesn't accept extra bytes after a valid stream\n\tstd::vector<unsigned char> largebuffer(buffer);\n\tlargebuffer.push_back(0);\n\n\tunsigned int decoded[index_count];\n\tassert(meshopt_decodeIndexSequence(decoded, index_count, &largebuffer[0], largebuffer.size()) < 0);\n}\n\nstatic void decodeIndexSequenceRejectMalformedHeaders()\n{\n\tconst size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]);\n\tconst size_t vertex_count = 1001;\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count));\n\tbuffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count));\n\n\t// check that decoder doesn't accept malformed headers\n\tstd::vector<unsigned char> brokenbuffer(buffer);\n\tbrokenbuffer[0] = 0;\n\n\tunsigned int decoded[index_count];\n\tassert(meshopt_decodeIndexSequence(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0);\n}\n\nstatic void decodeIndexSequenceRejectInvalidVersion()\n{\n\tconst size_t index_count = sizeof(kIndexSequence) / sizeof(kIndexSequence[0]);\n\tconst size_t vertex_count = 1001;\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexSequenceBound(index_count, vertex_count));\n\tbuffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), kIndexSequence, index_count));\n\n\t// check that decoder doesn't accept invalid version\n\tstd::vector<unsigned char> brokenbuffer(buffer);\n\tbrokenbuffer[0] |= 0x0f;\n\n\tunsigned int decoded[index_count];\n\tassert(meshopt_decodeIndexSequence(decoded, index_count, &brokenbuffer[0], brokenbuffer.size()) < 0);\n}\n\nstatic void encodeIndexSequenceEmpty()\n{\n\tstd::vector<unsigned char> buffer(meshopt_encodeIndexSequenceBound(0, 0));\n\tbuffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), NULL, 0));\n\n\tassert(meshopt_decodeIndexSequence(static_cast<unsigned int*>(NULL), 0, &buffer[0], buffer.size()) == 0);\n}\n\nstatic void decodeVertexV0()\n{\n\tconst size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]);\n\n\tstd::vector<unsigned char> buffer(kVertexDataV0, kVertexDataV0 + sizeof(kVertexDataV0));\n\n\tPV decoded[vertex_count];\n\tassert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &buffer[0], buffer.size()) == 0);\n\tassert(memcmp(decoded, kVertexBuffer, sizeof(kVertexBuffer)) == 0);\n}\n\nstatic void decodeVertexV0More()\n{\n\tconst unsigned char expected[] = {\n\t    0, 0, 0, 0, 0, 1, 2, 8, 0, 2, 4, 16, 0, 3, 6, 24,\n\t    0, 4, 8, 32, 0, 5, 10, 40, 0, 6, 12, 48, 0, 7, 14, 56,\n\t    0, 8, 16, 64, 0, 9, 18, 72, 0, 10, 20, 80, 0, 11, 22, 88,\n\t    0, 12, 24, 96, 0, 13, 26, 104, 0, 14, 28, 112, 0, 15, 30, 120, // clang-format :-/\n\t};\n\n\tconst unsigned char input[] = {\n\t    0xa0, 0x00, 0x01, 0x2a, 0xaa, 0xaa, 0xaa, 0x02, 0x04, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,\n\t    0x03, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,\n\t    0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t    0x00, // clang-format :-/\n\t};\n\n\tunsigned char decoded[sizeof(expected)];\n\tassert(meshopt_decodeVertexBuffer(decoded, 16, 4, input, sizeof(input)) == 0);\n\tassert(memcmp(decoded, expected, sizeof(expected)) == 0);\n}\n\nstatic void decodeVertexV0Mode2()\n{\n\tconst unsigned char expected[] = {\n\t    0, 0, 0, 0, 4, 5, 6, 7, 8, 10, 12, 14, 12, 15, 18, 21,\n\t    16, 20, 24, 28, 20, 25, 30, 35, 24, 30, 36, 42, 28, 35, 42, 49,\n\t    32, 40, 48, 56, 36, 45, 54, 63, 40, 50, 60, 70, 44, 55, 66, 77,\n\t    48, 60, 72, 84, 52, 65, 78, 91, 56, 70, 84, 98, 60, 75, 90, 105, // clang-format :-/\n\t};\n\n\tconst unsigned char input[] = {\n\t    0xa0, 0x02, 0x08, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x02, 0x0a, 0xaa, 0xaa, 0xaa, 0xaa,\n\t    0xaa, 0xaa, 0xaa, 0x02, 0x0c, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x02, 0x0e, 0xee, 0xee,\n\t    0xee, 0xee, 0xee, 0xee, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t    0x00, 0x00, 0x00, 0x00, 0x00, // clang-format :-/\n\t};\n\n\tunsigned char decoded[sizeof(expected)];\n\tassert(meshopt_decodeVertexBuffer(decoded, 16, 4, input, sizeof(input)) == 0);\n\tassert(memcmp(decoded, expected, sizeof(expected)) == 0);\n}\n\nstatic void decodeVertexV1()\n{\n\tconst size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]);\n\n\tstd::vector<unsigned char> buffer(kVertexDataV1, kVertexDataV1 + sizeof(kVertexDataV1));\n\n\tPV decoded[vertex_count];\n\tassert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &buffer[0], buffer.size()) == 0);\n\tassert(memcmp(decoded, kVertexBuffer, sizeof(kVertexBuffer)) == 0);\n}\n\nstatic void decodeVertexV1Custom()\n{\n\tconst size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]);\n\n\tstd::vector<unsigned char> buffer(kVertexDataV1Custom, kVertexDataV1Custom + sizeof(kVertexDataV1Custom));\n\n\tPV decoded[vertex_count];\n\tassert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &buffer[0], buffer.size()) == 0);\n\tassert(memcmp(decoded, kVertexBuffer, sizeof(kVertexBuffer)) == 0);\n}\n\nstatic void decodeVertexV1Deltas()\n{\n\tconst unsigned short expected[] = {\n\t    248, 248, 240, 240, 249, 250, 243, 244, 250, 252, 246, 248, 251, 254, 249, 252,\n\t    252, 256, 252, 256, 253, 258, 255, 260, 254, 260, 258, 264, 255, 262, 261, 268,\n\t    256, 264, 264, 272, 257, 262, 267, 268, 258, 260, 270, 264, 259, 258, 273, 260,\n\t    260, 256, 276, 256, 261, 254, 279, 252, 262, 252, 282, 248, 263, 250, 285, 244, // clang-format :-/\n\t};\n\n\tconst unsigned char input[] = {\n\t    0xa1, 0x99, 0x99, 0x01, 0x2a, 0xaa, 0xaa, 0xaa, 0x02, 0x04, 0x44, 0x44, 0x44, 0x43, 0x33, 0x33,\n\t    0x33, 0x02, 0x06, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x02, 0x08, 0x88, 0x88, 0x88, 0x87,\n\t    0x77, 0x77, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t    0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x01, 0x01, // clang-format :-/\n\t};\n\n\tunsigned short decoded[sizeof(expected) / sizeof(expected[0])];\n\tassert(meshopt_decodeVertexBuffer(decoded, 16, 8, input, sizeof(input)) == 0);\n\tassert(memcmp(decoded, expected, sizeof(expected)) == 0);\n}\n\nstatic void encodeVertexMemorySafe()\n{\n\tconst size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]);\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV)));\n\tbuffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV)));\n\n\t// check that encode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access\n\tfor (size_t i = 0; i <= buffer.size(); ++i)\n\t{\n\t\tstd::vector<unsigned char> shortbuffer(i);\n\t\tsize_t result = meshopt_encodeVertexBuffer(i == 0 ? NULL : &shortbuffer[0], i, kVertexBuffer, vertex_count, sizeof(PV));\n\n\t\tif (i == buffer.size())\n\t\t\tassert(result == buffer.size());\n\t\telse\n\t\t\tassert(result == 0);\n\t}\n}\n\nstatic void decodeVertexMemorySafe()\n{\n\tconst size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]);\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV)));\n\tbuffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV)));\n\n\t// check that decode is memory-safe; note that we reallocate the buffer for each try to make sure ASAN can verify buffer access\n\tPV decoded[vertex_count];\n\n\tfor (size_t i = 0; i <= buffer.size(); ++i)\n\t{\n\t\tstd::vector<unsigned char> shortbuffer(buffer.begin(), buffer.begin() + i);\n\t\tint result = meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), i == 0 ? NULL : &shortbuffer[0], i);\n\t\t(void)result;\n\n\t\tif (i == buffer.size())\n\t\t\tassert(result == 0);\n\t\telse\n\t\t\tassert(result < 0);\n\t}\n}\n\nstatic void decodeVertexRejectExtraBytes()\n{\n\tconst size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]);\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV)));\n\tbuffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV)));\n\n\t// check that decoder doesn't accept extra bytes after a valid stream\n\tstd::vector<unsigned char> largebuffer(buffer);\n\tlargebuffer.push_back(0);\n\n\tPV decoded[vertex_count];\n\tassert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &largebuffer[0], largebuffer.size()) < 0);\n}\n\nstatic void decodeVertexRejectMalformedHeaders()\n{\n\tconst size_t vertex_count = sizeof(kVertexBuffer) / sizeof(kVertexBuffer[0]);\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeVertexBufferBound(vertex_count, sizeof(PV)));\n\tbuffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), kVertexBuffer, vertex_count, sizeof(PV)));\n\n\t// check that decoder doesn't accept malformed headers\n\tstd::vector<unsigned char> brokenbuffer(buffer);\n\tbrokenbuffer[0] = 0;\n\n\tPV decoded[vertex_count];\n\tassert(meshopt_decodeVertexBuffer(decoded, vertex_count, sizeof(PV), &brokenbuffer[0], brokenbuffer.size()) < 0);\n}\n\nstatic void decodeVertexBitGroups()\n{\n\tunsigned char data[16 * 4];\n\n\t// this tests 0/2/4/8 bit groups in one stream\n\tfor (size_t i = 0; i < 16; ++i)\n\t{\n\t\tdata[i * 4 + 0] = 0;\n\t\tdata[i * 4 + 1] = (unsigned char)(i * 1);\n\t\tdata[i * 4 + 2] = (unsigned char)(i * 2);\n\t\tdata[i * 4 + 3] = (unsigned char)(i * 8);\n\t}\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeVertexBufferBound(16, 4));\n\tbuffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 16, 4));\n\n\tunsigned char decoded[16 * 4];\n\tassert(meshopt_decodeVertexBuffer(decoded, 16, 4, &buffer[0], buffer.size()) == 0);\n\tassert(memcmp(decoded, data, sizeof(data)) == 0);\n}\n\nstatic void decodeVertexBitGroupSentinels()\n{\n\tunsigned char data[16 * 4];\n\n\t// this tests 0/2/4/8 bit groups and sentinels in one stream\n\tfor (size_t i = 0; i < 16; ++i)\n\t{\n\t\tif (i == 7 || i == 13)\n\t\t{\n\t\t\tdata[i * 4 + 0] = 42;\n\t\t\tdata[i * 4 + 1] = 42;\n\t\t\tdata[i * 4 + 2] = 42;\n\t\t\tdata[i * 4 + 3] = 42;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdata[i * 4 + 0] = 0;\n\t\t\tdata[i * 4 + 1] = (unsigned char)(i * 1);\n\t\t\tdata[i * 4 + 2] = (unsigned char)(i * 2);\n\t\t\tdata[i * 4 + 3] = (unsigned char)(i * 8);\n\t\t}\n\t}\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeVertexBufferBound(16, 4));\n\tbuffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 16, 4));\n\n\tunsigned char decoded[16 * 4];\n\tassert(meshopt_decodeVertexBuffer(decoded, 16, 4, &buffer[0], buffer.size()) == 0);\n\tassert(memcmp(decoded, data, sizeof(data)) == 0);\n}\n\nstatic void decodeVertexDeltas()\n{\n\tunsigned short data[16 * 4];\n\n\t// this forces wider deltas by using values that cross byte boundary\n\tfor (size_t i = 0; i < 16; ++i)\n\t{\n\t\tdata[i * 4 + 0] = (unsigned short)(0xf8 + i * 1);\n\t\tdata[i * 4 + 1] = (unsigned short)(0xf8 + (i < 8 ? i : 16 - i) * 2);\n\t\tdata[i * 4 + 2] = (unsigned short)(0xf0 + i * 3);\n\t\tdata[i * 4 + 3] = (unsigned short)(0xf0 + (i < 8 ? i : 16 - i) * 4);\n\t}\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeVertexBufferBound(16, 8));\n\tbuffer.resize(meshopt_encodeVertexBufferLevel(&buffer[0], buffer.size(), data, 16, 8, 2, -1));\n\n\tunsigned short decoded[16 * 4];\n\tassert(meshopt_decodeVertexBuffer(decoded, 16, 8, &buffer[0], buffer.size()) == 0);\n\tassert(memcmp(decoded, data, sizeof(data)) == 0);\n}\n\nstatic void decodeVertexBitXor()\n{\n\tunsigned int data[16 * 4];\n\n\t// this forces xors by using bit values at an offset\n\tfor (size_t i = 0; i < 16; ++i)\n\t{\n\t\tdata[i * 4 + 0] = unsigned(i << 0);\n\t\tdata[i * 4 + 1] = unsigned(i << 2);\n\t\tdata[i * 4 + 2] = unsigned(i << 15);\n\t\tdata[i * 4 + 3] = unsigned(i << 28);\n\t}\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeVertexBufferBound(16, 16));\n\tbuffer.resize(meshopt_encodeVertexBufferLevel(&buffer[0], buffer.size(), data, 16, 16, 3, -1));\n\n\tunsigned int decoded[16 * 4];\n\tassert(meshopt_decodeVertexBuffer(decoded, 16, 16, &buffer[0], buffer.size()) == 0);\n\tassert(memcmp(decoded, data, sizeof(data)) == 0);\n}\n\nstatic void decodeVertexLarge()\n{\n\tunsigned char data[128 * 4];\n\n\t// this tests 0/2/4/8 bit groups in one stream\n\tfor (size_t i = 0; i < 128; ++i)\n\t{\n\t\tdata[i * 4 + 0] = 0;\n\t\tdata[i * 4 + 1] = (unsigned char)(i * 1);\n\t\tdata[i * 4 + 2] = (unsigned char)(i * 2);\n\t\tdata[i * 4 + 3] = (unsigned char)(i * 8);\n\t}\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeVertexBufferBound(128, 4));\n\tbuffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 128, 4));\n\n\tunsigned char decoded[128 * 4];\n\tassert(meshopt_decodeVertexBuffer(decoded, 128, 4, &buffer[0], buffer.size()) == 0);\n\tassert(memcmp(decoded, data, sizeof(data)) == 0);\n}\n\nstatic void decodeVertexSmall()\n{\n\tunsigned char data[13 * 4];\n\n\t// this tests 0/2/4/8 bit groups in one stream\n\tfor (size_t i = 0; i < 13; ++i)\n\t{\n\t\tdata[i * 4 + 0] = 0;\n\t\tdata[i * 4 + 1] = (unsigned char)(i * 1);\n\t\tdata[i * 4 + 2] = (unsigned char)(i * 2);\n\t\tdata[i * 4 + 3] = (unsigned char)(i * 8);\n\t}\n\n\tstd::vector<unsigned char> buffer(meshopt_encodeVertexBufferBound(13, 4));\n\tbuffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), data, 13, 4));\n\n\tunsigned char decoded[13 * 4];\n\tassert(meshopt_decodeVertexBuffer(decoded, 13, 4, &buffer[0], buffer.size()) == 0);\n\tassert(memcmp(decoded, data, sizeof(data)) == 0);\n}\n\nstatic void encodeVertexEmpty()\n{\n\tstd::vector<unsigned char> buffer(meshopt_encodeVertexBufferBound(0, 16));\n\tbuffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), NULL, 0, 16));\n\n\tassert(meshopt_decodeVertexBuffer(NULL, 0, 16, &buffer[0], buffer.size()) == 0);\n}\n\nstatic void decodeVersion()\n{\n\tassert(meshopt_decodeVertexVersion(reinterpret_cast<const unsigned char*>(\"\\xa0\"), 1) == 0);\n\tassert(meshopt_decodeVertexVersion(reinterpret_cast<const unsigned char*>(\"\\xa1\"), 1) == 1);\n\tassert(meshopt_decodeVertexVersion(reinterpret_cast<const unsigned char*>(\"\\xa1hello\"), 6) == 1);\n\n\tassert(meshopt_decodeVertexVersion(NULL, 0) == -1);\n\tassert(meshopt_decodeVertexVersion(reinterpret_cast<const unsigned char*>(\"\\xa7\"), 1) == -1);\n\tassert(meshopt_decodeVertexVersion(reinterpret_cast<const unsigned char*>(\"\\xb1\"), 1) == -1);\n\n\tassert(meshopt_decodeIndexVersion(reinterpret_cast<const unsigned char*>(\"\\xe0\"), 1) == 0);\n\tassert(meshopt_decodeIndexVersion(reinterpret_cast<const unsigned char*>(\"\\xd1\"), 1) == 1);\n\tassert(meshopt_decodeIndexVersion(reinterpret_cast<const unsigned char*>(\"\\xe1hello\"), 6) == 1);\n\n\tassert(meshopt_decodeIndexVersion(NULL, 0) == -1);\n\tassert(meshopt_decodeIndexVersion(reinterpret_cast<const unsigned char*>(\"\\xa7\"), 1) == -1);\n\tassert(meshopt_decodeIndexVersion(reinterpret_cast<const unsigned char*>(\"\\xa1\"), 1) == -1);\n}\n\nstatic void decodeFilterOct8()\n{\n\tconst unsigned char data[4 * 4] = {\n\t    0, 1, 127, 0,\n\t    0, 187, 127, 1,\n\t    255, 1, 127, 0,\n\t    14, 130, 127, 1, // clang-format :-/\n\t};\n\n\tconst unsigned char expected[4 * 4] = {\n\t    0, 1, 127, 0,\n\t    0, 159, 82, 1,\n\t    255, 1, 127, 0,\n\t    1, 130, 241, 1, // clang-format :-/\n\t};\n\n\t// Aligned by 4\n\tunsigned char full[4 * 4];\n\tmemcpy(full, data, sizeof(full));\n\tmeshopt_decodeFilterOct(full, 4, 4);\n\tassert(memcmp(full, expected, sizeof(full)) == 0);\n\n\t// Tail processing for unaligned data\n\tunsigned char tail[3 * 4];\n\tmemcpy(tail, data, sizeof(tail));\n\tmeshopt_decodeFilterOct(tail, 3, 4);\n\tassert(memcmp(tail, expected, sizeof(tail)) == 0);\n}\n\nstatic void decodeFilterOct12()\n{\n\tconst unsigned short data[4 * 4] = {\n\t    0, 1, 2047, 0,\n\t    0, 1870, 2047, 1,\n\t    2017, 1, 2047, 0,\n\t    14, 1300, 2047, 1, // clang-format :-/\n\t};\n\n\tconst unsigned short expected[4 * 4] = {\n\t    0, 16, 32767, 0,\n\t    0, 32621, 3088, 1,\n\t    32764, 16, 471, 0,\n\t    307, 28541, 16093, 1, // clang-format :-/\n\t};\n\n\t// Aligned by 4\n\tunsigned short full[4 * 4];\n\tmemcpy(full, data, sizeof(full));\n\tmeshopt_decodeFilterOct(full, 4, 8);\n\tassert(memcmp(full, expected, sizeof(full)) == 0);\n\n\t// Tail processing for unaligned data\n\tunsigned short tail[3 * 4];\n\tmemcpy(tail, data, sizeof(tail));\n\tmeshopt_decodeFilterOct(tail, 3, 8);\n\tassert(memcmp(tail, expected, sizeof(tail)) == 0);\n}\n\nstatic void decodeFilterQuat12()\n{\n\tconst unsigned short data[4 * 4] = {\n\t    0, 1, 0, 0x7fc,\n\t    0, 1870, 0, 0x7fd,\n\t    2017, 1, 0, 0x7fe,\n\t    14, 1300, 0, 0x7ff, // clang-format :-/\n\t};\n\n\tconst unsigned short expected[4 * 4] = {\n\t    32767, 0, 11, 0,\n\t    0, 25013, 0, 21166,\n\t    11, 0, 23504, 22830,\n\t    158, 14715, 0, 29277, // clang-format :-/\n\t};\n\n\t// Aligned by 4\n\tunsigned short full[4 * 4];\n\tmemcpy(full, data, sizeof(full));\n\tmeshopt_decodeFilterQuat(full, 4, 8);\n\tassert(memcmp(full, expected, sizeof(full)) == 0);\n\n\t// Tail processing for unaligned data\n\tunsigned short tail[3 * 4];\n\tmemcpy(tail, data, sizeof(tail));\n\tmeshopt_decodeFilterQuat(tail, 3, 8);\n\tassert(memcmp(tail, expected, sizeof(tail)) == 0);\n}\n\nstatic void decodeFilterExp()\n{\n\tconst unsigned int data[4] = {\n\t    0,\n\t    0xff000003,\n\t    0x02fffff7,\n\t    0xfe7fffff, // clang-format :-/\n\t};\n\n\tconst unsigned int expected[4] = {\n\t    0,\n\t    0x3fc00000,\n\t    0xc2100000,\n\t    0x49fffffe, // clang-format :-/\n\t};\n\n\t// Aligned by 4\n\tunsigned int full[4];\n\tmemcpy(full, data, sizeof(full));\n\tmeshopt_decodeFilterExp(full, 4, 4);\n\tassert(memcmp(full, expected, sizeof(full)) == 0);\n\n\t// Tail processing for unaligned data\n\tunsigned int tail[3];\n\tmemcpy(tail, data, sizeof(tail));\n\tmeshopt_decodeFilterExp(tail, 3, 4);\n\tassert(memcmp(tail, expected, sizeof(tail)) == 0);\n}\n\nstatic void encodeFilterOct8()\n{\n\tconst float data[4 * 4] = {\n\t    1, 0, 0, 0,\n\t    0, -1, 0, 0,\n\t    0.7071068f, 0, 0.707168f, 1,\n\t    -0.7071068f, 0, -0.707168f, 1, // clang-format :-/\n\t};\n\n\tconst unsigned char expected[4 * 4] = {\n\t    0x7f, 0, 0x7f, 0,\n\t    0, 0x81, 0x7f, 0,\n\t    0x3f, 0, 0x7f, 0x7f,\n\t    0x81, 0x40, 0x7f, 0x7f, // clang-format :-/\n\t};\n\n\tunsigned char encoded[4 * 4];\n\tmeshopt_encodeFilterOct(encoded, 4, 4, 8, data);\n\n\tassert(memcmp(encoded, expected, sizeof(expected)) == 0);\n\n\tsigned char decoded[4 * 4];\n\tmemcpy(decoded, encoded, sizeof(decoded));\n\tmeshopt_decodeFilterOct(decoded, 4, 4);\n\n\tfor (size_t i = 0; i < 4 * 4; ++i)\n\t\tassert(fabsf(decoded[i] / 127.f - data[i]) < 1e-2f);\n}\n\nstatic void encodeFilterOct12()\n{\n\tconst float data[4 * 4] = {\n\t    1, 0, 0, 0,\n\t    0, -1, 0, 0,\n\t    0.7071068f, 0, 0.707168f, 1,\n\t    -0.7071068f, 0, -0.707168f, 1, // clang-format :-/\n\t};\n\n\tconst unsigned short expected[4 * 4] = {\n\t    0x7ff, 0, 0x7ff, 0,\n\t    0x0, 0xf801, 0x7ff, 0,\n\t    0x3ff, 0, 0x7ff, 0x7fff,\n\t    0xf801, 0x400, 0x7ff, 0x7fff, // clang-format :-/\n\t};\n\n\tunsigned short encoded[4 * 4];\n\tmeshopt_encodeFilterOct(encoded, 4, 8, 12, data);\n\n\tassert(memcmp(encoded, expected, sizeof(expected)) == 0);\n\n\tshort decoded[4 * 4];\n\tmemcpy(decoded, encoded, sizeof(decoded));\n\tmeshopt_decodeFilterOct(decoded, 4, 8);\n\n\tfor (size_t i = 0; i < 4 * 4; ++i)\n\t\tassert(fabsf(decoded[i] / 32767.f - data[i]) < 1e-3f);\n}\n\nstatic void encodeFilterQuat12()\n{\n\tconst float data[4 * 4] = {\n\t    1, 0, 0, 0,\n\t    0, -1, 0, 0,\n\t    0.7071068f, 0, 0, 0.707168f,\n\t    -0.7071068f, 0, 0, -0.707168f, // clang-format :-/\n\t};\n\n\tconst unsigned short expected[4 * 4] = {\n\t    0, 0, 0, 0x7fc,\n\t    0, 0, 0, 0x7fd,\n\t    0x7ff, 0, 0, 0x7ff,\n\t    0x7ff, 0, 0, 0x7ff, // clang-format :-/\n\t};\n\n\tunsigned short encoded[4 * 4];\n\tmeshopt_encodeFilterQuat(encoded, 4, 8, 12, data);\n\n\tassert(memcmp(encoded, expected, sizeof(expected)) == 0);\n\n\tshort decoded[4 * 4];\n\tmemcpy(decoded, encoded, sizeof(decoded));\n\tmeshopt_decodeFilterQuat(decoded, 4, 8);\n\n\tfor (size_t i = 0; i < 4; ++i)\n\t{\n\t\tfloat dx = decoded[i * 4 + 0] / 32767.f;\n\t\tfloat dy = decoded[i * 4 + 1] / 32767.f;\n\t\tfloat dz = decoded[i * 4 + 2] / 32767.f;\n\t\tfloat dw = decoded[i * 4 + 3] / 32767.f;\n\n\t\tfloat dp =\n\t\t    data[i * 4 + 0] * dx +\n\t\t    data[i * 4 + 1] * dy +\n\t\t    data[i * 4 + 2] * dz +\n\t\t    data[i * 4 + 3] * dw;\n\n\t\tassert(fabsf(fabsf(dp) - 1.f) < 1e-4f);\n\t}\n}\n\nstatic void encodeFilterExp()\n{\n\tconst float data[4] = {\n\t    1,\n\t    -23.4f,\n\t    -0.1f,\n\t    11.0f,\n\t};\n\n\t// separate exponents: each component gets its own value\n\tconst unsigned int expected1[4] = {\n\t    0xf3002000,\n\t    0xf7ffd133,\n\t    0xefffcccd,\n\t    0xf6002c00,\n\t};\n\n\t// shared exponents (vector): all components of each vector get the same value\n\tconst unsigned int expected2[4] = {\n\t    0xf7000200,\n\t    0xf7ffd133,\n\t    0xf6ffff9a,\n\t    0xf6002c00,\n\t};\n\n\t// shared exponents (component): each component gets the same value across all vectors\n\tconst unsigned int expected3[4] = {\n\t    0xf3002000,\n\t    0xf7ffd133,\n\t    0xf3fffccd,\n\t    0xf7001600,\n\t};\n\n\tunsigned int encoded1[4];\n\tmeshopt_encodeFilterExp(encoded1, 2, 8, 15, data, meshopt_EncodeExpSeparate);\n\n\tunsigned int encoded2[4];\n\tmeshopt_encodeFilterExp(encoded2, 2, 8, 15, data, meshopt_EncodeExpSharedVector);\n\n\tunsigned int encoded3[4];\n\tmeshopt_encodeFilterExp(encoded3, 2, 8, 15, data, meshopt_EncodeExpSharedComponent);\n\n\tassert(memcmp(encoded1, expected1, sizeof(expected1)) == 0);\n\tassert(memcmp(encoded2, expected2, sizeof(expected2)) == 0);\n\tassert(memcmp(encoded3, expected3, sizeof(expected3)) == 0);\n\n\tfloat decoded1[4];\n\tmemcpy(decoded1, encoded1, sizeof(decoded1));\n\tmeshopt_decodeFilterExp(decoded1, 2, 8);\n\n\tfloat decoded2[4];\n\tmemcpy(decoded2, encoded2, sizeof(decoded2));\n\tmeshopt_decodeFilterExp(decoded2, 2, 8);\n\n\tfloat decoded3[4];\n\tmemcpy(decoded3, encoded3, sizeof(decoded3));\n\tmeshopt_decodeFilterExp(decoded3, 2, 8);\n\n\tfor (size_t i = 0; i < 4; ++i)\n\t{\n\t\tassert(fabsf(decoded1[i] - data[i]) < 1e-3f);\n\t\tassert(fabsf(decoded2[i] - data[i]) < 1e-3f);\n\t\tassert(fabsf(decoded3[i] - data[i]) < 1e-3f);\n\t}\n}\n\nstatic void encodeFilterExpZero()\n{\n\tconst float data[4] = {\n\t    0.f,\n\t    -0.f,\n\t    1.1754944e-38f,\n\t    -1.1754944e-38f,\n\t};\n\tconst unsigned int expected[4] = {\n\t    0xf2000000,\n\t    0xf2000000,\n\t    0x8e000000,\n\t    0x8e000000,\n\t};\n\n\tunsigned int encoded[4];\n\tmeshopt_encodeFilterExp(encoded, 4, 4, 15, data, meshopt_EncodeExpSeparate);\n\n\tassert(memcmp(encoded, expected, sizeof(expected)) == 0);\n\n\tfloat decoded[4];\n\tmemcpy(decoded, encoded, sizeof(decoded));\n\tmeshopt_decodeFilterExp(&decoded, 4, 4);\n\n\tfor (size_t i = 0; i < 4; ++i)\n\t\tassert(decoded[i] == 0);\n}\n\nstatic void encodeFilterExpAlias()\n{\n\tconst float data[4] = {\n\t    1,\n\t    -23.4f,\n\t    -0.1f,\n\t    11.0f,\n\t};\n\n\t// separate exponents: each component gets its own value\n\tconst unsigned int expected1[4] = {\n\t    0xf3002000,\n\t    0xf7ffd133,\n\t    0xefffcccd,\n\t    0xf6002c00,\n\t};\n\n\t// shared exponents (vector): all components of each vector get the same value\n\tconst unsigned int expected2[4] = {\n\t    0xf7000200,\n\t    0xf7ffd133,\n\t    0xf6ffff9a,\n\t    0xf6002c00,\n\t};\n\n\t// shared exponents (component): each component gets the same value across all vectors\n\tconst unsigned int expected3[4] = {\n\t    0xf3002000,\n\t    0xf7ffd133,\n\t    0xf3fffccd,\n\t    0xf7001600,\n\t};\n\n\tunsigned int encoded1[4];\n\tmemcpy(encoded1, data, sizeof(data));\n\tmeshopt_encodeFilterExp(encoded1, 2, 8, 15, reinterpret_cast<float*>(encoded1), meshopt_EncodeExpSeparate);\n\n\tunsigned int encoded2[4];\n\tmemcpy(encoded2, data, sizeof(data));\n\tmeshopt_encodeFilterExp(encoded2, 2, 8, 15, reinterpret_cast<float*>(encoded2), meshopt_EncodeExpSharedVector);\n\n\tunsigned int encoded3[4];\n\tmemcpy(encoded3, data, sizeof(data));\n\tmeshopt_encodeFilterExp(encoded3, 2, 8, 15, reinterpret_cast<float*>(encoded3), meshopt_EncodeExpSharedComponent);\n\n\tassert(memcmp(encoded1, expected1, sizeof(expected1)) == 0);\n\tassert(memcmp(encoded2, expected2, sizeof(expected2)) == 0);\n\tassert(memcmp(encoded3, expected3, sizeof(expected3)) == 0);\n}\n\nstatic void encodeFilterExpClamp()\n{\n\tconst float data[4] = {\n\t    1,\n\t    -23.4f,\n\t    -0.1f,\n\t    11.0f,\n\t};\n\n\t// separate exponents: each component gets its own value\n\t// note: third value is exponent clamped\n\tconst unsigned int expected[4] = {\n\t    0xf3002000,\n\t    0xf7ffd133,\n\t    0xf2fff99a,\n\t    0xf6002c00,\n\t};\n\n\tunsigned int encoded[4];\n\tmeshopt_encodeFilterExp(encoded, 2, 8, 15, data, meshopt_EncodeExpClamped);\n\n\tassert(memcmp(encoded, expected, sizeof(expected)) == 0);\n\n\tfloat decoded[4];\n\tmemcpy(decoded, encoded, sizeof(decoded));\n\tmeshopt_decodeFilterExp(decoded, 2, 8);\n\n\tfor (size_t i = 0; i < 4; ++i)\n\t\tassert(fabsf(decoded[i] - data[i]) < 1e-3f);\n}\n\nstatic void encodeFilterColor8()\n{\n\tconst float data[4 * 4] = {\n\t    1.0f, 0.0f, 0.0f, 1.0f,\n\t    0.0f, 1.0f, 0.0f, 0.5f,\n\t    0.0f, 0.0f, 1.0f, 0.25f,\n\t    0.4f, 0.4f, 0.4f, 0.75f, // clang-format :-/\n\t};\n\n\tconst unsigned char expected[4 * 4] = {\n\t    0x40, 0x7f, 0xc1, 0xff,\n\t    0x7f, 0x00, 0x7f, 0xc0,\n\t    0x40, 0x81, 0xc0, 0xa0,\n\t    0x66, 0x00, 0x00, 0xdf, // clang-format :-/\n\t};\n\n\tunsigned char encoded[4 * 4];\n\tmeshopt_encodeFilterColor(encoded, 4, 4, 8, data);\n\n\tassert(memcmp(encoded, expected, sizeof(expected)) == 0);\n\n\tunsigned char decoded[4 * 4];\n\tmemcpy(decoded, encoded, sizeof(decoded));\n\tmeshopt_decodeFilterColor(decoded, 4, 4);\n\n\tfor (size_t i = 0; i < 4 * 4; ++i)\n\t\tassert(fabsf(decoded[i] / 255.f - data[i]) < 1e-2f);\n\n\t// ensure grayscale is preserved\n\tassert(decoded[12] == decoded[13] && decoded[12] == decoded[14]);\n}\n\nstatic void encodeFilterColor12()\n{\n\tconst float data[4 * 4] = {\n\t    1.0f, 0.0f, 0.0f, 1.0f,\n\t    0.0f, 1.0f, 0.0f, 0.5f,\n\t    0.0f, 0.0f, 1.0f, 0.25f,\n\t    0.4f, 0.4f, 0.4f, 0.75f, // clang-format :-/\n\t};\n\n\tconst unsigned short expected[4 * 4] = {\n\t    0x0400, 0x07ff, 0xfc01, 0x0fff,\n\t    0x07ff, 0x0000, 0x07ff, 0x0c00,\n\t    0x0400, 0xf801, 0xfc00, 0x0a00,\n\t    0x0666, 0x0000, 0x0000, 0x0dff, // clang-format :-/\n\t};\n\n\tunsigned short encoded[4 * 4];\n\tmeshopt_encodeFilterColor(encoded, 4, 8, 12, data);\n\n\tassert(memcmp(encoded, expected, sizeof(expected)) == 0);\n\n\tunsigned short decoded[4 * 4];\n\tmemcpy(decoded, encoded, sizeof(decoded));\n\tmeshopt_decodeFilterColor(decoded, 4, 8);\n\n\tfor (size_t i = 0; i < 4 * 4; ++i)\n\t\tassert(fabsf(decoded[i] / 65535.f - data[i]) < 1e-3f);\n\n\t// ensure grayscale is preserved\n\tassert(decoded[12] == decoded[13] && decoded[12] == decoded[14]);\n}\n\nstatic void clusterBoundsDegenerate()\n{\n\tconst float vbd[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};\n\tconst unsigned int ibd[] = {0, 0, 0};\n\tconst unsigned int ib1[] = {0, 1, 2};\n\n\t// all of the bounds below are degenerate as they use 0 triangles, one topology-degenerate triangle and one position-degenerate triangle respectively\n\tmeshopt_Bounds bounds0 = meshopt_computeClusterBounds(NULL, 0, NULL, 0, 12);\n\tmeshopt_Bounds boundsd = meshopt_computeClusterBounds(ibd, 3, vbd, 3, 12);\n\tmeshopt_Bounds bounds1 = meshopt_computeClusterBounds(ib1, 3, vbd, 3, 12);\n\n\tassert(bounds0.center[0] == 0 && bounds0.center[1] == 0 && bounds0.center[2] == 0 && bounds0.radius == 0);\n\tassert(boundsd.center[0] == 0 && boundsd.center[1] == 0 && boundsd.center[2] == 0 && boundsd.radius == 0);\n\tassert(bounds1.center[0] == 0 && bounds1.center[1] == 0 && bounds1.center[2] == 0 && bounds1.radius == 0);\n\n\tconst float vb1[] = {1, 0, 0, 0, 1, 0, 0, 0, 1};\n\tconst unsigned int ib2[] = {0, 1, 2, 0, 2, 1};\n\n\t// these bounds have a degenerate cone since the cluster has two triangles with opposite normals\n\tmeshopt_Bounds bounds2 = meshopt_computeClusterBounds(ib2, 6, vb1, 3, 12);\n\n\tassert(bounds2.cone_apex[0] == 0 && bounds2.cone_apex[1] == 0 && bounds2.cone_apex[2] == 0);\n\tassert(bounds2.cone_axis[0] == 0 && bounds2.cone_axis[1] == 0 && bounds2.cone_axis[2] == 0);\n\tassert(bounds2.cone_cutoff == 1);\n\tassert(bounds2.cone_axis_s8[0] == 0 && bounds2.cone_axis_s8[1] == 0 && bounds2.cone_axis_s8[2] == 0);\n\tassert(bounds2.cone_cutoff_s8 == 127);\n\n\t// however, the bounding sphere needs to be in tact (here we only check bbox for simplicity)\n\tassert(bounds2.center[0] - bounds2.radius <= 0 && bounds2.center[0] + bounds2.radius >= 1);\n\tassert(bounds2.center[1] - bounds2.radius <= 0 && bounds2.center[1] + bounds2.radius >= 1);\n\tassert(bounds2.center[2] - bounds2.radius <= 0 && bounds2.center[2] + bounds2.radius >= 1);\n}\n\nstatic void sphereBounds()\n{\n\tconst float vbr[] = {\n\t    0, 0, 0, 0,\n\t    0, 1, 0, 1,\n\t    0, 0, 1, 2,\n\t    1, 0, 1, 3, // clang-format\n\t};\n\n\t// without the radius, the center is inside the tetrahedron\n\tmeshopt_Bounds bounds = meshopt_computeSphereBounds(vbr, 4, sizeof(float) * 4, NULL, 0);\n\tassert(fabsf(bounds.center[0] - 0.5f) < 1e-2f);\n\tassert(fabsf(bounds.center[1] - 0.5f) < 1e-2f);\n\tassert(fabsf(bounds.center[2] - 0.5f) < 1e-2f);\n\tassert(bounds.radius < 0.87f);\n\n\t// when using the radius, the last sphere envelops the entire set\n\tmeshopt_Bounds boundsr = meshopt_computeSphereBounds(vbr, 4, sizeof(float) * 4, vbr + 3, sizeof(float) * 4);\n\tassert(fabsf(boundsr.center[0] - 1.f) < 1e-2f);\n\tassert(fabsf(boundsr.center[1] - 0.f) < 1e-2f);\n\tassert(fabsf(boundsr.center[2] - 1.f) < 1e-2f);\n\tassert(fabsf(boundsr.radius - 3.f) < 1e-2f);\n}\n\nstatic void meshletsEmpty()\n{\n\tconst float vbd[4 * 3] = {};\n\n\tmeshopt_Meshlet ml[1];\n\tunsigned int mv[4];\n\tunsigned char mt[8];\n\tsize_t mc = meshopt_buildMeshlets(ml, mv, mt, NULL, 0, vbd, 4, sizeof(float) * 3, 64, 64, 0.f);\n\tassert(mc == 0);\n}\n\nstatic void meshletsDense()\n{\n\tconst float vbd[4 * 3] = {};\n\tconst unsigned int ibd[6] = {0, 2, 1, 1, 2, 3};\n\n\tmeshopt_Meshlet ml[1];\n\tunsigned int mv[4];\n\tunsigned char mt[8];\n\tsize_t mc = meshopt_buildMeshlets(ml, mv, mt, ibd, 6, vbd, 4, sizeof(float) * 3, 64, 64, 0.f);\n\n\tassert(mc == 1);\n\tassert(ml[0].triangle_count == 2);\n\tassert(ml[0].vertex_count == 4);\n\n\tunsigned int tri0[3] = {mv[mt[0]], mv[mt[1]], mv[mt[2]]};\n\tunsigned int tri1[3] = {mv[mt[3]], mv[mt[4]], mv[mt[5]]};\n\n\t// technically triangles could also be flipped in the meshlet but for now just assume they aren't\n\tassert(memcmp(tri0, ibd + 0, 3 * sizeof(unsigned int)) == 0);\n\tassert(memcmp(tri1, ibd + 3, 3 * sizeof(unsigned int)) == 0);\n}\n\nstatic void meshletsSparse()\n{\n\tconst float vbd[16 * 3] = {};\n\tconst unsigned int ibd[6] = {0, 7, 15, 15, 7, 3};\n\n\tmeshopt_Meshlet ml[1];\n\tunsigned int mv[4];\n\tunsigned char mt[8];\n\tsize_t mc = meshopt_buildMeshlets(ml, mv, mt, ibd, 6, vbd, 16, sizeof(float) * 3, 64, 64, 0.f);\n\n\tassert(mc == 1);\n\tassert(ml[0].triangle_count == 2);\n\tassert(ml[0].vertex_count == 4);\n\n\tunsigned int tri0[3] = {mv[mt[0]], mv[mt[1]], mv[mt[2]]};\n\tunsigned int tri1[3] = {mv[mt[3]], mv[mt[4]], mv[mt[5]]};\n\n\t// technically triangles could also be flipped in the meshlet but for now just assume they aren't\n\tassert(memcmp(tri0, ibd + 0, 3 * sizeof(unsigned int)) == 0);\n\tassert(memcmp(tri1, ibd + 3, 3 * sizeof(unsigned int)) == 0);\n}\n\nstatic void meshletsFlex()\n{\n\t// two tetrahedrons far apart\n\tfloat vb[2 * 4 * 3] = {\n\t    0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,\n\t    10, 0, 0, 11, 0, 0, 10, 1, 0, 10, 0, 1, // clang-format :-/\n\t};\n\n\tunsigned int ib[2 * 4 * 3] = {\n\t    0, 1, 2, 0, 2, 3, 0, 3, 1, 1, 3, 2,\n\t    4, 5, 6, 4, 6, 7, 4, 7, 5, 5, 7, 6, // clang-format :-/\n\t};\n\n\t// up to 2 meshlets with min_triangles=4\n\tassert(meshopt_buildMeshletsBound(2 * 4 * 3, 16, 4) == 2);\n\n\tmeshopt_Meshlet ml[2];\n\tunsigned int mv[2 * 16];\n\tunsigned char mt[2 * 8 * 3]; // 2 meshlets with up to 8 triangles\n\n\t// with regular function, we should get one meshlet (maxt=8) or two (maxt=4)\n\tassert(meshopt_buildMeshlets(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 16, 8, 0.f) == 1);\n\tassert(ml[0].triangle_count == 8);\n\tassert(ml[0].vertex_count == 8);\n\n\tassert(meshopt_buildMeshlets(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 16, 4, 0.f) == 2);\n\tassert(ml[0].triangle_count == 4);\n\tassert(ml[0].vertex_count == 4);\n\tassert(ml[1].triangle_count == 4);\n\tassert(ml[1].vertex_count == 4);\n\n\t// with flex function and mint=4 maxt=8 we should get one meshlet if split_factor is zero, or large enough to accomodate both\n\tassert(meshopt_buildMeshletsFlex(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 16, 4, 8, 0.f, 0.f) == 1);\n\tassert(ml[0].triangle_count == 8);\n\tassert(ml[0].vertex_count == 8);\n\n\tassert(meshopt_buildMeshletsFlex(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 16, 4, 8, 0.f, 10.f) == 1);\n\tassert(ml[0].triangle_count == 8);\n\tassert(ml[0].vertex_count == 8);\n\n\t// however, with a smaller split factor we should get two meshlets\n\tassert(meshopt_buildMeshletsFlex(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 16, 4, 8, 0.f, 1.f) == 2);\n\tassert(ml[0].triangle_count == 4);\n\tassert(ml[0].vertex_count == 4);\n\tassert(ml[1].triangle_count == 4);\n\tassert(ml[1].vertex_count == 4);\n}\n\nstatic void meshletsMax()\n{\n\tfloat vb[16 * 16 * 3];\n\tunsigned int ib[15 * 15 * 2 * 3];\n\n\t// 16x16 grid of vertices, 15x15 grid of triangles\n\tfor (int y = 0; y < 16; ++y)\n\t\tfor (int x = 0; x < 16; ++x)\n\t\t{\n\t\t\tvb[(y * 16 + x) * 3 + 0] = float(x);\n\t\t\tvb[(y * 16 + x) * 3 + 1] = float(y);\n\t\t\tvb[(y * 16 + x) * 3 + 2] = 0;\n\t\t}\n\n\tfor (int y = 0; y < 15; ++y)\n\t\tfor (int x = 0; x < 15; ++x)\n\t\t{\n\t\t\tib[(y * 15 + x) * 2 * 3 + 0] = (y + 0) * 16 + (x + 0);\n\t\t\tib[(y * 15 + x) * 2 * 3 + 1] = (y + 0) * 16 + (x + 1);\n\t\t\tib[(y * 15 + x) * 2 * 3 + 2] = (y + 1) * 16 + (x + 0);\n\t\t\tib[(y * 15 + x) * 2 * 3 + 3] = (y + 1) * 16 + (x + 0);\n\t\t\tib[(y * 15 + x) * 2 * 3 + 4] = (y + 0) * 16 + (x + 1);\n\t\t\tib[(y * 15 + x) * 2 * 3 + 5] = (y + 1) * 16 + (x + 1);\n\t\t}\n\n\tmeshopt_Meshlet ml[1];\n\tunsigned int mv[16 * 16];\n\tunsigned char mt[15 * 15 * 2 * 3 + 3];\n\n\tsize_t mc = meshopt_buildMeshlets(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 16 * 16, sizeof(float) * 3, 256, 512, 0.f);\n\tassert(mc == 1);\n\tassert(ml[0].triangle_count == 450);\n\tassert(ml[0].vertex_count == 256);\n\n\tmeshopt_optimizeMeshlet(mv, mt, ml[0].triangle_count, ml[0].vertex_count);\n\n\t// check sequential ordering of remapped indices\n\tint vmax = -1;\n\n\tfor (size_t i = 0; i < 450 * 3; ++i)\n\t{\n\t\tassert(mt[i] <= vmax + 1);\n\t\tvmax = vmax < mt[i] ? mt[i] : vmax;\n\t}\n}\n\nstatic void extractMeshlet()\n{\n\t// 15 and 1039 collide in low 10 bits\n\tconst unsigned int indices[] = {0, 7, 15, 1039, 7, 15};\n\n\tunsigned int vertices[4];\n\tunsigned char triangles[6];\n\tsize_t unique = meshopt_extractMeshletIndices(vertices, triangles, indices, 6);\n\tassert(unique == 4);\n\n\t// verify the invariant: vertices[triangles[i]] == indices[i]\n\tfor (size_t i = 0; i < 6; ++i)\n\t\tassert(vertices[triangles[i]] == indices[i]);\n\n\tassert(vertices[0] == 0 && vertices[1] == 7 && vertices[2] == 15 && vertices[3] == 1039);\n\tassert(triangles[0] == 0 && triangles[1] == 1 && triangles[2] == 2 && triangles[3] == 3 && triangles[4] == 1 && triangles[5] == 2);\n}\n\nstatic void meshletsSpatial()\n{\n\t// two tetrahedrons far apart\n\tfloat vb[2 * 4 * 3] = {\n\t    0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,\n\t    10, 0, 0, 11, 0, 0, 10, 1, 0, 10, 0, 1, // clang-format :-/\n\t};\n\n\tunsigned int ib[2 * 4 * 3] = {\n\t    0, 1, 2, 0, 2, 3, 0, 3, 1, 1, 3, 2,\n\t    4, 5, 6, 4, 6, 7, 4, 7, 5, 5, 7, 6, // clang-format :-/\n\t};\n\n\t// up to 2 meshlets with min_triangles=4\n\tassert(meshopt_buildMeshletsBound(2 * 4 * 3, 16, 4) == 2);\n\n\tmeshopt_Meshlet ml[2];\n\tunsigned int mv[2 * 16];\n\tunsigned char mt[2 * 8 * 3]; // 2 meshlets with up to 8 triangles\n\n\t// with strict limits, we should get one meshlet (maxt=8) or two (maxt=4)\n\tassert(meshopt_buildMeshletsSpatial(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 16, 8, 8, 0.f) == 1);\n\tassert(ml[0].triangle_count == 8);\n\tassert(ml[0].vertex_count == 8);\n\n\tassert(meshopt_buildMeshletsSpatial(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 16, 4, 4, 0.f) == 2);\n\tassert(ml[0].triangle_count == 4);\n\tassert(ml[0].vertex_count == 4);\n\tassert(ml[1].triangle_count == 4);\n\tassert(ml[1].vertex_count == 4);\n\n\t// with maxv=4 we should get two meshlets since we can't accomodate both\n\tassert(meshopt_buildMeshletsSpatial(ml, mv, mt, ib, sizeof(ib) / sizeof(ib[0]), vb, 8, sizeof(float) * 3, 4, 4, 8, 0.f) == 2);\n\tassert(ml[0].triangle_count == 4);\n\tassert(ml[0].vertex_count == 4);\n\tassert(ml[1].triangle_count == 4);\n\tassert(ml[1].vertex_count == 4);\n}\n\nstatic void meshletsSpatialDeep()\n{\n\tconst int N = 400;\n\tconst size_t max_vertices = 4;\n\tconst size_t max_triangles = 4;\n\n\tfloat vb[(N + 1) * 3];\n\tunsigned int ib[N * 3];\n\n\tvb[0] = vb[1] = vb[2] = 0;\n\n\tfor (size_t i = 0; i < N; ++i)\n\t{\n\t\tvb[(i + 1) * 3 + 0] = vb[(i + 1) * 3 + 1] = vb[(i + 1) * 3 + 2] = powf(1.2f, float(i));\n\n\t\tib[i * 3 + 0] = 0;\n\t\tib[i * 3 + 1] = ib[i * 3 + 2] = unsigned(i + 1);\n\t}\n\n\tsize_t max_meshlets = meshopt_buildMeshletsBound(N * 3, max_vertices, max_triangles);\n\tstd::vector<meshopt_Meshlet> meshlets(max_meshlets);\n\tstd::vector<unsigned int> meshlet_vertices(N * 3);\n\tstd::vector<unsigned char> meshlet_triangles(N * 3);\n\n\tsize_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);\n\tassert(result == N);\n}\n\nstatic void partitionBasic()\n{\n\t// 0   1   2\n\t//     3\n\t// 4 5 6 7 8\n\t//     9\n\t// 10 11  12\n\tconst unsigned int ci[] = {\n\t    0, 1, 3, 4, 5, 6,\n\t    1, 2, 3, 6, 7, 8,\n\t    4, 5, 6, 9, 10, 11,\n\t    6, 7, 8, 9, 11, 12, // clang-format :-/\n\t};\n\n\tconst unsigned int cc[4] = {6, 6, 6, 6};\n\tunsigned int part[4];\n\n\tassert(meshopt_partitionClusters(part, ci, sizeof(ci) / sizeof(ci[0]), cc, 4, NULL, 13, 0, 1) == 4);\n\tassert(part[0] == 0 && part[1] == 1 && part[2] == 2 && part[3] == 3);\n\n\tassert(meshopt_partitionClusters(part, ci, sizeof(ci) / sizeof(ci[0]), cc, 4, NULL, 13, 0, 2) == 2);\n\tassert(part[0] == 0 && part[1] == 0 && part[2] == 1 && part[3] == 1);\n\n\tassert(meshopt_partitionClusters(part, ci, sizeof(ci) / sizeof(ci[0]), cc, 4, NULL, 13, 0, 4) == 1);\n\tassert(part[0] == 0 && part[1] == 0 && part[2] == 0 && part[3] == 0);\n}\n\nstatic void partitionSpatial()\n{\n\tconst unsigned int ci[] = {\n\t    0, 1, 2,\n\t    0, 3, 4,\n\t    0, 5, 6, // clang-format :-/\n\t};\n\n\tconst float vb[] = {\n\t    0, 0, 0,\n\t    1, 0, 0, 0, 1, 0,\n\t    0, 2, 0, 2, 0, 0,\n\t    -1, 0, 0, 0, -1, 0, // clang-format :-/\n\t};\n\n\tconst unsigned int cc[3] = {3, 3, 3};\n\tunsigned int part[3];\n\n\tassert(meshopt_partitionClusters(part, ci, sizeof(ci) / sizeof(ci[0]), cc, 3, NULL, 7, 0, 2) == 2);\n\tassert(part[0] == 0 && part[1] == 0 && part[2] == 1);\n\n\tassert(meshopt_partitionClusters(part, ci, sizeof(ci) / sizeof(ci[0]), cc, 3, vb, 7, sizeof(float) * 3, 2) == 2);\n\tassert(part[0] == 0 && part[1] == 1 && part[2] == 0);\n}\n\nstatic void partitionSpatialMerge()\n{\n\tconst unsigned int ci[] = {\n\t    0, 1, 2,\n\t    3, 4, 5,\n\t    6, 7, 8, // clang-format :-/\n\t};\n\n\tconst float vb[] = {\n\t    0, 0, 0, 1, 0, 0, 0, 1, 0,\n\t    0, 0, 0, 0, 2, 0, 2, 0, 0,\n\t    10, 0, 0, 10, 1, 0, 10, 2, 0, // clang-format :-/\n\t};\n\n\tconst unsigned int cc[3] = {3, 3, 3};\n\tunsigned int part[3];\n\n\tassert(meshopt_partitionClusters(part, ci, sizeof(ci) / sizeof(ci[0]), cc, 3, NULL, 9, 0, 2) == 3);\n\tassert(part[0] == 0 && part[1] == 1 && part[2] == 2);\n\n\tassert(meshopt_partitionClusters(part, ci, sizeof(ci) / sizeof(ci[0]), cc, 3, vb, 9, sizeof(float) * 3, 2) == 2);\n\tassert(part[0] == 0 && part[1] == 0 && part[2] == 1);\n}\n\nstatic int remapCustomFalse(void*, unsigned int, unsigned int)\n{\n\treturn 0;\n}\n\nstatic int remapCustomTrue(void*, unsigned int, unsigned int)\n{\n\treturn 1;\n}\n\nstatic void remapCustom()\n{\n\tconst float vb[] = {\n\t    0, 0, 0,\n\t    1, 0, 0,\n\t    0, 1, 0,\n\t    0, 0, 1,\n\t    1, 0, 0,\n\t    0, -0.f, 1, // clang-format\n\t};\n\n\tunsigned int remap[6];\n\tsize_t res;\n\n\tres = meshopt_generateVertexRemapCustom(remap, NULL, 6, vb, 6, sizeof(float) * 3, NULL, NULL);\n\tassert(res == 4);\n\tfor (int i = 0; i < 4; ++i)\n\t\tassert(remap[i] == unsigned(i));\n\tassert(remap[4] == 1);\n\tassert(remap[5] == 3);\n\n\tres = meshopt_generateVertexRemapCustom(remap, NULL, 6, vb, 6, sizeof(float) * 3, remapCustomTrue, NULL);\n\tassert(res == 4);\n\tfor (int i = 0; i < 4; ++i)\n\t\tassert(remap[i] == unsigned(i));\n\tassert(remap[4] == 1);\n\tassert(remap[5] == 3);\n\n\tres = meshopt_generateVertexRemapCustom(remap, NULL, 6, vb, 6, sizeof(float) * 3, remapCustomFalse, NULL);\n\tassert(res == 6);\n\tfor (int i = 0; i < 6; ++i)\n\t\tassert(remap[i] == unsigned(i));\n}\n\nstatic size_t allocCount;\nstatic size_t freeCount;\n\nstatic void* customAlloc(size_t size)\n{\n\tallocCount++;\n\n\treturn malloc(size);\n}\n\nstatic void customFree(void* ptr)\n{\n\tfreeCount++;\n\n\tfree(ptr);\n}\n\nstatic void customAllocator()\n{\n\tmeshopt_setAllocator(customAlloc, customFree);\n\n\tassert(allocCount == 0 && freeCount == 0);\n\n\tfloat vb[] = {1, 0, 0, 0, 1, 0, 0, 0, 1};\n\tunsigned int ib[] = {0, 1, 2};\n\tunsigned short ibs[] = {0, 1, 2};\n\n\t// meshopt_computeClusterBounds doesn't allocate\n\tmeshopt_computeClusterBounds(ib, 3, vb, 3, 12);\n\tassert(allocCount == 0 && freeCount == 0);\n\n\t// ... unless IndexAdapter is used\n\tmeshopt_computeClusterBounds(ibs, 3, vb, 3, 12);\n\tassert(allocCount == 1 && freeCount == 1);\n\n\t// meshopt_optimizeVertexFetch allocates internal remap table and temporary storage for in-place remaps\n\tmeshopt_optimizeVertexFetch(vb, ib, 3, vb, 3, 12);\n\tassert(allocCount == 3 && freeCount == 3);\n\n\t// ... plus one for IndexAdapter\n\tmeshopt_optimizeVertexFetch(vb, ibs, 3, vb, 3, 12);\n\tassert(allocCount == 6 && freeCount == 6);\n\n\tmeshopt_setAllocator(operator new, operator delete);\n\n\t// customAlloc & customFree should not get called anymore\n\tmeshopt_optimizeVertexFetch(vb, ib, 3, vb, 3, 12);\n\tassert(allocCount == 6 && freeCount == 6);\n\n\tallocCount = freeCount = 0;\n}\n\nstatic void emptyMesh()\n{\n\tmeshopt_optimizeVertexCache(NULL, NULL, 0, 0);\n\tmeshopt_optimizeVertexCacheFifo(NULL, NULL, 0, 0, 16);\n\tmeshopt_optimizeOverdraw(NULL, NULL, 0, NULL, 0, 12, 1.f);\n}\n\nstatic void simplify()\n{\n\t// 0\n\t// 1 2\n\t// 3 4 5\n\tunsigned int ib[] = {\n\t    0, 2, 1,\n\t    1, 2, 3,\n\t    3, 2, 4,\n\t    2, 5, 4, // clang-format :-/\n\t};\n\n\tfloat vb[] = {\n\t    0, 4, 0,\n\t    0, 1, 0,\n\t    2, 2, 0,\n\t    0, 0, 0,\n\t    1, 0, 0,\n\t    4, 0, 0, // clang-format :-/\n\t};\n\n\tunsigned int expected[] = {\n\t    0,\n\t    5,\n\t    3,\n\t};\n\n\tfloat error;\n\tassert(meshopt_simplify(ib, ib, 12, vb, 6, 12, 3, 1e-2f, 0, &error) == 3);\n\tassert(error < 1e-4f);\n\tassert(memcmp(ib, expected, sizeof(expected)) == 0);\n}\n\nstatic void simplifyStuck()\n{\n\t// tetrahedron can't be simplified due to collapse error restrictions\n\tfloat vb1[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1};\n\tunsigned int ib1[] = {0, 1, 2, 0, 2, 3, 0, 3, 1, 2, 1, 3};\n\n\tassert(meshopt_simplify(ib1, ib1, 12, vb1, 4, 12, 6, 1e-3f) == 12);\n\n\t// 5-vertex strip can't be simplified due to topology restriction since middle triangle has flipped winding\n\tfloat vb2[] = {0, 0, 0, 1, 0, 0, 2, 0, 0, 0.5f, 1, 0, 1.5f, 1, 0};\n\tunsigned int ib2[] = {0, 1, 3, 3, 1, 4, 1, 2, 4}; // ok\n\tunsigned int ib3[] = {0, 1, 3, 1, 3, 4, 1, 2, 4}; // flipped\n\n\tassert(meshopt_simplify(ib2, ib2, 9, vb2, 5, 12, 6, 1e-3f) == 6);\n\tassert(meshopt_simplify(ib3, ib3, 9, vb2, 5, 12, 6, 1e-3f) == 9);\n\n\t// 4-vertex quad with a locked corner can't be simplified due to border error-induced restriction\n\tfloat vb4[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0};\n\tunsigned int ib4[] = {0, 1, 3, 0, 3, 2};\n\n\tassert(meshopt_simplify(ib4, ib4, 6, vb4, 4, 12, 3, 1e-3f) == 6);\n\n\t// 4-vertex quad with a locked corner can't be simplified due to border error-induced restriction\n\tfloat vb5[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0};\n\tunsigned int ib5[] = {0, 1, 4, 0, 3, 2};\n\n\tassert(meshopt_simplify(ib5, ib5, 6, vb5, 5, 12, 3, 1e-3f) == 6);\n}\n\nstatic void simplifySloppyStuck()\n{\n\tconst float vb[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};\n\tconst unsigned int ib[] = {0, 1, 2, 0, 1, 2};\n\n\tunsigned int* target = NULL;\n\n\t// simplifying down to 0 triangles results in 0 immediately\n\tassert(meshopt_simplifySloppy(target, ib, 3, vb, 3, 12, 0, 0.f) == 0);\n\n\t// simplifying down to 2 triangles given that all triangles are degenerate results in 0 as well\n\tassert(meshopt_simplifySloppy(target, ib, 6, vb, 3, 12, 6, 0.f) == 0);\n}\n\nstatic void simplifySloppyLocks()\n{\n\t// 0\n\t// 1 2\n\t// 3 4 5\n\tunsigned int ib[] = {\n\t    0, 2, 1,\n\t    1, 2, 3,\n\t    3, 2, 4,\n\t    2, 5, 4, // clang-format :-/\n\t};\n\n\tfloat vb[] = {\n\t    0, 4, 0,\n\t    0, 1, 0,\n\t    2, 2, 0,\n\t    0, 0, 0,\n\t    1, 0, 0,\n\t    4, 0, 0, // clang-format :-/\n\t};\n\n\t// lock spine\n\tunsigned char locks[] = {1, 0, 1, 0, 0, 1};\n\n\tunsigned int expected[] = {\n\t    0,\n\t    2,\n\t    1,\n\t    1,\n\t    2,\n\t    5,\n\t};\n\n\tfloat error;\n\tassert(meshopt_simplifySloppy(ib, ib, 12, vb, 6, 12, locks, 3, 1.f, &error) == 6);\n\tassert(error == 0.f);\n\tassert(memcmp(ib, expected, sizeof(expected)) == 0);\n}\n\nstatic void simplifyPointsStuck()\n{\n\tconst float vb[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};\n\n\t// simplifying down to 0 points results in 0 immediately\n\tassert(meshopt_simplifyPoints(NULL, vb, 3, 12, NULL, 0, 0, 0) == 0);\n}\n\nstatic void simplifyFlip()\n{\n\t// this mesh has been constructed by taking a tessellated irregular grid with a square cutout\n\t// and progressively collapsing edges until the only ones left violate border or flip constraints.\n\t// there is only one valid non-flip collapse, so we validate that we take it; when flips are allowed,\n\t// the wrong collapse is picked instead.\n\tfloat vb[] = {\n\t    1.000000f, 1.000000f, -1.000000f,\n\t    1.000000f, 1.000000f, 1.000000f,\n\t    1.000000f, -1.000000f, 1.000000f,\n\t    1.000000f, -0.200000f, -0.200000f,\n\t    1.000000f, 0.200000f, -0.200000f,\n\t    1.000000f, -0.200000f, 0.200000f,\n\t    1.000000f, 0.200000f, 0.200000f,\n\t    1.000000f, 0.500000f, -0.500000f,\n\t    1.000000f, -1.000000f, 0.000000f, // clang-format :-/\n\t};\n\n\t// the collapse we expect is 7 -> 0\n\tunsigned int ib[] = {\n\t    7, 4, 3,\n\t    1, 2, 5,\n\t    7, 1, 6,\n\t    7, 8, 0, // gets removed\n\t    7, 6, 4,\n\t    8, 5, 2,\n\t    8, 7, 3,\n\t    8, 3, 5,\n\t    5, 6, 1,\n\t    7, 0, 1, // gets removed\n\t};\n\n\tunsigned int expected[] = {\n\t    0, 4, 3,\n\t    1, 2, 5,\n\t    0, 1, 6,\n\t    0, 6, 4,\n\t    8, 5, 2,\n\t    8, 0, 3,\n\t    8, 3, 5,\n\t    5, 6, 1, // clang-format :-/\n\t};\n\n\tassert(meshopt_simplify(ib, ib, 30, vb, 9, 12, 3, 1e-3f) == 24);\n\tassert(memcmp(ib, expected, sizeof(expected)) == 0);\n}\n\nstatic void simplifyScale()\n{\n\tconst float vb[] = {0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3};\n\n\tassert(meshopt_simplifyScale(vb, 4, 12) == 3.f);\n}\n\nstatic void simplifyDegenerate()\n{\n\tfloat vb[] = {\n\t    0.000000f, 0.000000f, 0.000000f,\n\t    0.000000f, 1.000000f, 0.000000f,\n\t    0.000000f, 2.000000f, 0.000000f,\n\t    1.000000f, 0.000000f, 0.000000f,\n\t    2.000000f, 0.000000f, 0.000000f,\n\t    1.000000f, 1.000000f, 0.000000f, // clang-format :-/\n\t};\n\n\t// 0 1 2\n\t// 3 5\n\t// 4\n\n\tunsigned int ib[] = {\n\t    0, 1, 3,\n\t    3, 1, 5,\n\t    1, 2, 5,\n\t    3, 5, 4,\n\t    1, 0, 1, // these two degenerate triangles create a fake reverse edge\n\t    0, 3, 0, // which breaks border classification\n\t};\n\n\tunsigned int expected[] = {\n\t    0, 1, 4,\n\t    4, 1, 2, // clang-format :-/\n\t};\n\n\tassert(meshopt_simplify(ib, ib, 18, vb, 6, 12, 3, 1e-3f) == 6);\n\tassert(memcmp(ib, expected, sizeof(expected)) == 0);\n}\n\nstatic void simplifyLockBorder()\n{\n\tfloat vb[] = {\n\t    0.000000f, 0.000000f, 0.000000f,\n\t    0.000000f, 1.000000f, 0.000000f,\n\t    0.000000f, 2.000000f, 0.000000f,\n\t    1.000000f, 0.000000f, 0.000000f,\n\t    1.000000f, 1.000000f, 0.000000f,\n\t    1.000000f, 2.000000f, 0.000000f,\n\t    2.000000f, 0.000000f, 0.000000f,\n\t    2.000000f, 1.000000f, 0.000000f,\n\t    2.000000f, 2.000000f, 0.000000f, // clang-format :-/\n\t};\n\n\t// 0 1 2\n\t// 3 4 5\n\t// 6 7 8\n\n\tunsigned int ib[] = {\n\t    0, 1, 3,\n\t    3, 1, 4,\n\t    1, 2, 4,\n\t    4, 2, 5,\n\t    3, 4, 6,\n\t    6, 4, 7,\n\t    4, 5, 7,\n\t    7, 5, 8, // clang-format :-/\n\t};\n\n\tunsigned int expected[] = {\n\t    0, 1, 3,\n\t    1, 2, 3,\n\t    3, 2, 5,\n\t    6, 3, 7,\n\t    3, 5, 7,\n\t    7, 5, 8, // clang-format :-/\n\t};\n\n\tassert(meshopt_simplify(ib, ib, 24, vb, 9, 12, 3, 1e-3f, meshopt_SimplifyLockBorder) == 18);\n\tassert(memcmp(ib, expected, sizeof(expected)) == 0);\n}\n\nstatic void simplifyAttr(bool skip_g)\n{\n\tfloat vb[8 * 3][6];\n\n\tfor (int y = 0; y < 8; ++y)\n\t{\n\t\t// first four rows are a blue gradient, next four rows are a yellow gradient\n\t\tfloat r = (y < 4) ? 0.8f + y * 0.05f : 0.f;\n\t\tfloat g = (y < 4) ? 0.8f + y * 0.05f : 0.f;\n\t\tfloat b = (y < 4) ? 0.f : 0.8f + (7 - y) * 0.05f;\n\n\t\tfor (int x = 0; x < 3; ++x)\n\t\t{\n\t\t\tvb[y * 3 + x][0] = float(x);\n\t\t\tvb[y * 3 + x][1] = float(y);\n\t\t\tvb[y * 3 + x][2] = 0.03f * x + 0.03f * (y % 2) + (x == 2 && y == 7) * 0.03f;\n\t\t\tvb[y * 3 + x][3] = r;\n\t\t\tvb[y * 3 + x][4] = g;\n\t\t\tvb[y * 3 + x][5] = b;\n\t\t}\n\t}\n\n\tunsigned int ib[7 * 2][6];\n\n\tfor (int y = 0; y < 7; ++y)\n\t{\n\t\tfor (int x = 0; x < 2; ++x)\n\t\t{\n\t\t\tib[y * 2 + x][0] = (y + 0) * 3 + (x + 0);\n\t\t\tib[y * 2 + x][1] = (y + 0) * 3 + (x + 1);\n\t\t\tib[y * 2 + x][2] = (y + 1) * 3 + (x + 0);\n\t\t\tib[y * 2 + x][3] = (y + 1) * 3 + (x + 0);\n\t\t\tib[y * 2 + x][4] = (y + 0) * 3 + (x + 1);\n\t\t\tib[y * 2 + x][5] = (y + 1) * 3 + (x + 1);\n\t\t}\n\t}\n\n\tfloat attr_weights[3] = {0.5f, skip_g ? 0.f : 0.5f, 0.5f};\n\n\t// *0  1   *2\n\t//  3  4    5\n\t//  6  7    8\n\t// *9  10 *11\n\t// *12 13 *14\n\t//  15 16  17\n\t//  18 19  20\n\t// *21 22 *23\n\tunsigned int expected[3][6] = {\n\t    {0, 2, 11, 0, 11, 9},\n\t    {9, 11, 12, 12, 11, 14},\n\t    {12, 14, 23, 12, 23, 21},\n\t};\n\n\tassert(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);\n\tassert(memcmp(ib, expected, sizeof(expected)) == 0);\n}\n\nstatic void simplifyLockFlags()\n{\n\tfloat vb[] = {\n\t    0, 0, 0,\n\t    0, 1, 0,\n\t    0, 2, 0,\n\t    1, 0, 0,\n\t    1, 1, 0,\n\t    1, 2, 0,\n\t    2, 0, 0,\n\t    2, 1, 0,\n\t    2, 2, 0, // clang-format :-/\n\t};\n\n\tunsigned char lock[9] = {\n\t    1, 1, 1,\n\t    1, 0, 1,\n\t    1, 1, 1, // clang-format :-/\n\t};\n\n\t// 0 1 2\n\t// 3 4 5\n\t// 6 7 8\n\n\tunsigned int ib[] = {\n\t    0, 1, 3,\n\t    3, 1, 4,\n\t    1, 2, 4,\n\t    4, 2, 5,\n\t    3, 4, 6,\n\t    6, 4, 7,\n\t    4, 5, 7,\n\t    7, 5, 8, // clang-format :-/\n\t};\n\n\tunsigned int expected[] = {\n\t    0, 1, 3,\n\t    1, 2, 3,\n\t    3, 2, 5,\n\t    6, 3, 7,\n\t    3, 5, 7,\n\t    7, 5, 8, // clang-format :-/\n\t};\n\n\tassert(meshopt_simplifyWithAttributes(ib, ib, 24, vb, 9, 12, NULL, 0, NULL, 0, lock, 3, 1e-3f, 0) == 18);\n\tassert(memcmp(ib, expected, sizeof(expected)) == 0);\n}\n\nstatic void simplifyLockFlagsSeam()\n{\n\tfloat vb[] = {\n\t    0, 0, 0,\n\t    0, 1, 0,\n\t    0, 1, 0,\n\t    0, 2, 0,\n\t    1, 0, 0,\n\t    1, 1, 0,\n\t    1, 1, 0,\n\t    1, 2, 0,\n\t    2, 0, 0,\n\t    2, 1, 0,\n\t    2, 1, 0,\n\t    2, 2, 0, // clang-format :-/\n\t};\n\n\tunsigned char lock0[12] = {\n\t    1, 0, 0, 1,\n\t    0, 0, 0, 0,\n\t    1, 0, 0, 1, // clang-format :-/\n\t};\n\n\tunsigned char lock1[12] = {\n\t    1, 0, 0, 1,\n\t    1, 0, 0, 1,\n\t    1, 0, 0, 1, // clang-format :-/\n\t};\n\n\tunsigned char lock2[12] = {\n\t    1, 0, 1, 1,\n\t    1, 0, 1, 1,\n\t    1, 0, 1, 1, // clang-format :-/\n\t};\n\n\tunsigned char lock3[12] = {\n\t    1, 1, 0, 1,\n\t    1, 1, 0, 1,\n\t    1, 1, 0, 1, // clang-format :-/\n\t};\n\n\t// 0 1-2 3\n\t// 4 5-6 7\n\t// 8 9-10 11\n\n\tunsigned int ib[] = {\n\t    0, 1, 4,\n\t    4, 1, 5,\n\t    4, 5, 8,\n\t    8, 5, 9,\n\t    2, 3, 6,\n\t    6, 3, 7,\n\t    6, 7, 10,\n\t    10, 7, 11, // clang-format :-/\n\t};\n\n\tunsigned int res[24];\n\t// 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)\n\tassert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 12, NULL, 0, NULL, 0, NULL, 0, 1.f, 0) == 0);\n\n\t// with corners locked, we should get two quads\n\tassert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 12, NULL, 0, NULL, 0, lock0, 0, 1.f, 0) == 12);\n\n\t// with both sides locked, we can only collapse the seam spine\n\tassert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 12, NULL, 0, NULL, 0, lock1, 0, 1.f, 0) == 18);\n\n\t// with seam spine locked, we can collapse nothing; note that we intentionally test two different lock configurations\n\t// they each lock only one side of the seam spine, which should be equivalent\n\tassert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 12, NULL, 0, NULL, 0, lock2, 0, 1.f, 0) == 24);\n\tassert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 12, NULL, 0, NULL, 0, lock3, 0, 1.f, 0) == 24);\n}\n\nstatic void simplifySparse()\n{\n\tfloat vb[] = {\n\t    0, 0, 100,\n\t    0, 1, 0,\n\t    0, 2, 100,\n\t    1, 0, 0.1f,\n\t    1, 1, 0.1f,\n\t    1, 2, 0.1f,\n\t    2, 0, 100,\n\t    2, 1, 0,\n\t    2, 2, 100, // clang-format :-/\n\t};\n\n\tfloat vba[] = {\n\t    100,\n\t    0.5f,\n\t    100,\n\t    0.5f,\n\t    0.5f,\n\t    0,\n\t    100,\n\t    0.5f,\n\t    100, // clang-format :-/\n\t};\n\n\tfloat aw[] = {\n\t    0.5f};\n\n\tunsigned char lock[9] = {\n\t    8, 1, 8,\n\t    1, 0, 1,\n\t    8, 1, 8, // clang-format :-/\n\t};\n\n\t//   1\n\t// 3 4 5\n\t//   7\n\n\tunsigned int ib[] = {\n\t    3, 1, 4,\n\t    1, 5, 4,\n\t    3, 4, 7,\n\t    4, 5, 7, // clang-format :-/\n\t};\n\n\tunsigned int res[12];\n\n\t// vertices 3-4-5 are slightly elevated along Z which guides the collapses when only using geometry\n\tunsigned int expected[] = {\n\t    1, 5, 3,\n\t    3, 5, 7, // clang-format :-/\n\t};\n\n\tassert(meshopt_simplify(res, ib, 12, vb, 9, 12, 6, 1e-3f, meshopt_SimplifySparse) == 6);\n\tassert(memcmp(res, expected, sizeof(expected)) == 0);\n\n\t// vertices 1-4-7 have a crease in the attribute value which guides the collapses the opposite way when weighing attributes sufficiently\n\tunsigned int expecteda[] = {\n\t    3, 1, 7,\n\t    1, 5, 7, // clang-format :-/\n\t};\n\n\tassert(meshopt_simplifyWithAttributes(res, ib, 12, vb, 9, 12, vba, sizeof(float), aw, 1, lock, 6, 1e-1f, meshopt_SimplifySparse) == 6);\n\tassert(memcmp(res, expecteda, sizeof(expecteda)) == 0);\n\n\t// a final test validates that destination can alias when using sparsity\n\tassert(meshopt_simplify(ib, ib, 12, vb, 9, 12, 6, 1e-3f, meshopt_SimplifySparse) == 6);\n\tassert(memcmp(ib, expected, sizeof(expected)) == 0);\n}\n\nstatic void simplifyErrorAbsolute()\n{\n\tfloat vb[] = {\n\t    0, 0, 0,\n\t    0, 1, 0,\n\t    0, 2, 0,\n\t    1, 0, 0,\n\t    1, 1, 1,\n\t    1, 2, 0,\n\t    2, 0, 0,\n\t    2, 1, 0,\n\t    2, 2, 0, // clang-format :-/\n\t};\n\n\t// 0 1 2\n\t// 3 4 5\n\t// 6 7 8\n\n\tunsigned int ib[] = {\n\t    0, 1, 3,\n\t    3, 1, 4,\n\t    1, 2, 4,\n\t    4, 2, 5,\n\t    3, 4, 6,\n\t    6, 4, 7,\n\t    4, 5, 7,\n\t    7, 5, 8, // clang-format :-/\n\t};\n\n\tfloat error = 0.f;\n\tassert(meshopt_simplify(ib, ib, 24, vb, 9, 12, 18, 2.f, meshopt_SimplifyLockBorder | meshopt_SimplifyErrorAbsolute, &error) == 18);\n\tassert(fabsf(error - 0.85f) < 0.01f);\n}\n\nstatic void simplifySeam()\n{\n\t// xyz+attr\n\tfloat vb[] = {\n\t    0, 0, 0, 0,\n\t    0, 1, 0, 0,\n\t    0, 1, 0, 1,\n\t    0, 2, 0, 1,\n\t    1, 0, 0, 0,\n\t    1, 1, 0.3f, 0,\n\t    1, 1, 0.3f, 1,\n\t    1, 2, 0, 1,\n\t    2, 0, 0, 0,\n\t    2, 1, 0.1f, 0,\n\t    2, 1, 0.1f, 1,\n\t    2, 2, 0, 1,\n\t    3, 0, 0, 0,\n\t    3, 1, 0, 0,\n\t    3, 1, 0, 1,\n\t    3, 2, 0, 1, // clang-format :-/\n\t};\n\n\t// 0   1-2   3\n\t// 4   5-6   7\n\t// 8   9-10 11\n\t// 12 13-14 15\n\n\tunsigned int ib[] = {\n\t    0, 1, 4,\n\t    4, 1, 5,\n\t    2, 3, 6,\n\t    6, 3, 7,\n\t    4, 5, 8,\n\t    8, 5, 9,\n\t    6, 7, 10,\n\t    10, 7, 11,\n\t    8, 9, 12,\n\t    12, 9, 13,\n\t    10, 11, 14,\n\t    14, 11, 15, // clang-format :-/\n\t};\n\n\t// note: vertices 1-2 and 13-14 are classified as locked, because they are on a seam & a border\n\t// 0   1-2   3\n\t//     5-6\n\t//     9-10\n\t// 12 13-14 15\n\tunsigned int expected[] = {\n\t    0, 1, 13,\n\t    2, 3, 14,\n\t    0, 13, 12,\n\t    14, 3, 15, // clang-format :-/\n\t};\n\n\tunsigned int res[36];\n\tfloat error = 0.f;\n\n\tassert(meshopt_simplify(res, ib, 36, vb, 16, 16, 12, 1.f, 0, &error) == 12);\n\tassert(memcmp(res, expected, sizeof(expected)) == 0);\n\tassert(fabsf(error - 0.1f) < 0.01f); // note: the error is not zero because there is a difference in height between the seam vertices\n\n\tfloat aw = 1;\n\tassert(meshopt_simplifyWithAttributes(res, ib, 36, vb, 16, 16, vb + 3, 16, &aw, 1, NULL, 12, 2.f, 0, &error) == 12);\n\tassert(memcmp(res, expected, sizeof(expected)) == 0);\n\tassert(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\n}\n\nstatic void simplifySeamFake()\n{\n\t// xyz+attr\n\tfloat vb[] = {\n\t    0, 0, 0, 0,\n\t    1, 0, 0, 1,\n\t    1, 0, 0, 2,\n\t    0, 0, 0, 3, // clang-format :-/\n\t};\n\n\tunsigned int ib[] = {\n\t    0, 1, 2,\n\t    2, 1, 3, // clang-format :-/\n\t};\n\n\tassert(meshopt_simplify(ib, ib, 6, vb, 4, 16, 0, 1.f, 0, NULL) == 6);\n}\n\nstatic void simplifySeamAttr()\n{\n\t// xyz+attr\n\tfloat vb[] = {\n\t    0, 0, 0, 0,\n\t    0, 1, 0, 0,\n\t    0, 1, 0, 0,\n\t    0, 2, 0, 0,\n\t    1, 0, 0, 1,\n\t    1, 1, 0, 1,\n\t    1, 1, 0, 1,\n\t    1, 2, 0, 1,\n\t    4, 0, 0, 2,\n\t    4, 1, 0, 2,\n\t    4, 1, 0, 2,\n\t    4, 2, 0, 2, // clang-format :-/\n\t};\n\n\t// 0   1-2   3\n\t// 4   5-6   7\n\t// 8   9-10 11\n\n\tunsigned int ib[] = {\n\t    0, 1, 4,\n\t    4, 1, 5,\n\t    2, 3, 6,\n\t    6, 3, 7,\n\t    4, 5, 8,\n\t    8, 5, 9,\n\t    6, 7, 10,\n\t    10, 7, 11, // clang-format :-/\n\t};\n\n\t// note: vertices 1-2 and 9-10 are classified as locked, because they are on a seam & a border\n\t// 0   1-2   3\n\t// 4         7\n\t// 8   9-10 11\n\tunsigned int expected[] = {\n\t    0, 1, 4,\n\t    2, 3, 7,\n\t    4, 1, 8,\n\t    8, 1, 9,\n\t    2, 7, 10,\n\t    10, 7, 11, // clang-format :-/\n\t};\n\n\tunsigned int res[24];\n\tfloat error = 0.f;\n\n\tfloat aw = 1;\n\tassert(meshopt_simplifyWithAttributes(res, ib, 24, vb, 12, 16, vb + 3, 16, &aw, 1, NULL, 12, 2.f, meshopt_SimplifyLockBorder, &error) == 18);\n\tassert(memcmp(res, expected, sizeof(expected)) == 0);\n\tassert(fabsf(error - 0.35f) < 0.01f);\n}\n\nstatic void simplifyDebug()\n{\n\t// 0\n\t// 1 2\n\t// 3 4 5\n\tunsigned int ib[] = {\n\t    0, 2, 1,\n\t    1, 2, 3,\n\t    3, 2, 4,\n\t    2, 5, 4, // clang-format :-/\n\t};\n\n\tfloat vb[] = {\n\t    0, 4, 0,\n\t    0, 1, 0,\n\t    2, 2, 0,\n\t    0, 0, 0,\n\t    1, 0, 0,\n\t    4, 0, 0, // clang-format :-/\n\t};\n\n\tunsigned int expected[] = {\n\t    0 | (9u << 28),\n\t    5 | (9u << 28),\n\t    3 | (9u << 28),\n\t};\n\n\tconst unsigned int meshopt_SimplifyInternalDebug = 1 << 30;\n\n\tfloat error;\n\tassert(meshopt_simplify(ib, ib, 12, vb, 6, 12, 3, 1e-2f, meshopt_SimplifyInternalDebug, &error) == 3);\n\tassert(error < 1e-4f);\n\tassert(memcmp(ib, expected, sizeof(expected)) == 0);\n}\n\nstatic void simplifyPrune()\n{\n\t// 0\n\t// 1 2\n\t// 3 4 5\n\t// +\n\t// 6 7 8 (same position)\n\tunsigned int ib[] = {\n\t    3, 2, 4,\n\t    0, 2, 1,\n\t    1, 2, 3,\n\t    2, 5, 4,\n\t    6, 7, 8, // clang-format :-/\n\t};\n\n\tfloat vb[] = {\n\t    0, 4, 0,\n\t    0, 1, 0,\n\t    2, 2, 0,\n\t    0, 0, 0,\n\t    1, 0, 0,\n\t    4, 0, 0,\n\t    1, 1, 1,\n\t    1, 1, 1,\n\t    1, 1, 1, // clang-format :-/\n\t};\n\n\tunsigned int expected[] = {\n\t    0,\n\t    5,\n\t    3,\n\t};\n\n\tfloat error;\n\tassert(meshopt_simplify(ib, ib, 15, vb, 9, 12, 3, 1e-2f, meshopt_SimplifyPrune, &error) == 3);\n\tassert(error < 1e-4f);\n\tassert(memcmp(ib, expected, sizeof(expected)) == 0);\n\n\t// re-run prune with and without sparsity on a small subset to make sure the component code correctly handles sparse subsets\n\tassert(meshopt_simplify(ib, ib, 3, vb, 9, 12, 3, 1e-2f, meshopt_SimplifyPrune, &error) == 3);\n\tassert(meshopt_simplify(ib, ib, 3, vb, 9, 12, 3, 1e-2f, meshopt_SimplifyPrune | meshopt_SimplifySparse, &error) == 3);\n\tassert(memcmp(ib, expected, sizeof(expected)) == 0);\n}\n\nstatic void simplifyPruneCleanup()\n{\n\tunsigned int ib[] = {\n\t    0, 1, 2,\n\t    3, 4, 5,\n\t    6, 7, 8, // clang-format :-/\n\t};\n\n\tfloat vb[] = {\n\t    0, 0, 0,\n\t    0, 1, 0,\n\t    1, 0, 0,\n\t    0, 0, 1,\n\t    0, 2, 1,\n\t    2, 0, 1,\n\t    0, 0, 2,\n\t    0, 4, 2,\n\t    4, 0, 2, // clang-format :-/\n\t};\n\n\tunsigned int expected[] = {\n\t    6,\n\t    7,\n\t    8,\n\t};\n\n\tfloat error;\n\tassert(meshopt_simplify(ib, ib, 9, vb, 9, 12, 3, 1.f, meshopt_SimplifyLockBorder | meshopt_SimplifyPrune, &error) == 3);\n\tassert(fabsf(error - 0.37f) < 0.01f);\n\tassert(memcmp(ib, expected, sizeof(expected)) == 0);\n}\n\nstatic void simplifyPruneFunc()\n{\n\tunsigned int ib[] = {\n\t    0, 1, 2,\n\t    3, 4, 5,\n\t    6, 7, 8, // clang-format :-/\n\t};\n\n\tfloat vb[] = {\n\t    0, 0, 0,\n\t    0, 1, 0,\n\t    1, 0, 0,\n\t    0, 0, 1,\n\t    0, 2, 1,\n\t    2, 0, 1,\n\t    0, 0, 2,\n\t    0, 4, 2,\n\t    4, 0, 2, // clang-format :-/\n\t};\n\n\tunsigned int expected[] = {\n\t    6,\n\t    7,\n\t    8,\n\t};\n\n\tassert(meshopt_simplifyPrune(ib, ib, 9, vb, 9, 12, 0.5f) == 3);\n\tassert(memcmp(ib, expected, sizeof(expected)) == 0);\n}\n\nstatic void simplifyUpdate()\n{\n\tfloat vb[5][4] = {\n\t    {0, 0, 0, 0},\n\t    {1, 1, 0, 0},\n\t    {2, 0, 0, 0},\n\t    {0.9f, 0.2f, 0.1f, 0.2f},\n\t    {1.1f, 0.2f, 0.1f, 0.1f},\n\t};\n\n\t//     1\n\t//    3 4\n\t// 0       2\n\tunsigned int ib[15] = {\n\t    0, 1, 3, 3, 1, 4, 4, 1, 2, 0, 3, 2, 3, 4, 2, //\n\t};\n\n\tfloat attr_weight = 1.f;\n\n\tassert(meshopt_simplifyWithUpdate(ib, 15, vb[0], 5, 4 * sizeof(float), vb[0] + 3, 4 * sizeof(float), &attr_weight, 1, NULL, 9, 1.f) == 9);\n\n\tunsigned int expected[] = {\n\t    0, 1, 3, 3, 1, 2, 0, 3, 2, //\n\t};\n\n\tassert(memcmp(ib, expected, sizeof(expected)) == 0);\n\n\t// border vertices haven't moved but may have small floating point drift\n\tfor (int i = 0; i < 3; ++i)\n\t\tassert(fabsf(vb[i][3]) < 1e-6f);\n\n\t// center vertex got updated\n\tassert(fabsf(vb[3][0] - 0.88f) < 1e-2f);\n\tassert(fabsf(vb[3][1] - 0.19f) < 1e-2f);\n\tassert(fabsf(vb[3][2] - 0.11f) < 1e-2f);\n\tassert(fabsf(vb[3][3] - 0.18f) < 1e-2f);\n}\n\nstatic void simplifyUpdateLocked(unsigned int options)\n{\n\tfloat vb[5][4] = {\n\t    {0, 0, 0, 0},\n\t    {1, 1, 0, 0},\n\t    {2, 0, 0, 0},\n\t    {0.9f, 0.2f, 0.1f, 0.2f},\n\t    {1.1f, 0.2f, 0.1f, 0.1f},\n\t};\n\n\t//     1\n\t//    3 4\n\t// 0       2\n\tunsigned int ib[15] = {\n\t    0, 1, 3, 3, 1, 4, 4, 1, 2, 0, 3, 2, 3, 4, 2, //\n\t};\n\n\tfloat attr_weight = 1.f;\n\n\tunsigned char vertex_lock[5] = {0, 0, 0, 1, 0};\n\n\tassert(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);\n\n\tunsigned int expected[] = {\n\t    0, 1, 3, 3, 1, 2, 0, 3, 2, //\n\t};\n\n\tassert(memcmp(ib, expected, sizeof(expected)) == 0);\n\n\tfor (int i = 0; i < 3; ++i)\n\t\tassert(fabsf(vb[i][3]) < 1e-6f);\n\n\t// locking guarantees exact result\n\tassert(vb[3][0] == 0.9f);\n\tassert(vb[3][1] == 0.2f);\n\tassert(vb[3][2] == 0.1f);\n\tassert(vb[3][3] == 0.2f);\n}\n\nstatic void adjacency()\n{\n\t// 0 1/4\n\t// 2/5 3\n\tconst float vb[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0};\n\tconst unsigned int ib[] = {0, 1, 2, 5, 4, 3};\n\n\tunsigned int adjib[12];\n\tmeshopt_generateAdjacencyIndexBuffer(adjib, ib, 6, vb, 6, 12);\n\n\tunsigned int expected[] = {\n\t    // patch 0\n\t    0, 0,\n\t    1, 3,\n\t    2, 2,\n\n\t    // patch 1\n\t    5, 0,\n\t    4, 4,\n\t    3, 3,\n\n\t    // clang-format :-/\n\t};\n\n\tassert(memcmp(adjib, expected, sizeof(expected)) == 0);\n}\n\nstatic void tessellation()\n{\n\t// 0 1/4\n\t// 2/5 3\n\tconst float vb[] = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0};\n\tconst unsigned int ib[] = {0, 1, 2, 5, 4, 3};\n\n\tunsigned int tessib[24];\n\tmeshopt_generateTessellationIndexBuffer(tessib, ib, 6, vb, 6, 12);\n\n\tunsigned int expected[] = {\n\t    // patch 0\n\t    0, 1, 2,\n\t    0, 1,\n\t    4, 5,\n\t    2, 0,\n\t    0, 1, 2,\n\n\t    // patch 1\n\t    5, 4, 3,\n\t    2, 1,\n\t    4, 3,\n\t    3, 5,\n\t    2, 1, 3,\n\n\t    // clang-format :-/\n\t};\n\n\tassert(memcmp(tessib, expected, sizeof(expected)) == 0);\n}\n\nstatic void provoking()\n{\n\t// 0 1 2\n\t// 3 4 5\n\tconst unsigned int ib[] = {\n\t    0, 1, 3,\n\t    3, 1, 4,\n\t    1, 2, 4,\n\t    4, 2, 5,\n\t    0, 2, 4,\n\t    // clang-format :-/\n\t};\n\n\tunsigned int pib[15];\n\tunsigned int pre[6 + 5]; // limit is vertex count + triangle count\n\tsize_t res = meshopt_generateProvokingIndexBuffer(pib, pre, ib, 15, 6);\n\n\tunsigned int expectedib[] = {\n\t    0, 5, 1,\n\t    1, 4, 0,\n\t    2, 4, 1,\n\t    3, 4, 2,\n\t    4, 5, 2,\n\t    // clang-format :-/\n\t};\n\n\tunsigned int expectedre[] = {\n\t    3, 1, 2, 5, 4, 0,\n\t    // clang-format :-/\n\t};\n\n\tassert(res == 6);\n\tassert(memcmp(pib, expectedib, sizeof(expectedib)) == 0);\n\tassert(memcmp(pre, expectedre, sizeof(expectedre)) == 0);\n}\n\nstatic void quantizeFloat()\n{\n\tvolatile float zero = 0.f; // avoids div-by-zero warnings\n\n\tassert(meshopt_quantizeFloat(1.2345f, 23) == 1.2345f);\n\n\tassert(meshopt_quantizeFloat(1.2345f, 16) == 1.2344971f);\n\tassert(meshopt_quantizeFloat(1.2345f, 8) == 1.2343750f);\n\tassert(meshopt_quantizeFloat(1.2345f, 4) == 1.25f);\n\tassert(meshopt_quantizeFloat(1.2345f, 1) == 1.0);\n\n\tassert(meshopt_quantizeFloat(1.f, 0) == 1.0f);\n\n\tassert(meshopt_quantizeFloat(1.f / zero, 0) == 1.f / zero);\n\tassert(meshopt_quantizeFloat(-1.f / zero, 0) == -1.f / zero);\n\n\tfloat nanf = meshopt_quantizeFloat(zero / zero, 8);\n\tassert(nanf != nanf);\n}\n\nstatic void quantizeHalf()\n{\n\tvolatile float zero = 0.f; // avoids div-by-zero warnings\n\n\t// normal\n\tassert(meshopt_quantizeHalf(1.2345f) == 0x3cf0);\n\n\t// overflow\n\tassert(meshopt_quantizeHalf(65535.f) == 0x7c00);\n\tassert(meshopt_quantizeHalf(-65535.f) == 0xfc00);\n\n\t// large\n\tassert(meshopt_quantizeHalf(65000.f) == 0x7bef);\n\tassert(meshopt_quantizeHalf(-65000.f) == 0xfbef);\n\n\t// small\n\tassert(meshopt_quantizeHalf(0.125f) == 0x3000);\n\tassert(meshopt_quantizeHalf(-0.125f) == 0xb000);\n\n\t// very small\n\tassert(meshopt_quantizeHalf(1e-4f) == 0x068e);\n\tassert(meshopt_quantizeHalf(-1e-4f) == 0x868e);\n\n\t// underflow\n\tassert(meshopt_quantizeHalf(1e-5f) == 0x0000);\n\tassert(meshopt_quantizeHalf(-1e-5f) == 0x8000);\n\n\t// exponent underflow\n\tassert(meshopt_quantizeHalf(1e-20f) == 0x0000);\n\tassert(meshopt_quantizeHalf(-1e-20f) == 0x8000);\n\n\t// exponent overflow\n\tassert(meshopt_quantizeHalf(1e20f) == 0x7c00);\n\tassert(meshopt_quantizeHalf(-1e20f) == 0xfc00);\n\n\t// inf\n\tassert(meshopt_quantizeHalf(1.f / zero) == 0x7c00);\n\tassert(meshopt_quantizeHalf(-1.f / zero) == 0xfc00);\n\n\t// nan\n\tunsigned short nanh = meshopt_quantizeHalf(zero / zero);\n\tassert(nanh == 0x7e00 || nanh == 0xfe00);\n}\n\nstatic void dequantizeHalf()\n{\n\tvolatile float zero = 0.f; // avoids div-by-zero warnings\n\n\t// normal\n\tassert(meshopt_dequantizeHalf(0x3cf0) == 1.234375f);\n\n\t// large\n\tassert(meshopt_dequantizeHalf(0x7bef) == 64992.f);\n\tassert(meshopt_dequantizeHalf(0xfbef) == -64992.f);\n\n\t// small\n\tassert(meshopt_dequantizeHalf(0x3000) == 0.125f);\n\tassert(meshopt_dequantizeHalf(0xb000) == -0.125f);\n\n\t// very small\n\tassert(meshopt_dequantizeHalf(0x068e) == 1.00016594e-4f);\n\tassert(meshopt_dequantizeHalf(0x868e) == -1.00016594e-4f);\n\n\t// denormal\n\tassert(meshopt_dequantizeHalf(0x00ff) == 0.f);\n\tassert(meshopt_dequantizeHalf(0x80ff) == 0.f); // actually this is -0.f\n\tassert(1.f / meshopt_dequantizeHalf(0x80ff) == -1.f / zero);\n\n\t// inf\n\tassert(meshopt_dequantizeHalf(0x7c00) == 1.f / zero);\n\tassert(meshopt_dequantizeHalf(0xfc00) == -1.f / zero);\n\n\t// nan\n\tfloat nanf = meshopt_dequantizeHalf(0x7e00);\n\tassert(nanf != nanf);\n}\n\nstatic void encodeMeshletBound()\n{\n\tconst unsigned char triangles[5 * 3] = {\n\t    0, 1, 2,\n\t    2, 1, 3,\n\t    3, 5, 4,\n\t    2, 0, 6,\n\t    7, 7, 7, // clang-format :-/\n\t};\n\n\tconst unsigned int vertices[7] = {\n\t    5,\n\t    12,\n\t    140,\n\t    0,\n\t    12389,\n\t    123456789,\n\t    7,\n\t};\n\n\tsize_t bound = meshopt_encodeMeshletBound(7, 5);\n\n\tunsigned char enc[256];\n\tsize_t size = meshopt_encodeMeshlet(enc, sizeof(enc), vertices, 7, triangles, 5);\n\tassert(size > 0 && size <= bound);\n\n\tassert(meshopt_encodeMeshlet(enc, size - 1, vertices, 7, triangles, 5) == 0);\n}\n\ntemplate <typename V, typename T, size_t VS, size_t TS>\nstatic 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)\n{\n\tV rv[VS];\n\tT rt[TS];\n\n\tint rc = meshopt_decodeMeshlet(rv, vertex_count, rt, triangle_count, data, size);\n\tassert(rc == 0);\n\n\tfor (size_t j = 0; j < vertex_count; ++j)\n\t\tassert(rv[j] == V(vertices[j]));\n\n\tfor (size_t j = 0; j < triangle_count; ++j)\n\t{\n\t\tunsigned int a = triangles[j * 3 + 0];\n\t\tunsigned int b = triangles[j * 3 + 1];\n\t\tunsigned int c = triangles[j * 3 + 2];\n\n\t\tunsigned int tri = sizeof(T) == 1 ? rt[j * 3] | (rt[j * 3 + 1] << 8) | (rt[j * 3 + 2] << 16) : rt[j];\n\n\t\tunsigned int abc = (a << 0) | (b << 8) | (c << 16);\n\t\tunsigned int bca = (b << 0) | (c << 8) | (a << 16);\n\t\tunsigned int cba = (c << 0) | (a << 8) | (b << 16);\n\n\t\tassert(tri == abc || tri == bca || tri == cba);\n\t}\n}\n\nstatic void decodeMeshletSafety()\n{\n\tconst unsigned char triangles[5 * 3] = {\n\t    0, 1, 2,\n\t    2, 1, 3,\n\t    3, 5, 4,\n\t    2, 0, 6,\n\t    6, 6, 6, // clang-format :-/\n\t};\n\n\tconst unsigned int vertices[7] = {\n\t    5,\n\t    12,\n\t    140,\n\t    0,\n\t    12389,\n\t    123456789,\n\t    7,\n\t};\n\n\tunsigned char encb[256];\n\tsize_t size = meshopt_encodeMeshlet(encb, sizeof(encb), vertices, 7, triangles, 5);\n\tassert(size > 0);\n\n\t// move encoded buffer to the end to make sure any over-reads trigger sanitizers\n\tunsigned char* enc = encb + sizeof(encb) - size;\n\tmemmove(enc, encb, size);\n\n\tvalidateDecodeMeshlet<unsigned int, unsigned int, /* VS= */ 7, /* TS= */ 5>(enc, size, vertices, 7, triangles, 5);\n\n\t// decodeMeshlet uses aligned 32-bit writes => must round vertex/triangle storage up when using short/char decoding\n\t// note the +1 in triangle storage is because align(5*3, 4) = 16; it's up to +3 in general case\n\tvalidateDecodeMeshlet<unsigned int, unsigned char, /* VS= */ 7, /* TS= */ 5 * 3 + 1>(enc, size, vertices, 7, triangles, 5);\n\tvalidateDecodeMeshlet<unsigned short, unsigned int, /* VS= */ 7 + 1, /* TS= */ 5>(enc, size, vertices, 7, triangles, 5);\n\tvalidateDecodeMeshlet<unsigned short, unsigned char, /* VS= */ 7 + 1, /* TS= */ 5 * 3 + 1>(enc, size, vertices, 7, triangles, 5);\n\n\t// any truncated input should not be decodable; we check both prefix and suffix truncation\n\tunsigned int rv[7], rt[5];\n\n\tfor (size_t i = 1; i < size; ++i)\n\t\tassert(meshopt_decodeMeshlet(rv, 7, rt, 5, enc, i) < 0);\n\tfor (size_t i = 1; i < size; ++i)\n\t\tassert(meshopt_decodeMeshlet(rv, 7, rt, 5, enc + i, size - i) < 0);\n\n\t// because SIMD implementation is specialized by size, we need to test truncated inputs for short representations\n\tunsigned short rvs[7 + 1];    // 32b alignment\n\tunsigned char rts[5 * 3 + 1]; // 32b alignment\n\n\tfor (size_t i = 1; i < size; ++i)\n\t\tassert(meshopt_decodeMeshlet(rvs, 7, rts, 5, enc, i) < 0);\n\tfor (size_t i = 1; i < size; ++i)\n\t\tassert(meshopt_decodeMeshlet(rvs, 7, rts, 5, enc + i, size - i) < 0);\n\n\t// when using decodeMeshletRaw, the output buffer sizes must be 16b aligned\n\tunsigned int rvr[8], rtr[8];\n\n\tfor (size_t i = 1; i < size; ++i)\n\t\tassert(meshopt_decodeMeshletRaw(rvr, 7, rtr, 5, enc, i) < 0);\n\tfor (size_t i = 1; i < size; ++i)\n\t\tassert(meshopt_decodeMeshletRaw(rvr, 7, rtr, 5, enc + i, size - i) < 0);\n\n\t// otherwise, decodeMeshlet and decodeMeshletRaw should agree\n\tint rc = meshopt_decodeMeshlet(rv, 7, rt, 5, enc, size);\n\tint rcr = meshopt_decodeMeshletRaw(rvr, 7, rtr, 5, enc, size);\n\n\tassert(rc == 0 && rcr == 0);\n\tassert(memcmp(rv, rvr, 7 * sizeof(unsigned int)) == 0);\n\tassert(memcmp(rt, rtr, 5 * sizeof(unsigned int)) == 0);\n}\n\nstatic void decodeMeshletBasic()\n{\n\tconst unsigned char triangles[5 * 3] = {\n\t    0, 1, 2,\n\t    2, 1, 3,\n\t    4, 3, 5,\n\t    2, 0, 6,\n\t    6, 6, 6, // clang-format :-/\n\t};\n\n\tconst unsigned int vertices[7] = {\n\t    5,\n\t    12,\n\t    140,\n\t    0,\n\t    12389,\n\t    123456789,\n\t    7,\n\t};\n\n\tconst unsigned char encoded[46] = {\n\t    0x0a, 0x0c, 0xfe, 0x19, 0x01, 0xc8, 0x60, 0x00, 0x00, 0x5e, 0x39, 0xb7, 0x0e, 0x1d, 0x9a, 0xb7,\n\t    0x0e, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x05, 0x02, 0x00, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00,\n\t    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0xff, 0x2c, 0xff, 0x0f, // clang-format :-/\n\t};\n\n\tunsigned int rv[7];\n\tunsigned char rt[5 * 3 + 1];\n\tint rc = meshopt_decodeMeshlet(rv, 7, rt, 5, encoded, sizeof(encoded));\n\n\tassert(rc == 0);\n\tassert(memcmp(rv, vertices, sizeof(vertices)) == 0);\n\tassert(memcmp(rt, triangles, sizeof(triangles)) == 0);\n}\n\nstatic void decodeMeshletTypical()\n{\n\tconst unsigned char triangles[44 * 3] = {\n\t    0, 1, 2, 0, 2, 3, 3, 2, 4, 3, 4, 5, 0, 3, 6, 6, 3, 5, 0, 6, 7, 7, 6, 8,\n\t    8, 6, 5, 7, 8, 9, 8, 5, 10, 10, 5, 4, 10, 4, 11, 11, 4, 12, 11, 12, 13, 10, 11, 14,\n\t    10, 14, 8, 14, 11, 15, 15, 11, 13, 15, 13, 16, 15, 16, 14, 16, 13, 17, 14, 16, 18, 14, 18, 8,\n\t    18, 16, 19, 19, 16, 20, 20, 16, 17, 20, 17, 21, 20, 21, 22, 20, 22, 19, 22, 21, 23, 19, 22, 24,\n\t    19, 24, 25, 19, 25, 18, 18, 25, 26, 18, 26, 8, 8, 26, 9, 22, 23, 27, 22, 27, 24, 27, 23, 28,\n\t    27, 28, 29, 27, 29, 24, 29, 28, 30, 24, 29, 31, // clang-format :-/\n\t};\n\n\tconst unsigned int vertices[32] = {\n\t    10, 11, 9, 12, 8, 13, 14, 15, 16, 17, 18, 19, 0, 20, 21, 22,\n\t    23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // clang-format :-/\n\t};\n\n\tconst unsigned char encoded[53] = {\n\t    0x14, 0x05, 0x04, 0x09, 0x08, 0x27, 0x26, 0x05, 0x05, 0x04, 0x08, 0x0d, 0x0e, 0x08, 0x11, 0x13,\n\t    0x12, 0x08, 0x09, 0x16, 0x17, 0x18, 0x18, 0x0d, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0c,\n\t    0x02, 0x38, 0x24, 0x43, 0x34, 0x20, 0x80, 0x61, 0x03, 0x61, 0x16, 0x26, 0x03, 0x10, 0x66, 0x10,\n\t    0x12, 0xe3, 0x61, 0x10, 0x66, // clang-format :-/\n\t};\n\n\tunsigned int rv[32];\n\tunsigned char rt[44 * 3];\n\tint rc = meshopt_decodeMeshlet(rv, 32, rt, 44, encoded, sizeof(encoded));\n\n\tassert(rc == 0);\n\tassert(memcmp(rv, vertices, sizeof(vertices)) == 0);\n\tassert(memcmp(rt, triangles, sizeof(triangles)) == 0);\n}\n\nstatic void opacityMap()\n{\n\tconst size_t triangle_count = 6;\n\tconst size_t vertex_count = 8;\n\n\tconst unsigned int indices[triangle_count * 3] = {\n\t    // 4 corner triangles\n\t    0, 4, 7,\n\t    1, 5, 4,\n\t    2, 6, 5,\n\t    3, 7, 6,\n\n\t    // 2 center triangles (2x larger)\n\t    4, 6, 5, // note: this triangle is flipped from its correct orientation to produce the same OMM to test compaction\n\t    4, 6, 7, // clang-format :-/\n\t};\n\n\tconst float uvs[vertex_count * 2] = {\n\t    0.f, 0.f,\n\t    1.f, 0.f,\n\t    1.f, 1.f,\n\t    0.f, 1.f,\n\t    0.5f, 0.f,\n\t    1.f, 0.5f,\n\t    0.5f, 1.f,\n\t    0.f, 0.5f, // clang-format :-/\n\t};\n\n\tconst unsigned int texture_size = 32;\n\tunsigned char texture[texture_size * texture_size];\n\n\tfloat center = float(texture_size) * 0.5f;\n\tfloat radius = 10.f;\n\n\tfor (unsigned int y = 0; y < texture_size; ++y)\n\t\tfor (unsigned int x = 0; x < texture_size; ++x)\n\t\t{\n\t\t\tfloat dx = float(x) + 0.5f - center;\n\t\t\tfloat dy = float(y) + 0.5f - center;\n\t\t\tfloat dc = radius - sqrtf(dx * dx + dy * dy);\n\n\t\t\ttexture[y * texture_size + x] = (unsigned char)meshopt_quantizeUnorm(dc + 0.5f, 8);\n\t\t}\n\n\t// subdivision parameterrs\n\tconst float target_edge = 2.5f;\n\tconst int max_level = 4;\n\n\t// state histogram for testing\n\tint histogram[2][4] = {};\n\n\tfor (int k = 0; k < 2; ++k)\n\t{\n\t\tint states = 2 << k;\n\n\t\tunsigned char levels[triangle_count];\n\t\tunsigned int sources[triangle_count];\n\t\tint omm_indices[triangle_count];\n\n\t\t// 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\n\t\tsize_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);\n\t\tassert(omm_count <= triangle_count);\n\n\t\t// validate expected levels/special indices based on underlying test data; depends on implementation specifics, might change\n\t\tassert(levels[0] == 2 && levels[1] == 2 && levels[2] == 2 && levels[3] == 2);\n\t\tassert(levels[4] == 3 && levels[5] == 3);\n\n\t\t// layout OMM data\n\t\tstd::vector<unsigned int> offsets(omm_count);\n\t\tsize_t data_size = 0;\n\n\t\tfor (size_t i = 0; i < omm_count; ++i)\n\t\t{\n\t\t\toffsets[i] = unsigned(data_size);\n\t\t\tdata_size += meshopt_opacityMapEntrySize(levels[i], states);\n\t\t}\n\n\t\tstd::vector<unsigned char> data(data_size);\n\n\t\tfor (size_t i = 0; i < omm_count; ++i)\n\t\t{\n\t\t\tunsigned int tri = sources[i];\n\t\t\tassert(tri < triangle_count);\n\n\t\t\tconst float* uv0 = &uvs[indices[tri * 3 + 0] * 2];\n\t\t\tconst float* uv1 = &uvs[indices[tri * 3 + 1] * 2];\n\t\t\tconst float* uv2 = &uvs[indices[tri * 3 + 2] * 2];\n\n\t\t\tmeshopt_opacityMapRasterize(&data[offsets[i]], levels[i], states, uv0, uv1, uv2, texture, 1, texture_size, texture_size, texture_size);\n\n\t\t\tsize_t micro_triangle_count = size_t(1) << (levels[i] * 2);\n\n\t\t\tfor (size_t j = 0; j < micro_triangle_count; ++j)\n\t\t\t{\n\t\t\t\tunsigned char byte = data[offsets[i] + (j >> (states == 2 ? 3 : 2))];\n\t\t\t\tint state = (byte >> (states == 2 ? j & 7 : (j & 3) * 2)) & (states - 1);\n\t\t\t\thistogram[k][state]++;\n\t\t\t}\n\t\t}\n\n\t\t// 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\n\t\tsize_t compact_count = meshopt_opacityMapCompact(&data[0], data.size(), levels, &offsets[0], omm_count, omm_indices, triangle_count, states);\n\t\tassert(compact_count <= omm_count);\n\t\tdata.resize(compact_count == 0 ? 0 : offsets[compact_count - 1] + meshopt_opacityMapEntrySize(levels[compact_count - 1], states));\n\n\t\t// after compaction, some OMM indices may be replaced with a special index (-4..-1)\n\t\tfor (size_t i = 0; i < triangle_count; ++i)\n\t\t\tassert(omm_indices[i] < 0 || size_t(omm_indices[i]) < compact_count);\n\n\t\t// validate expected levels/special indices based on underlying test data; depends on implementation specifics, might change\n\t\tassert(levels[0] == 3 && levels[1] == 3); // note: OMM data got compacted so we only have 3-level OMMs left\n\t\tassert(omm_indices[0] == -1 && omm_indices[1] == -1 && omm_indices[2] == -1 && omm_indices[3] == -1);\n\t\tassert(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\n\t}\n\n\t// validate expected histogram based on underlying test data; depends on rasterization specifics, might change\n\tassert(histogram[0][0] == histogram[1][0] + histogram[1][2]);\n\tassert(histogram[0][1] == histogram[1][1] + histogram[1][3]);\n\n\tfloat opaque = float(histogram[0][1]) / float(histogram[0][0] + histogram[0][1]);\n\tfloat known = float(histogram[1][0] + histogram[1][1]) / float(histogram[1][0] + histogram[1][1] + histogram[1][2] + histogram[1][3]);\n\n\tassert(fabsf(opaque - 0.36f) < 1e-2f);\n\tassert(fabsf(known - 0.76f) < 1e-2f);\n}\n\nvoid runTests()\n{\n\tdecodeIndexV0();\n\tdecodeIndexV1();\n\tdecodeIndexV1More();\n\tdecodeIndexV1ThreeEdges();\n\tdecodeIndex16();\n\tencodeIndexMemorySafe();\n\tdecodeIndexMemorySafe();\n\tdecodeIndexRejectExtraBytes();\n\tdecodeIndexRejectMalformedHeaders();\n\tdecodeIndexRejectInvalidVersion();\n\tdecodeIndexMalformedVByte();\n\troundtripIndexTricky();\n\tencodeIndexEmpty();\n\n\tdecodeIndexSequence();\n\tdecodeIndexSequence16();\n\tencodeIndexSequenceMemorySafe();\n\tdecodeIndexSequenceMemorySafe();\n\tdecodeIndexSequenceRejectExtraBytes();\n\tdecodeIndexSequenceRejectMalformedHeaders();\n\tdecodeIndexSequenceRejectInvalidVersion();\n\tencodeIndexSequenceEmpty();\n\n\tdecodeVertexV0();\n\tdecodeVertexV0More();\n\tdecodeVertexV0Mode2();\n\tdecodeVertexV1();\n\tdecodeVertexV1Custom();\n\tdecodeVertexV1Deltas();\n\n\tfor (int version = 0; version <= 1; ++version)\n\t{\n\t\tmeshopt_encodeVertexVersion(version);\n\n\t\tdecodeVertexMemorySafe();\n\t\tdecodeVertexRejectExtraBytes();\n\t\tdecodeVertexRejectMalformedHeaders();\n\t\tdecodeVertexBitGroups();\n\t\tdecodeVertexBitGroupSentinels();\n\t\tdecodeVertexDeltas();\n\t\tdecodeVertexBitXor();\n\t\tdecodeVertexLarge();\n\t\tdecodeVertexSmall();\n\t\tencodeVertexEmpty();\n\t\tencodeVertexMemorySafe();\n\t}\n\n\tdecodeVersion();\n\n\tdecodeFilterOct8();\n\tdecodeFilterOct12();\n\tdecodeFilterQuat12();\n\tdecodeFilterExp();\n\n\tencodeFilterOct8();\n\tencodeFilterOct12();\n\tencodeFilterQuat12();\n\tencodeFilterExp();\n\tencodeFilterExpZero();\n\tencodeFilterExpAlias();\n\tencodeFilterExpClamp();\n\tencodeFilterColor8();\n\tencodeFilterColor12();\n\n\tclusterBoundsDegenerate();\n\tsphereBounds();\n\n\textractMeshlet();\n\n\tmeshletsEmpty();\n\tmeshletsDense();\n\tmeshletsSparse();\n\tmeshletsFlex();\n\tmeshletsMax();\n\tmeshletsSpatial();\n\tmeshletsSpatialDeep();\n\n\tpartitionBasic();\n\tpartitionSpatial();\n\tpartitionSpatialMerge();\n\n\tremapCustom();\n\n\tcustomAllocator();\n\n\temptyMesh();\n\n\tsimplify();\n\tsimplifyStuck();\n\tsimplifySloppyStuck();\n\tsimplifySloppyLocks();\n\tsimplifyPointsStuck();\n\tsimplifyFlip();\n\tsimplifyScale();\n\tsimplifyDegenerate();\n\tsimplifyLockBorder();\n\tsimplifyAttr(/* skip_g= */ false);\n\tsimplifyAttr(/* skip_g= */ true);\n\tsimplifyLockFlags();\n\tsimplifyLockFlagsSeam();\n\tsimplifySparse();\n\tsimplifyErrorAbsolute();\n\tsimplifySeam();\n\tsimplifySeamFake();\n\tsimplifySeamAttr();\n\tsimplifyDebug();\n\tsimplifyPrune();\n\tsimplifyPruneCleanup();\n\tsimplifyPruneFunc();\n\tsimplifyUpdate();\n\tsimplifyUpdateLocked(0);\n\tsimplifyUpdateLocked(meshopt_SimplifySparse);\n\n\tadjacency();\n\ttessellation();\n\tprovoking();\n\n\tquantizeFloat();\n\tquantizeHalf();\n\tdequantizeHalf();\n\n\tencodeMeshletBound();\n\tdecodeMeshletSafety();\n\tdecodeMeshletBasic();\n\tdecodeMeshletTypical();\n\n\topacityMap();\n}\n"
  },
  {
    "path": "extern/.clang-format",
    "content": "DisableFormat: true\n"
  },
  {
    "path": "extern/cgltf.h",
    "content": "/**\n * cgltf - a single-file glTF 2.0 parser written in C99.\n *\n * Version: 1.15\n *\n * Website: https://github.com/jkuhlmann/cgltf\n *\n * Distributed under the MIT License, see notice at the end of this file.\n *\n * Building:\n * Include this file where you need the struct and function\n * declarations. Have exactly one source file where you define\n * `CGLTF_IMPLEMENTATION` before including this file to get the\n * function definitions.\n *\n * Reference:\n * `cgltf_result cgltf_parse(const cgltf_options*, const void*,\n * cgltf_size, cgltf_data**)` parses both glTF and GLB data. If\n * this function returns `cgltf_result_success`, you have to call\n * `cgltf_free()` on the created `cgltf_data*` variable.\n * Note that contents of external files for buffers and images are not\n * automatically loaded. You'll need to read these files yourself using\n * URIs in the `cgltf_data` structure.\n *\n * `cgltf_options` is the struct passed to `cgltf_parse()` to control\n * parts of the parsing process. You can use it to force the file type\n * and provide memory allocation as well as file operation callbacks.\n * Should be zero-initialized to trigger default behavior.\n *\n * `cgltf_data` is the struct allocated and filled by `cgltf_parse()`.\n * It generally mirrors the glTF format as described by the spec (see\n * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0).\n *\n * `void cgltf_free(cgltf_data*)` frees the allocated `cgltf_data`\n * variable.\n *\n * `cgltf_result cgltf_load_buffers(const cgltf_options*, cgltf_data*,\n * const char* gltf_path)` can be optionally called to open and read buffer\n * files using the `FILE*` APIs. The `gltf_path` argument is the path to\n * the original glTF file, which allows the parser to resolve the path to\n * buffer files.\n *\n * `cgltf_result cgltf_load_buffer_base64(const cgltf_options* options,\n * cgltf_size size, const char* base64, void** out_data)` decodes\n * base64-encoded data content. Used internally by `cgltf_load_buffers()`.\n * This is useful when decoding data URIs in images.\n *\n * `cgltf_result cgltf_parse_file(const cgltf_options* options, const\n * char* path, cgltf_data** out_data)` can be used to open the given\n * file using `FILE*` APIs and parse the data using `cgltf_parse()`.\n *\n * `cgltf_result cgltf_validate(cgltf_data*)` can be used to do additional\n * checks to make sure the parsed glTF data is valid.\n *\n * `cgltf_node_transform_local` converts the translation / rotation / scale properties of a node\n * into a mat4.\n *\n * `cgltf_node_transform_world` calls `cgltf_node_transform_local` on every ancestor in order\n * to compute the root-to-node transformation.\n *\n * `cgltf_accessor_unpack_floats` reads in the data from an accessor, applies sparse data (if any),\n * and converts them to floating point. Assumes that `cgltf_load_buffers` has already been called.\n * By passing null for the output pointer, users can find out how many floats are required in the\n * output buffer.\n *\n * `cgltf_accessor_unpack_indices` reads in the index data from an accessor. Assumes that\n * `cgltf_load_buffers` has already been called. By passing null for the output pointer, users can\n * find out how many indices are required in the output buffer. Returns 0 if the accessor is\n * sparse or if the output component size is less than the accessor's component size.\n *\n * `cgltf_num_components` is a tiny utility that tells you the dimensionality of\n * a certain accessor type. This can be used before `cgltf_accessor_unpack_floats` to help allocate\n * the necessary amount of memory. `cgltf_component_size` and `cgltf_calc_size` exist for\n * similar purposes.\n *\n * `cgltf_accessor_read_float` reads a certain element from a non-sparse accessor and converts it to\n * floating point, assuming that `cgltf_load_buffers` has already been called. The passed-in element\n * size is the number of floats in the output buffer, which should be in the range [1, 16]. Returns\n * false if the passed-in element_size is too small, or if the accessor is sparse.\n *\n * `cgltf_accessor_read_uint` is similar to its floating-point counterpart, but limited to reading\n * vector types and does not support matrix types. The passed-in element size is the number of uints\n * in the output buffer, which should be in the range [1, 4]. Returns false if the passed-in\n * element_size is too small, or if the accessor is sparse.\n *\n * `cgltf_accessor_read_index` is similar to its floating-point counterpart, but it returns size_t\n * and only works with single-component data types.\n *\n * `cgltf_copy_extras_json` allows users to retrieve the \"extras\" data that can be attached to many\n * glTF objects (which can be arbitrary JSON data). This is a legacy function, consider using\n * cgltf_extras::data directly instead. You can parse this data using your own JSON parser\n * or, if you've included the cgltf implementation using the integrated JSMN JSON parser.\n */\n#ifndef CGLTF_H_INCLUDED__\n#define CGLTF_H_INCLUDED__\n\n#include <stddef.h>\n#include <stdint.h> /* For uint8_t, uint32_t */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef size_t cgltf_size;\ntypedef long long int cgltf_ssize;\ntypedef float cgltf_float;\ntypedef int cgltf_int;\ntypedef unsigned int cgltf_uint;\ntypedef int cgltf_bool;\n\ntypedef enum cgltf_file_type\n{\n\tcgltf_file_type_invalid,\n\tcgltf_file_type_gltf,\n\tcgltf_file_type_glb,\n\tcgltf_file_type_max_enum\n} cgltf_file_type;\n\ntypedef enum cgltf_result\n{\n\tcgltf_result_success,\n\tcgltf_result_data_too_short,\n\tcgltf_result_unknown_format,\n\tcgltf_result_invalid_json,\n\tcgltf_result_invalid_gltf,\n\tcgltf_result_invalid_options,\n\tcgltf_result_file_not_found,\n\tcgltf_result_io_error,\n\tcgltf_result_out_of_memory,\n\tcgltf_result_legacy_gltf,\n    cgltf_result_max_enum\n} cgltf_result;\n\ntypedef struct cgltf_memory_options\n{\n\tvoid* (*alloc_func)(void* user, cgltf_size size);\n\tvoid (*free_func) (void* user, void* ptr);\n\tvoid* user_data;\n} cgltf_memory_options;\n\ntypedef struct cgltf_file_options\n{\n\tcgltf_result(*read)(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, const char* path, cgltf_size* size, void** data);\n\tvoid (*release)(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, void* data, cgltf_size size);\n\tvoid* user_data;\n} cgltf_file_options;\n\ntypedef struct cgltf_options\n{\n\tcgltf_file_type type; /* invalid == auto detect */\n\tcgltf_size json_token_count; /* 0 == auto */\n\tcgltf_memory_options memory;\n\tcgltf_file_options file;\n} cgltf_options;\n\ntypedef enum cgltf_buffer_view_type\n{\n\tcgltf_buffer_view_type_invalid,\n\tcgltf_buffer_view_type_indices,\n\tcgltf_buffer_view_type_vertices,\n\tcgltf_buffer_view_type_max_enum\n} cgltf_buffer_view_type;\n\ntypedef enum cgltf_attribute_type\n{\n\tcgltf_attribute_type_invalid,\n\tcgltf_attribute_type_position,\n\tcgltf_attribute_type_normal,\n\tcgltf_attribute_type_tangent,\n\tcgltf_attribute_type_texcoord,\n\tcgltf_attribute_type_color,\n\tcgltf_attribute_type_joints,\n\tcgltf_attribute_type_weights,\n\tcgltf_attribute_type_custom,\n\tcgltf_attribute_type_max_enum\n} cgltf_attribute_type;\n\ntypedef enum cgltf_component_type\n{\n\tcgltf_component_type_invalid,\n\tcgltf_component_type_r_8, /* BYTE */\n\tcgltf_component_type_r_8u, /* UNSIGNED_BYTE */\n\tcgltf_component_type_r_16, /* SHORT */\n\tcgltf_component_type_r_16u, /* UNSIGNED_SHORT */\n\tcgltf_component_type_r_32u, /* UNSIGNED_INT */\n\tcgltf_component_type_r_32f, /* FLOAT */\n    cgltf_component_type_max_enum\n} cgltf_component_type;\n\ntypedef enum cgltf_type\n{\n\tcgltf_type_invalid,\n\tcgltf_type_scalar,\n\tcgltf_type_vec2,\n\tcgltf_type_vec3,\n\tcgltf_type_vec4,\n\tcgltf_type_mat2,\n\tcgltf_type_mat3,\n\tcgltf_type_mat4,\n\tcgltf_type_max_enum\n} cgltf_type;\n\ntypedef enum cgltf_primitive_type\n{\n\tcgltf_primitive_type_invalid,\n\tcgltf_primitive_type_points,\n\tcgltf_primitive_type_lines,\n\tcgltf_primitive_type_line_loop,\n\tcgltf_primitive_type_line_strip,\n\tcgltf_primitive_type_triangles,\n\tcgltf_primitive_type_triangle_strip,\n\tcgltf_primitive_type_triangle_fan,\n\tcgltf_primitive_type_max_enum\n} cgltf_primitive_type;\n\ntypedef enum cgltf_alpha_mode\n{\n\tcgltf_alpha_mode_opaque,\n\tcgltf_alpha_mode_mask,\n\tcgltf_alpha_mode_blend,\n\tcgltf_alpha_mode_max_enum\n} cgltf_alpha_mode;\n\ntypedef enum cgltf_animation_path_type {\n\tcgltf_animation_path_type_invalid,\n\tcgltf_animation_path_type_translation,\n\tcgltf_animation_path_type_rotation,\n\tcgltf_animation_path_type_scale,\n\tcgltf_animation_path_type_weights,\n\tcgltf_animation_path_type_max_enum\n} cgltf_animation_path_type;\n\ntypedef enum cgltf_interpolation_type {\n\tcgltf_interpolation_type_linear,\n\tcgltf_interpolation_type_step,\n\tcgltf_interpolation_type_cubic_spline,\n\tcgltf_interpolation_type_max_enum\n} cgltf_interpolation_type;\n\ntypedef enum cgltf_camera_type {\n\tcgltf_camera_type_invalid,\n\tcgltf_camera_type_perspective,\n\tcgltf_camera_type_orthographic,\n\tcgltf_camera_type_max_enum\n} cgltf_camera_type;\n\ntypedef enum cgltf_light_type {\n\tcgltf_light_type_invalid,\n\tcgltf_light_type_directional,\n\tcgltf_light_type_point,\n\tcgltf_light_type_spot,\n\tcgltf_light_type_max_enum\n} cgltf_light_type;\n\ntypedef enum cgltf_data_free_method {\n\tcgltf_data_free_method_none,\n\tcgltf_data_free_method_file_release,\n\tcgltf_data_free_method_memory_free,\n\tcgltf_data_free_method_max_enum\n} cgltf_data_free_method;\n\ntypedef struct cgltf_extras {\n\tcgltf_size start_offset; /* this field is deprecated and will be removed in the future; use data instead */\n\tcgltf_size end_offset; /* this field is deprecated and will be removed in the future; use data instead */\n\n\tchar* data;\n} cgltf_extras;\n\ntypedef struct cgltf_extension {\n\tchar* name;\n\tchar* data;\n} cgltf_extension;\n\ntypedef struct cgltf_buffer\n{\n\tchar* name;\n\tcgltf_size size;\n\tchar* uri;\n\tvoid* data; /* loaded by cgltf_load_buffers */\n\tcgltf_data_free_method data_free_method;\n\tcgltf_extras extras;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_buffer;\n\ntypedef enum cgltf_meshopt_compression_mode {\n\tcgltf_meshopt_compression_mode_invalid,\n\tcgltf_meshopt_compression_mode_attributes,\n\tcgltf_meshopt_compression_mode_triangles,\n\tcgltf_meshopt_compression_mode_indices,\n\tcgltf_meshopt_compression_mode_max_enum\n} cgltf_meshopt_compression_mode;\n\ntypedef enum cgltf_meshopt_compression_filter {\n\tcgltf_meshopt_compression_filter_none,\n\tcgltf_meshopt_compression_filter_octahedral,\n\tcgltf_meshopt_compression_filter_quaternion,\n\tcgltf_meshopt_compression_filter_exponential,\n\tcgltf_meshopt_compression_filter_color,\n\tcgltf_meshopt_compression_filter_max_enum\n} cgltf_meshopt_compression_filter;\n\ntypedef struct cgltf_meshopt_compression\n{\n\tcgltf_buffer* buffer;\n\tcgltf_size offset;\n\tcgltf_size size;\n\tcgltf_size stride;\n\tcgltf_size count;\n\tcgltf_meshopt_compression_mode mode;\n\tcgltf_meshopt_compression_filter filter;\n\tcgltf_bool is_khr;\n} cgltf_meshopt_compression;\n\ntypedef struct cgltf_buffer_view\n{\n\tchar *name;\n\tcgltf_buffer* buffer;\n\tcgltf_size offset;\n\tcgltf_size size;\n\tcgltf_size stride; /* 0 == automatically determined by accessor */\n\tcgltf_buffer_view_type type;\n\tvoid* data; /* overrides buffer->data if present, filled by extensions */\n\tcgltf_bool has_meshopt_compression;\n\tcgltf_meshopt_compression meshopt_compression;\n\tcgltf_extras extras;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_buffer_view;\n\ntypedef struct cgltf_accessor_sparse\n{\n\tcgltf_size count;\n\tcgltf_buffer_view* indices_buffer_view;\n\tcgltf_size indices_byte_offset;\n\tcgltf_component_type indices_component_type;\n\tcgltf_buffer_view* values_buffer_view;\n\tcgltf_size values_byte_offset;\n} cgltf_accessor_sparse;\n\ntypedef struct cgltf_accessor\n{\n\tchar* name;\n\tcgltf_component_type component_type;\n\tcgltf_bool normalized;\n\tcgltf_type type;\n\tcgltf_size offset;\n\tcgltf_size count;\n\tcgltf_size stride;\n\tcgltf_buffer_view* buffer_view;\n\tcgltf_bool has_min;\n\tcgltf_float min[16];\n\tcgltf_bool has_max;\n\tcgltf_float max[16];\n\tcgltf_bool is_sparse;\n\tcgltf_accessor_sparse sparse;\n\tcgltf_extras extras;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_accessor;\n\ntypedef struct cgltf_attribute\n{\n\tchar* name;\n\tcgltf_attribute_type type;\n\tcgltf_int index;\n\tcgltf_accessor* data;\n} cgltf_attribute;\n\ntypedef struct cgltf_image\n{\n\tchar* name;\n\tchar* uri;\n\tcgltf_buffer_view* buffer_view;\n\tchar* mime_type;\n\tcgltf_extras extras;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_image;\n\ntypedef enum cgltf_filter_type {\n    cgltf_filter_type_undefined = 0,\n    cgltf_filter_type_nearest = 9728,\n    cgltf_filter_type_linear = 9729,\n    cgltf_filter_type_nearest_mipmap_nearest = 9984,\n    cgltf_filter_type_linear_mipmap_nearest = 9985,\n    cgltf_filter_type_nearest_mipmap_linear = 9986,\n    cgltf_filter_type_linear_mipmap_linear = 9987\n} cgltf_filter_type;\n\ntypedef enum cgltf_wrap_mode {\n    cgltf_wrap_mode_clamp_to_edge = 33071,\n    cgltf_wrap_mode_mirrored_repeat = 33648,\n    cgltf_wrap_mode_repeat = 10497\n} cgltf_wrap_mode;\n\ntypedef struct cgltf_sampler\n{\n\tchar* name;\n\tcgltf_filter_type mag_filter;\n\tcgltf_filter_type min_filter;\n\tcgltf_wrap_mode wrap_s;\n\tcgltf_wrap_mode wrap_t;\n\tcgltf_extras extras;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_sampler;\n\ntypedef struct cgltf_texture\n{\n\tchar* name;\n\tcgltf_image* image;\n\tcgltf_sampler* sampler;\n\tcgltf_bool has_basisu;\n\tcgltf_image* basisu_image;\n\tcgltf_bool has_webp;\n\tcgltf_image* webp_image;\n\tcgltf_extras extras;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_texture;\n\ntypedef struct cgltf_texture_transform\n{\n\tcgltf_float offset[2];\n\tcgltf_float rotation;\n\tcgltf_float scale[2];\n\tcgltf_bool has_texcoord;\n\tcgltf_int texcoord;\n} cgltf_texture_transform;\n\ntypedef struct cgltf_texture_view\n{\n\tcgltf_texture* texture;\n\tcgltf_int texcoord;\n\tcgltf_float scale; /* equivalent to strength for occlusion_texture */\n\tcgltf_bool has_transform;\n\tcgltf_texture_transform transform;\n} cgltf_texture_view;\n\ntypedef struct cgltf_pbr_metallic_roughness\n{\n\tcgltf_texture_view base_color_texture;\n\tcgltf_texture_view metallic_roughness_texture;\n\n\tcgltf_float base_color_factor[4];\n\tcgltf_float metallic_factor;\n\tcgltf_float roughness_factor;\n} cgltf_pbr_metallic_roughness;\n\ntypedef struct cgltf_pbr_specular_glossiness\n{\n\tcgltf_texture_view diffuse_texture;\n\tcgltf_texture_view specular_glossiness_texture;\n\n\tcgltf_float diffuse_factor[4];\n\tcgltf_float specular_factor[3];\n\tcgltf_float glossiness_factor;\n} cgltf_pbr_specular_glossiness;\n\ntypedef struct cgltf_clearcoat\n{\n\tcgltf_texture_view clearcoat_texture;\n\tcgltf_texture_view clearcoat_roughness_texture;\n\tcgltf_texture_view clearcoat_normal_texture;\n\n\tcgltf_float clearcoat_factor;\n\tcgltf_float clearcoat_roughness_factor;\n} cgltf_clearcoat;\n\ntypedef struct cgltf_transmission\n{\n\tcgltf_texture_view transmission_texture;\n\tcgltf_float transmission_factor;\n} cgltf_transmission;\n\ntypedef struct cgltf_ior\n{\n\tcgltf_float ior;\n} cgltf_ior;\n\ntypedef struct cgltf_specular\n{\n\tcgltf_texture_view specular_texture;\n\tcgltf_texture_view specular_color_texture;\n\tcgltf_float specular_color_factor[3];\n\tcgltf_float specular_factor;\n} cgltf_specular;\n\ntypedef struct cgltf_volume\n{\n\tcgltf_texture_view thickness_texture;\n\tcgltf_float thickness_factor;\n\tcgltf_float attenuation_color[3];\n\tcgltf_float attenuation_distance;\n} cgltf_volume;\n\ntypedef struct cgltf_sheen\n{\n\tcgltf_texture_view sheen_color_texture;\n\tcgltf_float sheen_color_factor[3];\n\tcgltf_texture_view sheen_roughness_texture;\n\tcgltf_float sheen_roughness_factor;\n} cgltf_sheen;\n\ntypedef struct cgltf_emissive_strength\n{\n\tcgltf_float emissive_strength;\n} cgltf_emissive_strength;\n\ntypedef struct cgltf_iridescence\n{\n\tcgltf_float iridescence_factor;\n\tcgltf_texture_view iridescence_texture;\n\tcgltf_float iridescence_ior;\n\tcgltf_float iridescence_thickness_min;\n\tcgltf_float iridescence_thickness_max;\n\tcgltf_texture_view iridescence_thickness_texture;\n} cgltf_iridescence;\n\ntypedef struct cgltf_diffuse_transmission\n{\n\tcgltf_texture_view diffuse_transmission_texture;\n\tcgltf_float diffuse_transmission_factor;\n\tcgltf_float diffuse_transmission_color_factor[3];\n\tcgltf_texture_view diffuse_transmission_color_texture;\n} cgltf_diffuse_transmission;\n\ntypedef struct cgltf_anisotropy\n{\n\tcgltf_float anisotropy_strength;\n\tcgltf_float anisotropy_rotation;\n\tcgltf_texture_view anisotropy_texture;\n} cgltf_anisotropy;\n\ntypedef struct cgltf_dispersion\n{\n\tcgltf_float dispersion;\n} cgltf_dispersion;\n\ntypedef struct cgltf_material\n{\n\tchar* name;\n\tcgltf_bool has_pbr_metallic_roughness;\n\tcgltf_bool has_pbr_specular_glossiness;\n\tcgltf_bool has_clearcoat;\n\tcgltf_bool has_transmission;\n\tcgltf_bool has_volume;\n\tcgltf_bool has_ior;\n\tcgltf_bool has_specular;\n\tcgltf_bool has_sheen;\n\tcgltf_bool has_emissive_strength;\n\tcgltf_bool has_iridescence;\n\tcgltf_bool has_diffuse_transmission;\n\tcgltf_bool has_anisotropy;\n\tcgltf_bool has_dispersion;\n\tcgltf_pbr_metallic_roughness pbr_metallic_roughness;\n\tcgltf_pbr_specular_glossiness pbr_specular_glossiness;\n\tcgltf_clearcoat clearcoat;\n\tcgltf_ior ior;\n\tcgltf_specular specular;\n\tcgltf_sheen sheen;\n\tcgltf_transmission transmission;\n\tcgltf_volume volume;\n\tcgltf_emissive_strength emissive_strength;\n\tcgltf_iridescence iridescence;\n\tcgltf_diffuse_transmission diffuse_transmission;\n\tcgltf_anisotropy anisotropy;\n\tcgltf_dispersion dispersion;\n\tcgltf_texture_view normal_texture;\n\tcgltf_texture_view occlusion_texture;\n\tcgltf_texture_view emissive_texture;\n\tcgltf_float emissive_factor[3];\n\tcgltf_alpha_mode alpha_mode;\n\tcgltf_float alpha_cutoff;\n\tcgltf_bool double_sided;\n\tcgltf_bool unlit;\n\tcgltf_extras extras;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_material;\n\ntypedef struct cgltf_material_mapping\n{\n\tcgltf_size variant;\n\tcgltf_material* material;\n\tcgltf_extras extras;\n} cgltf_material_mapping;\n\ntypedef struct cgltf_morph_target {\n\tcgltf_attribute* attributes;\n\tcgltf_size attributes_count;\n} cgltf_morph_target;\n\ntypedef struct cgltf_draco_mesh_compression {\n\tcgltf_buffer_view* buffer_view;\n\tcgltf_attribute* attributes;\n\tcgltf_size attributes_count;\n} cgltf_draco_mesh_compression;\n\ntypedef struct cgltf_mesh_gpu_instancing {\n\tcgltf_attribute* attributes;\n\tcgltf_size attributes_count;\n} cgltf_mesh_gpu_instancing;\n\ntypedef struct cgltf_primitive {\n\tcgltf_primitive_type type;\n\tcgltf_accessor* indices;\n\tcgltf_material* material;\n\tcgltf_attribute* attributes;\n\tcgltf_size attributes_count;\n\tcgltf_morph_target* targets;\n\tcgltf_size targets_count;\n\tcgltf_extras extras;\n\tcgltf_bool has_draco_mesh_compression;\n\tcgltf_draco_mesh_compression draco_mesh_compression;\n\tcgltf_material_mapping* mappings;\n\tcgltf_size mappings_count;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_primitive;\n\ntypedef struct cgltf_mesh {\n\tchar* name;\n\tcgltf_primitive* primitives;\n\tcgltf_size primitives_count;\n\tcgltf_float* weights;\n\tcgltf_size weights_count;\n\tchar** target_names;\n\tcgltf_size target_names_count;\n\tcgltf_extras extras;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_mesh;\n\ntypedef struct cgltf_node cgltf_node;\n\ntypedef struct cgltf_skin {\n\tchar* name;\n\tcgltf_node** joints;\n\tcgltf_size joints_count;\n\tcgltf_node* skeleton;\n\tcgltf_accessor* inverse_bind_matrices;\n\tcgltf_extras extras;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_skin;\n\ntypedef struct cgltf_camera_perspective {\n\tcgltf_bool has_aspect_ratio;\n\tcgltf_float aspect_ratio;\n\tcgltf_float yfov;\n\tcgltf_bool has_zfar;\n\tcgltf_float zfar;\n\tcgltf_float znear;\n\tcgltf_extras extras;\n} cgltf_camera_perspective;\n\ntypedef struct cgltf_camera_orthographic {\n\tcgltf_float xmag;\n\tcgltf_float ymag;\n\tcgltf_float zfar;\n\tcgltf_float znear;\n\tcgltf_extras extras;\n} cgltf_camera_orthographic;\n\ntypedef struct cgltf_camera {\n\tchar* name;\n\tcgltf_camera_type type;\n\tunion {\n\t\tcgltf_camera_perspective perspective;\n\t\tcgltf_camera_orthographic orthographic;\n\t} data;\n\tcgltf_extras extras;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_camera;\n\ntypedef struct cgltf_light {\n\tchar* name;\n\tcgltf_float color[3];\n\tcgltf_float intensity;\n\tcgltf_light_type type;\n\tcgltf_float range;\n\tcgltf_float spot_inner_cone_angle;\n\tcgltf_float spot_outer_cone_angle;\n\tcgltf_extras extras;\n} cgltf_light;\n\nstruct cgltf_node {\n\tchar* name;\n\tcgltf_node* parent;\n\tcgltf_node** children;\n\tcgltf_size children_count;\n\tcgltf_skin* skin;\n\tcgltf_mesh* mesh;\n\tcgltf_camera* camera;\n\tcgltf_light* light;\n\tcgltf_float* weights;\n\tcgltf_size weights_count;\n\tcgltf_bool has_translation;\n\tcgltf_bool has_rotation;\n\tcgltf_bool has_scale;\n\tcgltf_bool has_matrix;\n\tcgltf_float translation[3];\n\tcgltf_float rotation[4];\n\tcgltf_float scale[3];\n\tcgltf_float matrix[16];\n\tcgltf_extras extras;\n\tcgltf_bool has_mesh_gpu_instancing;\n\tcgltf_mesh_gpu_instancing mesh_gpu_instancing;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n};\n\ntypedef struct cgltf_scene {\n\tchar* name;\n\tcgltf_node** nodes;\n\tcgltf_size nodes_count;\n\tcgltf_extras extras;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_scene;\n\ntypedef struct cgltf_animation_sampler {\n\tcgltf_accessor* input;\n\tcgltf_accessor* output;\n\tcgltf_interpolation_type interpolation;\n\tcgltf_extras extras;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_animation_sampler;\n\ntypedef struct cgltf_animation_channel {\n\tcgltf_animation_sampler* sampler;\n\tcgltf_node* target_node;\n\tcgltf_animation_path_type target_path;\n\tcgltf_extras extras;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_animation_channel;\n\ntypedef struct cgltf_animation {\n\tchar* name;\n\tcgltf_animation_sampler* samplers;\n\tcgltf_size samplers_count;\n\tcgltf_animation_channel* channels;\n\tcgltf_size channels_count;\n\tcgltf_extras extras;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_animation;\n\ntypedef struct cgltf_material_variant\n{\n\tchar* name;\n\tcgltf_extras extras;\n} cgltf_material_variant;\n\ntypedef struct cgltf_asset {\n\tchar* copyright;\n\tchar* generator;\n\tchar* version;\n\tchar* min_version;\n\tcgltf_extras extras;\n\tcgltf_size extensions_count;\n\tcgltf_extension* extensions;\n} cgltf_asset;\n\ntypedef struct cgltf_data\n{\n\tcgltf_file_type file_type;\n\tvoid* file_data;\n\tcgltf_size file_size;\n\n\tcgltf_asset asset;\n\n\tcgltf_mesh* meshes;\n\tcgltf_size meshes_count;\n\n\tcgltf_material* materials;\n\tcgltf_size materials_count;\n\n\tcgltf_accessor* accessors;\n\tcgltf_size accessors_count;\n\n\tcgltf_buffer_view* buffer_views;\n\tcgltf_size buffer_views_count;\n\n\tcgltf_buffer* buffers;\n\tcgltf_size buffers_count;\n\n\tcgltf_image* images;\n\tcgltf_size images_count;\n\n\tcgltf_texture* textures;\n\tcgltf_size textures_count;\n\n\tcgltf_sampler* samplers;\n\tcgltf_size samplers_count;\n\n\tcgltf_skin* skins;\n\tcgltf_size skins_count;\n\n\tcgltf_camera* cameras;\n\tcgltf_size cameras_count;\n\n\tcgltf_light* lights;\n\tcgltf_size lights_count;\n\n\tcgltf_node* nodes;\n\tcgltf_size nodes_count;\n\n\tcgltf_scene* scenes;\n\tcgltf_size scenes_count;\n\n\tcgltf_scene* scene;\n\n\tcgltf_animation* animations;\n\tcgltf_size animations_count;\n\n\tcgltf_material_variant* variants;\n\tcgltf_size variants_count;\n\n\tcgltf_extras extras;\n\n\tcgltf_size data_extensions_count;\n\tcgltf_extension* data_extensions;\n\n\tchar** extensions_used;\n\tcgltf_size extensions_used_count;\n\n\tchar** extensions_required;\n\tcgltf_size extensions_required_count;\n\n\tconst char* json;\n\tcgltf_size json_size;\n\n\tconst void* bin;\n\tcgltf_size bin_size;\n\n\tcgltf_memory_options memory;\n\tcgltf_file_options file;\n} cgltf_data;\n\ncgltf_result cgltf_parse(\n\t\tconst cgltf_options* options,\n\t\tconst void* data,\n\t\tcgltf_size size,\n\t\tcgltf_data** out_data);\n\ncgltf_result cgltf_parse_file(\n\t\tconst cgltf_options* options,\n\t\tconst char* path,\n\t\tcgltf_data** out_data);\n\ncgltf_result cgltf_load_buffers(\n\t\tconst cgltf_options* options,\n\t\tcgltf_data* data,\n\t\tconst char* gltf_path);\n\ncgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data);\n\ncgltf_size cgltf_decode_string(char* string);\ncgltf_size cgltf_decode_uri(char* uri);\n\ncgltf_result cgltf_validate(cgltf_data* data);\n\nvoid cgltf_free(cgltf_data* data);\n\nvoid cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix);\nvoid cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix);\n\nconst uint8_t* cgltf_buffer_view_data(const cgltf_buffer_view* view);\n\nconst cgltf_accessor* cgltf_find_accessor(const cgltf_primitive* prim, cgltf_attribute_type type, cgltf_int index);\n\ncgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size);\ncgltf_bool cgltf_accessor_read_uint(const cgltf_accessor* accessor, cgltf_size index, cgltf_uint* out, cgltf_size element_size);\ncgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index);\n\ncgltf_size cgltf_num_components(cgltf_type type);\ncgltf_size cgltf_component_size(cgltf_component_type component_type);\ncgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type);\n\ncgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, cgltf_float* out, cgltf_size float_count);\ncgltf_size cgltf_accessor_unpack_indices(const cgltf_accessor* accessor, void* out, cgltf_size out_component_size, cgltf_size index_count);\n\n/* this function is deprecated and will be removed in the future; use cgltf_extras::data instead */\ncgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size);\n\ncgltf_size cgltf_mesh_index(const cgltf_data* data, const cgltf_mesh* object);\ncgltf_size cgltf_material_index(const cgltf_data* data, const cgltf_material* object);\ncgltf_size cgltf_accessor_index(const cgltf_data* data, const cgltf_accessor* object);\ncgltf_size cgltf_buffer_view_index(const cgltf_data* data, const cgltf_buffer_view* object);\ncgltf_size cgltf_buffer_index(const cgltf_data* data, const cgltf_buffer* object);\ncgltf_size cgltf_image_index(const cgltf_data* data, const cgltf_image* object);\ncgltf_size cgltf_texture_index(const cgltf_data* data, const cgltf_texture* object);\ncgltf_size cgltf_sampler_index(const cgltf_data* data, const cgltf_sampler* object);\ncgltf_size cgltf_skin_index(const cgltf_data* data, const cgltf_skin* object);\ncgltf_size cgltf_camera_index(const cgltf_data* data, const cgltf_camera* object);\ncgltf_size cgltf_light_index(const cgltf_data* data, const cgltf_light* object);\ncgltf_size cgltf_node_index(const cgltf_data* data, const cgltf_node* object);\ncgltf_size cgltf_scene_index(const cgltf_data* data, const cgltf_scene* object);\ncgltf_size cgltf_animation_index(const cgltf_data* data, const cgltf_animation* object);\ncgltf_size cgltf_animation_sampler_index(const cgltf_animation* animation, const cgltf_animation_sampler* object);\ncgltf_size cgltf_animation_channel_index(const cgltf_animation* animation, const cgltf_animation_channel* object);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* #ifndef CGLTF_H_INCLUDED__ */\n\n/*\n *\n * Stop now, if you are only interested in the API.\n * Below, you find the implementation.\n *\n */\n\n#if defined(__INTELLISENSE__) || defined(__JETBRAINS_IDE__)\n/* This makes MSVC/CLion intellisense work. */\n#define CGLTF_IMPLEMENTATION\n#endif\n\n#ifdef CGLTF_IMPLEMENTATION\n\n#include <assert.h> /* For assert */\n#include <string.h> /* For strncpy */\n#include <stdio.h>  /* For fopen */\n#include <limits.h> /* For UINT_MAX etc */\n#include <float.h>  /* For FLT_MAX */\n\n#if !defined(CGLTF_MALLOC) || !defined(CGLTF_FREE) || !defined(CGLTF_ATOI) || !defined(CGLTF_ATOF) || !defined(CGLTF_ATOLL)\n#include <stdlib.h> /* For malloc, free, atoi, atof */\n#endif\n\n/* JSMN_PARENT_LINKS is necessary to make parsing large structures linear in input size */\n#define JSMN_PARENT_LINKS\n\n/* JSMN_STRICT is necessary to reject invalid JSON documents */\n#define JSMN_STRICT\n\n/*\n * -- jsmn.h start --\n * Source: https://github.com/zserge/jsmn\n * License: MIT\n */\ntypedef enum {\n\tJSMN_UNDEFINED = 0,\n\tJSMN_OBJECT = 1,\n\tJSMN_ARRAY = 2,\n\tJSMN_STRING = 3,\n\tJSMN_PRIMITIVE = 4\n} jsmntype_t;\nenum jsmnerr {\n\t/* Not enough tokens were provided */\n\tJSMN_ERROR_NOMEM = -1,\n\t/* Invalid character inside JSON string */\n\tJSMN_ERROR_INVAL = -2,\n\t/* The string is not a full JSON packet, more bytes expected */\n\tJSMN_ERROR_PART = -3\n};\ntypedef struct {\n\tjsmntype_t type;\n\tptrdiff_t start;\n\tptrdiff_t end;\n\tint size;\n#ifdef JSMN_PARENT_LINKS\n\tint parent;\n#endif\n} jsmntok_t;\ntypedef struct {\n\tsize_t pos; /* offset in the JSON string */\n\tunsigned int toknext; /* next token to allocate */\n\tint toksuper; /* superior token node, e.g parent object or array */\n} jsmn_parser;\nstatic void jsmn_init(jsmn_parser *parser);\nstatic int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens);\n/*\n * -- jsmn.h end --\n */\n\n\n#ifndef CGLTF_CONSTS\n#define GlbHeaderSize 12\n#define GlbChunkHeaderSize 8\nstatic const uint32_t GlbVersion = 2;\nstatic const uint32_t GlbMagic = 0x46546C67;\nstatic const uint32_t GlbMagicJsonChunk = 0x4E4F534A;\nstatic const uint32_t GlbMagicBinChunk = 0x004E4942;\n#define CGLTF_CONSTS\n#endif\n\n#ifndef CGLTF_MALLOC\n#define CGLTF_MALLOC(size) malloc(size)\n#endif\n#ifndef CGLTF_FREE\n#define CGLTF_FREE(ptr) free(ptr)\n#endif\n#ifndef CGLTF_ATOI\n#define CGLTF_ATOI(str) atoi(str)\n#endif\n#ifndef CGLTF_ATOF\n#define CGLTF_ATOF(str) atof(str)\n#endif\n#ifndef CGLTF_ATOLL\n#define CGLTF_ATOLL(str) atoll(str)\n#endif\n#ifndef CGLTF_VALIDATE_ENABLE_ASSERTS\n#define CGLTF_VALIDATE_ENABLE_ASSERTS 0\n#endif\n\nstatic void* cgltf_default_alloc(void* user, cgltf_size size)\n{\n\t(void)user;\n\treturn CGLTF_MALLOC(size);\n}\n\nstatic void cgltf_default_free(void* user, void* ptr)\n{\n\t(void)user;\n\tCGLTF_FREE(ptr);\n}\n\nstatic void* cgltf_calloc(cgltf_options* options, size_t element_size, cgltf_size count)\n{\n\tif (SIZE_MAX / element_size < count)\n\t{\n\t\treturn NULL;\n\t}\n\tvoid* result = options->memory.alloc_func(options->memory.user_data, element_size * count);\n\tif (!result)\n\t{\n\t\treturn NULL;\n\t}\n\tmemset(result, 0, element_size * count);\n\treturn result;\n}\n\nstatic 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)\n{\n\t(void)file_options;\n\tvoid* (*memory_alloc)(void*, cgltf_size) = memory_options->alloc_func ? memory_options->alloc_func : &cgltf_default_alloc;\n\tvoid (*memory_free)(void*, void*) = memory_options->free_func ? memory_options->free_func : &cgltf_default_free;\n\n\tFILE* file = fopen(path, \"rb\");\n\tif (!file)\n\t{\n\t\treturn cgltf_result_file_not_found;\n\t}\n\n\tcgltf_size file_size = size ? *size : 0;\n\n\tif (file_size == 0)\n\t{\n\t\tfseek(file, 0, SEEK_END);\n\n#ifdef _MSC_VER\n\t\t__int64 length = _ftelli64(file);\n#else\n\t\tlong length = ftell(file);\n#endif\n\n\t\tif (length < 0)\n\t\t{\n\t\t\tfclose(file);\n\t\t\treturn cgltf_result_io_error;\n\t\t}\n\n\t\tfseek(file, 0, SEEK_SET);\n\t\tfile_size = (cgltf_size)length;\n\t}\n\n\tchar* file_data = (char*)memory_alloc(memory_options->user_data, file_size);\n\tif (!file_data)\n\t{\n\t\tfclose(file);\n\t\treturn cgltf_result_out_of_memory;\n\t}\n\n\tcgltf_size read_size = fread(file_data, 1, file_size, file);\n\n\tfclose(file);\n\n\tif (read_size != file_size)\n\t{\n\t\tmemory_free(memory_options->user_data, file_data);\n\t\treturn cgltf_result_io_error;\n\t}\n\n\tif (size)\n\t{\n\t\t*size = file_size;\n\t}\n\tif (data)\n\t{\n\t\t*data = file_data;\n\t}\n\n\treturn cgltf_result_success;\n}\n\nstatic void cgltf_default_file_release(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, void* data, cgltf_size size)\n{\n\t(void)file_options;\n\t(void)size;\n\tvoid (*memfree)(void*, void*) = memory_options->free_func ? memory_options->free_func : &cgltf_default_free;\n\tmemfree(memory_options->user_data, data);\n}\n\nstatic cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data);\n\ncgltf_result cgltf_parse(const cgltf_options* options, const void* data, cgltf_size size, cgltf_data** out_data)\n{\n\tif (size < GlbHeaderSize)\n\t{\n\t\treturn cgltf_result_data_too_short;\n\t}\n\n\tif (options == NULL)\n\t{\n\t\treturn cgltf_result_invalid_options;\n\t}\n\n\tcgltf_options fixed_options = *options;\n\tif (fixed_options.memory.alloc_func == NULL)\n\t{\n\t\tfixed_options.memory.alloc_func = &cgltf_default_alloc;\n\t}\n\tif (fixed_options.memory.free_func == NULL)\n\t{\n\t\tfixed_options.memory.free_func = &cgltf_default_free;\n\t}\n\n\tuint32_t tmp;\n\t// Magic\n\tmemcpy(&tmp, data, 4);\n\tif (tmp != GlbMagic)\n\t{\n\t\tif (fixed_options.type == cgltf_file_type_invalid)\n\t\t{\n\t\t\tfixed_options.type = cgltf_file_type_gltf;\n\t\t}\n\t\telse if (fixed_options.type == cgltf_file_type_glb)\n\t\t{\n\t\t\treturn cgltf_result_unknown_format;\n\t\t}\n\t}\n\n\tif (fixed_options.type == cgltf_file_type_gltf)\n\t{\n\t\tcgltf_result json_result = cgltf_parse_json(&fixed_options, (const uint8_t*)data, size, out_data);\n\t\tif (json_result != cgltf_result_success)\n\t\t{\n\t\t\treturn json_result;\n\t\t}\n\n\t\t(*out_data)->file_type = cgltf_file_type_gltf;\n\n\t\treturn cgltf_result_success;\n\t}\n\n\tconst uint8_t* ptr = (const uint8_t*)data;\n\t// Version\n\tmemcpy(&tmp, ptr + 4, 4);\n\tuint32_t version = tmp;\n\tif (version != GlbVersion)\n\t{\n\t\treturn version < GlbVersion ? cgltf_result_legacy_gltf : cgltf_result_unknown_format;\n\t}\n\n\t// Total length\n\tmemcpy(&tmp, ptr + 8, 4);\n\tif (tmp > size)\n\t{\n\t\treturn cgltf_result_data_too_short;\n\t}\n\n\tconst uint8_t* json_chunk = ptr + GlbHeaderSize;\n\n\tif (GlbHeaderSize + GlbChunkHeaderSize > size)\n\t{\n\t\treturn cgltf_result_data_too_short;\n\t}\n\n\t// JSON chunk: length\n\tuint32_t json_length;\n\tmemcpy(&json_length, json_chunk, 4);\n\tif (json_length > size - GlbHeaderSize - GlbChunkHeaderSize)\n\t{\n\t\treturn cgltf_result_data_too_short;\n\t}\n\n\t// JSON chunk: magic\n\tmemcpy(&tmp, json_chunk + 4, 4);\n\tif (tmp != GlbMagicJsonChunk)\n\t{\n\t\treturn cgltf_result_unknown_format;\n\t}\n\n\tjson_chunk += GlbChunkHeaderSize;\n\n\tconst void* bin = NULL;\n\tcgltf_size bin_size = 0;\n\n\tif (GlbChunkHeaderSize <= size - GlbHeaderSize - GlbChunkHeaderSize - json_length)\n\t{\n\t\t// We can read another chunk\n\t\tconst uint8_t* bin_chunk = json_chunk + json_length;\n\n\t\t// Bin chunk: length\n\t\tuint32_t bin_length;\n\t\tmemcpy(&bin_length, bin_chunk, 4);\n\t\tif (bin_length > size - GlbHeaderSize - GlbChunkHeaderSize - json_length - GlbChunkHeaderSize)\n\t\t{\n\t\t\treturn cgltf_result_data_too_short;\n\t\t}\n\n\t\t// Bin chunk: magic\n\t\tmemcpy(&tmp, bin_chunk + 4, 4);\n\t\tif (tmp != GlbMagicBinChunk)\n\t\t{\n\t\t\treturn cgltf_result_unknown_format;\n\t\t}\n\n\t\tbin_chunk += GlbChunkHeaderSize;\n\n\t\tbin = bin_chunk;\n\t\tbin_size = bin_length;\n\t}\n\n\tcgltf_result json_result = cgltf_parse_json(&fixed_options, json_chunk, json_length, out_data);\n\tif (json_result != cgltf_result_success)\n\t{\n\t\treturn json_result;\n\t}\n\n\t(*out_data)->file_type = cgltf_file_type_glb;\n\t(*out_data)->bin = bin;\n\t(*out_data)->bin_size = bin_size;\n\n\treturn cgltf_result_success;\n}\n\ncgltf_result cgltf_parse_file(const cgltf_options* options, const char* path, cgltf_data** out_data)\n{\n\tif (options == NULL)\n\t{\n\t\treturn cgltf_result_invalid_options;\n\t}\n\n\tcgltf_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;\n\tvoid (*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;\n\n\tvoid* file_data = NULL;\n\tcgltf_size file_size = 0;\n\tcgltf_result result = file_read(&options->memory, &options->file, path, &file_size, &file_data);\n\tif (result != cgltf_result_success)\n\t{\n\t\treturn result;\n\t}\n\n\tresult = cgltf_parse(options, file_data, file_size, out_data);\n\n\tif (result != cgltf_result_success)\n\t{\n\t\tfile_release(&options->memory, &options->file, file_data, file_size);\n\t\treturn result;\n\t}\n\n\t(*out_data)->file_data = file_data;\n\t(*out_data)->file_size = file_size;\n\n\treturn cgltf_result_success;\n}\n\nstatic void cgltf_combine_paths(char* path, const char* base, const char* uri)\n{\n\tconst char* s0 = strrchr(base, '/');\n\tconst char* s1 = strrchr(base, '\\\\');\n\tconst char* slash = s0 ? (s1 && s1 > s0 ? s1 : s0) : s1;\n\n\tif (slash)\n\t{\n\t\tsize_t prefix = slash - base + 1;\n\n\t\tstrncpy(path, base, prefix);\n\t\tstrcpy(path + prefix, uri);\n\t}\n\telse\n\t{\n\t\tstrcpy(path, uri);\n\t}\n}\n\nstatic cgltf_result cgltf_load_buffer_file(const cgltf_options* options, cgltf_size size, const char* uri, const char* gltf_path, void** out_data)\n{\n\tvoid* (*memory_alloc)(void*, cgltf_size) = options->memory.alloc_func ? options->memory.alloc_func : &cgltf_default_alloc;\n\tvoid (*memory_free)(void*, void*) = options->memory.free_func ? options->memory.free_func : &cgltf_default_free;\n\tcgltf_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;\n\n\tchar* path = (char*)memory_alloc(options->memory.user_data, strlen(uri) + strlen(gltf_path) + 1);\n\tif (!path)\n\t{\n\t\treturn cgltf_result_out_of_memory;\n\t}\n\n\tcgltf_combine_paths(path, gltf_path, uri);\n\n\t// after combining, the tail of the resulting path is a uri; decode_uri converts it into path\n\tcgltf_decode_uri(path + strlen(path) - strlen(uri));\n\n\tvoid* file_data = NULL;\n\tcgltf_result result = file_read(&options->memory, &options->file, path, &size, &file_data);\n\n\tmemory_free(options->memory.user_data, path);\n\n\t*out_data = (result == cgltf_result_success) ? file_data : NULL;\n\n\treturn result;\n}\n\ncgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data)\n{\n\tvoid* (*memory_alloc)(void*, cgltf_size) = options->memory.alloc_func ? options->memory.alloc_func : &cgltf_default_alloc;\n\tvoid (*memory_free)(void*, void*) = options->memory.free_func ? options->memory.free_func : &cgltf_default_free;\n\n\tunsigned char* data = (unsigned char*)memory_alloc(options->memory.user_data, size);\n\tif (!data)\n\t{\n\t\treturn cgltf_result_out_of_memory;\n\t}\n\n\tunsigned int buffer = 0;\n\tunsigned int buffer_bits = 0;\n\n\tfor (cgltf_size i = 0; i < size; ++i)\n\t{\n\t\twhile (buffer_bits < 8)\n\t\t{\n\t\t\tchar ch = *base64++;\n\n\t\t\tint index =\n\t\t\t\t(unsigned)(ch - 'A') < 26 ? (ch - 'A') :\n\t\t\t\t(unsigned)(ch - 'a') < 26 ? (ch - 'a') + 26 :\n\t\t\t\t(unsigned)(ch - '0') < 10 ? (ch - '0') + 52 :\n\t\t\t\tch == '+' ? 62 :\n\t\t\t\tch == '/' ? 63 :\n\t\t\t\t-1;\n\n\t\t\tif (index < 0)\n\t\t\t{\n\t\t\t\tmemory_free(options->memory.user_data, data);\n\t\t\t\treturn cgltf_result_io_error;\n\t\t\t}\n\n\t\t\tbuffer = (buffer << 6) | index;\n\t\t\tbuffer_bits += 6;\n\t\t}\n\n\t\tdata[i] = (unsigned char)(buffer >> (buffer_bits - 8));\n\t\tbuffer_bits -= 8;\n\t}\n\n\t*out_data = data;\n\n\treturn cgltf_result_success;\n}\n\nstatic int cgltf_unhex(char ch)\n{\n\treturn\n\t\t(unsigned)(ch - '0') < 10 ? (ch - '0') :\n\t\t(unsigned)(ch - 'A') < 6 ? (ch - 'A') + 10 :\n\t\t(unsigned)(ch - 'a') < 6 ? (ch - 'a') + 10 :\n\t\t-1;\n}\n\ncgltf_size cgltf_decode_string(char* string)\n{\n\tchar* read = string + strcspn(string, \"\\\\\");\n\tif (*read == 0)\n\t{\n\t\treturn read - string;\n\t}\n\tchar* write = string;\n\tchar* last = string;\n\n\tfor (;;)\n\t{\n\t\t// Copy characters since last escaped sequence\n\t\tcgltf_size written = read - last;\n\t\tmemmove(write, last, written);\n\t\twrite += written;\n\n\t\tif (*read++ == 0)\n\t\t{\n\t\t\tbreak;\n\t\t}\n\n\t\t// jsmn already checked that all escape sequences are valid\n\t\tswitch (*read++)\n\t\t{\n\t\tcase '\\\"': *write++ = '\\\"'; break;\n\t\tcase '/':  *write++ = '/';  break;\n\t\tcase '\\\\': *write++ = '\\\\'; break;\n\t\tcase 'b':  *write++ = '\\b'; break;\n\t\tcase 'f':  *write++ = '\\f'; break;\n\t\tcase 'r':  *write++ = '\\r'; break;\n\t\tcase 'n':  *write++ = '\\n'; break;\n\t\tcase 't':  *write++ = '\\t'; break;\n\t\tcase 'u':\n\t\t{\n\t\t\t// UCS-2 codepoint \\uXXXX to UTF-8\n\t\t\tint character = 0;\n\t\t\tfor (cgltf_size i = 0; i < 4; ++i)\n\t\t\t{\n\t\t\t\tcharacter = (character << 4) + cgltf_unhex(*read++);\n\t\t\t}\n\n\t\t\tif (character <= 0x7F)\n\t\t\t{\n\t\t\t\t*write++ = character & 0xFF;\n\t\t\t}\n\t\t\telse if (character <= 0x7FF)\n\t\t\t{\n\t\t\t\t*write++ = 0xC0 | ((character >> 6) & 0xFF);\n\t\t\t\t*write++ = 0x80 | (character & 0x3F);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t*write++ = 0xE0 | ((character >> 12) & 0xFF);\n\t\t\t\t*write++ = 0x80 | ((character >> 6) & 0x3F);\n\t\t\t\t*write++ = 0x80 | (character & 0x3F);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\n\t\tlast = read;\n\t\tread += strcspn(read, \"\\\\\");\n\t}\n\n\t*write = 0;\n\treturn write - string;\n}\n\ncgltf_size cgltf_decode_uri(char* uri)\n{\n\tchar* write = uri;\n\tchar* i = uri;\n\n\twhile (*i)\n\t{\n\t\tif (*i == '%')\n\t\t{\n\t\t\tint ch1 = cgltf_unhex(i[1]);\n\n\t\t\tif (ch1 >= 0)\n\t\t\t{\n\t\t\t\tint ch2 = cgltf_unhex(i[2]);\n\n\t\t\t\tif (ch2 >= 0)\n\t\t\t\t{\n\t\t\t\t\t*write++ = (char)(ch1 * 16 + ch2);\n\t\t\t\t\ti += 3;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t*write++ = *i++;\n\t}\n\n\t*write = 0;\n\treturn write - uri;\n}\n\ncgltf_result cgltf_load_buffers(const cgltf_options* options, cgltf_data* data, const char* gltf_path)\n{\n\tif (options == NULL)\n\t{\n\t\treturn cgltf_result_invalid_options;\n\t}\n\n\tif (data->buffers_count && data->buffers[0].data == NULL && data->buffers[0].uri == NULL && data->bin)\n\t{\n\t\tif (data->bin_size < data->buffers[0].size)\n\t\t{\n\t\t\treturn cgltf_result_data_too_short;\n\t\t}\n\n\t\tdata->buffers[0].data = (void*)data->bin;\n\t\tdata->buffers[0].data_free_method = cgltf_data_free_method_none;\n\t}\n\n\tfor (cgltf_size i = 0; i < data->buffers_count; ++i)\n\t{\n\t\tif (data->buffers[i].data)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst char* uri = data->buffers[i].uri;\n\n\t\tif (uri == NULL)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncmp(uri, \"data:\", 5) == 0)\n\t\t{\n\t\t\tconst char* comma = strchr(uri, ',');\n\n\t\t\tif (comma && comma - uri >= 7 && strncmp(comma - 7, \";base64\", 7) == 0)\n\t\t\t{\n\t\t\t\tcgltf_result res = cgltf_load_buffer_base64(options, data->buffers[i].size, comma + 1, &data->buffers[i].data);\n\t\t\t\tdata->buffers[i].data_free_method = cgltf_data_free_method_memory_free;\n\n\t\t\t\tif (res != cgltf_result_success)\n\t\t\t\t{\n\t\t\t\t\treturn res;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn cgltf_result_unknown_format;\n\t\t\t}\n\t\t}\n\t\telse if (strstr(uri, \"://\") == NULL && gltf_path)\n\t\t{\n\t\t\tcgltf_result res = cgltf_load_buffer_file(options, data->buffers[i].size, uri, gltf_path, &data->buffers[i].data);\n\t\t\tdata->buffers[i].data_free_method = cgltf_data_free_method_file_release;\n\n\t\t\tif (res != cgltf_result_success)\n\t\t\t{\n\t\t\t\treturn res;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\treturn cgltf_result_unknown_format;\n\t\t}\n\t}\n\n\treturn cgltf_result_success;\n}\n\nstatic cgltf_size cgltf_calc_index_bound(cgltf_buffer_view* buffer_view, cgltf_size offset, cgltf_component_type component_type, cgltf_size count)\n{\n\tchar* data = (char*)buffer_view->buffer->data + offset + buffer_view->offset;\n\tcgltf_size bound = 0;\n\n\tswitch (component_type)\n\t{\n\tcase cgltf_component_type_r_8u:\n\t\tfor (size_t i = 0; i < count; ++i)\n\t\t{\n\t\t\tcgltf_size v = ((unsigned char*)data)[i];\n\t\t\tbound = bound > v ? bound : v;\n\t\t}\n\t\tbreak;\n\n\tcase cgltf_component_type_r_16u:\n\t\tfor (size_t i = 0; i < count; ++i)\n\t\t{\n\t\t\tcgltf_size v = ((unsigned short*)data)[i];\n\t\t\tbound = bound > v ? bound : v;\n\t\t}\n\t\tbreak;\n\n\tcase cgltf_component_type_r_32u:\n\t\tfor (size_t i = 0; i < count; ++i)\n\t\t{\n\t\t\tcgltf_size v = ((unsigned int*)data)[i];\n\t\t\tbound = bound > v ? bound : v;\n\t\t}\n\t\tbreak;\n\n\tdefault:\n\t\t;\n\t}\n\n\treturn bound;\n}\n\n#if CGLTF_VALIDATE_ENABLE_ASSERTS\n#define CGLTF_ASSERT_IF(cond, result) assert(!(cond)); if (cond) return result;\n#else\n#define CGLTF_ASSERT_IF(cond, result) if (cond) return result;\n#endif\n\ncgltf_result cgltf_validate(cgltf_data* data)\n{\n\tfor (cgltf_size i = 0; i < data->accessors_count; ++i)\n\t{\n\t\tcgltf_accessor* accessor = &data->accessors[i];\n\n\t\tCGLTF_ASSERT_IF(data->accessors[i].component_type == cgltf_component_type_invalid, cgltf_result_invalid_gltf);\n\t\tCGLTF_ASSERT_IF(data->accessors[i].type == cgltf_type_invalid, cgltf_result_invalid_gltf);\n\n\t\tcgltf_size element_size = cgltf_calc_size(accessor->type, accessor->component_type);\n\n\t\tif (accessor->buffer_view)\n\t\t{\n\t\t\tcgltf_size req_size = accessor->offset + accessor->stride * (accessor->count - 1) + element_size;\n\n\t\t\tCGLTF_ASSERT_IF(accessor->buffer_view->size < req_size, cgltf_result_data_too_short);\n\t\t}\n\n\t\tif (accessor->is_sparse)\n\t\t{\n\t\t\tcgltf_accessor_sparse* sparse = &accessor->sparse;\n\n\t\t\tcgltf_size indices_component_size = cgltf_component_size(sparse->indices_component_type);\n\t\t\tcgltf_size indices_req_size = sparse->indices_byte_offset + indices_component_size * sparse->count;\n\t\t\tcgltf_size values_req_size = sparse->values_byte_offset + element_size * sparse->count;\n\n\t\t\tCGLTF_ASSERT_IF(sparse->indices_buffer_view->size < indices_req_size ||\n\t\t\t\t\t\t\tsparse->values_buffer_view->size < values_req_size, cgltf_result_data_too_short);\n\n\t\t\tCGLTF_ASSERT_IF(sparse->indices_component_type != cgltf_component_type_r_8u &&\n\t\t\t\t\t\t\tsparse->indices_component_type != cgltf_component_type_r_16u &&\n\t\t\t\t\t\t\tsparse->indices_component_type != cgltf_component_type_r_32u, cgltf_result_invalid_gltf);\n\n\t\t\tif (sparse->indices_buffer_view->buffer->data)\n\t\t\t{\n\t\t\t\tcgltf_size index_bound = cgltf_calc_index_bound(sparse->indices_buffer_view, sparse->indices_byte_offset, sparse->indices_component_type, sparse->count);\n\n\t\t\t\tCGLTF_ASSERT_IF(index_bound >= accessor->count, cgltf_result_data_too_short);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (cgltf_size i = 0; i < data->buffer_views_count; ++i)\n\t{\n\t\tcgltf_size req_size = data->buffer_views[i].offset + data->buffer_views[i].size;\n\n\t\tCGLTF_ASSERT_IF(data->buffer_views[i].buffer && data->buffer_views[i].buffer->size < req_size, cgltf_result_data_too_short);\n\n\t\tif (data->buffer_views[i].has_meshopt_compression)\n\t\t{\n\t\t\tcgltf_meshopt_compression* mc = &data->buffer_views[i].meshopt_compression;\n\n\t\t\tCGLTF_ASSERT_IF(mc->buffer == NULL || mc->buffer->size < mc->offset + mc->size, cgltf_result_data_too_short);\n\n\t\t\tCGLTF_ASSERT_IF(data->buffer_views[i].stride && mc->stride != data->buffer_views[i].stride, cgltf_result_invalid_gltf);\n\n\t\t\tCGLTF_ASSERT_IF(data->buffer_views[i].size != mc->stride * mc->count, cgltf_result_invalid_gltf);\n\n\t\t\tCGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_invalid, cgltf_result_invalid_gltf);\n\n\t\t\tCGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_attributes && !(mc->stride % 4 == 0 && mc->stride <= 256), cgltf_result_invalid_gltf);\n\n\t\t\tCGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_triangles && mc->count % 3 != 0, cgltf_result_invalid_gltf);\n\n\t\t\tCGLTF_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);\n\n\t\t\tCGLTF_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);\n\n\t\t\tCGLTF_ASSERT_IF(mc->filter == cgltf_meshopt_compression_filter_octahedral && mc->stride != 4 && mc->stride != 8, cgltf_result_invalid_gltf);\n\t\t\tCGLTF_ASSERT_IF(mc->filter == cgltf_meshopt_compression_filter_quaternion && mc->stride != 8, cgltf_result_invalid_gltf);\n\t\t\tCGLTF_ASSERT_IF(mc->filter == cgltf_meshopt_compression_filter_color && mc->stride != 4 && mc->stride != 8, cgltf_result_invalid_gltf);\n\t\t}\n\t}\n\n\tfor (cgltf_size i = 0; i < data->meshes_count; ++i)\n\t{\n\t\tif (data->meshes[i].weights)\n\t\t{\n\t\t\tCGLTF_ASSERT_IF(data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].weights_count, cgltf_result_invalid_gltf);\n\t\t}\n\n\t\tif (data->meshes[i].target_names)\n\t\t{\n\t\t\tCGLTF_ASSERT_IF(data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].target_names_count, cgltf_result_invalid_gltf);\n\t\t}\n\n\t\tfor (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j)\n\t\t{\n\t\t\tCGLTF_ASSERT_IF(data->meshes[i].primitives[j].type == cgltf_primitive_type_invalid, cgltf_result_invalid_gltf);\n\t\t\tCGLTF_ASSERT_IF(data->meshes[i].primitives[j].targets_count != data->meshes[i].primitives[0].targets_count, cgltf_result_invalid_gltf);\n\n\t\t\tCGLTF_ASSERT_IF(data->meshes[i].primitives[j].attributes_count == 0, cgltf_result_invalid_gltf);\n\n\t\t\tcgltf_accessor* first = data->meshes[i].primitives[j].attributes[0].data;\n\n\t\t\tCGLTF_ASSERT_IF(first->count == 0, cgltf_result_invalid_gltf);\n\n\t\t\tfor (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_ASSERT_IF(data->meshes[i].primitives[j].attributes[k].data->count != first->count, cgltf_result_invalid_gltf);\n\t\t\t}\n\n\t\t\tfor (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k)\n\t\t\t{\n\t\t\t\tfor (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m)\n\t\t\t\t{\n\t\t\t\t\tCGLTF_ASSERT_IF(data->meshes[i].primitives[j].targets[k].attributes[m].data->count != first->count, cgltf_result_invalid_gltf);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcgltf_accessor* indices = data->meshes[i].primitives[j].indices;\n\n\t\t\tCGLTF_ASSERT_IF(indices &&\n\t\t\t\tindices->component_type != cgltf_component_type_r_8u &&\n\t\t\t\tindices->component_type != cgltf_component_type_r_16u &&\n\t\t\t\tindices->component_type != cgltf_component_type_r_32u, cgltf_result_invalid_gltf);\n\n\t\t\tCGLTF_ASSERT_IF(indices && indices->type != cgltf_type_scalar, cgltf_result_invalid_gltf);\n\t\t\tCGLTF_ASSERT_IF(indices && indices->stride != cgltf_component_size(indices->component_type), cgltf_result_invalid_gltf);\n\n\t\t\tif (indices && indices->buffer_view && indices->buffer_view->buffer->data)\n\t\t\t{\n\t\t\t\tcgltf_size index_bound = cgltf_calc_index_bound(indices->buffer_view, indices->offset, indices->component_type, indices->count);\n\n\t\t\t\tCGLTF_ASSERT_IF(index_bound >= first->count, cgltf_result_data_too_short);\n\t\t\t}\n\n\t\t\tfor (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_ASSERT_IF(data->meshes[i].primitives[j].mappings[k].variant >= data->variants_count, cgltf_result_invalid_gltf);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (cgltf_size i = 0; i < data->nodes_count; ++i)\n\t{\n\t\tif (data->nodes[i].weights && data->nodes[i].mesh)\n\t\t{\n\t\t\tCGLTF_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);\n\t\t}\n\n\t\tif (data->nodes[i].has_mesh_gpu_instancing)\n\t\t{\n\t\t\tCGLTF_ASSERT_IF(data->nodes[i].mesh == NULL, cgltf_result_invalid_gltf);\n\t\t\tCGLTF_ASSERT_IF(data->nodes[i].mesh_gpu_instancing.attributes_count == 0, cgltf_result_invalid_gltf);\n\n\t\t\tcgltf_accessor* first = data->nodes[i].mesh_gpu_instancing.attributes[0].data;\n\n\t\t\tfor (cgltf_size k = 0; k < data->nodes[i].mesh_gpu_instancing.attributes_count; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_ASSERT_IF(data->nodes[i].mesh_gpu_instancing.attributes[k].data->count != first->count, cgltf_result_invalid_gltf);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (cgltf_size i = 0; i < data->nodes_count; ++i)\n\t{\n\t\tcgltf_node* p1 = data->nodes[i].parent;\n\t\tcgltf_node* p2 = p1 ? p1->parent : NULL;\n\n\t\twhile (p1 && p2)\n\t\t{\n\t\t\tCGLTF_ASSERT_IF(p1 == p2, cgltf_result_invalid_gltf);\n\n\t\t\tp1 = p1->parent;\n\t\t\tp2 = p2->parent ? p2->parent->parent : NULL;\n\t\t}\n\t}\n\n\tfor (cgltf_size i = 0; i < data->scenes_count; ++i)\n\t{\n\t\tfor (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j)\n\t\t{\n\t\t\tCGLTF_ASSERT_IF(data->scenes[i].nodes[j]->parent, cgltf_result_invalid_gltf);\n\t\t}\n\t}\n\n\tfor (cgltf_size i = 0; i < data->animations_count; ++i)\n\t{\n\t\tfor (cgltf_size j = 0; j < data->animations[i].channels_count; ++j)\n\t\t{\n\t\t\tcgltf_animation_channel* channel = &data->animations[i].channels[j];\n\n\t\t\tif (!channel->target_node)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcgltf_size components = 1;\n\n\t\t\tif (channel->target_path == cgltf_animation_path_type_weights)\n\t\t\t{\n\t\t\t\tCGLTF_ASSERT_IF(!channel->target_node->mesh || !channel->target_node->mesh->primitives_count, cgltf_result_invalid_gltf);\n\n\t\t\t\tcomponents = channel->target_node->mesh->primitives[0].targets_count;\n\t\t\t}\n\n\t\t\tcgltf_size values = channel->sampler->interpolation == cgltf_interpolation_type_cubic_spline ? 3 : 1;\n\n\t\t\tCGLTF_ASSERT_IF(channel->sampler->input->count * components * values != channel->sampler->output->count, cgltf_result_invalid_gltf);\n\t\t}\n\t}\n\n\tfor (cgltf_size i = 0; i < data->variants_count; ++i)\n\t{\n\t\tCGLTF_ASSERT_IF(!data->variants[i].name, cgltf_result_invalid_gltf);\n\t}\n\n\treturn cgltf_result_success;\n}\n\ncgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size)\n{\n\tcgltf_size json_size = extras->end_offset - extras->start_offset;\n\n\tif (!dest)\n\t{\n\t\tif (dest_size)\n\t\t{\n\t\t\t*dest_size = json_size + 1;\n\t\t\treturn cgltf_result_success;\n\t\t}\n\t\treturn cgltf_result_invalid_options;\n\t}\n\n\tif (*dest_size + 1 < json_size)\n\t{\n\t\tstrncpy(dest, data->json + extras->start_offset, *dest_size - 1);\n\t\tdest[*dest_size - 1] = 0;\n\t}\n\telse\n\t{\n\t\tstrncpy(dest, data->json + extras->start_offset, json_size);\n\t\tdest[json_size] = 0;\n\t}\n\n\treturn cgltf_result_success;\n}\n\nstatic void cgltf_free_extras(cgltf_data* data, cgltf_extras* extras)\n{\n\tdata->memory.free_func(data->memory.user_data, extras->data);\n}\n\nstatic void cgltf_free_extensions(cgltf_data* data, cgltf_extension* extensions, cgltf_size extensions_count)\n{\n\tfor (cgltf_size i = 0; i < extensions_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, extensions[i].name);\n\t\tdata->memory.free_func(data->memory.user_data, extensions[i].data);\n\t}\n\tdata->memory.free_func(data->memory.user_data, extensions);\n}\n\nvoid cgltf_free(cgltf_data* data)\n{\n\tif (!data)\n\t{\n\t\treturn;\n\t}\n\n\tvoid (*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;\n\n\tdata->memory.free_func(data->memory.user_data, data->asset.copyright);\n\tdata->memory.free_func(data->memory.user_data, data->asset.generator);\n\tdata->memory.free_func(data->memory.user_data, data->asset.version);\n\tdata->memory.free_func(data->memory.user_data, data->asset.min_version);\n\n\tcgltf_free_extensions(data, data->asset.extensions, data->asset.extensions_count);\n\tcgltf_free_extras(data, &data->asset.extras);\n\n\tfor (cgltf_size i = 0; i < data->accessors_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->accessors[i].name);\n\n\t\tcgltf_free_extensions(data, data->accessors[i].extensions, data->accessors[i].extensions_count);\n\t\tcgltf_free_extras(data, &data->accessors[i].extras);\n\t}\n\tdata->memory.free_func(data->memory.user_data, data->accessors);\n\n\tfor (cgltf_size i = 0; i < data->buffer_views_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->buffer_views[i].name);\n\t\tdata->memory.free_func(data->memory.user_data, data->buffer_views[i].data);\n\n\t\tcgltf_free_extensions(data, data->buffer_views[i].extensions, data->buffer_views[i].extensions_count);\n\t\tcgltf_free_extras(data, &data->buffer_views[i].extras);\n\t}\n\tdata->memory.free_func(data->memory.user_data, data->buffer_views);\n\n\tfor (cgltf_size i = 0; i < data->buffers_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->buffers[i].name);\n\n\t\tif (data->buffers[i].data_free_method == cgltf_data_free_method_file_release)\n\t\t{\n\t\t\tfile_release(&data->memory, &data->file, data->buffers[i].data, data->buffers[i].size);\n\t\t}\n\t\telse if (data->buffers[i].data_free_method == cgltf_data_free_method_memory_free)\n\t\t{\n\t\t\tdata->memory.free_func(data->memory.user_data, data->buffers[i].data);\n\t\t}\n\n\t\tdata->memory.free_func(data->memory.user_data, data->buffers[i].uri);\n\n\t\tcgltf_free_extensions(data, data->buffers[i].extensions, data->buffers[i].extensions_count);\n\t\tcgltf_free_extras(data, &data->buffers[i].extras);\n\t}\n\tdata->memory.free_func(data->memory.user_data, data->buffers);\n\n\tfor (cgltf_size i = 0; i < data->meshes_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->meshes[i].name);\n\n\t\tfor (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j)\n\t\t{\n\t\t\tfor (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k)\n\t\t\t{\n\t\t\t\tdata->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].attributes[k].name);\n\t\t\t}\n\n\t\t\tdata->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].attributes);\n\n\t\t\tfor (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k)\n\t\t\t{\n\t\t\t\tfor (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m)\n\t\t\t\t{\n\t\t\t\t\tdata->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].targets[k].attributes[m].name);\n\t\t\t\t}\n\n\t\t\t\tdata->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].targets[k].attributes);\n\t\t\t}\n\n\t\t\tdata->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].targets);\n\n\t\t\tif (data->meshes[i].primitives[j].has_draco_mesh_compression)\n\t\t\t{\n\t\t\t\tfor (cgltf_size k = 0; k < data->meshes[i].primitives[j].draco_mesh_compression.attributes_count; ++k)\n\t\t\t\t{\n\t\t\t\t\tdata->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].draco_mesh_compression.attributes[k].name);\n\t\t\t\t}\n\n\t\t\t\tdata->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].draco_mesh_compression.attributes);\n\t\t\t}\n\n\t\t\tfor (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k)\n\t\t\t{\n\t\t\t\tcgltf_free_extras(data, &data->meshes[i].primitives[j].mappings[k].extras);\n\t\t\t}\n\n\t\t\tdata->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].mappings);\n\n\t\t\tcgltf_free_extensions(data, data->meshes[i].primitives[j].extensions, data->meshes[i].primitives[j].extensions_count);\n\t\t\tcgltf_free_extras(data, &data->meshes[i].primitives[j].extras);\n\t\t}\n\n\t\tdata->memory.free_func(data->memory.user_data, data->meshes[i].primitives);\n\t\tdata->memory.free_func(data->memory.user_data, data->meshes[i].weights);\n\n\t\tfor (cgltf_size j = 0; j < data->meshes[i].target_names_count; ++j)\n\t\t{\n\t\t\tdata->memory.free_func(data->memory.user_data, data->meshes[i].target_names[j]);\n\t\t}\n\n\t\tcgltf_free_extensions(data, data->meshes[i].extensions, data->meshes[i].extensions_count);\n\t\tcgltf_free_extras(data, &data->meshes[i].extras);\n\n\t\tdata->memory.free_func(data->memory.user_data, data->meshes[i].target_names);\n\t}\n\n\tdata->memory.free_func(data->memory.user_data, data->meshes);\n\n\tfor (cgltf_size i = 0; i < data->materials_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->materials[i].name);\n\n\t\tcgltf_free_extensions(data, data->materials[i].extensions, data->materials[i].extensions_count);\n\t\tcgltf_free_extras(data, &data->materials[i].extras);\n\t}\n\n\tdata->memory.free_func(data->memory.user_data, data->materials);\n\n\tfor (cgltf_size i = 0; i < data->images_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->images[i].name);\n\t\tdata->memory.free_func(data->memory.user_data, data->images[i].uri);\n\t\tdata->memory.free_func(data->memory.user_data, data->images[i].mime_type);\n\n\t\tcgltf_free_extensions(data, data->images[i].extensions, data->images[i].extensions_count);\n\t\tcgltf_free_extras(data, &data->images[i].extras);\n\t}\n\n\tdata->memory.free_func(data->memory.user_data, data->images);\n\n\tfor (cgltf_size i = 0; i < data->textures_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->textures[i].name);\n\n\t\tcgltf_free_extensions(data, data->textures[i].extensions, data->textures[i].extensions_count);\n\t\tcgltf_free_extras(data, &data->textures[i].extras);\n\t}\n\n\tdata->memory.free_func(data->memory.user_data, data->textures);\n\n\tfor (cgltf_size i = 0; i < data->samplers_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->samplers[i].name);\n\n\t\tcgltf_free_extensions(data, data->samplers[i].extensions, data->samplers[i].extensions_count);\n\t\tcgltf_free_extras(data, &data->samplers[i].extras);\n\t}\n\n\tdata->memory.free_func(data->memory.user_data, data->samplers);\n\n\tfor (cgltf_size i = 0; i < data->skins_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->skins[i].name);\n\t\tdata->memory.free_func(data->memory.user_data, data->skins[i].joints);\n\n\t\tcgltf_free_extensions(data, data->skins[i].extensions, data->skins[i].extensions_count);\n\t\tcgltf_free_extras(data, &data->skins[i].extras);\n\t}\n\n\tdata->memory.free_func(data->memory.user_data, data->skins);\n\n\tfor (cgltf_size i = 0; i < data->cameras_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->cameras[i].name);\n\n\t\tif (data->cameras[i].type == cgltf_camera_type_perspective)\n\t\t{\n\t\t\tcgltf_free_extras(data, &data->cameras[i].data.perspective.extras);\n\t\t}\n\t\telse if (data->cameras[i].type == cgltf_camera_type_orthographic)\n\t\t{\n\t\t\tcgltf_free_extras(data, &data->cameras[i].data.orthographic.extras);\n\t\t}\n\n\t\tcgltf_free_extensions(data, data->cameras[i].extensions, data->cameras[i].extensions_count);\n\t\tcgltf_free_extras(data, &data->cameras[i].extras);\n\t}\n\n\tdata->memory.free_func(data->memory.user_data, data->cameras);\n\n\tfor (cgltf_size i = 0; i < data->lights_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->lights[i].name);\n\n\t\tcgltf_free_extras(data, &data->lights[i].extras);\n\t}\n\n\tdata->memory.free_func(data->memory.user_data, data->lights);\n\n\tfor (cgltf_size i = 0; i < data->nodes_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->nodes[i].name);\n\t\tdata->memory.free_func(data->memory.user_data, data->nodes[i].children);\n\t\tdata->memory.free_func(data->memory.user_data, data->nodes[i].weights);\n\n\t\tif (data->nodes[i].has_mesh_gpu_instancing)\n\t\t{\n\t\t\tfor (cgltf_size j = 0; j < data->nodes[i].mesh_gpu_instancing.attributes_count; ++j)\n\t\t\t{\n\t\t\t\tdata->memory.free_func(data->memory.user_data, data->nodes[i].mesh_gpu_instancing.attributes[j].name);\n\t\t\t}\n\n\t\t\tdata->memory.free_func(data->memory.user_data, data->nodes[i].mesh_gpu_instancing.attributes);\n\t\t}\n\n\t\tcgltf_free_extensions(data, data->nodes[i].extensions, data->nodes[i].extensions_count);\n\t\tcgltf_free_extras(data, &data->nodes[i].extras);\n\t}\n\n\tdata->memory.free_func(data->memory.user_data, data->nodes);\n\n\tfor (cgltf_size i = 0; i < data->scenes_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->scenes[i].name);\n\t\tdata->memory.free_func(data->memory.user_data, data->scenes[i].nodes);\n\n\t\tcgltf_free_extensions(data, data->scenes[i].extensions, data->scenes[i].extensions_count);\n\t\tcgltf_free_extras(data, &data->scenes[i].extras);\n\t}\n\n\tdata->memory.free_func(data->memory.user_data, data->scenes);\n\n\tfor (cgltf_size i = 0; i < data->animations_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->animations[i].name);\n\t\tfor (cgltf_size j = 0; j <  data->animations[i].samplers_count; ++j)\n\t\t{\n\t\t\tcgltf_free_extensions(data, data->animations[i].samplers[j].extensions, data->animations[i].samplers[j].extensions_count);\n\t\t\tcgltf_free_extras(data, &data->animations[i].samplers[j].extras);\n\t\t}\n\t\tdata->memory.free_func(data->memory.user_data, data->animations[i].samplers);\n\n\t\tfor (cgltf_size j = 0; j <  data->animations[i].channels_count; ++j)\n\t\t{\n\t\t\tcgltf_free_extensions(data, data->animations[i].channels[j].extensions, data->animations[i].channels[j].extensions_count);\n\t\t\tcgltf_free_extras(data, &data->animations[i].channels[j].extras);\n\t\t}\n\t\tdata->memory.free_func(data->memory.user_data, data->animations[i].channels);\n\n\t\tcgltf_free_extensions(data, data->animations[i].extensions, data->animations[i].extensions_count);\n\t\tcgltf_free_extras(data, &data->animations[i].extras);\n\t}\n\n\tdata->memory.free_func(data->memory.user_data, data->animations);\n\n\tfor (cgltf_size i = 0; i < data->variants_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->variants[i].name);\n\n\t\tcgltf_free_extras(data, &data->variants[i].extras);\n\t}\n\n\tdata->memory.free_func(data->memory.user_data, data->variants);\n\n\tcgltf_free_extensions(data, data->data_extensions, data->data_extensions_count);\n\tcgltf_free_extras(data, &data->extras);\n\n\tfor (cgltf_size i = 0; i < data->extensions_used_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->extensions_used[i]);\n\t}\n\n\tdata->memory.free_func(data->memory.user_data, data->extensions_used);\n\n\tfor (cgltf_size i = 0; i < data->extensions_required_count; ++i)\n\t{\n\t\tdata->memory.free_func(data->memory.user_data, data->extensions_required[i]);\n\t}\n\n\tdata->memory.free_func(data->memory.user_data, data->extensions_required);\n\n\tfile_release(&data->memory, &data->file, data->file_data, data->file_size);\n\n\tdata->memory.free_func(data->memory.user_data, data);\n}\n\nvoid cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix)\n{\n\tcgltf_float* lm = out_matrix;\n\n\tif (node->has_matrix)\n\t{\n\t\tmemcpy(lm, node->matrix, sizeof(float) * 16);\n\t}\n\telse\n\t{\n\t\tfloat tx = node->translation[0];\n\t\tfloat ty = node->translation[1];\n\t\tfloat tz = node->translation[2];\n\n\t\tfloat qx = node->rotation[0];\n\t\tfloat qy = node->rotation[1];\n\t\tfloat qz = node->rotation[2];\n\t\tfloat qw = node->rotation[3];\n\n\t\tfloat sx = node->scale[0];\n\t\tfloat sy = node->scale[1];\n\t\tfloat sz = node->scale[2];\n\n\t\tlm[0] = (1 - 2 * qy*qy - 2 * qz*qz) * sx;\n\t\tlm[1] = (2 * qx*qy + 2 * qz*qw) * sx;\n\t\tlm[2] = (2 * qx*qz - 2 * qy*qw) * sx;\n\t\tlm[3] = 0.f;\n\n\t\tlm[4] = (2 * qx*qy - 2 * qz*qw) * sy;\n\t\tlm[5] = (1 - 2 * qx*qx - 2 * qz*qz) * sy;\n\t\tlm[6] = (2 * qy*qz + 2 * qx*qw) * sy;\n\t\tlm[7] = 0.f;\n\n\t\tlm[8] = (2 * qx*qz + 2 * qy*qw) * sz;\n\t\tlm[9] = (2 * qy*qz - 2 * qx*qw) * sz;\n\t\tlm[10] = (1 - 2 * qx*qx - 2 * qy*qy) * sz;\n\t\tlm[11] = 0.f;\n\n\t\tlm[12] = tx;\n\t\tlm[13] = ty;\n\t\tlm[14] = tz;\n\t\tlm[15] = 1.f;\n\t}\n}\n\nvoid cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix)\n{\n\tcgltf_float* lm = out_matrix;\n\tcgltf_node_transform_local(node, lm);\n\n\tconst cgltf_node* parent = node->parent;\n\n\twhile (parent)\n\t{\n\t\tfloat pm[16];\n\t\tcgltf_node_transform_local(parent, pm);\n\n\t\tfor (int i = 0; i < 4; ++i)\n\t\t{\n\t\t\tfloat l0 = lm[i * 4 + 0];\n\t\t\tfloat l1 = lm[i * 4 + 1];\n\t\t\tfloat l2 = lm[i * 4 + 2];\n\n\t\t\tfloat r0 = l0 * pm[0] + l1 * pm[4] + l2 * pm[8];\n\t\t\tfloat r1 = l0 * pm[1] + l1 * pm[5] + l2 * pm[9];\n\t\t\tfloat r2 = l0 * pm[2] + l1 * pm[6] + l2 * pm[10];\n\n\t\t\tlm[i * 4 + 0] = r0;\n\t\t\tlm[i * 4 + 1] = r1;\n\t\t\tlm[i * 4 + 2] = r2;\n\t\t}\n\n\t\tlm[12] += pm[12];\n\t\tlm[13] += pm[13];\n\t\tlm[14] += pm[14];\n\n\t\tparent = parent->parent;\n\t}\n}\n\nstatic cgltf_ssize cgltf_component_read_integer(const void* in, cgltf_component_type component_type)\n{\n\tswitch (component_type)\n\t{\n\t\tcase cgltf_component_type_r_16:\n\t\t\treturn *((const int16_t*) in);\n\t\tcase cgltf_component_type_r_16u:\n\t\t\treturn *((const uint16_t*) in);\n\t\tcase cgltf_component_type_r_32u:\n\t\t\treturn *((const uint32_t*) in);\n\t\tcase cgltf_component_type_r_8:\n\t\t\treturn *((const int8_t*) in);\n\t\tcase cgltf_component_type_r_8u:\n\t\t\treturn *((const uint8_t*) in);\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n\nstatic cgltf_size cgltf_component_read_index(const void* in, cgltf_component_type component_type)\n{\n\tswitch (component_type)\n\t{\n\t\tcase cgltf_component_type_r_16u:\n\t\t\treturn *((const uint16_t*) in);\n\t\tcase cgltf_component_type_r_32u:\n\t\t\treturn *((const uint32_t*) in);\n\t\tcase cgltf_component_type_r_8u:\n\t\t\treturn *((const uint8_t*) in);\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n\nstatic cgltf_float cgltf_component_read_float(const void* in, cgltf_component_type component_type, cgltf_bool normalized)\n{\n\tif (component_type == cgltf_component_type_r_32f)\n\t{\n\t\treturn *((const float*) in);\n\t}\n\n\tif (normalized)\n\t{\n\t\tswitch (component_type)\n\t\t{\n\t\t\t// note: glTF spec doesn't currently define normalized conversions for 32-bit integers\n\t\t\tcase cgltf_component_type_r_16:\n\t\t\t\treturn *((const int16_t*) in) / (cgltf_float)32767;\n\t\t\tcase cgltf_component_type_r_16u:\n\t\t\t\treturn *((const uint16_t*) in) / (cgltf_float)65535;\n\t\t\tcase cgltf_component_type_r_8:\n\t\t\t\treturn *((const int8_t*) in) / (cgltf_float)127;\n\t\t\tcase cgltf_component_type_r_8u:\n\t\t\t\treturn *((const uint8_t*) in) / (cgltf_float)255;\n\t\t\tdefault:\n\t\t\t\treturn 0;\n\t\t}\n\t}\n\n\treturn (cgltf_float)cgltf_component_read_integer(in, component_type);\n}\n\nstatic 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)\n{\n\tcgltf_size num_components = cgltf_num_components(type);\n\n\tif (element_size < num_components) {\n\t\treturn 0;\n\t}\n\n\t// There are three special cases for component extraction, see #data-alignment in the 2.0 spec.\n\n\tcgltf_size component_size = cgltf_component_size(component_type);\n\n\tif (type == cgltf_type_mat2 && component_size == 1)\n\t{\n\t\tout[0] = cgltf_component_read_float(element, component_type, normalized);\n\t\tout[1] = cgltf_component_read_float(element + 1, component_type, normalized);\n\t\tout[2] = cgltf_component_read_float(element + 4, component_type, normalized);\n\t\tout[3] = cgltf_component_read_float(element + 5, component_type, normalized);\n\t\treturn 1;\n\t}\n\n\tif (type == cgltf_type_mat3 && component_size == 1)\n\t{\n\t\tout[0] = cgltf_component_read_float(element, component_type, normalized);\n\t\tout[1] = cgltf_component_read_float(element + 1, component_type, normalized);\n\t\tout[2] = cgltf_component_read_float(element + 2, component_type, normalized);\n\t\tout[3] = cgltf_component_read_float(element + 4, component_type, normalized);\n\t\tout[4] = cgltf_component_read_float(element + 5, component_type, normalized);\n\t\tout[5] = cgltf_component_read_float(element + 6, component_type, normalized);\n\t\tout[6] = cgltf_component_read_float(element + 8, component_type, normalized);\n\t\tout[7] = cgltf_component_read_float(element + 9, component_type, normalized);\n\t\tout[8] = cgltf_component_read_float(element + 10, component_type, normalized);\n\t\treturn 1;\n\t}\n\n\tif (type == cgltf_type_mat3 && component_size == 2)\n\t{\n\t\tout[0] = cgltf_component_read_float(element, component_type, normalized);\n\t\tout[1] = cgltf_component_read_float(element + 2, component_type, normalized);\n\t\tout[2] = cgltf_component_read_float(element + 4, component_type, normalized);\n\t\tout[3] = cgltf_component_read_float(element + 8, component_type, normalized);\n\t\tout[4] = cgltf_component_read_float(element + 10, component_type, normalized);\n\t\tout[5] = cgltf_component_read_float(element + 12, component_type, normalized);\n\t\tout[6] = cgltf_component_read_float(element + 16, component_type, normalized);\n\t\tout[7] = cgltf_component_read_float(element + 18, component_type, normalized);\n\t\tout[8] = cgltf_component_read_float(element + 20, component_type, normalized);\n\t\treturn 1;\n\t}\n\n\tfor (cgltf_size i = 0; i < num_components; ++i)\n\t{\n\t\tout[i] = cgltf_component_read_float(element + component_size * i, component_type, normalized);\n\t}\n\treturn 1;\n}\n\nconst uint8_t* cgltf_buffer_view_data(const cgltf_buffer_view* view)\n{\n\tif (view->data)\n\t\treturn (const uint8_t*)view->data;\n\n\tif (!view->buffer->data)\n\t\treturn NULL;\n\n\tconst uint8_t* result = (const uint8_t*)view->buffer->data;\n\tresult += view->offset;\n\treturn result;\n}\n\nconst cgltf_accessor* cgltf_find_accessor(const cgltf_primitive* prim, cgltf_attribute_type type, cgltf_int index)\n{\n\tfor (cgltf_size i = 0; i < prim->attributes_count; ++i)\n\t{\n\t\tconst cgltf_attribute* attr = &prim->attributes[i];\n\t\tif (attr->type == type && attr->index == index)\n\t\t\treturn attr->data;\n\t}\n\n\treturn NULL;\n}\n\nstatic const uint8_t* cgltf_find_sparse_index(const cgltf_accessor* accessor, cgltf_size needle)\n{\n\tconst cgltf_accessor_sparse* sparse = &accessor->sparse;\n\tconst uint8_t* index_data = cgltf_buffer_view_data(sparse->indices_buffer_view);\n\tconst uint8_t* value_data = cgltf_buffer_view_data(sparse->values_buffer_view);\n\n\tif (index_data == NULL || value_data == NULL)\n\t\treturn NULL;\n\n\tindex_data += sparse->indices_byte_offset;\n\tvalue_data += sparse->values_byte_offset;\n\n\tcgltf_size index_stride = cgltf_component_size(sparse->indices_component_type);\n\n\tcgltf_size offset = 0;\n\tcgltf_size length = sparse->count;\n\n\twhile (length)\n\t{\n\t\tcgltf_size rem = length % 2;\n\t\tlength /= 2;\n\n\t\tcgltf_size index = cgltf_component_read_index(index_data + (offset + length) * index_stride, sparse->indices_component_type);\n\t\toffset += index < needle ? length + rem : 0;\n\t}\n\n\tif (offset == sparse->count)\n\t\treturn NULL;\n\n\tcgltf_size index = cgltf_component_read_index(index_data + offset * index_stride, sparse->indices_component_type);\n\treturn index == needle ? value_data + offset * accessor->stride : NULL;\n}\n\ncgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size)\n{\n\tif (accessor->is_sparse)\n\t{\n\t\tconst uint8_t* element = cgltf_find_sparse_index(accessor, index);\n\t\tif (element)\n\t\t\treturn cgltf_element_read_float(element, accessor->type, accessor->component_type, accessor->normalized, out, element_size);\n\t}\n\tif (accessor->buffer_view == NULL)\n\t{\n\t\tmemset(out, 0, element_size * sizeof(cgltf_float));\n\t\treturn 1;\n\t}\n\tconst uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view);\n\tif (element == NULL)\n\t{\n\t\treturn 0;\n\t}\n\telement += accessor->offset + accessor->stride * index;\n\treturn cgltf_element_read_float(element, accessor->type, accessor->component_type, accessor->normalized, out, element_size);\n}\n\ncgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, cgltf_float* out, cgltf_size float_count)\n{\n\tcgltf_size floats_per_element = cgltf_num_components(accessor->type);\n\tcgltf_size available_floats = accessor->count * floats_per_element;\n\tif (out == NULL)\n\t{\n\t\treturn available_floats;\n\t}\n\n\tfloat_count = available_floats < float_count ? available_floats : float_count;\n\tcgltf_size element_count = float_count / floats_per_element;\n\n\t// First pass: convert each element in the base accessor.\n\tif (accessor->buffer_view == NULL)\n\t{\n\t\tmemset(out, 0, element_count * floats_per_element * sizeof(cgltf_float));\n\t}\n\telse\n\t{\n\t\tconst uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view);\n\t\tif (element == NULL)\n\t\t{\n\t\t\treturn 0;\n\t\t}\n\t\telement += accessor->offset;\n\n\t\tif (accessor->component_type == cgltf_component_type_r_32f && accessor->stride == floats_per_element * sizeof(cgltf_float))\n\t\t{\n\t\t\tmemcpy(out, element, element_count * floats_per_element * sizeof(cgltf_float));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcgltf_float* dest = out;\n\n\t\t\tfor (cgltf_size index = 0; index < element_count; index++, dest += floats_per_element, element += accessor->stride)\n\t\t\t{\n\t\t\t\tif (!cgltf_element_read_float(element, accessor->type, accessor->component_type, accessor->normalized, dest, floats_per_element))\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Second pass: write out each element in the sparse accessor.\n\tif (accessor->is_sparse)\n\t{\n\t\tconst cgltf_accessor_sparse* sparse = &accessor->sparse;\n\n\t\tconst uint8_t* index_data = cgltf_buffer_view_data(sparse->indices_buffer_view);\n\t\tconst uint8_t* reader_head = cgltf_buffer_view_data(sparse->values_buffer_view);\n\n\t\tif (index_data == NULL || reader_head == NULL)\n\t\t{\n\t\t\treturn 0;\n\t\t}\n\n\t\tindex_data += sparse->indices_byte_offset;\n\t\treader_head += sparse->values_byte_offset;\n\n\t\tcgltf_size index_stride = cgltf_component_size(sparse->indices_component_type);\n\t\tfor (cgltf_size reader_index = 0; reader_index < sparse->count; reader_index++, index_data += index_stride, reader_head += accessor->stride)\n\t\t{\n\t\t\tsize_t writer_index = cgltf_component_read_index(index_data, sparse->indices_component_type);\n\t\t\tfloat* writer_head = out + writer_index * floats_per_element;\n\n\t\t\tif (!cgltf_element_read_float(reader_head, accessor->type, accessor->component_type, accessor->normalized, writer_head, floats_per_element))\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn element_count * floats_per_element;\n}\n\nstatic cgltf_uint cgltf_component_read_uint(const void* in, cgltf_component_type component_type)\n{\n\tswitch (component_type)\n\t{\n\t\tcase cgltf_component_type_r_8:\n\t\t\treturn *((const int8_t*) in);\n\n\t\tcase cgltf_component_type_r_8u:\n\t\t\treturn *((const uint8_t*) in);\n\n\t\tcase cgltf_component_type_r_16:\n\t\t\treturn *((const int16_t*) in);\n\n\t\tcase cgltf_component_type_r_16u:\n\t\t\treturn *((const uint16_t*) in);\n\n\t\tcase cgltf_component_type_r_32u:\n\t\t\treturn *((const uint32_t*) in);\n\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n\nstatic 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)\n{\n\tcgltf_size num_components = cgltf_num_components(type);\n\n\tif (element_size < num_components)\n\t{\n\t\treturn 0;\n\t}\n\n\t// Reading integer matrices is not a valid use case\n\tif (type == cgltf_type_mat2 || type == cgltf_type_mat3 || type == cgltf_type_mat4)\n\t{\n\t\treturn 0;\n\t}\n\n\tcgltf_size component_size = cgltf_component_size(component_type);\n\n\tfor (cgltf_size i = 0; i < num_components; ++i)\n\t{\n\t\tout[i] = cgltf_component_read_uint(element + component_size * i, component_type);\n\t}\n\treturn 1;\n}\n\ncgltf_bool cgltf_accessor_read_uint(const cgltf_accessor* accessor, cgltf_size index, cgltf_uint* out, cgltf_size element_size)\n{\n\tif (accessor->is_sparse)\n\t{\n\t\tconst uint8_t* element = cgltf_find_sparse_index(accessor, index);\n\t\tif (element)\n\t\t\treturn cgltf_element_read_uint(element, accessor->type, accessor->component_type, out, element_size);\n\t}\n\tif (accessor->buffer_view == NULL)\n\t{\n\t\tmemset(out, 0, element_size * sizeof(cgltf_uint));\n\t\treturn 1;\n\t}\n\tconst uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view);\n\tif (element == NULL)\n\t{\n\t\treturn 0;\n\t}\n\telement += accessor->offset + accessor->stride * index;\n\treturn cgltf_element_read_uint(element, accessor->type, accessor->component_type, out, element_size);\n}\n\ncgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index)\n{\n\tif (accessor->is_sparse)\n\t{\n\t\tconst uint8_t* element = cgltf_find_sparse_index(accessor, index);\n\t\tif (element)\n\t\t\treturn cgltf_component_read_index(element, accessor->component_type);\n\t}\n\tif (accessor->buffer_view == NULL)\n\t{\n\t\treturn 0;\n\t}\n\tconst uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view);\n\tif (element == NULL)\n\t{\n\t\treturn 0; // This is an error case, but we can't communicate the error with existing interface.\n\t}\n\telement += accessor->offset + accessor->stride * index;\n\treturn cgltf_component_read_index(element, accessor->component_type);\n}\n\ncgltf_size cgltf_mesh_index(const cgltf_data* data, const cgltf_mesh* object)\n{\n\tassert(object && (cgltf_size)(object - data->meshes) < data->meshes_count);\n\treturn (cgltf_size)(object - data->meshes);\n}\n\ncgltf_size cgltf_material_index(const cgltf_data* data, const cgltf_material* object)\n{\n\tassert(object && (cgltf_size)(object - data->materials) < data->materials_count);\n\treturn (cgltf_size)(object - data->materials);\n}\n\ncgltf_size cgltf_accessor_index(const cgltf_data* data, const cgltf_accessor* object)\n{\n\tassert(object && (cgltf_size)(object - data->accessors) < data->accessors_count);\n\treturn (cgltf_size)(object - data->accessors);\n}\n\ncgltf_size cgltf_buffer_view_index(const cgltf_data* data, const cgltf_buffer_view* object)\n{\n\tassert(object && (cgltf_size)(object - data->buffer_views) < data->buffer_views_count);\n\treturn (cgltf_size)(object - data->buffer_views);\n}\n\ncgltf_size cgltf_buffer_index(const cgltf_data* data, const cgltf_buffer* object)\n{\n\tassert(object && (cgltf_size)(object - data->buffers) < data->buffers_count);\n\treturn (cgltf_size)(object - data->buffers);\n}\n\ncgltf_size cgltf_image_index(const cgltf_data* data, const cgltf_image* object)\n{\n\tassert(object && (cgltf_size)(object - data->images) < data->images_count);\n\treturn (cgltf_size)(object - data->images);\n}\n\ncgltf_size cgltf_texture_index(const cgltf_data* data, const cgltf_texture* object)\n{\n\tassert(object && (cgltf_size)(object - data->textures) < data->textures_count);\n\treturn (cgltf_size)(object - data->textures);\n}\n\ncgltf_size cgltf_sampler_index(const cgltf_data* data, const cgltf_sampler* object)\n{\n\tassert(object && (cgltf_size)(object - data->samplers) < data->samplers_count);\n\treturn (cgltf_size)(object - data->samplers);\n}\n\ncgltf_size cgltf_skin_index(const cgltf_data* data, const cgltf_skin* object)\n{\n\tassert(object && (cgltf_size)(object - data->skins) < data->skins_count);\n\treturn (cgltf_size)(object - data->skins);\n}\n\ncgltf_size cgltf_camera_index(const cgltf_data* data, const cgltf_camera* object)\n{\n\tassert(object && (cgltf_size)(object - data->cameras) < data->cameras_count);\n\treturn (cgltf_size)(object - data->cameras);\n}\n\ncgltf_size cgltf_light_index(const cgltf_data* data, const cgltf_light* object)\n{\n\tassert(object && (cgltf_size)(object - data->lights) < data->lights_count);\n\treturn (cgltf_size)(object - data->lights);\n}\n\ncgltf_size cgltf_node_index(const cgltf_data* data, const cgltf_node* object)\n{\n\tassert(object && (cgltf_size)(object - data->nodes) < data->nodes_count);\n\treturn (cgltf_size)(object - data->nodes);\n}\n\ncgltf_size cgltf_scene_index(const cgltf_data* data, const cgltf_scene* object)\n{\n\tassert(object && (cgltf_size)(object - data->scenes) < data->scenes_count);\n\treturn (cgltf_size)(object - data->scenes);\n}\n\ncgltf_size cgltf_animation_index(const cgltf_data* data, const cgltf_animation* object)\n{\n\tassert(object && (cgltf_size)(object - data->animations) < data->animations_count);\n\treturn (cgltf_size)(object - data->animations);\n}\n\ncgltf_size cgltf_animation_sampler_index(const cgltf_animation* animation, const cgltf_animation_sampler* object)\n{\n\tassert(object && (cgltf_size)(object - animation->samplers) < animation->samplers_count);\n\treturn (cgltf_size)(object - animation->samplers);\n}\n\ncgltf_size cgltf_animation_channel_index(const cgltf_animation* animation, const cgltf_animation_channel* object)\n{\n\tassert(object && (cgltf_size)(object - animation->channels) < animation->channels_count);\n\treturn (cgltf_size)(object - animation->channels);\n}\n\ncgltf_size cgltf_accessor_unpack_indices(const cgltf_accessor* accessor, void* out, cgltf_size out_component_size, cgltf_size index_count)\n{\n\tif (out == NULL)\n\t{\n\t\treturn accessor->count;\n\t}\n\n\tcgltf_size numbers_per_element = cgltf_num_components(accessor->type);\n\tcgltf_size available_numbers = accessor->count * numbers_per_element;\n\n\tindex_count = available_numbers < index_count ? available_numbers : index_count;\n\tcgltf_size index_component_size = cgltf_component_size(accessor->component_type);\n\n\tif (accessor->is_sparse)\n\t{\n\t\treturn 0;\n\t}\n\tif (accessor->buffer_view == NULL)\n\t{\n\t\treturn 0;\n\t}\n\tif (index_component_size > out_component_size)\n\t{\n\t\treturn 0;\n\t}\n\tconst uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view);\n\tif (element == NULL)\n\t{\n\t\treturn 0;\n\t}\n\telement += accessor->offset;\n\n\tif (index_component_size == out_component_size && accessor->stride == out_component_size * numbers_per_element)\n\t{\n\t\tmemcpy(out, element, index_count * index_component_size);\n\t\treturn index_count;\n\t}\n\n\t// Data couldn't be copied with memcpy due to stride being larger than the component size.\n\t// OR\n\t// The component size of the output array is larger than the component size of the index data, so index data will be padded.\n\tswitch (out_component_size)\n\t{\n\tcase 1:\n\t\tfor (cgltf_size index = 0; index < index_count; index++, element += accessor->stride)\n\t\t{\n\t\t\t((uint8_t*)out)[index] = (uint8_t)cgltf_component_read_index(element, accessor->component_type);\n\t\t}\n\t\tbreak;\n\tcase 2:\n\t\tfor (cgltf_size index = 0; index < index_count; index++, element += accessor->stride)\n\t\t{\n\t\t\t((uint16_t*)out)[index] = (uint16_t)cgltf_component_read_index(element, accessor->component_type);\n\t\t}\n\t\tbreak;\n\tcase 4:\n\t\tfor (cgltf_size index = 0; index < index_count; index++, element += accessor->stride)\n\t\t{\n\t\t\t((uint32_t*)out)[index] = (uint32_t)cgltf_component_read_index(element, accessor->component_type);\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\treturn 0;\n\t}\n\n\treturn index_count;\n}\n\n#define CGLTF_ERROR_JSON -1\n#define CGLTF_ERROR_NOMEM -2\n#define CGLTF_ERROR_LEGACY -3\n\n#define CGLTF_CHECK_TOKTYPE(tok_, type_) if ((tok_).type != (type_)) { return CGLTF_ERROR_JSON; }\n#define CGLTF_CHECK_TOKTYPE_RET(tok_, type_, ret_) if ((tok_).type != (type_)) { return ret_; }\n#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 */\n\n#define CGLTF_PTRINDEX(type, idx) (type*)((cgltf_size)idx + 1)\n#define CGLTF_PTRFIXUP(var, data, size) if (var) { if ((cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1]; }\n#define CGLTF_PTRFIXUP_REQ(var, data, size) if (!var || (cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1];\n\nstatic int cgltf_json_strcmp(jsmntok_t const* tok, const uint8_t* json_chunk, const char* str)\n{\n\tCGLTF_CHECK_TOKTYPE(*tok, JSMN_STRING);\n\tsize_t const str_len = strlen(str);\n\tsize_t const name_length = (size_t)(tok->end - tok->start);\n\treturn (str_len == name_length) ? strncmp((const char*)json_chunk + tok->start, str, str_len) : 128;\n}\n\nstatic int cgltf_json_to_int(jsmntok_t const* tok, const uint8_t* json_chunk)\n{\n\tCGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE);\n\tchar tmp[128];\n\tint size = (size_t)(tok->end - tok->start) < sizeof(tmp) ? (int)(tok->end - tok->start) : (int)(sizeof(tmp) - 1);\n\tstrncpy(tmp, (const char*)json_chunk + tok->start, size);\n\ttmp[size] = 0;\n\treturn CGLTF_ATOI(tmp);\n}\n\nstatic cgltf_size cgltf_json_to_size(jsmntok_t const* tok, const uint8_t* json_chunk)\n{\n\tCGLTF_CHECK_TOKTYPE_RET(*tok, JSMN_PRIMITIVE, 0);\n\tchar tmp[128];\n\tint size = (size_t)(tok->end - tok->start) < sizeof(tmp) ? (int)(tok->end - tok->start) : (int)(sizeof(tmp) - 1);\n\tstrncpy(tmp, (const char*)json_chunk + tok->start, size);\n\ttmp[size] = 0;\n\tlong long res = CGLTF_ATOLL(tmp);\n\treturn res < 0 ? 0 : (cgltf_size)res;\n}\n\nstatic cgltf_float cgltf_json_to_float(jsmntok_t const* tok, const uint8_t* json_chunk)\n{\n\tCGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE);\n\tchar tmp[128];\n\tint size = (size_t)(tok->end - tok->start) < sizeof(tmp) ? (int)(tok->end - tok->start) : (int)(sizeof(tmp) - 1);\n\tstrncpy(tmp, (const char*)json_chunk + tok->start, size);\n\ttmp[size] = 0;\n\treturn (cgltf_float)CGLTF_ATOF(tmp);\n}\n\nstatic cgltf_bool cgltf_json_to_bool(jsmntok_t const* tok, const uint8_t* json_chunk)\n{\n\tint size = (int)(tok->end - tok->start);\n\treturn size == 4 && memcmp(json_chunk + tok->start, \"true\", 4) == 0;\n}\n\nstatic int cgltf_skip_json(jsmntok_t const* tokens, int i)\n{\n\tint end = i + 1;\n\n\twhile (i < end)\n\t{\n\t\tswitch (tokens[i].type)\n\t\t{\n\t\tcase JSMN_OBJECT:\n\t\t\tend += tokens[i].size * 2;\n\t\t\tbreak;\n\n\t\tcase JSMN_ARRAY:\n\t\t\tend += tokens[i].size;\n\t\t\tbreak;\n\n\t\tcase JSMN_PRIMITIVE:\n\t\tcase JSMN_STRING:\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\treturn -1;\n\t\t}\n\n\t\ti++;\n\t}\n\n\treturn i;\n}\n\nstatic void cgltf_fill_float_array(float* out_array, int size, float value)\n{\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tout_array[j] = value;\n\t}\n}\n\nstatic int cgltf_parse_json_float_array(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, float* out_array, int size)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY);\n\tif (tokens[i].size != size)\n\t{\n\t\treturn CGLTF_ERROR_JSON;\n\t}\n\t++i;\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE);\n\t\tout_array[j] = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t++i;\n\t}\n\treturn i;\n}\n\nstatic int cgltf_parse_json_string(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, char** out_string)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING);\n\tif (*out_string)\n\t{\n\t\treturn CGLTF_ERROR_JSON;\n\t}\n\tint size = (int)(tokens[i].end - tokens[i].start);\n\tchar* result = (char*)options->memory.alloc_func(options->memory.user_data, size + 1);\n\tif (!result)\n\t{\n\t\treturn CGLTF_ERROR_NOMEM;\n\t}\n\tstrncpy(result, (const char*)json_chunk + tokens[i].start, size);\n\tresult[size] = 0;\n\t*out_string = result;\n\treturn i + 1;\n}\n\nstatic 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)\n{\n\t(void)json_chunk;\n\tif (tokens[i].type != JSMN_ARRAY)\n\t{\n\t\treturn tokens[i].type == JSMN_OBJECT ? CGLTF_ERROR_LEGACY : CGLTF_ERROR_JSON;\n\t}\n\tif (*out_array)\n\t{\n\t\treturn CGLTF_ERROR_JSON;\n\t}\n\tint size = tokens[i].size;\n\tvoid* result = cgltf_calloc(options, element_size, size);\n\tif (!result)\n\t{\n\t\treturn CGLTF_ERROR_NOMEM;\n\t}\n\t*out_array = result;\n\t*out_size = size;\n\treturn i + 1;\n}\n\nstatic 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)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY);\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(char*), (void**)out_array, out_size);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < *out_size; ++j)\n\t{\n\t\ti = cgltf_parse_json_string(options, tokens, i, json_chunk, j + (*out_array));\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic void cgltf_parse_attribute_type(const char* name, cgltf_attribute_type* out_type, int* out_index)\n{\n\tif (*name == '_')\n\t{\n\t\t*out_type = cgltf_attribute_type_custom;\n\t\treturn;\n\t}\n\n\tconst char* us = strchr(name, '_');\n\tsize_t len = us ? (size_t)(us - name) : strlen(name);\n\n\tif (len == 8 && strncmp(name, \"POSITION\", 8) == 0)\n\t{\n\t\t*out_type = cgltf_attribute_type_position;\n\t}\n\telse if (len == 6 && strncmp(name, \"NORMAL\", 6) == 0)\n\t{\n\t\t*out_type = cgltf_attribute_type_normal;\n\t}\n\telse if (len == 7 && strncmp(name, \"TANGENT\", 7) == 0)\n\t{\n\t\t*out_type = cgltf_attribute_type_tangent;\n\t}\n\telse if (len == 8 && strncmp(name, \"TEXCOORD\", 8) == 0)\n\t{\n\t\t*out_type = cgltf_attribute_type_texcoord;\n\t}\n\telse if (len == 5 && strncmp(name, \"COLOR\", 5) == 0)\n\t{\n\t\t*out_type = cgltf_attribute_type_color;\n\t}\n\telse if (len == 6 && strncmp(name, \"JOINTS\", 6) == 0)\n\t{\n\t\t*out_type = cgltf_attribute_type_joints;\n\t}\n\telse if (len == 7 && strncmp(name, \"WEIGHTS\", 7) == 0)\n\t{\n\t\t*out_type = cgltf_attribute_type_weights;\n\t}\n\telse\n\t{\n\t\t*out_type = cgltf_attribute_type_invalid;\n\t}\n\n\tif (us && *out_type != cgltf_attribute_type_invalid)\n\t{\n\t\t*out_index = CGLTF_ATOI(us + 1);\n\t\tif (*out_index < 0)\n\t\t{\n\t\t\t*out_type = cgltf_attribute_type_invalid;\n\t\t\t*out_index = 0;\n\t\t}\n\t}\n}\n\nstatic 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)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tif (*out_attributes)\n\t{\n\t\treturn CGLTF_ERROR_JSON;\n\t}\n\n\t*out_attributes_count = tokens[i].size;\n\t*out_attributes = (cgltf_attribute*)cgltf_calloc(options, sizeof(cgltf_attribute), *out_attributes_count);\n\t++i;\n\n\tif (!*out_attributes)\n\t{\n\t\treturn CGLTF_ERROR_NOMEM;\n\t}\n\n\tfor (cgltf_size j = 0; j < *out_attributes_count; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\ti = cgltf_parse_json_string(options, tokens, i, json_chunk, &(*out_attributes)[j].name);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn CGLTF_ERROR_JSON;\n\t\t}\n\n\t\tcgltf_parse_attribute_type((*out_attributes)[j].name, &(*out_attributes)[j].type, &(*out_attributes)[j].index);\n\n\t\t(*out_attributes)[j].data = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t++i;\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_extras(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extras* out_extras)\n{\n\tif (out_extras->data)\n\t{\n\t\treturn CGLTF_ERROR_JSON;\n\t}\n\n\t/* fill deprecated fields for now, this will be removed in the future */\n\tout_extras->start_offset = tokens[i].start;\n\tout_extras->end_offset = tokens[i].end;\n\n\tsize_t start = tokens[i].start;\n\tsize_t size = tokens[i].end - start;\n\tout_extras->data = (char*)options->memory.alloc_func(options->memory.user_data, size + 1);\n\tif (!out_extras->data)\n\t{\n\t\treturn CGLTF_ERROR_NOMEM;\n\t}\n\tstrncpy(out_extras->data, (const char*)json_chunk + start, size);\n\tout_extras->data[size] = '\\0';\n\n\ti = cgltf_skip_json(tokens, i);\n\treturn i;\n}\n\nstatic int cgltf_parse_json_unprocessed_extension(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extension* out_extension)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING);\n\tCGLTF_CHECK_TOKTYPE(tokens[i+1], JSMN_OBJECT);\n\tif (out_extension->name)\n\t{\n\t\treturn CGLTF_ERROR_JSON;\n\t}\n\n\tcgltf_size name_length = tokens[i].end - tokens[i].start;\n\tout_extension->name = (char*)options->memory.alloc_func(options->memory.user_data, name_length + 1);\n\tif (!out_extension->name)\n\t{\n\t\treturn CGLTF_ERROR_NOMEM;\n\t}\n\tstrncpy(out_extension->name, (const char*)json_chunk + tokens[i].start, name_length);\n\tout_extension->name[name_length] = 0;\n\ti++;\n\n\tsize_t start = tokens[i].start;\n\tsize_t size = tokens[i].end - start;\n\tout_extension->data = (char*)options->memory.alloc_func(options->memory.user_data, size + 1);\n\tif (!out_extension->data)\n\t{\n\t\treturn CGLTF_ERROR_NOMEM;\n\t}\n\tstrncpy(out_extension->data, (const char*)json_chunk + start, size);\n\tout_extension->data[size] = '\\0';\n\n\ti = cgltf_skip_json(tokens, i);\n\n\treturn i;\n}\n\nstatic 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)\n{\n\t++i;\n\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\tif(*out_extensions)\n\t{\n\t\treturn CGLTF_ERROR_JSON;\n\t}\n\n\tint extensions_size = tokens[i].size;\n\t*out_extensions_count = 0;\n\t*out_extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size);\n\n\tif (!*out_extensions)\n\t{\n\t\treturn CGLTF_ERROR_NOMEM;\n\t}\n\n\t++i;\n\n\tfor (int j = 0; j < extensions_size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tcgltf_size extension_index = (*out_extensions_count)++;\n\t\tcgltf_extension* extension = &((*out_extensions)[extension_index]);\n\t\ti = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, extension);\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic 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)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"attributes\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_draco_mesh_compression->attributes, &out_draco_mesh_compression->attributes_count);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"bufferView\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_draco_mesh_compression->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic 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)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"attributes\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_mesh_gpu_instancing->attributes, &out_mesh_gpu_instancing->attributes_count);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic 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)\n{\n\t(void)options;\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\t\tint obj_size = tokens[i].size;\n\t\t++i;\n\n\t\tint material = -1;\n\t\tint variants_tok = -1;\n\t\tint extras_tok = -1;\n\n\t\tfor (int k = 0; k < obj_size; ++k)\n\t\t{\n\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"material\") == 0)\n\t\t\t{\n\t\t\t\t++i;\n\t\t\t\tmaterial = cgltf_json_to_int(tokens + i, json_chunk);\n\t\t\t\t++i;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"variants\") == 0)\n\t\t\t{\n\t\t\t\tvariants_tok = i+1;\n\t\t\t\tCGLTF_CHECK_TOKTYPE(tokens[variants_tok], JSMN_ARRAY);\n\n\t\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t\t{\n\t\t\t\textras_tok = i + 1;\n\t\t\t\ti = cgltf_skip_json(tokens, extras_tok);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t\t}\n\n\t\t\tif (i < 0)\n\t\t\t{\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\n\t\tif (material < 0 || variants_tok < 0)\n\t\t{\n\t\t\treturn CGLTF_ERROR_JSON;\n\t\t}\n\n\t\tif (out_mappings)\n\t\t{\n\t\t\tfor (int k = 0; k < tokens[variants_tok].size; ++k)\n\t\t\t{\n\t\t\t\tint variant = cgltf_json_to_int(&tokens[variants_tok + 1 + k], json_chunk);\n\t\t\t\tif (variant < 0)\n\t\t\t\t\treturn variant;\n\n\t\t\t\tout_mappings[*offset].material = CGLTF_PTRINDEX(cgltf_material, material);\n\t\t\t\tout_mappings[*offset].variant = variant;\n\n\t\t\t\tif (extras_tok >= 0)\n\t\t\t\t{\n\t\t\t\t\tint e = cgltf_parse_json_extras(options, tokens, extras_tok, json_chunk, &out_mappings[*offset].extras);\n\t\t\t\t\tif (e < 0)\n\t\t\t\t\t\treturn e;\n\t\t\t\t}\n\n\t\t\t\t(*offset)++;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t(*offset) += tokens[variants_tok].size;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_material_mappings(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_primitive* out_prim)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"mappings\") == 0)\n\t\t{\n\t\t\tif (out_prim->mappings)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_JSON;\n\t\t\t}\n\n\t\t\tcgltf_size mappings_offset = 0;\n\t\t\tint k = cgltf_parse_json_material_mapping_data(options, tokens, i + 1, json_chunk, NULL, &mappings_offset);\n\t\t\tif (k < 0)\n\t\t\t{\n\t\t\t\treturn k;\n\t\t\t}\n\n\t\t\tout_prim->mappings_count = mappings_offset;\n\t\t\tout_prim->mappings = (cgltf_material_mapping*)cgltf_calloc(options, sizeof(cgltf_material_mapping), out_prim->mappings_count);\n\n\t\t\tmappings_offset = 0;\n\t\t\ti = cgltf_parse_json_material_mapping_data(options, tokens, i + 1, json_chunk, out_prim->mappings, &mappings_offset);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic cgltf_primitive_type cgltf_json_to_primitive_type(jsmntok_t const* tok, const uint8_t* json_chunk)\n{\n\tint type = cgltf_json_to_int(tok, json_chunk);\n\n\tswitch (type)\n\t{\n\tcase 0:\n\t\treturn cgltf_primitive_type_points;\n\tcase 1:\n\t\treturn cgltf_primitive_type_lines;\n\tcase 2:\n\t\treturn cgltf_primitive_type_line_loop;\n\tcase 3:\n\t\treturn cgltf_primitive_type_line_strip;\n\tcase 4:\n\t\treturn cgltf_primitive_type_triangles;\n\tcase 5:\n\t\treturn cgltf_primitive_type_triangle_strip;\n\tcase 6:\n\t\treturn cgltf_primitive_type_triangle_fan;\n\tdefault:\n\t\treturn cgltf_primitive_type_invalid;\n\t}\n}\n\nstatic int cgltf_parse_json_primitive(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_primitive* out_prim)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tout_prim->type = cgltf_primitive_type_triangles;\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"mode\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_prim->type = cgltf_json_to_primitive_type(tokens+i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"indices\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_prim->indices = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"material\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_prim->material = CGLTF_PTRINDEX(cgltf_material, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"attributes\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_prim->attributes, &out_prim->attributes_count);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"targets\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_morph_target), (void**)&out_prim->targets, &out_prim->targets_count);\n\t\t\tif (i < 0)\n\t\t\t{\n\t\t\t\treturn i;\n\t\t\t}\n\n\t\t\tfor (cgltf_size k = 0; k < out_prim->targets_count; ++k)\n\t\t\t{\n\t\t\t\ti = cgltf_parse_json_attribute_list(options, tokens, i, json_chunk, &out_prim->targets[k].attributes, &out_prim->targets[k].attributes_count);\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_prim->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\t++i;\n\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\t\t\tif(out_prim->extensions)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_JSON;\n\t\t\t}\n\n\t\t\tint extensions_size = tokens[i].size;\n\t\t\tout_prim->extensions_count = 0;\n\t\t\tout_prim->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size);\n\n\t\t\tif (!out_prim->extensions)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_NOMEM;\n\t\t\t}\n\n\t\t\t++i;\n\t\t\tfor (int k = 0; k < extensions_size; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"KHR_draco_mesh_compression\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_prim->has_draco_mesh_compression = 1;\n\t\t\t\t\ti = cgltf_parse_json_draco_mesh_compression(options, tokens, i + 1, json_chunk, &out_prim->draco_mesh_compression);\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"KHR_materials_variants\") == 0)\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_parse_json_material_mappings(options, tokens, i + 1, json_chunk, out_prim);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_prim->extensions[out_prim->extensions_count++]));\n\t\t\t\t}\n\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_mesh(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_mesh* out_mesh)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"name\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_mesh->name);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"primitives\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_primitive), (void**)&out_mesh->primitives, &out_mesh->primitives_count);\n\t\t\tif (i < 0)\n\t\t\t{\n\t\t\t\treturn i;\n\t\t\t}\n\n\t\t\tfor (cgltf_size prim_index = 0; prim_index < out_mesh->primitives_count; ++prim_index)\n\t\t\t{\n\t\t\t\ti = cgltf_parse_json_primitive(options, tokens, i, json_chunk, &out_mesh->primitives[prim_index]);\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"weights\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_mesh->weights, &out_mesh->weights_count);\n\t\t\tif (i < 0)\n\t\t\t{\n\t\t\t\treturn i;\n\t\t\t}\n\n\t\t\ti = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_mesh->weights, (int)out_mesh->weights_count);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\t++i;\n\n\t\t\tout_mesh->extras.start_offset = tokens[i].start;\n\t\t\tout_mesh->extras.end_offset = tokens[i].end;\n\n\t\t\tif (tokens[i].type == JSMN_OBJECT)\n\t\t\t{\n\t\t\t\tint extras_size = tokens[i].size;\n\t\t\t\t++i;\n\n\t\t\t\tfor (int k = 0; k < extras_size; ++k)\n\t\t\t\t{\n\t\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"targetNames\") == 0 && tokens[i+1].type == JSMN_ARRAY)\n\t\t\t\t\t{\n\t\t\t\t\t\ti = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_mesh->target_names, &out_mesh->target_names_count);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (i < 0)\n\t\t\t\t\t{\n\t\t\t\t\t\treturn i;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\ti = cgltf_skip_json(tokens, i);\n\t\t\t}\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_mesh->extensions_count, &out_mesh->extensions);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_meshes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_mesh), (void**)&out_data->meshes, &out_data->meshes_count);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < out_data->meshes_count; ++j)\n\t{\n\t\ti = cgltf_parse_json_mesh(options, tokens, i, json_chunk, &out_data->meshes[j]);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic cgltf_component_type cgltf_json_to_component_type(jsmntok_t const* tok, const uint8_t* json_chunk)\n{\n\tint type = cgltf_json_to_int(tok, json_chunk);\n\n\tswitch (type)\n\t{\n\tcase 5120:\n\t\treturn cgltf_component_type_r_8;\n\tcase 5121:\n\t\treturn cgltf_component_type_r_8u;\n\tcase 5122:\n\t\treturn cgltf_component_type_r_16;\n\tcase 5123:\n\t\treturn cgltf_component_type_r_16u;\n\tcase 5125:\n\t\treturn cgltf_component_type_r_32u;\n\tcase 5126:\n\t\treturn cgltf_component_type_r_32f;\n\tdefault:\n\t\treturn cgltf_component_type_invalid;\n\t}\n}\n\nstatic int cgltf_parse_json_accessor_sparse(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor_sparse* out_sparse)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"count\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_sparse->count = cgltf_json_to_size(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"indices\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\t\t\tint indices_size = tokens[i].size;\n\t\t\t++i;\n\n\t\t\tfor (int k = 0; k < indices_size; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"bufferView\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_sparse->indices_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"byteOffset\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_sparse->indices_byte_offset = cgltf_json_to_size(tokens + i, json_chunk);\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"componentType\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_sparse->indices_component_type = cgltf_json_to_component_type(tokens + i, json_chunk);\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t\t\t}\n\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"values\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\t\t\tint values_size = tokens[i].size;\n\t\t\t++i;\n\n\t\t\tfor (int k = 0; k < values_size; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"bufferView\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_sparse->values_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"byteOffset\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_sparse->values_byte_offset = cgltf_json_to_size(tokens + i, json_chunk);\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t\t\t}\n\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_accessor(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor* out_accessor)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"name\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_accessor->name);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"bufferView\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_accessor->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"byteOffset\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_accessor->offset =\n\t\t\t\t\tcgltf_json_to_size(tokens+i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"componentType\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_accessor->component_type = cgltf_json_to_component_type(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"normalized\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_accessor->normalized = cgltf_json_to_bool(tokens+i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"count\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_accessor->count = cgltf_json_to_size(tokens+i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"type\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"SCALAR\") == 0)\n\t\t\t{\n\t\t\t\tout_accessor->type = cgltf_type_scalar;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"VEC2\") == 0)\n\t\t\t{\n\t\t\t\tout_accessor->type = cgltf_type_vec2;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"VEC3\") == 0)\n\t\t\t{\n\t\t\t\tout_accessor->type = cgltf_type_vec3;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"VEC4\") == 0)\n\t\t\t{\n\t\t\t\tout_accessor->type = cgltf_type_vec4;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"MAT2\") == 0)\n\t\t\t{\n\t\t\t\tout_accessor->type = cgltf_type_mat2;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"MAT3\") == 0)\n\t\t\t{\n\t\t\t\tout_accessor->type = cgltf_type_mat3;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"MAT4\") == 0)\n\t\t\t{\n\t\t\t\tout_accessor->type = cgltf_type_mat4;\n\t\t\t}\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"min\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_accessor->has_min = 1;\n\t\t\t// note: we can't parse the precise number of elements since type may not have been computed yet\n\t\t\tint min_size = tokens[i].size > 16 ? 16 : tokens[i].size;\n\t\t\ti = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->min, min_size);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"max\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_accessor->has_max = 1;\n\t\t\t// note: we can't parse the precise number of elements since type may not have been computed yet\n\t\t\tint max_size = tokens[i].size > 16 ? 16 : tokens[i].size;\n\t\t\ti = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->max, max_size);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"sparse\") == 0)\n\t\t{\n\t\t\tout_accessor->is_sparse = 1;\n\t\t\ti = cgltf_parse_json_accessor_sparse(tokens, i + 1, json_chunk, &out_accessor->sparse);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_accessor->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_accessor->extensions_count, &out_accessor->extensions);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_texture_transform(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_transform* out_texture_transform)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"offset\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->offset, 2);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"rotation\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_texture_transform->rotation = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"scale\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->scale, 2);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"texCoord\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_texture_transform->has_texcoord = 1;\n\t\t\tout_texture_transform->texcoord = cgltf_json_to_int(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic 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)\n{\n\t(void)options;\n\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tout_texture_view->scale = 1.0f;\n\tcgltf_fill_float_array(out_texture_view->transform.scale, 2, 1.0f);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"index\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_texture_view->texture = CGLTF_PTRINDEX(cgltf_texture, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"texCoord\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_texture_view->texcoord = cgltf_json_to_int(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"scale\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"strength\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\t++i;\n\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\t\t\tint extensions_size = tokens[i].size;\n\n\t\t\t++i;\n\n\t\t\tfor (int k = 0; k < extensions_size; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"KHR_texture_transform\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_texture_view->has_transform = 1;\n\t\t\t\t\ti = cgltf_parse_json_texture_transform(tokens, i + 1, json_chunk, &out_texture_view->transform);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t\t\t}\n\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic 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)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"metallicFactor\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_pbr->metallic_factor =\n\t\t\t\tcgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"roughnessFactor\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_pbr->roughness_factor =\n\t\t\t\tcgltf_json_to_float(tokens+i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"baseColorFactor\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->base_color_factor, 4);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"baseColorTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->base_color_texture);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"metallicRoughnessTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->metallic_roughness_texture);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic 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)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"diffuseFactor\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->diffuse_factor, 4);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"specularFactor\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->specular_factor, 3);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"glossinessFactor\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_pbr->glossiness_factor = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"diffuseTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->diffuse_texture);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"specularGlossinessTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->specular_glossiness_texture);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_clearcoat(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_clearcoat* out_clearcoat)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"clearcoatFactor\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_clearcoat->clearcoat_factor = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"clearcoatRoughnessFactor\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_clearcoat->clearcoat_roughness_factor = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"clearcoatTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_texture);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"clearcoatRoughnessTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_roughness_texture);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"clearcoatNormalTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_normal_texture);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_ior(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_ior* out_ior)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\tint size = tokens[i].size;\n\t++i;\n\n\t// Default values\n\tout_ior->ior = 1.5f;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"ior\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_ior->ior = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_specular(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_specular* out_specular)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\tint size = tokens[i].size;\n\t++i;\n\n\t// Default values\n\tout_specular->specular_factor = 1.0f;\n\tcgltf_fill_float_array(out_specular->specular_color_factor, 3, 1.0f);\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"specularFactor\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_specular->specular_factor = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"specularColorFactor\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_specular->specular_color_factor, 3);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"specularTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_specular->specular_texture);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"specularColorTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_specular->specular_color_texture);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_transmission(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_transmission* out_transmission)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"transmissionFactor\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_transmission->transmission_factor = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"transmissionTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_transmission->transmission_texture);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_volume(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_volume* out_volume)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"thicknessFactor\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_volume->thickness_factor = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"thicknessTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_volume->thickness_texture);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"attenuationColor\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_volume->attenuation_color, 3);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"attenuationDistance\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_volume->attenuation_distance = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_sheen(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_sheen* out_sheen)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"sheenColorFactor\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_sheen->sheen_color_factor, 3);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"sheenColorTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_sheen->sheen_color_texture);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"sheenRoughnessFactor\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_sheen->sheen_roughness_factor = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"sheenRoughnessTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_sheen->sheen_roughness_texture);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_emissive_strength(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_emissive_strength* out_emissive_strength)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\tint size = tokens[i].size;\n\t++i;\n\n\t// Default\n\tout_emissive_strength->emissive_strength = 1.f;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"emissiveStrength\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_emissive_strength->emissive_strength = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_iridescence(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_iridescence* out_iridescence)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\tint size = tokens[i].size;\n\t++i;\n\n\t// Default\n\tout_iridescence->iridescence_ior = 1.3f;\n\tout_iridescence->iridescence_thickness_min = 100.f;\n\tout_iridescence->iridescence_thickness_max = 400.f;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"iridescenceFactor\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_iridescence->iridescence_factor = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"iridescenceTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_iridescence->iridescence_texture);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"iridescenceIor\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_iridescence->iridescence_ior = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"iridescenceThicknessMinimum\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_iridescence->iridescence_thickness_min = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"iridescenceThicknessMaximum\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_iridescence->iridescence_thickness_max = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"iridescenceThicknessTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_iridescence->iridescence_thickness_texture);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic 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)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\tint size = tokens[i].size;\n\t++i;\n\n\t// Defaults\n\tcgltf_fill_float_array(out_diff_transmission->diffuse_transmission_color_factor, 3, 1.0f);\n\tout_diff_transmission->diffuse_transmission_factor = 0.f;\n\t\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"diffuseTransmissionFactor\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_diff_transmission->diffuse_transmission_factor = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"diffuseTransmissionTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_diff_transmission->diffuse_transmission_texture);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"diffuseTransmissionColorFactor\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_diff_transmission->diffuse_transmission_color_factor, 3);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"diffuseTransmissionColorTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_diff_transmission->diffuse_transmission_color_texture);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_anisotropy(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_anisotropy* out_anisotropy)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\tint size = tokens[i].size;\n\t++i;\n\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"anisotropyStrength\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_anisotropy->anisotropy_strength = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"anisotropyRotation\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_anisotropy->anisotropy_rotation = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"anisotropyTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_anisotropy->anisotropy_texture);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_dispersion(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_dispersion* out_dispersion)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\tint size = tokens[i].size;\n\t++i;\n\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"dispersion\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_dispersion->dispersion = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_image(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_image* out_image)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"uri\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->uri);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"bufferView\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_image->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"mimeType\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->mime_type);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"name\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->name);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_image->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_image->extensions_count, &out_image->extensions);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_sampler* out_sampler)\n{\n\t(void)options;\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tout_sampler->wrap_s = cgltf_wrap_mode_repeat;\n\tout_sampler->wrap_t = cgltf_wrap_mode_repeat;\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"name\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_sampler->name);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"magFilter\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_sampler->mag_filter\n\t\t\t\t= (cgltf_filter_type)cgltf_json_to_int(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"minFilter\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_sampler->min_filter\n\t\t\t\t= (cgltf_filter_type)cgltf_json_to_int(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"wrapS\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_sampler->wrap_s\n\t\t\t\t= (cgltf_wrap_mode)cgltf_json_to_int(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"wrapT\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_sampler->wrap_t\n\t\t\t\t= (cgltf_wrap_mode)cgltf_json_to_int(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_sampler->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sampler->extensions_count, &out_sampler->extensions);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_texture(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture* out_texture)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"name\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_texture->name);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"sampler\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_texture->sampler = CGLTF_PTRINDEX(cgltf_sampler, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"source\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_texture->image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_texture->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\t++i;\n\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\t\t\tif (out_texture->extensions)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_JSON;\n\t\t\t}\n\n\t\t\tint extensions_size = tokens[i].size;\n\t\t\t++i;\n\t\t\tout_texture->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size);\n\t\t\tout_texture->extensions_count = 0;\n\n\t\t\tif (!out_texture->extensions)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_NOMEM;\n\t\t\t}\n\n\t\t\tfor (int k = 0; k < extensions_size; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"KHR_texture_basisu\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_texture->has_basisu = 1;\n\t\t\t\t\t++i;\n\t\t\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\t\t\t\t\tint num_properties = tokens[i].size;\n\t\t\t\t\t++i;\n\n\t\t\t\t\tfor (int t = 0; t < num_properties; ++t)\n\t\t\t\t\t{\n\t\t\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\t\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"source\") == 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t++i;\n\t\t\t\t\t\t\tout_texture->basisu_image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t\t\t\t\t++i;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (i < 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn i;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"EXT_texture_webp\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_texture->has_webp = 1;\n\t\t\t\t\t++i;\n\t\t\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\t\t\t\t\tint num_properties = tokens[i].size;\n\t\t\t\t\t++i;\n\n\t\t\t\t\tfor (int t = 0; t < num_properties; ++t)\n\t\t\t\t\t{\n\t\t\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\t\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"source\") == 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t++i;\n\t\t\t\t\t\t\tout_texture->webp_image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t\t\t\t\t++i;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (i < 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn i;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_texture->extensions[out_texture->extensions_count++]));\n\t\t\t\t}\n\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material* out_material)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tcgltf_fill_float_array(out_material->pbr_metallic_roughness.base_color_factor, 4, 1.0f);\n\tout_material->pbr_metallic_roughness.metallic_factor = 1.0f;\n\tout_material->pbr_metallic_roughness.roughness_factor = 1.0f;\n\n\tcgltf_fill_float_array(out_material->pbr_specular_glossiness.diffuse_factor, 4, 1.0f);\n\tcgltf_fill_float_array(out_material->pbr_specular_glossiness.specular_factor, 3, 1.0f);\n\tout_material->pbr_specular_glossiness.glossiness_factor = 1.0f;\n\n\tcgltf_fill_float_array(out_material->volume.attenuation_color, 3, 1.0f);\n\tout_material->volume.attenuation_distance = FLT_MAX;\n\n\tout_material->alpha_cutoff = 0.5f;\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"name\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_material->name);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"pbrMetallicRoughness\") == 0)\n\t\t{\n\t\t\tout_material->has_pbr_metallic_roughness = 1;\n\t\t\ti = cgltf_parse_json_pbr_metallic_roughness(options, tokens, i + 1, json_chunk, &out_material->pbr_metallic_roughness);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"emissiveFactor\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_material->emissive_factor, 3);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"normalTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk,\n\t\t\t\t&out_material->normal_texture);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"occlusionTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk,\n\t\t\t\t&out_material->occlusion_texture);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"emissiveTexture\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk,\n\t\t\t\t&out_material->emissive_texture);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"alphaMode\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"OPAQUE\") == 0)\n\t\t\t{\n\t\t\t\tout_material->alpha_mode = cgltf_alpha_mode_opaque;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"MASK\") == 0)\n\t\t\t{\n\t\t\t\tout_material->alpha_mode = cgltf_alpha_mode_mask;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"BLEND\") == 0)\n\t\t\t{\n\t\t\t\tout_material->alpha_mode = cgltf_alpha_mode_blend;\n\t\t\t}\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"alphaCutoff\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_material->alpha_cutoff = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"doubleSided\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_material->double_sided =\n\t\t\t\tcgltf_json_to_bool(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_material->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\t++i;\n\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\t\t\tif(out_material->extensions)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_JSON;\n\t\t\t}\n\n\t\t\tint extensions_size = tokens[i].size;\n\t\t\t++i;\n\t\t\tout_material->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size);\n\t\t\tout_material->extensions_count= 0;\n\n\t\t\tif (!out_material->extensions)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_NOMEM;\n\t\t\t}\n\n\t\t\tfor (int k = 0; k < extensions_size; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"KHR_materials_pbrSpecularGlossiness\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_material->has_pbr_specular_glossiness = 1;\n\t\t\t\t\ti = cgltf_parse_json_pbr_specular_glossiness(options, tokens, i + 1, json_chunk, &out_material->pbr_specular_glossiness);\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"KHR_materials_unlit\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_material->unlit = 1;\n\t\t\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"KHR_materials_clearcoat\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_material->has_clearcoat = 1;\n\t\t\t\t\ti = cgltf_parse_json_clearcoat(options, tokens, i + 1, json_chunk, &out_material->clearcoat);\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"KHR_materials_ior\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_material->has_ior = 1;\n\t\t\t\t\ti = cgltf_parse_json_ior(tokens, i + 1, json_chunk, &out_material->ior);\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"KHR_materials_specular\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_material->has_specular = 1;\n\t\t\t\t\ti = cgltf_parse_json_specular(options, tokens, i + 1, json_chunk, &out_material->specular);\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"KHR_materials_transmission\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_material->has_transmission = 1;\n\t\t\t\t\ti = cgltf_parse_json_transmission(options, tokens, i + 1, json_chunk, &out_material->transmission);\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"KHR_materials_volume\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_material->has_volume = 1;\n\t\t\t\t\ti = cgltf_parse_json_volume(options, tokens, i + 1, json_chunk, &out_material->volume);\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"KHR_materials_sheen\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_material->has_sheen = 1;\n\t\t\t\t\ti = cgltf_parse_json_sheen(options, tokens, i + 1, json_chunk, &out_material->sheen);\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"KHR_materials_emissive_strength\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_material->has_emissive_strength = 1;\n\t\t\t\t\ti = cgltf_parse_json_emissive_strength(tokens, i + 1, json_chunk, &out_material->emissive_strength);\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"KHR_materials_iridescence\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_material->has_iridescence = 1;\n\t\t\t\t\ti = cgltf_parse_json_iridescence(options, tokens, i + 1, json_chunk, &out_material->iridescence);\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"KHR_materials_diffuse_transmission\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_material->has_diffuse_transmission = 1;\n\t\t\t\t\ti = cgltf_parse_json_diffuse_transmission(options, tokens, i + 1, json_chunk, &out_material->diffuse_transmission);\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"KHR_materials_anisotropy\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_material->has_anisotropy = 1;\n\t\t\t\t\ti = cgltf_parse_json_anisotropy(options, tokens, i + 1, json_chunk, &out_material->anisotropy);\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"KHR_materials_dispersion\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_material->has_dispersion = 1;\n\t\t\t\t\ti = cgltf_parse_json_dispersion(tokens, i + 1, json_chunk, &out_material->dispersion);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_material->extensions[out_material->extensions_count++]));\n\t\t\t\t}\n\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_accessors(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_accessor), (void**)&out_data->accessors, &out_data->accessors_count);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < out_data->accessors_count; ++j)\n\t{\n\t\ti = cgltf_parse_json_accessor(options, tokens, i, json_chunk, &out_data->accessors[j]);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic int cgltf_parse_json_materials(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_material), (void**)&out_data->materials, &out_data->materials_count);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < out_data->materials_count; ++j)\n\t{\n\t\ti = cgltf_parse_json_material(options, tokens, i, json_chunk, &out_data->materials[j]);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic int cgltf_parse_json_images(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_image), (void**)&out_data->images, &out_data->images_count);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < out_data->images_count; ++j)\n\t{\n\t\ti = cgltf_parse_json_image(options, tokens, i, json_chunk, &out_data->images[j]);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic int cgltf_parse_json_textures(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_texture), (void**)&out_data->textures, &out_data->textures_count);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < out_data->textures_count; ++j)\n\t{\n\t\ti = cgltf_parse_json_texture(options, tokens, i, json_chunk, &out_data->textures[j]);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic int cgltf_parse_json_samplers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_sampler), (void**)&out_data->samplers, &out_data->samplers_count);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < out_data->samplers_count; ++j)\n\t{\n\t\ti = cgltf_parse_json_sampler(options, tokens, i, json_chunk, &out_data->samplers[j]);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic 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)\n{\n\t(void)options;\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"buffer\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_meshopt_compression->buffer = CGLTF_PTRINDEX(cgltf_buffer, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"byteOffset\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_meshopt_compression->offset = cgltf_json_to_size(tokens+i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"byteLength\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_meshopt_compression->size = cgltf_json_to_size(tokens+i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"byteStride\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_meshopt_compression->stride = cgltf_json_to_size(tokens+i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"count\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_meshopt_compression->count = cgltf_json_to_size(tokens+i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"mode\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"ATTRIBUTES\") == 0)\n\t\t\t{\n\t\t\t\tout_meshopt_compression->mode = cgltf_meshopt_compression_mode_attributes;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"TRIANGLES\") == 0)\n\t\t\t{\n\t\t\t\tout_meshopt_compression->mode = cgltf_meshopt_compression_mode_triangles;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"INDICES\") == 0)\n\t\t\t{\n\t\t\t\tout_meshopt_compression->mode = cgltf_meshopt_compression_mode_indices;\n\t\t\t}\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"filter\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"NONE\") == 0)\n\t\t\t{\n\t\t\t\tout_meshopt_compression->filter = cgltf_meshopt_compression_filter_none;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"OCTAHEDRAL\") == 0)\n\t\t\t{\n\t\t\t\tout_meshopt_compression->filter = cgltf_meshopt_compression_filter_octahedral;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"QUATERNION\") == 0)\n\t\t\t{\n\t\t\t\tout_meshopt_compression->filter = cgltf_meshopt_compression_filter_quaternion;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"EXPONENTIAL\") == 0)\n\t\t\t{\n\t\t\t\tout_meshopt_compression->filter = cgltf_meshopt_compression_filter_exponential;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"COLOR\") == 0)\n\t\t\t{\n\t\t\t\tout_meshopt_compression->filter = cgltf_meshopt_compression_filter_color;\n\t\t\t}\n\t\t\t++i;\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic 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)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"name\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer_view->name);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"buffer\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_buffer_view->buffer = CGLTF_PTRINDEX(cgltf_buffer, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"byteOffset\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_buffer_view->offset =\n\t\t\t\t\tcgltf_json_to_size(tokens+i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"byteLength\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_buffer_view->size =\n\t\t\t\t\tcgltf_json_to_size(tokens+i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"byteStride\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_buffer_view->stride =\n\t\t\t\t\tcgltf_json_to_size(tokens+i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"target\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tint type = cgltf_json_to_int(tokens+i, json_chunk);\n\t\t\tswitch (type)\n\t\t\t{\n\t\t\tcase 34962:\n\t\t\t\ttype = cgltf_buffer_view_type_vertices;\n\t\t\t\tbreak;\n\t\t\tcase 34963:\n\t\t\t\ttype = cgltf_buffer_view_type_indices;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\ttype = cgltf_buffer_view_type_invalid;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tout_buffer_view->type = (cgltf_buffer_view_type)type;\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_buffer_view->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\t++i;\n\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\t\t\tif(out_buffer_view->extensions)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_JSON;\n\t\t\t}\n\n\t\t\tint extensions_size = tokens[i].size;\n\t\t\tout_buffer_view->extensions_count = 0;\n\t\t\tout_buffer_view->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size);\n\n\t\t\tif (!out_buffer_view->extensions)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_NOMEM;\n\t\t\t}\n\n\t\t\t++i;\n\t\t\tfor (int k = 0; k < extensions_size; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"EXT_meshopt_compression\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_buffer_view->has_meshopt_compression = 1;\n\t\t\t\t\ti = cgltf_parse_json_meshopt_compression(options, tokens, i + 1, json_chunk, &out_buffer_view->meshopt_compression);\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"KHR_meshopt_compression\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_buffer_view->has_meshopt_compression = 1;\n\t\t\t\t\tout_buffer_view->meshopt_compression.is_khr = 1;\n\t\t\t\t\ti = cgltf_parse_json_meshopt_compression(options, tokens, i + 1, json_chunk, &out_buffer_view->meshopt_compression);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_buffer_view->extensions[out_buffer_view->extensions_count++]));\n\t\t\t\t}\n\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_buffer_views(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer_view), (void**)&out_data->buffer_views, &out_data->buffer_views_count);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < out_data->buffer_views_count; ++j)\n\t{\n\t\ti = cgltf_parse_json_buffer_view(options, tokens, i, json_chunk, &out_data->buffer_views[j]);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic int cgltf_parse_json_buffer(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_buffer* out_buffer)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"name\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer->name);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"byteLength\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_buffer->size =\n\t\t\t\t\tcgltf_json_to_size(tokens+i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"uri\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer->uri);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_buffer->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_buffer->extensions_count, &out_buffer->extensions);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_buffers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer), (void**)&out_data->buffers, &out_data->buffers_count);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < out_data->buffers_count; ++j)\n\t{\n\t\ti = cgltf_parse_json_buffer(options, tokens, i, json_chunk, &out_data->buffers[j]);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic int cgltf_parse_json_skin(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_skin* out_skin)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"name\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_skin->name);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"joints\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_skin->joints, &out_skin->joints_count);\n\t\t\tif (i < 0)\n\t\t\t{\n\t\t\t\treturn i;\n\t\t\t}\n\n\t\t\tfor (cgltf_size k = 0; k < out_skin->joints_count; ++k)\n\t\t\t{\n\t\t\t\tout_skin->joints[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t\t++i;\n\t\t\t}\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"skeleton\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE);\n\t\t\tout_skin->skeleton = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"inverseBindMatrices\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE);\n\t\t\tout_skin->inverse_bind_matrices = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_skin->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_skin->extensions_count, &out_skin->extensions);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_skins(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_skin), (void**)&out_data->skins, &out_data->skins_count);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < out_data->skins_count; ++j)\n\t{\n\t\ti = cgltf_parse_json_skin(options, tokens, i, json_chunk, &out_data->skins[j]);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_camera* out_camera)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"name\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_camera->name);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"perspective\") == 0)\n\t\t{\n\t\t\t++i;\n\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\t\t\tint data_size = tokens[i].size;\n\t\t\t++i;\n\n\t\t\tif (out_camera->type != cgltf_camera_type_invalid)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_JSON;\n\t\t\t}\n\n\t\t\tout_camera->type = cgltf_camera_type_perspective;\n\n\t\t\tfor (int k = 0; k < data_size; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"aspectRatio\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_camera->data.perspective.has_aspect_ratio = 1;\n\t\t\t\t\tout_camera->data.perspective.aspect_ratio = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"yfov\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_camera->data.perspective.yfov = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"zfar\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_camera->data.perspective.has_zfar = 1;\n\t\t\t\t\tout_camera->data.perspective.zfar = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"znear\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_camera->data.perspective.znear = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_camera->data.perspective.extras);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t\t\t}\n\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"orthographic\") == 0)\n\t\t{\n\t\t\t++i;\n\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\t\t\tint data_size = tokens[i].size;\n\t\t\t++i;\n\n\t\t\tif (out_camera->type != cgltf_camera_type_invalid)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_JSON;\n\t\t\t}\n\n\t\t\tout_camera->type = cgltf_camera_type_orthographic;\n\n\t\t\tfor (int k = 0; k < data_size; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"xmag\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_camera->data.orthographic.xmag = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"ymag\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_camera->data.orthographic.ymag = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"zfar\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_camera->data.orthographic.zfar = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"znear\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_camera->data.orthographic.znear = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_camera->data.orthographic.extras);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t\t\t}\n\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_camera->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_camera->extensions_count, &out_camera->extensions);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_cameras(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_camera), (void**)&out_data->cameras, &out_data->cameras_count);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < out_data->cameras_count; ++j)\n\t{\n\t\ti = cgltf_parse_json_camera(options, tokens, i, json_chunk, &out_data->cameras[j]);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic int cgltf_parse_json_light(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_light* out_light)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tout_light->color[0] = 1.f;\n\tout_light->color[1] = 1.f;\n\tout_light->color[2] = 1.f;\n\tout_light->intensity = 1.f;\n\n\tout_light->spot_inner_cone_angle = 0.f;\n\tout_light->spot_outer_cone_angle = 3.1415926535f / 4.0f;\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"name\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_light->name);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"color\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_light->color, 3);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"intensity\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_light->intensity = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"type\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"directional\") == 0)\n\t\t\t{\n\t\t\t\tout_light->type = cgltf_light_type_directional;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"point\") == 0)\n\t\t\t{\n\t\t\t\tout_light->type = cgltf_light_type_point;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"spot\") == 0)\n\t\t\t{\n\t\t\t\tout_light->type = cgltf_light_type_spot;\n\t\t\t}\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"range\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_light->range = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"spot\") == 0)\n\t\t{\n\t\t\t++i;\n\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\t\t\tint data_size = tokens[i].size;\n\t\t\t++i;\n\n\t\t\tfor (int k = 0; k < data_size; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"innerConeAngle\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_light->spot_inner_cone_angle = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"outerConeAngle\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_light->spot_outer_cone_angle = cgltf_json_to_float(tokens + i, json_chunk);\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t\t\t}\n\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_light->extras);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_lights(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_light), (void**)&out_data->lights, &out_data->lights_count);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < out_data->lights_count; ++j)\n\t{\n\t\ti = cgltf_parse_json_light(options, tokens, i, json_chunk, &out_data->lights[j]);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic int cgltf_parse_json_node(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_node* out_node)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tout_node->rotation[3] = 1.0f;\n\tout_node->scale[0] = 1.0f;\n\tout_node->scale[1] = 1.0f;\n\tout_node->scale[2] = 1.0f;\n\tout_node->matrix[0] = 1.0f;\n\tout_node->matrix[5] = 1.0f;\n\tout_node->matrix[10] = 1.0f;\n\tout_node->matrix[15] = 1.0f;\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"name\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_node->name);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"children\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_node->children, &out_node->children_count);\n\t\t\tif (i < 0)\n\t\t\t{\n\t\t\t\treturn i;\n\t\t\t}\n\n\t\t\tfor (cgltf_size k = 0; k < out_node->children_count; ++k)\n\t\t\t{\n\t\t\t\tout_node->children[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t\t++i;\n\t\t\t}\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"mesh\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE);\n\t\t\tout_node->mesh = CGLTF_PTRINDEX(cgltf_mesh, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"skin\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE);\n\t\t\tout_node->skin = CGLTF_PTRINDEX(cgltf_skin, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"camera\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE);\n\t\t\tout_node->camera = CGLTF_PTRINDEX(cgltf_camera, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"translation\") == 0)\n\t\t{\n\t\t\tout_node->has_translation = 1;\n\t\t\ti = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->translation, 3);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"rotation\") == 0)\n\t\t{\n\t\t\tout_node->has_rotation = 1;\n\t\t\ti = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->rotation, 4);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"scale\") == 0)\n\t\t{\n\t\t\tout_node->has_scale = 1;\n\t\t\ti = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->scale, 3);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"matrix\") == 0)\n\t\t{\n\t\t\tout_node->has_matrix = 1;\n\t\t\ti = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->matrix, 16);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"weights\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_node->weights, &out_node->weights_count);\n\t\t\tif (i < 0)\n\t\t\t{\n\t\t\t\treturn i;\n\t\t\t}\n\n\t\t\ti = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_node->weights, (int)out_node->weights_count);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_node->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\t++i;\n\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\t\t\tif(out_node->extensions)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_JSON;\n\t\t\t}\n\n\t\t\tint extensions_size = tokens[i].size;\n\t\t\tout_node->extensions_count= 0;\n\t\t\tout_node->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size);\n\n\t\t\tif (!out_node->extensions)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_NOMEM;\n\t\t\t}\n\n\t\t\t++i;\n\n\t\t\tfor (int k = 0; k < extensions_size; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"KHR_lights_punctual\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\n\t\t\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\t\t\t\t\tint data_size = tokens[i].size;\n\t\t\t\t\t++i;\n\n\t\t\t\t\tfor (int m = 0; m < data_size; ++m)\n\t\t\t\t\t{\n\t\t\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\t\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"light\") == 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t++i;\n\t\t\t\t\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE);\n\t\t\t\t\t\t\tout_node->light = CGLTF_PTRINDEX(cgltf_light, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t\t\t\t\t++i;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (i < 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn i;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"EXT_mesh_gpu_instancing\") == 0)\n\t\t\t\t{\n\t\t\t\t\tout_node->has_mesh_gpu_instancing = 1;\n\t\t\t\t\ti = cgltf_parse_json_mesh_gpu_instancing(options, tokens, i + 1, json_chunk, &out_node->mesh_gpu_instancing);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_node->extensions[out_node->extensions_count++]));\n\t\t\t\t}\n\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_nodes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_node), (void**)&out_data->nodes, &out_data->nodes_count);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < out_data->nodes_count; ++j)\n\t{\n\t\ti = cgltf_parse_json_node(options, tokens, i, json_chunk, &out_data->nodes[j]);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic int cgltf_parse_json_scene(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_scene* out_scene)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"name\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_scene->name);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"nodes\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_scene->nodes, &out_scene->nodes_count);\n\t\t\tif (i < 0)\n\t\t\t{\n\t\t\t\treturn i;\n\t\t\t}\n\n\t\t\tfor (cgltf_size k = 0; k < out_scene->nodes_count; ++k)\n\t\t\t{\n\t\t\t\tout_scene->nodes[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t\t++i;\n\t\t\t}\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_scene->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_scene->extensions_count, &out_scene->extensions);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_scenes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_scene), (void**)&out_data->scenes, &out_data->scenes_count);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < out_data->scenes_count; ++j)\n\t{\n\t\ti = cgltf_parse_json_scene(options, tokens, i, json_chunk, &out_data->scenes[j]);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic 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)\n{\n\t(void)options;\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"input\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_sampler->input = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"output\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_sampler->output = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"interpolation\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"LINEAR\") == 0)\n\t\t\t{\n\t\t\t\tout_sampler->interpolation = cgltf_interpolation_type_linear;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"STEP\") == 0)\n\t\t\t{\n\t\t\t\tout_sampler->interpolation = cgltf_interpolation_type_step;\n\t\t\t}\n\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"CUBICSPLINE\") == 0)\n\t\t\t{\n\t\t\t\tout_sampler->interpolation = cgltf_interpolation_type_cubic_spline;\n\t\t\t}\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_sampler->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sampler->extensions_count, &out_sampler->extensions);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic 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)\n{\n\t(void)options;\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"sampler\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_channel->sampler = CGLTF_PTRINDEX(cgltf_animation_sampler, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"target\") == 0)\n\t\t{\n\t\t\t++i;\n\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\t\t\tint target_size = tokens[i].size;\n\t\t\t++i;\n\n\t\t\tfor (int k = 0; k < target_size; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"node\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tout_channel->target_node = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"path\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"translation\") == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tout_channel->target_path = cgltf_animation_path_type_translation;\n\t\t\t\t\t}\n\t\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"rotation\") == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tout_channel->target_path = cgltf_animation_path_type_rotation;\n\t\t\t\t\t}\n\t\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"scale\") == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tout_channel->target_path = cgltf_animation_path_type_scale;\n\t\t\t\t\t}\n\t\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"weights\") == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tout_channel->target_path = cgltf_animation_path_type_weights;\n\t\t\t\t\t}\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_channel->extras);\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_channel->extensions_count, &out_channel->extensions);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t\t\t}\n\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_animation(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation* out_animation)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"name\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_animation->name);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"samplers\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_sampler), (void**)&out_animation->samplers, &out_animation->samplers_count);\n\t\t\tif (i < 0)\n\t\t\t{\n\t\t\t\treturn i;\n\t\t\t}\n\n\t\t\tfor (cgltf_size k = 0; k < out_animation->samplers_count; ++k)\n\t\t\t{\n\t\t\t\ti = cgltf_parse_json_animation_sampler(options, tokens, i, json_chunk, &out_animation->samplers[k]);\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"channels\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_channel), (void**)&out_animation->channels, &out_animation->channels_count);\n\t\t\tif (i < 0)\n\t\t\t{\n\t\t\t\treturn i;\n\t\t\t}\n\n\t\t\tfor (cgltf_size k = 0; k < out_animation->channels_count; ++k)\n\t\t\t{\n\t\t\t\ti = cgltf_parse_json_animation_channel(options, tokens, i, json_chunk, &out_animation->channels[k]);\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_animation->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_animation->extensions_count, &out_animation->extensions);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_animations(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_animation), (void**)&out_data->animations, &out_data->animations_count);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < out_data->animations_count; ++j)\n\t{\n\t\ti = cgltf_parse_json_animation(options, tokens, i, json_chunk, &out_data->animations[j]);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic int cgltf_parse_json_variant(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material_variant* out_variant)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"name\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_variant->name);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_variant->extras);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\nstatic int cgltf_parse_json_variants(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\ti = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_material_variant), (void**)&out_data->variants, &out_data->variants_count);\n\tif (i < 0)\n\t{\n\t\treturn i;\n\t}\n\n\tfor (cgltf_size j = 0; j < out_data->variants_count; ++j)\n\t{\n\t\ti = cgltf_parse_json_variant(options, tokens, i, json_chunk, &out_data->variants[j]);\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic int cgltf_parse_json_asset(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_asset* out_asset)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"copyright\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->copyright);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"generator\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->generator);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"version\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->version);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"minVersion\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->min_version);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_asset->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_asset->extensions_count, &out_asset->extensions);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i+1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\tif (out_asset->version && CGLTF_ATOF(out_asset->version) < 2)\n\t{\n\t\treturn CGLTF_ERROR_LEGACY;\n\t}\n\n\treturn i;\n}\n\ncgltf_size cgltf_num_components(cgltf_type type) {\n\tswitch (type)\n\t{\n\tcase cgltf_type_vec2:\n\t\treturn 2;\n\tcase cgltf_type_vec3:\n\t\treturn 3;\n\tcase cgltf_type_vec4:\n\t\treturn 4;\n\tcase cgltf_type_mat2:\n\t\treturn 4;\n\tcase cgltf_type_mat3:\n\t\treturn 9;\n\tcase cgltf_type_mat4:\n\t\treturn 16;\n\tcase cgltf_type_invalid:\n\tcase cgltf_type_scalar:\n\tdefault:\n\t\treturn 1;\n\t}\n}\n\ncgltf_size cgltf_component_size(cgltf_component_type component_type) {\n\tswitch (component_type)\n\t{\n\tcase cgltf_component_type_r_8:\n\tcase cgltf_component_type_r_8u:\n\t\treturn 1;\n\tcase cgltf_component_type_r_16:\n\tcase cgltf_component_type_r_16u:\n\t\treturn 2;\n\tcase cgltf_component_type_r_32u:\n\tcase cgltf_component_type_r_32f:\n\t\treturn 4;\n\tcase cgltf_component_type_invalid:\n\tdefault:\n\t\treturn 0;\n\t}\n}\n\ncgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type)\n{\n\tcgltf_size component_size = cgltf_component_size(component_type);\n\tif (type == cgltf_type_mat2 && component_size == 1)\n\t{\n\t\treturn 8 * component_size;\n\t}\n\telse if (type == cgltf_type_mat3 && (component_size == 1 || component_size == 2))\n\t{\n\t\treturn 12 * component_size;\n\t}\n\treturn component_size * cgltf_num_components(type);\n}\n\nstatic int cgltf_fixup_pointers(cgltf_data* out_data);\n\nstatic int cgltf_parse_json_root(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)\n{\n\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\tint size = tokens[i].size;\n\t++i;\n\n\tfor (int j = 0; j < size; ++j)\n\t{\n\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"asset\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_asset(options, tokens, i + 1, json_chunk, &out_data->asset);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"meshes\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_meshes(options, tokens, i + 1, json_chunk, out_data);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"accessors\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_accessors(options, tokens, i + 1, json_chunk, out_data);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"bufferViews\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_buffer_views(options, tokens, i + 1, json_chunk, out_data);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"buffers\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_buffers(options, tokens, i + 1, json_chunk, out_data);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"materials\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_materials(options, tokens, i + 1, json_chunk, out_data);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"images\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_images(options, tokens, i + 1, json_chunk, out_data);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"textures\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_textures(options, tokens, i + 1, json_chunk, out_data);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"samplers\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_samplers(options, tokens, i + 1, json_chunk, out_data);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"skins\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_skins(options, tokens, i + 1, json_chunk, out_data);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"cameras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_cameras(options, tokens, i + 1, json_chunk, out_data);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"nodes\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_nodes(options, tokens, i + 1, json_chunk, out_data);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"scenes\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_scenes(options, tokens, i + 1, json_chunk, out_data);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"scene\") == 0)\n\t\t{\n\t\t\t++i;\n\t\t\tout_data->scene = CGLTF_PTRINDEX(cgltf_scene, cgltf_json_to_int(tokens + i, json_chunk));\n\t\t\t++i;\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"animations\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_animations(options, tokens, i + 1, json_chunk, out_data);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"extras\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_data->extras);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensions\") == 0)\n\t\t{\n\t\t\t++i;\n\n\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\t\t\tif(out_data->data_extensions)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_JSON;\n\t\t\t}\n\n\t\t\tint extensions_size = tokens[i].size;\n\t\t\tout_data->data_extensions_count = 0;\n\t\t\tout_data->data_extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size);\n\n\t\t\tif (!out_data->data_extensions)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_NOMEM;\n\t\t\t}\n\n\t\t\t++i;\n\n\t\t\tfor (int k = 0; k < extensions_size; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\tif (cgltf_json_strcmp(tokens+i, json_chunk, \"KHR_lights_punctual\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\n\t\t\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\t\t\t\t\tint data_size = tokens[i].size;\n\t\t\t\t\t++i;\n\n\t\t\t\t\tfor (int m = 0; m < data_size; ++m)\n\t\t\t\t\t{\n\t\t\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\t\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"lights\") == 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ti = cgltf_parse_json_lights(options, tokens, i + 1, json_chunk, out_data);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (i < 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn i;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (cgltf_json_strcmp(tokens+i, json_chunk, \"KHR_materials_variants\") == 0)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\n\t\t\t\t\tCGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);\n\n\t\t\t\t\tint data_size = tokens[i].size;\n\t\t\t\t\t++i;\n\n\t\t\t\t\tfor (int m = 0; m < data_size; ++m)\n\t\t\t\t\t{\n\t\t\t\t\t\tCGLTF_CHECK_KEY(tokens[i]);\n\n\t\t\t\t\t\tif (cgltf_json_strcmp(tokens + i, json_chunk, \"variants\") == 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ti = cgltf_parse_json_variants(options, tokens, i + 1, json_chunk, out_data);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (i < 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn i;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\ti = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_data->data_extensions[out_data->data_extensions_count++]));\n\t\t\t\t}\n\n\t\t\t\tif (i < 0)\n\t\t\t\t{\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensionsUsed\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_used, &out_data->extensions_used_count);\n\t\t}\n\t\telse if (cgltf_json_strcmp(tokens + i, json_chunk, \"extensionsRequired\") == 0)\n\t\t{\n\t\t\ti = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_required, &out_data->extensions_required_count);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = cgltf_skip_json(tokens, i + 1);\n\t\t}\n\n\t\tif (i < 0)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn i;\n}\n\ncgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data)\n{\n\tjsmn_parser parser = { 0, 0, 0 };\n\n\tif (options->json_token_count == 0)\n\t{\n\t\tint token_count = jsmn_parse(&parser, (const char*)json_chunk, size, NULL, 0);\n\n\t\tif (token_count <= 0)\n\t\t{\n\t\t\treturn cgltf_result_invalid_json;\n\t\t}\n\n\t\toptions->json_token_count = token_count;\n\t}\n\n\tjsmntok_t* tokens = (jsmntok_t*)options->memory.alloc_func(options->memory.user_data, sizeof(jsmntok_t) * (options->json_token_count + 1));\n\n\tif (!tokens)\n\t{\n\t\treturn cgltf_result_out_of_memory;\n\t}\n\n\tjsmn_init(&parser);\n\n\tint token_count = jsmn_parse(&parser, (const char*)json_chunk, size, tokens, options->json_token_count);\n\n\tif (token_count <= 0)\n\t{\n\t\toptions->memory.free_func(options->memory.user_data, tokens);\n\t\treturn cgltf_result_invalid_json;\n\t}\n\n\t// this makes sure that we always have an UNDEFINED token at the end of the stream\n\t// for invalid JSON inputs this makes sure we don't perform out of bound reads of token data\n\ttokens[token_count].type = JSMN_UNDEFINED;\n\n\tcgltf_data* data = (cgltf_data*)options->memory.alloc_func(options->memory.user_data, sizeof(cgltf_data));\n\n\tif (!data)\n\t{\n\t\toptions->memory.free_func(options->memory.user_data, tokens);\n\t\treturn cgltf_result_out_of_memory;\n\t}\n\n\tmemset(data, 0, sizeof(cgltf_data));\n\tdata->memory = options->memory;\n\tdata->file = options->file;\n\n\tint i = cgltf_parse_json_root(options, tokens, 0, json_chunk, data);\n\n\toptions->memory.free_func(options->memory.user_data, tokens);\n\n\tif (i < 0)\n\t{\n\t\tcgltf_free(data);\n\n\t\tswitch (i)\n\t\t{\n\t\tcase CGLTF_ERROR_NOMEM: return cgltf_result_out_of_memory;\n\t\tcase CGLTF_ERROR_LEGACY: return cgltf_result_legacy_gltf;\n\t\tdefault: return cgltf_result_invalid_gltf;\n\t\t}\n\t}\n\n\tif (cgltf_fixup_pointers(data) < 0)\n\t{\n\t\tcgltf_free(data);\n\t\treturn cgltf_result_invalid_gltf;\n\t}\n\n\tdata->json = (const char*)json_chunk;\n\tdata->json_size = size;\n\n\t*out_data = data;\n\n\treturn cgltf_result_success;\n}\n\nstatic int cgltf_fixup_pointers(cgltf_data* data)\n{\n\tfor (cgltf_size i = 0; i < data->meshes_count; ++i)\n\t{\n\t\tfor (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j)\n\t\t{\n\t\t\tCGLTF_PTRFIXUP(data->meshes[i].primitives[j].indices, data->accessors, data->accessors_count);\n\t\t\tCGLTF_PTRFIXUP(data->meshes[i].primitives[j].material, data->materials, data->materials_count);\n\n\t\t\tfor (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].attributes[k].data, data->accessors, data->accessors_count);\n\t\t\t}\n\n\t\t\tfor (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k)\n\t\t\t{\n\t\t\t\tfor (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m)\n\t\t\t\t{\n\t\t\t\t\tCGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].targets[k].attributes[m].data, data->accessors, data->accessors_count);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (data->meshes[i].primitives[j].has_draco_mesh_compression)\n\t\t\t{\n\t\t\t\tCGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].draco_mesh_compression.buffer_view, data->buffer_views, data->buffer_views_count);\n\t\t\t\tfor (cgltf_size m = 0; m < data->meshes[i].primitives[j].draco_mesh_compression.attributes_count; ++m)\n\t\t\t\t{\n\t\t\t\t\tCGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].draco_mesh_compression.attributes[m].data, data->accessors, data->accessors_count);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k)\n\t\t\t{\n\t\t\t\tCGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].mappings[k].material, data->materials, data->materials_count);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (cgltf_size i = 0; i < data->accessors_count; ++i)\n\t{\n\t\tCGLTF_PTRFIXUP(data->accessors[i].buffer_view, data->buffer_views, data->buffer_views_count);\n\n\t\tif (data->accessors[i].is_sparse)\n\t\t{\n\t\t\tCGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.indices_buffer_view, data->buffer_views, data->buffer_views_count);\n\t\t\tCGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.values_buffer_view, data->buffer_views, data->buffer_views_count);\n\t\t}\n\n\t\tif (data->accessors[i].buffer_view)\n\t\t{\n\t\t\tdata->accessors[i].stride = data->accessors[i].buffer_view->stride;\n\t\t}\n\n\t\tif (data->accessors[i].stride == 0)\n\t\t{\n\t\t\tdata->accessors[i].stride = cgltf_calc_size(data->accessors[i].type, data->accessors[i].component_type);\n\t\t}\n\t}\n\n\tfor (cgltf_size i = 0; i < data->textures_count; ++i)\n\t{\n\t\tCGLTF_PTRFIXUP(data->textures[i].image, data->images, data->images_count);\n\t\tCGLTF_PTRFIXUP(data->textures[i].basisu_image, data->images, data->images_count);\n\t\tCGLTF_PTRFIXUP(data->textures[i].webp_image, data->images, data->images_count);\n\t\tCGLTF_PTRFIXUP(data->textures[i].sampler, data->samplers, data->samplers_count);\n\t}\n\n\tfor (cgltf_size i = 0; i < data->images_count; ++i)\n\t{\n\t\tCGLTF_PTRFIXUP(data->images[i].buffer_view, data->buffer_views, data->buffer_views_count);\n\t}\n\n\tfor (cgltf_size i = 0; i < data->materials_count; ++i)\n\t{\n\t\tCGLTF_PTRFIXUP(data->materials[i].normal_texture.texture, data->textures, data->textures_count);\n\t\tCGLTF_PTRFIXUP(data->materials[i].emissive_texture.texture, data->textures, data->textures_count);\n\t\tCGLTF_PTRFIXUP(data->materials[i].occlusion_texture.texture, data->textures, data->textures_count);\n\n\t\tCGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.base_color_texture.texture, data->textures, data->textures_count);\n\t\tCGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture, data->textures, data->textures_count);\n\n\t\tCGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.diffuse_texture.texture, data->textures, data->textures_count);\n\t\tCGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.texture, data->textures, data->textures_count);\n\n\t\tCGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_texture.texture, data->textures, data->textures_count);\n\t\tCGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_roughness_texture.texture, data->textures, data->textures_count);\n\t\tCGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_normal_texture.texture, data->textures, data->textures_count);\n\n\t\tCGLTF_PTRFIXUP(data->materials[i].specular.specular_texture.texture, data->textures, data->textures_count);\n\t\tCGLTF_PTRFIXUP(data->materials[i].specular.specular_color_texture.texture, data->textures, data->textures_count);\n\n\t\tCGLTF_PTRFIXUP(data->materials[i].transmission.transmission_texture.texture, data->textures, data->textures_count);\n\n\t\tCGLTF_PTRFIXUP(data->materials[i].volume.thickness_texture.texture, data->textures, data->textures_count);\n\n\t\tCGLTF_PTRFIXUP(data->materials[i].sheen.sheen_color_texture.texture, data->textures, data->textures_count);\n\t\tCGLTF_PTRFIXUP(data->materials[i].sheen.sheen_roughness_texture.texture, data->textures, data->textures_count);\n\n\t\tCGLTF_PTRFIXUP(data->materials[i].iridescence.iridescence_texture.texture, data->textures, data->textures_count);\n\t\tCGLTF_PTRFIXUP(data->materials[i].iridescence.iridescence_thickness_texture.texture, data->textures, data->textures_count);\n\n\t\tCGLTF_PTRFIXUP(data->materials[i].diffuse_transmission.diffuse_transmission_texture.texture, data->textures, data->textures_count);\n\t\tCGLTF_PTRFIXUP(data->materials[i].diffuse_transmission.diffuse_transmission_color_texture.texture, data->textures, data->textures_count);\n\n\t\tCGLTF_PTRFIXUP(data->materials[i].anisotropy.anisotropy_texture.texture, data->textures, data->textures_count);\n\t}\n\n\tfor (cgltf_size i = 0; i < data->buffer_views_count; ++i)\n\t{\n\t\tCGLTF_PTRFIXUP_REQ(data->buffer_views[i].buffer, data->buffers, data->buffers_count);\n\n\t\tif (data->buffer_views[i].has_meshopt_compression)\n\t\t{\n\t\t\tCGLTF_PTRFIXUP_REQ(data->buffer_views[i].meshopt_compression.buffer, data->buffers, data->buffers_count);\n\t\t}\n\t}\n\n\tfor (cgltf_size i = 0; i < data->skins_count; ++i)\n\t{\n\t\tfor (cgltf_size j = 0; j < data->skins[i].joints_count; ++j)\n\t\t{\n\t\t\tCGLTF_PTRFIXUP_REQ(data->skins[i].joints[j], data->nodes, data->nodes_count);\n\t\t}\n\n\t\tCGLTF_PTRFIXUP(data->skins[i].skeleton, data->nodes, data->nodes_count);\n\t\tCGLTF_PTRFIXUP(data->skins[i].inverse_bind_matrices, data->accessors, data->accessors_count);\n\t}\n\n\tfor (cgltf_size i = 0; i < data->nodes_count; ++i)\n\t{\n\t\tfor (cgltf_size j = 0; j < data->nodes[i].children_count; ++j)\n\t\t{\n\t\t\tCGLTF_PTRFIXUP_REQ(data->nodes[i].children[j], data->nodes, data->nodes_count);\n\n\t\t\tif (data->nodes[i].children[j]->parent)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_JSON;\n\t\t\t}\n\n\t\t\tdata->nodes[i].children[j]->parent = &data->nodes[i];\n\t\t}\n\n\t\tCGLTF_PTRFIXUP(data->nodes[i].mesh, data->meshes, data->meshes_count);\n\t\tCGLTF_PTRFIXUP(data->nodes[i].skin, data->skins, data->skins_count);\n\t\tCGLTF_PTRFIXUP(data->nodes[i].camera, data->cameras, data->cameras_count);\n\t\tCGLTF_PTRFIXUP(data->nodes[i].light, data->lights, data->lights_count);\n\n\t\tif (data->nodes[i].has_mesh_gpu_instancing)\n\t\t{\n\t\t\tfor (cgltf_size m = 0; m < data->nodes[i].mesh_gpu_instancing.attributes_count; ++m)\n\t\t\t{\n\t\t\t\tCGLTF_PTRFIXUP_REQ(data->nodes[i].mesh_gpu_instancing.attributes[m].data, data->accessors, data->accessors_count);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (cgltf_size i = 0; i < data->scenes_count; ++i)\n\t{\n\t\tfor (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j)\n\t\t{\n\t\t\tCGLTF_PTRFIXUP_REQ(data->scenes[i].nodes[j], data->nodes, data->nodes_count);\n\n\t\t\tif (data->scenes[i].nodes[j]->parent)\n\t\t\t{\n\t\t\t\treturn CGLTF_ERROR_JSON;\n\t\t\t}\n\t\t}\n\t}\n\n\tCGLTF_PTRFIXUP(data->scene, data->scenes, data->scenes_count);\n\n\tfor (cgltf_size i = 0; i < data->animations_count; ++i)\n\t{\n\t\tfor (cgltf_size j = 0; j < data->animations[i].samplers_count; ++j)\n\t\t{\n\t\t\tCGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].input, data->accessors, data->accessors_count);\n\t\t\tCGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].output, data->accessors, data->accessors_count);\n\t\t}\n\n\t\tfor (cgltf_size j = 0; j < data->animations[i].channels_count; ++j)\n\t\t{\n\t\t\tCGLTF_PTRFIXUP_REQ(data->animations[i].channels[j].sampler, data->animations[i].samplers, data->animations[i].samplers_count);\n\t\t\tCGLTF_PTRFIXUP(data->animations[i].channels[j].target_node, data->nodes, data->nodes_count);\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/*\n * -- jsmn.c start --\n * Source: https://github.com/zserge/jsmn\n * License: MIT\n *\n * Copyright (c) 2010 Serge A. Zaitsev\n\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/**\n * Allocates a fresh unused token from the token pull.\n */\nstatic jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,\n\t\t\t\t   jsmntok_t *tokens, size_t num_tokens) {\n\tjsmntok_t *tok;\n\tif (parser->toknext >= num_tokens) {\n\t\treturn NULL;\n\t}\n\ttok = &tokens[parser->toknext++];\n\ttok->start = tok->end = -1;\n\ttok->size = 0;\n#ifdef JSMN_PARENT_LINKS\n\ttok->parent = -1;\n#endif\n\treturn tok;\n}\n\n/**\n * Fills token type and boundaries.\n */\nstatic void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,\n\t\t\t\tptrdiff_t start, ptrdiff_t end) {\n\ttoken->type = type;\n\ttoken->start = start;\n\ttoken->end = end;\n\ttoken->size = 0;\n}\n\n/**\n * Fills next available token with JSON primitive.\n */\nstatic int jsmn_parse_primitive(jsmn_parser *parser, const char *js,\n\t\t\t\tsize_t len, jsmntok_t *tokens, size_t num_tokens) {\n\tjsmntok_t *token;\n\tptrdiff_t start;\n\n\tstart = parser->pos;\n\n\tfor (; parser->pos < len && js[parser->pos] != '\\0'; parser->pos++) {\n\t\tswitch (js[parser->pos]) {\n#ifndef JSMN_STRICT\n\t\t/* In strict mode primitive must be followed by \",\" or \"}\" or \"]\" */\n\t\tcase ':':\n#endif\n\t\tcase '\\t' : case '\\r' : case '\\n' : case ' ' :\n\t\tcase ','  : case ']'  : case '}' :\n\t\t\tgoto found;\n\t\t}\n\t\tif (js[parser->pos] < 32 || js[parser->pos] >= 127) {\n\t\t\tparser->pos = start;\n\t\t\treturn JSMN_ERROR_INVAL;\n\t\t}\n\t}\n#ifdef JSMN_STRICT\n\t/* In strict mode primitive must be followed by a comma/object/array */\n\tparser->pos = start;\n\treturn JSMN_ERROR_PART;\n#endif\n\nfound:\n\tif (tokens == NULL) {\n\t\tparser->pos--;\n\t\treturn 0;\n\t}\n\ttoken = jsmn_alloc_token(parser, tokens, num_tokens);\n\tif (token == NULL) {\n\t\tparser->pos = start;\n\t\treturn JSMN_ERROR_NOMEM;\n\t}\n\tjsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);\n#ifdef JSMN_PARENT_LINKS\n\ttoken->parent = parser->toksuper;\n#endif\n\tparser->pos--;\n\treturn 0;\n}\n\n/**\n * Fills next token with JSON string.\n */\nstatic int jsmn_parse_string(jsmn_parser *parser, const char *js,\n\t\t\t\t size_t len, jsmntok_t *tokens, size_t num_tokens) {\n\tjsmntok_t *token;\n\n\tptrdiff_t start = parser->pos;\n\n\tparser->pos++;\n\n\t/* Skip starting quote */\n\tfor (; parser->pos < len && js[parser->pos] != '\\0'; parser->pos++) {\n\t\tchar c = js[parser->pos];\n\n\t\t/* Quote: end of string */\n\t\tif (c == '\\\"') {\n\t\t\tif (tokens == NULL) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\ttoken = jsmn_alloc_token(parser, tokens, num_tokens);\n\t\t\tif (token == NULL) {\n\t\t\t\tparser->pos = start;\n\t\t\t\treturn JSMN_ERROR_NOMEM;\n\t\t\t}\n\t\t\tjsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);\n#ifdef JSMN_PARENT_LINKS\n\t\t\ttoken->parent = parser->toksuper;\n#endif\n\t\t\treturn 0;\n\t\t}\n\n\t\t/* Backslash: Quoted symbol expected */\n\t\tif (c == '\\\\' && parser->pos + 1 < len) {\n\t\t\tint i;\n\t\t\tparser->pos++;\n\t\t\tswitch (js[parser->pos]) {\n\t\t\t/* Allowed escaped symbols */\n\t\t\tcase '\\\"': case '/' : case '\\\\' : case 'b' :\n\t\t\tcase 'f' : case 'r' : case 'n'  : case 't' :\n\t\t\t\tbreak;\n\t\t\t\t/* Allows escaped symbol \\uXXXX */\n\t\t\tcase 'u':\n\t\t\t\tparser->pos++;\n\t\t\t\tfor(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\\0'; i++) {\n\t\t\t\t\t/* If it isn't a hex character we have an error */\n\t\t\t\t\tif(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */\n\t\t\t\t\t\t (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */\n\t\t\t\t\t\t (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */\n\t\t\t\t\t\tparser->pos = start;\n\t\t\t\t\t\treturn JSMN_ERROR_INVAL;\n\t\t\t\t\t}\n\t\t\t\t\tparser->pos++;\n\t\t\t\t}\n\t\t\t\tparser->pos--;\n\t\t\t\tbreak;\n\t\t\t\t/* Unexpected symbol */\n\t\t\tdefault:\n\t\t\t\tparser->pos = start;\n\t\t\t\treturn JSMN_ERROR_INVAL;\n\t\t\t}\n\t\t}\n\t}\n\tparser->pos = start;\n\treturn JSMN_ERROR_PART;\n}\n\n/**\n * Parse JSON string and fill tokens.\n */\nstatic int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,\n\t\t   jsmntok_t *tokens, size_t num_tokens) {\n\tint r;\n\tint i;\n\tjsmntok_t *token;\n\tint count = parser->toknext;\n\n\tfor (; parser->pos < len && js[parser->pos] != '\\0'; parser->pos++) {\n\t\tchar c;\n\t\tjsmntype_t type;\n\n\t\tc = js[parser->pos];\n\t\tswitch (c) {\n\t\tcase '{': case '[':\n\t\t\tcount++;\n\t\t\tif (tokens == NULL) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\ttoken = jsmn_alloc_token(parser, tokens, num_tokens);\n\t\t\tif (token == NULL)\n\t\t\t\treturn JSMN_ERROR_NOMEM;\n\t\t\tif (parser->toksuper != -1) {\n\t\t\t\ttokens[parser->toksuper].size++;\n#ifdef JSMN_PARENT_LINKS\n\t\t\t\ttoken->parent = parser->toksuper;\n#endif\n\t\t\t}\n\t\t\ttoken->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);\n\t\t\ttoken->start = parser->pos;\n\t\t\tparser->toksuper = parser->toknext - 1;\n\t\t\tbreak;\n\t\tcase '}': case ']':\n\t\t\tif (tokens == NULL)\n\t\t\t\tbreak;\n\t\t\ttype = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);\n#ifdef JSMN_PARENT_LINKS\n\t\t\tif (parser->toknext < 1) {\n\t\t\t\treturn JSMN_ERROR_INVAL;\n\t\t\t}\n\t\t\ttoken = &tokens[parser->toknext - 1];\n\t\t\tfor (;;) {\n\t\t\t\tif (token->start != -1 && token->end == -1) {\n\t\t\t\t\tif (token->type != type) {\n\t\t\t\t\t\treturn JSMN_ERROR_INVAL;\n\t\t\t\t\t}\n\t\t\t\t\ttoken->end = parser->pos + 1;\n\t\t\t\t\tparser->toksuper = token->parent;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (token->parent == -1) {\n\t\t\t\t\tif(token->type != type || parser->toksuper == -1) {\n\t\t\t\t\t\treturn JSMN_ERROR_INVAL;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\ttoken = &tokens[token->parent];\n\t\t\t}\n#else\n\t\t\tfor (i = parser->toknext - 1; i >= 0; i--) {\n\t\t\t\ttoken = &tokens[i];\n\t\t\t\tif (token->start != -1 && token->end == -1) {\n\t\t\t\t\tif (token->type != type) {\n\t\t\t\t\t\treturn JSMN_ERROR_INVAL;\n\t\t\t\t\t}\n\t\t\t\t\tparser->toksuper = -1;\n\t\t\t\t\ttoken->end = parser->pos + 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\t/* Error if unmatched closing bracket */\n\t\t\tif (i == -1) return JSMN_ERROR_INVAL;\n\t\t\tfor (; i >= 0; i--) {\n\t\t\t\ttoken = &tokens[i];\n\t\t\t\tif (token->start != -1 && token->end == -1) {\n\t\t\t\t\tparser->toksuper = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n#endif\n\t\t\tbreak;\n\t\tcase '\\\"':\n\t\t\tr = jsmn_parse_string(parser, js, len, tokens, num_tokens);\n\t\t\tif (r < 0) return r;\n\t\t\tcount++;\n\t\t\tif (parser->toksuper != -1 && tokens != NULL)\n\t\t\t\ttokens[parser->toksuper].size++;\n\t\t\tbreak;\n\t\tcase '\\t' : case '\\r' : case '\\n' : case ' ':\n\t\t\tbreak;\n\t\tcase ':':\n\t\t\tparser->toksuper = parser->toknext - 1;\n\t\t\tbreak;\n\t\tcase ',':\n\t\t\tif (tokens != NULL && parser->toksuper != -1 &&\n\t\t\t\t\ttokens[parser->toksuper].type != JSMN_ARRAY &&\n\t\t\t\t\ttokens[parser->toksuper].type != JSMN_OBJECT) {\n#ifdef JSMN_PARENT_LINKS\n\t\t\t\tparser->toksuper = tokens[parser->toksuper].parent;\n#else\n\t\t\t\tfor (i = parser->toknext - 1; i >= 0; i--) {\n\t\t\t\t\tif (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {\n\t\t\t\t\t\tif (tokens[i].start != -1 && tokens[i].end == -1) {\n\t\t\t\t\t\t\tparser->toksuper = i;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n#endif\n\t\t\t}\n\t\t\tbreak;\n#ifdef JSMN_STRICT\n\t\t\t/* In strict mode primitives are: numbers and booleans */\n\t\tcase '-': case '0': case '1' : case '2': case '3' : case '4':\n\t\tcase '5': case '6': case '7' : case '8': case '9':\n\t\tcase 't': case 'f': case 'n' :\n\t\t\t/* And they must not be keys of the object */\n\t\t\tif (tokens != NULL && parser->toksuper != -1) {\n\t\t\t\tjsmntok_t *t = &tokens[parser->toksuper];\n\t\t\t\tif (t->type == JSMN_OBJECT ||\n\t\t\t\t\t\t(t->type == JSMN_STRING && t->size != 0)) {\n\t\t\t\t\treturn JSMN_ERROR_INVAL;\n\t\t\t\t}\n\t\t\t}\n#else\n\t\t\t/* In non-strict mode every unquoted value is a primitive */\n\t\tdefault:\n#endif\n\t\t\tr = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);\n\t\t\tif (r < 0) return r;\n\t\t\tcount++;\n\t\t\tif (parser->toksuper != -1 && tokens != NULL)\n\t\t\t\ttokens[parser->toksuper].size++;\n\t\t\tbreak;\n\n#ifdef JSMN_STRICT\n\t\t\t/* Unexpected char in strict mode */\n\t\tdefault:\n\t\t\treturn JSMN_ERROR_INVAL;\n#endif\n\t\t}\n\t}\n\n\tif (tokens != NULL) {\n\t\tfor (i = parser->toknext - 1; i >= 0; i--) {\n\t\t\t/* Unmatched opened object or array */\n\t\t\tif (tokens[i].start != -1 && tokens[i].end == -1) {\n\t\t\t\treturn JSMN_ERROR_PART;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn count;\n}\n\n/**\n * Creates a new parser based over a given  buffer with an array of tokens\n * available.\n */\nstatic void jsmn_init(jsmn_parser *parser) {\n\tparser->pos = 0;\n\tparser->toknext = 0;\n\tparser->toksuper = -1;\n}\n/*\n * -- jsmn.c end --\n */\n\n#endif /* #ifdef CGLTF_IMPLEMENTATION */\n\n/* cgltf is distributed under MIT license:\n *\n * Copyright (c) 2018-2021 Johannes Kuhlmann\n\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n"
  },
  {
    "path": "extern/fast_obj.h",
    "content": "/*\n * fast_obj\n *\n * Version 1.3\n *\n * MIT License\n *\n * Copyright (c) 2018-2021 Richard Knight\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n *\n */\n\n#ifndef FAST_OBJ_HDR\n#define FAST_OBJ_HDR\n\n#define FAST_OBJ_VERSION_MAJOR  1\n#define FAST_OBJ_VERSION_MINOR  3\n#define FAST_OBJ_VERSION        ((FAST_OBJ_VERSION_MAJOR << 8) | FAST_OBJ_VERSION_MINOR)\n\n#include <stdlib.h>\n\n\ntypedef struct\n{\n    /* Texture name from .mtl file */\n    char*                       name;\n\n    /* Resolved path to texture */\n    char*                       path;\n\n} fastObjTexture;\n\n\ntypedef struct\n{\n    /* Material name */\n    char*                       name;\n\n    /* Parameters */\n    float                       Ka[3];  /* Ambient */\n    float                       Kd[3];  /* Diffuse */\n    float                       Ks[3];  /* Specular */\n    float                       Ke[3];  /* Emission */\n    float                       Kt[3];  /* Transmittance */\n    float                       Ns;     /* Shininess */\n    float                       Ni;     /* Index of refraction */\n    float                       Tf[3];  /* Transmission filter */\n    float                       d;      /* Disolve (alpha) */\n    int                         illum;  /* Illumination model */\n\n    /* Set for materials that don't come from the associated mtllib */\n    int                         fallback;\n\n    /* Texture map indices in fastObjMesh textures array */\n    unsigned int                map_Ka;\n    unsigned int                map_Kd;\n    unsigned int                map_Ks;\n    unsigned int                map_Ke;\n    unsigned int                map_Kt;\n    unsigned int                map_Ns;\n    unsigned int                map_Ni;\n    unsigned int                map_d;\n    unsigned int                map_bump;\n\n} fastObjMaterial;\n\n/* Allows user override to bigger indexable array */\n#ifndef FAST_OBJ_UINT_TYPE\n#define FAST_OBJ_UINT_TYPE unsigned int\n#endif\n\ntypedef FAST_OBJ_UINT_TYPE fastObjUInt;\n\ntypedef struct\n{\n    fastObjUInt                 p;\n    fastObjUInt                 t;\n    fastObjUInt                 n;\n\n} fastObjIndex;\n\n\ntypedef struct\n{\n    /* Group name */\n    char*                       name;\n\n    /* Number of faces */\n    unsigned int                face_count;\n\n    /* First face in fastObjMesh face_* arrays */\n    unsigned int                face_offset;\n\n    /* First index in fastObjMesh indices array */\n    unsigned int                index_offset;\n\n} fastObjGroup;\n\n\n/* Note: a dummy zero-initialized value is added to the first index\n   of the positions, texcoords, normals and textures arrays. Hence,\n   valid indices into these arrays start from 1, with an index of 0\n   indicating that the attribute is not present. */\ntypedef struct\n{\n    /* Vertex data */\n    unsigned int                position_count;\n    float*                      positions;\n\n    unsigned int                texcoord_count;\n    float*                      texcoords;\n\n    unsigned int                normal_count;\n    float*                      normals;\n\n    unsigned int                color_count;\n    float*                      colors;\n\n    /* Face data: one element for each face */\n    unsigned int                face_count;\n    unsigned int*               face_vertices;\n    unsigned int*               face_materials;\n    unsigned char*              face_lines;\n\n    /* Index data: one element for each face vertex */\n    unsigned int                index_count;\n    fastObjIndex*               indices;\n\n    /* Materials */\n    unsigned int                material_count;\n    fastObjMaterial*            materials;\n\n    /* Texture maps */\n    unsigned int                texture_count;\n    fastObjTexture*             textures;\n\n    /* Mesh objects ('o' tag in .obj file) */\n    unsigned int                object_count;\n    fastObjGroup*               objects;\n\n    /* Mesh groups ('g' tag in .obj file) */\n    unsigned int                group_count;\n    fastObjGroup*               groups;\n\n} fastObjMesh;\n\ntypedef struct\n{\n    void*                       (*file_open)(const char* path, void* user_data);\n    void                        (*file_close)(void* file, void* user_data);\n    size_t                      (*file_read)(void* file, void* dst, size_t bytes, void* user_data);\n    unsigned long               (*file_size)(void* file, void* user_data);\n} fastObjCallbacks;\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nfastObjMesh*                    fast_obj_read(const char* path);\nfastObjMesh*                    fast_obj_read_with_callbacks(const char* path, const fastObjCallbacks* callbacks, void* user_data);\nvoid                            fast_obj_destroy(fastObjMesh* mesh);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n\n\n#ifdef FAST_OBJ_IMPLEMENTATION\n\n#include <stdio.h>\n#include <string.h>\n\n#ifndef FAST_OBJ_REALLOC\n#define FAST_OBJ_REALLOC        realloc\n#endif\n\n#ifndef FAST_OBJ_FREE\n#define FAST_OBJ_FREE           free\n#endif\n\n#ifdef _WIN32\n#define FAST_OBJ_SEPARATOR      '\\\\'\n#define FAST_OBJ_OTHER_SEP      '/'\n#else\n#define FAST_OBJ_SEPARATOR      '/'\n#define FAST_OBJ_OTHER_SEP      '\\\\'\n#endif\n\n\n/* Size of buffer to read into */\n#define BUFFER_SIZE             65536\n\n/* Max supported power when parsing float */\n#define MAX_POWER               20\n\ntypedef struct\n{\n    /* Final mesh */\n    fastObjMesh*                mesh;\n\n    /* Current object/group */\n    fastObjGroup                object;\n    fastObjGroup                group;\n\n    /* Current material index */\n    unsigned int                material;\n\n    /* Current line in file */\n    unsigned int                line;\n\n    /* Base path for materials/textures */\n    char*                       base;\n\n} fastObjData;\n\n\nstatic const\ndouble POWER_10_POS[MAX_POWER] =\n{\n    1.0e0,  1.0e1,  1.0e2,  1.0e3,  1.0e4,  1.0e5,  1.0e6,  1.0e7,  1.0e8,  1.0e9,\n    1.0e10, 1.0e11, 1.0e12, 1.0e13, 1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e18, 1.0e19,\n};\n\nstatic const\ndouble POWER_10_NEG[MAX_POWER] =\n{\n    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,\n    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,\n};\n\n\nstatic void* memory_realloc(void* ptr, size_t bytes)\n{\n    return FAST_OBJ_REALLOC(ptr, bytes);\n}\n\n\nstatic\nvoid memory_dealloc(void* ptr)\n{\n    FAST_OBJ_FREE(ptr);\n}\n\n\n#define array_clean(_arr)       ((_arr) ? memory_dealloc(_array_header(_arr)), 0 : 0)\n#define array_push(_arr, _val)  (_array_mgrow(_arr, 1) ? ((_arr)[_array_size(_arr)++] = (_val), _array_size(_arr) - 1) : 0)\n#define array_size(_arr)        ((_arr) ? _array_size(_arr) : 0)\n#define array_capacity(_arr)    ((_arr) ? _array_capacity(_arr) : 0)\n#define array_empty(_arr)       (array_size(_arr) == 0)\n\n#define _array_header(_arr)     ((fastObjUInt*)(_arr)-2)\n#define _array_size(_arr)       (_array_header(_arr)[0])\n#define _array_capacity(_arr)   (_array_header(_arr)[1])\n#define _array_ngrow(_arr, _n)  ((_arr) == 0 || (_array_size(_arr) + (_n) >= _array_capacity(_arr)))\n#define _array_mgrow(_arr, _n)  (_array_ngrow(_arr, _n) ? (_array_grow(_arr, _n) != 0) : 1)\n#define _array_grow(_arr, _n)   (*((void**)&(_arr)) = array_realloc(_arr, _n, sizeof(*(_arr))))\n\n\nstatic void* array_realloc(void* ptr, fastObjUInt n, fastObjUInt b)\n{\n    fastObjUInt sz = array_size(ptr);\n    fastObjUInt nsz = sz + n;\n    fastObjUInt cap = array_capacity(ptr);\n    fastObjUInt ncap = cap + cap / 2;\n    fastObjUInt* r;\n\n    if (ncap < nsz)\n        ncap = nsz;\n    ncap = (ncap + 15) & ~15u;\n\n    r = (fastObjUInt*)(memory_realloc(ptr ? _array_header(ptr) : 0, (size_t)b * ncap + 2 * sizeof(fastObjUInt)));\n    if (!r)\n        return 0;\n\n    r[0] = sz;\n    r[1] = ncap;\n\n    return (r + 2);\n}\n\n\nstatic\nvoid* file_open(const char* path, void* user_data)\n{\n    (void)(user_data);\n    return fopen(path, \"rb\");\n}\n\n\nstatic\nvoid file_close(void* file, void* user_data)\n{\n    FILE* f;\n    (void)(user_data);\n    \n    f = (FILE*)(file);\n    fclose(f);\n}\n\n\nstatic\nsize_t file_read(void* file, void* dst, size_t bytes, void* user_data)\n{\n    FILE* f;\n    (void)(user_data);\n    \n    f = (FILE*)(file);\n    return fread(dst, 1, bytes, f);\n}\n\n\nstatic\nunsigned long file_size(void* file, void* user_data)\n{\n    FILE* f;\n    long p;\n    long n;\n    (void)(user_data);\n\n    f = (FILE*)(file);\n\n    p = ftell(f);\n    fseek(f, 0, SEEK_END);\n    n = ftell(f);\n    fseek(f, p, SEEK_SET);\n\n    if (n > 0)\n        return (unsigned long)(n);\n    else\n        return 0;\n}\n\n\nstatic\nchar* string_copy(const char* s, const char* e)\n{\n    size_t n;\n    char*  p;\n        \n    n = (size_t)(e - s);\n    p = (char*)(memory_realloc(0, n + 1));\n    if (p)\n    {\n        memcpy(p, s, n);\n        p[n] = '\\0';\n    }\n\n    return p;\n}\n\n\nstatic\nchar* string_substr(const char* s, size_t a, size_t b)\n{\n    return string_copy(s + a, s + b);\n}\n\n\nstatic\nchar* string_concat(const char* a, const char* s, const char* e)\n{\n    size_t an;\n    size_t sn;\n    char*  p;\n        \n    an = a ? strlen(a) : 0;\n    sn = (size_t)(e - s);\n    p = (char*)(memory_realloc(0, an + sn + 1));\n    if (p)\n    {\n        if (a)\n            memcpy(p, a, an);\n        memcpy(p + an, s, sn);\n        p[an + sn] = '\\0';\n    }\n\n    return p;\n}\n\n\nstatic\nint string_equal(const char* a, const char* s, const char* e)\n{\n    size_t an = strlen(a);\n    size_t sn = (size_t)(e - s);\n\n    return an == sn && memcmp(a, s, an) == 0;\n}\n\n\nstatic\nvoid string_fix_separators(char* s)\n{\n    while (*s)\n    {\n        if (*s == FAST_OBJ_OTHER_SEP)\n            *s = FAST_OBJ_SEPARATOR;\n        s++;\n    }\n}\n\n\nstatic\nint is_whitespace(char c)\n{\n    return (c == ' ' || c == '\\t' || c == '\\r');\n}\n\nstatic\nint is_newline(char c)\n{\n    return (c == '\\n');\n}\n\n\nstatic\nint is_digit(char c)\n{\n    return (c >= '0' && c <= '9');\n}\n\n\nstatic\nint is_exponent(char c)\n{\n    return (c == 'e' || c == 'E');\n}\n\n\nstatic\nconst char* skip_name(const char* ptr)\n{\n    const char* s = ptr;\n\n    while (!is_newline(*ptr))\n        ptr++;\n\n    while (ptr > s && is_whitespace(*(ptr - 1)))\n        ptr--;\n\n    return ptr;\n}\n\n\nstatic\nconst char* skip_whitespace(const char* ptr)\n{\n    while (is_whitespace(*ptr))\n        ptr++;\n\n    return ptr;\n}\n\n\nstatic\nconst char* skip_line(const char* ptr)\n{\n    while (!is_newline(*ptr++))\n        ;\n\n    return ptr;\n}\n\n\nstatic\nfastObjGroup object_default(void)\n{\n    fastObjGroup object;\n\n    object.name         = 0;\n    object.face_count   = 0;\n    object.face_offset  = 0;\n    object.index_offset = 0;\n\n    return object;\n}\n\n\nstatic\nvoid object_clean(fastObjGroup* object)\n{\n    memory_dealloc(object->name);\n}\n\n\nstatic\nvoid flush_object(fastObjData* data)\n{\n    /* Add object if not empty */\n    if (data->object.face_count > 0)\n        array_push(data->mesh->objects, data->object);\n    else\n        object_clean(&data->object);\n\n    /* Reset for more data */\n    data->object = object_default();\n    data->object.face_offset  = array_size(data->mesh->face_vertices);\n    data->object.index_offset = array_size(data->mesh->indices);\n}\n\n\n\nstatic\nfastObjGroup group_default(void)\n{\n    fastObjGroup group;\n\n    group.name         = 0;\n    group.face_count   = 0;\n    group.face_offset  = 0;\n    group.index_offset = 0;\n\n    return group;\n}\n\n\nstatic\nvoid group_clean(fastObjGroup* group)\n{\n    memory_dealloc(group->name);\n}\n\n\nstatic\nvoid flush_group(fastObjData* data)\n{\n    /* Add group if not empty */\n    if (data->group.face_count > 0)\n        array_push(data->mesh->groups, data->group);\n    else\n        group_clean(&data->group);\n\n    /* Reset for more data */\n    data->group = group_default();\n    data->group.face_offset  = array_size(data->mesh->face_vertices);\n    data->group.index_offset = array_size(data->mesh->indices);\n}\n\n\nstatic\nconst char* parse_int(const char* ptr, int* val)\n{\n    int sign;\n    int num;\n\n\n    if (*ptr == '-')\n    {\n        sign = -1;\n        ptr++;\n    }\n    else\n    {\n        sign = +1;\n    }\n\n    num = 0;\n    while (is_digit(*ptr))\n        num = 10 * num + (*ptr++ - '0');\n\n    *val = sign * num;\n\n    return ptr;\n}\n\n\nstatic\nconst char* parse_float(const char* ptr, float* val)\n{\n    double        sign;\n    double        num;\n    double        fra;\n    double        div;\n    unsigned int  eval;\n    const double* powers;\n\n\n    ptr = skip_whitespace(ptr);\n\n    switch (*ptr)\n    {\n    case '+':\n        sign = 1.0;\n        ptr++;\n        break;\n\n    case '-':\n        sign = -1.0;\n        ptr++;\n        break;\n\n    default:\n        sign = 1.0;\n        break;\n    }\n\n\n    num = 0.0;\n    while (is_digit(*ptr))\n        num = 10.0 * num + (double)(*ptr++ - '0');\n\n    if (*ptr == '.')\n        ptr++;\n\n    fra = 0.0;\n    div = 1.0;\n\n    while (is_digit(*ptr))\n    {\n        fra  = 10.0 * fra + (double)(*ptr++ - '0');\n        div *= 10.0;\n    }\n\n    num += fra / div;\n\n    if (is_exponent(*ptr))\n    {\n        ptr++;\n\n        switch (*ptr)\n        {\n        case '+':\n            powers = POWER_10_POS;\n            ptr++;\n            break;\n\n        case '-':\n            powers = POWER_10_NEG;\n            ptr++;\n            break;\n\n        default:\n            powers = POWER_10_POS;\n            break;\n        }\n\n        eval = 0;\n        while (is_digit(*ptr))\n            eval = 10 * eval + (*ptr++ - '0');\n\n        num *= (eval >= MAX_POWER) ? 0.0 : powers[eval];\n    }\n\n    *val = (float)(sign * num);\n\n    return ptr;\n}\n\n\nstatic\nconst char* parse_vertex(fastObjData* data, const char* ptr)\n{\n    unsigned int ii;\n    float        v;\n\n\n    for (ii = 0; ii < 3; ii++)\n    {\n        ptr = parse_float(ptr, &v);\n        array_push(data->mesh->positions, v);\n    }\n\n\n    ptr = skip_whitespace(ptr);\n    if (!is_newline(*ptr))\n    {\n        /* Fill the colors array until it matches the size of the positions array */\n        for (ii = array_size(data->mesh->colors); ii < array_size(data->mesh->positions) - 3; ++ii)\n        {\n            array_push(data->mesh->colors, 1.0f);\n        }\n\n        for (ii = 0; ii < 3; ++ii)\n        {\n            ptr = parse_float(ptr, &v);\n            array_push(data->mesh->colors, v);\n        }\n    }\n\n    return ptr;\n}\n\n\nstatic\nconst char* parse_texcoord(fastObjData* data, const char* ptr)\n{\n    unsigned int ii;\n    float        v;\n\n\n    for (ii = 0; ii < 2; ii++)\n    {\n        ptr = parse_float(ptr, &v);\n        array_push(data->mesh->texcoords, v);\n    }\n\n    return ptr;\n}\n\n\nstatic\nconst char* parse_normal(fastObjData* data, const char* ptr)\n{\n    unsigned int ii;\n    float        v;\n\n\n    for (ii = 0; ii < 3; ii++)\n    {\n        ptr = parse_float(ptr, &v);\n        array_push(data->mesh->normals, v);\n    }\n\n    return ptr;\n}\n\n\nstatic\nconst char* parse_face(fastObjData* data, const char* ptr, unsigned char line)\n{\n    unsigned int count;\n    fastObjIndex vn;\n    int          v;\n    int          t;\n    int          n;\n\n\n    ptr = skip_whitespace(ptr);\n\n    count = 0;\n    while (!is_newline(*ptr))\n    {\n        v = 0;\n        t = 0;\n        n = 0;\n\n        ptr = parse_int(ptr, &v);\n        if (*ptr == '/')\n        {\n            ptr++;\n            if (*ptr != '/')\n                ptr = parse_int(ptr, &t);\n\n            if (*ptr == '/')\n            {\n                ptr++;\n                ptr = parse_int(ptr, &n);\n            }\n        }\n\n        if (v < 0)\n            vn.p = (array_size(data->mesh->positions) / 3) - (fastObjUInt)(-v);\n        else if (v > 0)\n            vn.p = (fastObjUInt)(v);\n        else\n            return ptr; /* Skip lines with no valid vertex index */\n\n        if (t < 0)\n            vn.t = (array_size(data->mesh->texcoords) / 2) - (fastObjUInt)(-t);\n        else if (t > 0)\n            vn.t = (fastObjUInt)(t);\n        else\n            vn.t = 0;\n\n        if (n < 0)\n            vn.n = (array_size(data->mesh->normals) / 3) - (fastObjUInt)(-n);\n        else if (n > 0)\n            vn.n = (fastObjUInt)(n);\n        else\n            vn.n = 0;\n\n        array_push(data->mesh->indices, vn);\n        count++;\n\n        ptr = skip_whitespace(ptr);\n    }\n\n    array_push(data->mesh->face_vertices, count);\n    array_push(data->mesh->face_materials, data->material);\n\n    if (line || data->mesh->face_lines)\n    {\n        /* when line info exists, ensure it uses aligned indexing with other face data */\n        size_t skipped = array_size(data->mesh->face_vertices) - array_size(data->mesh->face_lines);\n        while (--skipped > 0)\n            array_push(data->mesh->face_lines, 0);\n\n        array_push(data->mesh->face_lines, line);\n    }\n\n    data->group.face_count++;\n    data->object.face_count++;\n\n    return ptr;\n}\n\n\nstatic\nconst char* parse_object(fastObjData* data, const char* ptr)\n{\n    const char* s;\n    const char* e;\n\n\n    ptr = skip_whitespace(ptr);\n\n    s = ptr;\n    ptr = skip_name(ptr);\n    e = ptr;\n\n    flush_object(data);\n    data->object.name = string_copy(s, e);\n\n    return ptr;\n}\n\n\nstatic\nconst char* parse_group(fastObjData* data, const char* ptr)\n{\n    const char* s;\n    const char* e;\n\n\n    ptr = skip_whitespace(ptr);\n\n    s = ptr;\n    ptr = skip_name(ptr);\n    e = ptr;\n\n    flush_group(data);\n    data->group.name = string_copy(s, e);\n\n    return ptr;\n}\n\n\nstatic\nfastObjTexture map_default(void)\n{\n    fastObjTexture map;\n\n    map.name = 0;\n    map.path = 0;\n\n    return map;\n}\n\n\nstatic\nfastObjMaterial mtl_default(void)\n{\n    fastObjMaterial mtl;\n\n    mtl.name = 0;\n\n    mtl.Ka[0] = 0.0;\n    mtl.Ka[1] = 0.0;\n    mtl.Ka[2] = 0.0;\n    mtl.Kd[0] = 1.0;\n    mtl.Kd[1] = 1.0;\n    mtl.Kd[2] = 1.0;\n    mtl.Ks[0] = 0.0;\n    mtl.Ks[1] = 0.0;\n    mtl.Ks[2] = 0.0;\n    mtl.Ke[0] = 0.0;\n    mtl.Ke[1] = 0.0;\n    mtl.Ke[2] = 0.0;\n    mtl.Kt[0] = 0.0;\n    mtl.Kt[1] = 0.0;\n    mtl.Kt[2] = 0.0;\n    mtl.Ns    = 1.0;\n    mtl.Ni    = 1.0;\n    mtl.Tf[0] = 1.0;\n    mtl.Tf[1] = 1.0;\n    mtl.Tf[2] = 1.0;\n    mtl.d     = 1.0;\n    mtl.illum = 1;\n\n    mtl.fallback = 0;\n\n    mtl.map_Ka   = 0;\n    mtl.map_Kd   = 0;\n    mtl.map_Ks   = 0;\n    mtl.map_Ke   = 0;\n    mtl.map_Kt   = 0;\n    mtl.map_Ns   = 0;\n    mtl.map_Ni   = 0;\n    mtl.map_d    = 0;\n    mtl.map_bump = 0;\n\n    return mtl;\n}\n\n\nstatic\nconst char* parse_usemtl(fastObjData* data, const char* ptr)\n{\n    const char*      s;\n    const char*      e;\n    unsigned int     idx;\n    fastObjMaterial* mtl;\n\n\n    ptr = skip_whitespace(ptr);\n\n    /* Parse the material name */\n    s = ptr;\n    ptr = skip_name(ptr);\n    e = ptr;\n\n    /* Find an existing material with the same name */\n    idx = 0;\n    while (idx < array_size(data->mesh->materials))\n    {\n        mtl = &data->mesh->materials[idx];\n        if (mtl->name && string_equal(mtl->name, s, e))\n            break;\n\n        idx++;\n    }\n\n    /* If doesn't exist, create a default one with this name\n       Note: this case happens when OBJ doesn't have its MTL */\n    if (idx == array_size(data->mesh->materials))\n    {\n        fastObjMaterial new_mtl = mtl_default();\n        new_mtl.name = string_copy(s, e);\n        new_mtl.fallback = 1;\n        array_push(data->mesh->materials, new_mtl);\n    }\n\n    data->material = idx;\n\n    return ptr;\n}\n\n\nstatic\nvoid map_clean(fastObjTexture* map)\n{\n    memory_dealloc(map->name);\n    memory_dealloc(map->path);\n}\n\n\nstatic\nvoid mtl_clean(fastObjMaterial* mtl)\n{\n    memory_dealloc(mtl->name);\n}\n\n\nstatic\nconst char* read_mtl_int(const char* p, int* v)\n{\n    return parse_int(p, v);\n}\n\n\nstatic\nconst char* read_mtl_single(const char* p, float* v)\n{\n    return parse_float(p, v);\n}\n\n\nstatic\nconst char* read_mtl_triple(const char* p, float v[3])\n{\n    p = read_mtl_single(p, &v[0]);\n    p = read_mtl_single(p, &v[1]);\n    p = read_mtl_single(p, &v[2]);\n\n    return p;\n}\n\n\nstatic\nconst char* read_map(fastObjData* data, const char* ptr, unsigned int* idx)\n{\n    const char*     s;\n    const char*     e;\n    fastObjTexture* map;\n\n\n    ptr = skip_whitespace(ptr);\n\n    /* Don't support options at present */\n    if (*ptr == '-')\n        return ptr;\n\n\n    /* Read name */\n    s = ptr;\n    ptr = skip_name(ptr);\n    e = ptr;\n\n    /* Try to find an existing texture map with the same name */\n    *idx = 1; /* skip dummy at index 0 */\n    while (*idx < array_size(data->mesh->textures))\n    {\n        map = &data->mesh->textures[*idx];\n        if (map->name && string_equal(map->name, s, e))\n            break;\n\n        (*idx)++;\n    }\n\n    /* Add it to the texture array if it didn't already exist */\n    if (*idx == array_size(data->mesh->textures))\n    {\n        fastObjTexture new_map = map_default();\n        new_map.name = string_copy(s, e);\n        new_map.path = string_concat(data->base, s, e);\n        string_fix_separators(new_map.path);\n        array_push(data->mesh->textures, new_map);\n    }\n\n    return e;\n}\n\n\nstatic\nint read_mtllib(fastObjData* data, void* file, const fastObjCallbacks* callbacks, void* user_data)\n{\n    unsigned long   n;\n    const char*     s;\n    char*           contents;\n    size_t          l;\n    const char*     p;\n    const char*     e;\n    int             found_d;\n    fastObjMaterial mtl;\n\n\n    /* Read entire file */\n    n = callbacks->file_size(file, user_data);\n\n    contents = (char*)(memory_realloc(0, n + 1));\n    if (!contents)\n        return 0;\n\n    l = callbacks->file_read(file, contents, n, user_data);\n    contents[l] = '\\n';\n\n    mtl = mtl_default();\n\n    found_d = 0;\n\n    p = contents;\n    e = contents + l;\n    while (p < e)\n    {\n        p = skip_whitespace(p);\n\n        switch (*p)\n        {\n        case 'n':\n            p++;\n            if (p[0] == 'e' &&\n                p[1] == 'w' &&\n                p[2] == 'm' &&\n                p[3] == 't' &&\n                p[4] == 'l' &&\n                is_whitespace(p[5]))\n            {\n                /* Push previous material (if there is one) */\n                if (mtl.name)\n                {\n                    array_push(data->mesh->materials, mtl);\n                    mtl = mtl_default();\n                }\n\n\n                /* Read name */\n                p += 5;\n\n                while (is_whitespace(*p))\n                    p++;\n\n                s = p;\n                p = skip_name(p);\n\n                mtl.name = string_copy(s, p);\n            }\n            break;\n\n        case 'K':\n            if (p[1] == 'a')\n                p = read_mtl_triple(p + 2, mtl.Ka);\n            else if (p[1] == 'd')\n                p = read_mtl_triple(p + 2, mtl.Kd);\n            else if (p[1] == 's')\n                p = read_mtl_triple(p + 2, mtl.Ks);\n            else if (p[1] == 'e')\n                p = read_mtl_triple(p + 2, mtl.Ke);\n            else if (p[1] == 't')\n                p = read_mtl_triple(p + 2, mtl.Kt);\n            break;\n\n        case 'N':\n            if (p[1] == 's')\n                p = read_mtl_single(p + 2, &mtl.Ns);\n            else if (p[1] == 'i')\n                p = read_mtl_single(p + 2, &mtl.Ni);\n            break;\n\n        case 'T':\n            if (p[1] == 'r')\n            {\n                float Tr;\n                p = read_mtl_single(p + 2, &Tr);\n                if (!found_d)\n                {\n                    /* Ignore Tr if we've already read d */\n                    mtl.d = 1.0f - Tr;\n                }\n            }\n            else if (p[1] == 'f')\n                p = read_mtl_triple(p + 2, mtl.Tf);\n            break;\n\n        case 'd':\n            if (is_whitespace(p[1]))\n            {\n                p = read_mtl_single(p + 1, &mtl.d);\n                found_d = 1;\n            }\n            break;\n\n        case 'i':\n            p++;\n            if (p[0] == 'l' &&\n                p[1] == 'l' &&\n                p[2] == 'u' &&\n                p[3] == 'm' &&\n                is_whitespace(p[4]))\n            {\n                p = read_mtl_int(p + 4, &mtl.illum);\n            }\n            break;\n\n        case 'm':\n            p++;\n            if (p[0] == 'a' &&\n                p[1] == 'p' &&\n                p[2] == '_')\n            {\n                p += 3;\n                if (*p == 'K')\n                {\n                    p++;\n                    if (is_whitespace(p[1]))\n                    {\n                        if (*p == 'a')\n                            p = read_map(data, p + 1, &mtl.map_Ka);\n                        else if (*p == 'd')\n                            p = read_map(data, p + 1, &mtl.map_Kd);\n                        else if (*p == 's')\n                            p = read_map(data, p + 1, &mtl.map_Ks);\n                        else if (*p == 'e')\n                            p = read_map(data, p + 1, &mtl.map_Ke);\n                        else if (*p == 't')\n                            p = read_map(data, p + 1, &mtl.map_Kt);\n                    }\n                }\n                else if (*p == 'N')\n                {\n                    p++;\n                    if (is_whitespace(p[1]))\n                    {\n                        if (*p == 's')\n                            p = read_map(data, p + 1, &mtl.map_Ns);\n                        else if (*p == 'i')\n                            p = read_map(data, p + 1, &mtl.map_Ni);\n                    }\n                }\n                else if (*p == 'd')\n                {\n                    p++;\n                    if (is_whitespace(*p))\n                        p = read_map(data, p, &mtl.map_d);\n                }\n                else if ((p[0] == 'b' || p[0] == 'B') &&\n                         p[1] == 'u' &&\n                         p[2] == 'm' &&\n                         p[3] == 'p' &&\n                         is_whitespace(p[4]))\n                {\n                    p = read_map(data, p + 4, &mtl.map_bump);\n                }\n            }\n            break;\n\n        case '#':\n            break;\n        }\n\n        p = skip_line(p);\n    }\n\n    /* Push final material */\n    if (mtl.name)\n        array_push(data->mesh->materials, mtl);\n\n    memory_dealloc(contents);\n\n    return 1;\n}\n\n\nstatic\nconst char* parse_mtllib(fastObjData* data, const char* ptr, const fastObjCallbacks* callbacks, void* user_data)\n{\n    const char* s;\n    const char* e;\n    char*       lib;\n    void*       file;\n\n\n    ptr = skip_whitespace(ptr);\n\n    s = ptr;\n    ptr = skip_name(ptr);\n    e = ptr;\n\n    lib = string_concat(data->base, s, e);\n    if (lib)\n    {\n        string_fix_separators(lib);\n\n        file = callbacks->file_open(lib, user_data);\n        if (file)\n        {\n            read_mtllib(data, file, callbacks, user_data);\n            callbacks->file_close(file, user_data);\n        }\n\n        memory_dealloc(lib);\n    }\n\n    return ptr;\n}\n\n\nstatic\nvoid parse_buffer(fastObjData* data, const char* ptr, const char* end, const fastObjCallbacks* callbacks, void* user_data)\n{\n    const char* p;\n    \n    \n    p = ptr;\n    while (p != end)\n    {\n        p = skip_whitespace(p);\n\n        switch (*p)\n        {\n        case 'v':\n            p++;\n\n            switch (*p++)\n            {\n            case ' ':\n            case '\\t':\n                p = parse_vertex(data, p);\n                break;\n\n            case 't':\n                p = parse_texcoord(data, p);\n                break;\n\n            case 'n':\n                p = parse_normal(data, p);\n                break;\n\n            default:\n                p--; /* roll p++ back in case *p was a newline */\n            }\n            break;\n\n        case 'f':\n            p++;\n\n            switch (*p++)\n            {\n            case ' ':\n            case '\\t':\n                p = parse_face(data, p, 0);\n                break;\n\n            default:\n                p--; /* roll p++ back in case *p was a newline */\n            }\n            break;\n\n        case 'l':\n            p++;\n\n            switch (*p++)\n            {\n            case ' ':\n            case '\\t':\n                p = parse_face(data, p, 1);\n                break;\n\n            default:\n                p--; /* roll p++ back in case *p was a newline */\n            }\n            break;\n\n        case 'o':\n            p++;\n\n            switch (*p++)\n            {\n            case ' ':\n            case '\\t':\n                p = parse_object(data, p);\n                break;\n\n            default:\n                p--; /* roll p++ back in case *p was a newline */\n            }\n            break;\n\n        case 'g':\n            p++;\n\n            switch (*p++)\n            {\n            case ' ':\n            case '\\t':\n                p = parse_group(data, p);\n                break;\n\n            default:\n                p--; /* roll p++ back in case *p was a newline */\n            }\n            break;\n\n        case 'm':\n            p++;\n            if (p[0] == 't' &&\n                p[1] == 'l' &&\n                p[2] == 'l' &&\n                p[3] == 'i' &&\n                p[4] == 'b' &&\n                is_whitespace(p[5]))\n                p = parse_mtllib(data, p + 5, callbacks, user_data);\n            break;\n\n        case 'u':\n            p++;\n            if (p[0] == 's' &&\n                p[1] == 'e' &&\n                p[2] == 'm' &&\n                p[3] == 't' &&\n                p[4] == 'l' &&\n                is_whitespace(p[5]))\n                p = parse_usemtl(data, p + 5);\n            break;\n\n        case '#':\n            break;\n        }\n\n        p = skip_line(p);\n\n        data->line++;\n    }\n    if (array_size(data->mesh->colors) > 0)\n    {\n        /* Fill the remaining slots in the colors array */\n        unsigned int ii;\n        for (ii = array_size(data->mesh->colors); ii < array_size(data->mesh->positions); ++ii)\n        {\n            array_push(data->mesh->colors, 1.0f);\n        }\n    }\n}\n\n\nvoid fast_obj_destroy(fastObjMesh* m)\n{\n    unsigned int ii;\n\n\n    for (ii = 0; ii < array_size(m->objects); ii++)\n        object_clean(&m->objects[ii]);\n\n    for (ii = 0; ii < array_size(m->groups); ii++)\n        group_clean(&m->groups[ii]);\n\n    for (ii = 0; ii < array_size(m->materials); ii++)\n        mtl_clean(&m->materials[ii]);\n\n    for (ii = 0; ii < array_size(m->textures); ii++)\n        map_clean(&m->textures[ii]);\n\n    array_clean(m->positions);\n    array_clean(m->texcoords);\n    array_clean(m->normals);\n    array_clean(m->colors);\n    array_clean(m->face_vertices);\n    array_clean(m->face_materials);\n    array_clean(m->face_lines);\n    array_clean(m->indices);\n    array_clean(m->objects);\n    array_clean(m->groups);\n    array_clean(m->materials);\n    array_clean(m->textures);\n\n    memory_dealloc(m);\n}\n\n\nfastObjMesh* fast_obj_read(const char* path)\n{\n    fastObjCallbacks callbacks;\n    callbacks.file_open = file_open;\n    callbacks.file_close = file_close;\n    callbacks.file_read = file_read;\n    callbacks.file_size = file_size;\n\n    return fast_obj_read_with_callbacks(path, &callbacks, 0);\n}\n\n\nfastObjMesh* fast_obj_read_with_callbacks(const char* path, const fastObjCallbacks* callbacks, void* user_data)\n{\n    fastObjData  data;\n    fastObjMesh* m;\n    void*        file;\n    char*        buffer;\n    char*        start;\n    char*        end;\n    char*        last;\n    fastObjUInt  read;\n    fastObjUInt  bytes;\n\n    /* Check if callbacks are valid */\n    if(!callbacks)\n        return 0;\n\n\n    /* Open file */\n    file = callbacks->file_open(path, user_data);\n    if (!file)\n        return 0;\n\n\n    /* Empty mesh */\n    m = (fastObjMesh*)(memory_realloc(0, sizeof(fastObjMesh)));\n    if (!m)\n        return 0;\n\n    m->positions      = 0;\n    m->texcoords      = 0;\n    m->normals        = 0;\n    m->colors         = 0;\n    m->face_vertices  = 0;\n    m->face_materials = 0;\n    m->face_lines     = 0;\n    m->indices        = 0;\n    m->materials      = 0;\n    m->textures       = 0;\n    m->objects        = 0;\n    m->groups         = 0;\n\n\n    /* Add dummy position/texcoord/normal/texture */\n    array_push(m->positions, 0.0f);\n    array_push(m->positions, 0.0f);\n    array_push(m->positions, 0.0f);\n\n    array_push(m->texcoords, 0.0f);\n    array_push(m->texcoords, 0.0f);\n\n    array_push(m->normals, 0.0f);\n    array_push(m->normals, 0.0f);\n    array_push(m->normals, 1.0f);\n\n    array_push(m->textures, map_default());\n\n\n    /* Data needed during parsing */\n    data.mesh     = m;\n    data.object   = object_default();\n    data.group    = group_default();\n    data.material = 0;\n    data.line     = 1;\n    data.base     = 0;\n\n\n    /* Find base path for materials/textures */\n    {\n        const char* sep1 = strrchr(path, FAST_OBJ_SEPARATOR);\n        const char* sep2 = strrchr(path, FAST_OBJ_OTHER_SEP);\n\n        /* Use the last separator in the path */\n        const char* sep = sep2 && (!sep1 || sep1 < sep2) ? sep2 : sep1;\n\n        if (sep)\n            data.base = string_substr(path, 0, sep - path + 1);\n    }\n\n\n    /* Create buffer for reading file */\n    buffer = (char*)(memory_realloc(0, 2 * BUFFER_SIZE * sizeof(char)));\n    if (!buffer)\n        return 0;\n\n    start = buffer;\n    for (;;)\n    {\n        /* Read another buffer's worth from file */\n        read = (fastObjUInt)(callbacks->file_read(file, start, BUFFER_SIZE, user_data));\n        if (read == 0 && start == buffer)\n            break;\n\n\n        /* Ensure buffer ends in a newline */\n        if (read < BUFFER_SIZE)\n        {\n            if (read == 0 || start[read - 1] != '\\n')\n                start[read++] = '\\n';\n        }\n\n        end = start + read;\n        if (end == buffer)\n            break;\n\n\n        /* Find last new line */\n        last = end;\n        while (last > buffer)\n        {\n            last--;\n            if (*last == '\\n')\n                break;\n        }\n\n\n        /* Check there actually is a new line */\n        if (*last != '\\n')\n            break;\n\n        last++;\n\n\n        /* Process buffer */\n        parse_buffer(&data, buffer, last, callbacks, user_data);\n\n\n        /* Copy overflow for next buffer */\n        bytes = (fastObjUInt)(end - last);\n        memmove(buffer, last, bytes);\n        start = buffer + bytes;\n    }\n\n\n    /* Flush final object/group */\n    flush_object(&data);\n    object_clean(&data.object);\n\n    flush_group(&data);\n    group_clean(&data.group);\n\n    m->position_count = array_size(m->positions) / 3;\n    m->texcoord_count = array_size(m->texcoords) / 2;\n    m->normal_count   = array_size(m->normals) / 3;\n    m->color_count    = array_size(m->colors) / 3;\n    m->face_count     = array_size(m->face_vertices);\n    m->index_count    = array_size(m->indices);\n    m->material_count = array_size(m->materials);\n    m->texture_count  = array_size(m->textures);\n    m->object_count   = array_size(m->objects);\n    m->group_count    = array_size(m->groups);\n\n\n    /* Clean up */\n    memory_dealloc(buffer);\n    memory_dealloc(data.base);\n\n    callbacks->file_close(file, user_data);\n\n    return m;\n}\n\n#endif"
  },
  {
    "path": "extern/sdefl.h",
    "content": "/*# Small Deflate\n`sdefl` is a small bare bone lossless compression library in ANSI C (ISO C90)\nwhich implements the Deflate (RFC 1951) compressed data format specification standard.\nIt is mainly tuned to get as much speed and compression ratio from as little code\nas needed to keep the implementation as concise as possible.\n\n## Features\n- Portable single header and source file duo written in ANSI C (ISO C90)\n- Dual license with either MIT or public domain\n- Small implementation\n    - Deflate: 525 LoC\n    - Inflate: 320 LoC\n- Webassembly:\n    - Deflate ~3.7 KB (~2.2KB compressed)\n    - Inflate ~3.6 KB (~2.2KB compressed)\n\n## Usage:\nThis file behaves differently depending on what symbols you define\nbefore including it.\n\nHeader-File mode:\nIf you do not define `SDEFL_IMPLEMENTATION` before including this file, it\nwill operate in header only mode. In this mode it declares all used structs\nand the API of the library without including the implementation of the library.\n\nImplementation mode:\nIf you define `SDEFL_IMPLEMENTATION` before including this file, it will\ncompile the implementation . Make sure that you only include\nthis file implementation in *one* C or C++ file to prevent collisions.\n\n### Benchmark\n\n| Compressor name         | Compression| Decompress.| Compr. size | Ratio |\n| ------------------------| -----------| -----------| ----------- | ----- |\n| miniz 1.0 -1            |   122 MB/s |   208 MB/s |    48510028 | 48.51 |\n| miniz 1.0 -6            |    27 MB/s |   260 MB/s |    36513697 | 36.51 |\n| miniz 1.0 -9            |    23 MB/s |   261 MB/s |    36460101 | 36.46 |\n| zlib 1.2.11 -1          |    72 MB/s |   307 MB/s |    42298774 | 42.30 |\n| zlib 1.2.11 -6          |    24 MB/s |   313 MB/s |    36548921 | 36.55 |\n| zlib 1.2.11 -9          |    20 MB/s |   314 MB/s |    36475792 | 36.48 |\n| sdefl 1.0 -0            |   127 MB/s |   355 MB/s |    40004116 | 39.88 |\n| sdefl 1.0 -1            |   111 MB/s |   413 MB/s |    38940674 | 38.82 |\n| sdefl 1.0 -5            |    45 MB/s |   436 MB/s |    36577183 | 36.46 |\n| sdefl 1.0 -7            |    38 MB/s |   432 MB/s |    36523781 | 36.41 |\n| libdeflate 1.3 -1       |   147 MB/s |   667 MB/s |    39597378 | 39.60 |\n| libdeflate 1.3 -6       |    69 MB/s |   689 MB/s |    36648318 | 36.65 |\n| libdeflate 1.3 -9       |    13 MB/s |   672 MB/s |    35197141 | 35.20 |\n| libdeflate 1.3 -12      |  8.13 MB/s |   670 MB/s |    35100568 | 35.10 |\n\n### Compression\nResults on the [Silesia compression corpus](http://sun.aei.polsl.pl/~sdeor/index.php?page=silesia):\n\n| File    |   Original | `sdefl 0`    | `sdefl 5`  | `sdefl 7`   |\n| --------| -----------| -------------| ---------- | ------------|\n| dickens | 10.192.446 | 4,260,187    |  3,845,261 |   3,833,657 |\n| mozilla | 51.220.480 | 20,774,706   | 19,607,009 |  19,565,867 |\n| mr      |  9.970.564 | 3,860,531    |  3,673,460 |   3,665,627 |\n| nci     | 33.553.445 | 4,030,283    |  3,094,526 |   3,006,075 |\n| ooffice |  6.152.192 | 3,320,063    |  3,186,373 |   3,183,815 |\n| osdb    | 10.085.684 | 3,919,646    |  3,649,510 |   3,649,477 |\n| reymont |  6.627.202 | 2,263,378    |  1,857,588 |   1,827,237 |\n| samba   | 21.606.400 | 6,121,797    |  5,462,670 |   5,450,762 |\n| sao     |  7.251.944 | 5,612,421    |  5,485,380 |   5,481,765 |\n| webster | 41.458.703 | 13,972,648   | 12,059,432 |  11,991,421 |\n| xml     |  5.345.280 | 886,620      |    674,009 |     662,141 |\n| x-ray   |  8.474.240 | 6,304,655    |  6,244,779 |   6,244,779 |\n\n## License\n```\n------------------------------------------------------------------------------\nThis software is available under 2 licenses -- choose whichever you prefer.\n------------------------------------------------------------------------------\nALTERNATIVE A - MIT License\nCopyright (c) 2020-2023 Micha Mettke\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n------------------------------------------------------------------------------\nALTERNATIVE B - Public Domain (www.unlicense.org)\nThis is free and unencumbered software released into the public domain.\nAnyone is free to copy, modify, publish, use, compile, sell, or distribute this\nsoftware, either in source code form or as a compiled binary, for any purpose,\ncommercial or non-commercial, and by any means.\nIn jurisdictions that recognize copyright laws, the author or authors of this\nsoftware dedicate any and all copyright interest in the software to the public\ndomain. We make this dedication for the benefit of the public at large and to\nthe detriment of our heirs and successors. We intend this dedication to be an\novert act of relinquishment in perpetuity of all present and future rights to\nthis software under copyright law.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\nACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n------------------------------------------------------------------------------\n```\n*/\n#ifndef SDEFL_H_INCLUDED\n#define SDEFL_H_INCLUDED\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define SDEFL_MAX_OFF   (1 << 15)\n#define SDEFL_WIN_SIZ   SDEFL_MAX_OFF\n#define SDEFL_WIN_MSK   (SDEFL_WIN_SIZ-1)\n\n#define SDEFL_HASH_BITS 15\n#define SDEFL_HASH_SIZ  (1 << SDEFL_HASH_BITS)\n#define SDEFL_HASH_MSK  (SDEFL_HASH_SIZ-1)\n\n#define SDEFL_MIN_MATCH 4\n#define SDEFL_BLK_MAX   (256*1024)\n#define SDEFL_SEQ_SIZ   ((SDEFL_BLK_MAX+2)/3)\n\n#define SDEFL_SYM_MAX   (288)\n#define SDEFL_OFF_MAX   (32)\n#define SDEFL_PRE_MAX   (19)\n\n#define SDEFL_LVL_MIN   0\n#define SDEFL_LVL_DEF   5\n#define SDEFL_LVL_MAX   8\n\nstruct sdefl_freq {\n  unsigned lit[SDEFL_SYM_MAX];\n  unsigned off[SDEFL_OFF_MAX];\n};\nstruct sdefl_code_words {\n  unsigned lit[SDEFL_SYM_MAX];\n  unsigned off[SDEFL_OFF_MAX];\n};\nstruct sdefl_lens {\n  unsigned char lit[SDEFL_SYM_MAX];\n  unsigned char off[SDEFL_OFF_MAX];\n};\nstruct sdefl_codes {\n  struct sdefl_code_words word;\n  struct sdefl_lens len;\n};\nstruct sdefl_seqt {\n  int off, len;\n};\nstruct sdefl {\n  int bits, bitcnt;\n  int tbl[SDEFL_HASH_SIZ];\n  int prv[SDEFL_WIN_SIZ];\n\n  int seq_cnt;\n  struct sdefl_seqt seq[SDEFL_SEQ_SIZ];\n  struct sdefl_freq freq;\n  struct sdefl_codes cod;\n};\nextern int sdefl_bound(int in_len);\nextern int sdeflate(struct sdefl *s, void *o, const void *i, int n, int lvl);\nextern int zsdeflate(struct sdefl *s, void *o, const void *i, int n, int lvl);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* SDEFL_H_INCLUDED */\n\n#ifdef SDEFL_IMPLEMENTATION\n\n#include <assert.h> /* assert */\n#include <string.h> /* memcpy */\n#include <limits.h> /* CHAR_BIT */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define SDEFL_NIL               (-1)\n#define SDEFL_MAX_MATCH         258\n#define SDEFL_MAX_CODE_LEN      (15)\n#define SDEFL_SYM_BITS          (10u)\n#define SDEFL_SYM_MSK           ((1u << SDEFL_SYM_BITS)-1u)\n#define SDEFL_RAW_BLK_SIZE      (65535)\n#define SDEFL_LIT_LEN_CODES     (14)\n#define SDEFL_OFF_CODES         (15)\n#define SDEFL_PRE_CODES         (7)\n#define SDEFL_CNT_NUM(n)        ((((n)+3u/4u)+3u)&~3u)\n#define SDEFL_EOB               (256)\n\n#define sdefl_npow2(n) (1 << (sdefl_ilog2((n)-1) + 1))\n#define sdefl_div_round_up(n,d) (((n)+((d)-1))/(d))\n\nstatic int\nsdefl_ilog2(int n) {\n  if (!n) return 0;\n#ifdef _MSC_VER\n  unsigned long msbp = 0;\n  _BitScanReverse(&msbp, (unsigned long)n);\n  return (int)msbp;\n#elif defined(__GNUC__) || defined(__clang__)\n  return (int)sizeof(unsigned long) * CHAR_BIT - 1 - __builtin_clzl((unsigned long)n);\n#else\n  #define lt(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n\n  static const char tbl[256] = {\n    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),\n    lt(7), lt(7), lt(7), lt(7), lt(7), lt(7), lt(7), lt(7)};\n  int tt, t;\n  if ((tt = (n >> 16))) {\n    return (t = (tt >> 8)) ? 24 + tbl[t] : 16 + tbl[tt];\n  } else {\n    return (t = (n >> 8)) ? 8 + tbl[t] : tbl[n];\n  }\n  #undef lt\n#endif\n}\nstatic unsigned\nsdefl_uload32(const void *p) {\n  /* hopefully will be optimized to an unaligned read */\n  unsigned n = 0;\n  memcpy(&n, p, sizeof(n));\n  return n;\n}\nstatic unsigned\nsdefl_hash32(const void *p) {\n  unsigned n = sdefl_uload32(p);\n  return (n * 0x9E377989) >> (32 - SDEFL_HASH_BITS);\n}\nstatic void\nsdefl_put(unsigned char **dst, struct sdefl *s, int code, int bitcnt) {\n  s->bits |= (code << s->bitcnt);\n  s->bitcnt += bitcnt;\n  while (s->bitcnt >= 8) {\n    unsigned char *tar = *dst;\n    *tar = (unsigned char)(s->bits & 0xFF);\n    s->bits >>= 8;\n    s->bitcnt -= 8;\n    *dst = *dst + 1;\n  }\n}\nstatic void\nsdefl_heap_sub(unsigned A[], unsigned len, unsigned sub) {\n  unsigned c, p = sub;\n  unsigned v = A[sub];\n  while ((c = p << 1) <= len) {\n    if (c < len && A[c + 1] > A[c]) c++;\n    if (v >= A[c]) break;\n    A[p] = A[c], p = c;\n  }\n  A[p] = v;\n}\nstatic void\nsdefl_heap_array(unsigned *A, unsigned len) {\n  unsigned sub;\n  for (sub = len >> 1; sub >= 1; sub--)\n    sdefl_heap_sub(A, len, sub);\n}\nstatic void\nsdefl_heap_sort(unsigned *A, unsigned n) {\n  A--;\n  sdefl_heap_array(A, n);\n  while (n >= 2) {\n    unsigned tmp = A[n];\n    A[n--] = A[1];\n    A[1] = tmp;\n    sdefl_heap_sub(A, n, 1);\n  }\n}\nstatic unsigned\nsdefl_sort_sym(unsigned sym_cnt, unsigned *freqs,\n               unsigned char *lens, unsigned *sym_out) {\n  unsigned cnts[SDEFL_CNT_NUM(SDEFL_SYM_MAX)] = {0};\n  unsigned cnt_num = SDEFL_CNT_NUM(sym_cnt);\n  unsigned used_sym = 0;\n  unsigned sym, i;\n  for (sym = 0; sym < sym_cnt; sym++)\n    cnts[freqs[sym] < cnt_num-1 ? freqs[sym]: cnt_num-1]++;\n  for (i = 1; i < cnt_num; i++) {\n    unsigned cnt = cnts[i];\n    cnts[i] = used_sym;\n    used_sym += cnt;\n  }\n  for (sym = 0; sym < sym_cnt; sym++) {\n    unsigned freq = freqs[sym];\n    if (freq) {\n        unsigned idx = freq < cnt_num-1 ? freq : cnt_num-1;\n        sym_out[cnts[idx]++] = sym | (freq << SDEFL_SYM_BITS);\n    } else lens[sym] = 0;\n  }\n  sdefl_heap_sort(sym_out + cnts[cnt_num-2], cnts[cnt_num-1] - cnts[cnt_num-2]);\n  return used_sym;\n}\nstatic void\nsdefl_build_tree(unsigned *A, unsigned sym_cnt) {\n  unsigned i = 0, b = 0, e = 0;\n  do {\n    unsigned m, n, freq_shift;\n    if (i != sym_cnt && (b == e || (A[i] >> SDEFL_SYM_BITS) <= (A[b] >> SDEFL_SYM_BITS)))\n      m = i++;\n    else m = b++;\n    if (i != sym_cnt && (b == e || (A[i] >> SDEFL_SYM_BITS) <= (A[b] >> SDEFL_SYM_BITS)))\n      n = i++;\n    else n = b++;\n\n    freq_shift = (A[m] & ~SDEFL_SYM_MSK) + (A[n] & ~SDEFL_SYM_MSK);\n    A[m] = (A[m] & SDEFL_SYM_MSK) | (e << SDEFL_SYM_BITS);\n    A[n] = (A[n] & SDEFL_SYM_MSK) | (e << SDEFL_SYM_BITS);\n    A[e] = (A[e] & SDEFL_SYM_MSK) | freq_shift;\n  } while (sym_cnt - ++e > 1);\n}\nstatic void\nsdefl_gen_len_cnt(unsigned *A, unsigned root, unsigned *len_cnt,\n                  unsigned max_code_len) {\n  int n;\n  unsigned i;\n  for (i = 0; i <= max_code_len; i++)\n    len_cnt[i] = 0;\n  len_cnt[1] = 2;\n\n  A[root] &= SDEFL_SYM_MSK;\n  for (n = (int)root - 1; n >= 0; n--) {\n    unsigned p = A[n] >> SDEFL_SYM_BITS;\n    unsigned pdepth = A[p] >> SDEFL_SYM_BITS;\n    unsigned depth = pdepth + 1;\n    unsigned len = depth;\n\n    A[n] = (A[n] & SDEFL_SYM_MSK) | (depth << SDEFL_SYM_BITS);\n    if (len >= max_code_len) {\n      len = max_code_len;\n      do len--; while (!len_cnt[len]);\n    }\n    len_cnt[len]--;\n    len_cnt[len+1] += 2;\n  }\n}\nstatic void\nsdefl_gen_codes(unsigned *A, unsigned char *lens, const unsigned *len_cnt,\n                unsigned max_code_word_len, unsigned sym_cnt) {\n  unsigned i, sym, len, nxt[SDEFL_MAX_CODE_LEN + 1];\n  for (i = 0, len = max_code_word_len; len >= 1; len--) {\n    unsigned cnt = len_cnt[len];\n    while (cnt--) lens[A[i++] & SDEFL_SYM_MSK] = (unsigned char)len;\n  }\n  nxt[0] = nxt[1] = 0;\n  for (len = 2; len <= max_code_word_len; len++)\n    nxt[len] = (nxt[len-1] + len_cnt[len-1]) << 1;\n  for (sym = 0; sym < sym_cnt; sym++)\n    A[sym] = nxt[lens[sym]]++;\n}\nstatic unsigned\nsdefl_rev(unsigned c, unsigned char n) {\n  c = ((c & 0x5555) << 1) | ((c & 0xAAAA) >> 1);\n  c = ((c & 0x3333) << 2) | ((c & 0xCCCC) >> 2);\n  c = ((c & 0x0F0F) << 4) | ((c & 0xF0F0) >> 4);\n  c = ((c & 0x00FF) << 8) | ((c & 0xFF00) >> 8);\n  return c >> (16-n);\n}\nstatic void\nsdefl_huff(unsigned char *lens, unsigned *codes, unsigned *freqs,\n           unsigned num_syms, unsigned max_code_len) {\n  unsigned c, *A = codes;\n  unsigned len_cnt[SDEFL_MAX_CODE_LEN + 1];\n  unsigned used_syms = sdefl_sort_sym(num_syms, freqs, lens, A);\n  if (!used_syms) return;\n  if (used_syms == 1) {\n    unsigned s = A[0] & SDEFL_SYM_MSK;\n    unsigned i = s ? s : 1;\n    codes[0] = 0, lens[0] = 1;\n    codes[i] = 1, lens[i] = 1;\n    return;\n  }\n  sdefl_build_tree(A, used_syms);\n  sdefl_gen_len_cnt(A, used_syms-2, len_cnt, max_code_len);\n  sdefl_gen_codes(A, lens, len_cnt, max_code_len, num_syms);\n  for (c = 0; c < num_syms; c++) {\n    codes[c] = sdefl_rev(codes[c], lens[c]);\n  }\n}\nstruct sdefl_symcnt {\n  int items;\n  int lit;\n  int off;\n};\nstatic void\nsdefl_precode(struct sdefl_symcnt *cnt, unsigned *freqs, unsigned *items,\n              const unsigned char *litlen, const unsigned char *offlen) {\n  unsigned *at = items;\n  unsigned run_start = 0;\n\n  unsigned total = 0;\n  unsigned char lens[SDEFL_SYM_MAX + SDEFL_OFF_MAX];\n  for (cnt->lit = SDEFL_SYM_MAX; cnt->lit > 257; cnt->lit--)\n    if (litlen[cnt->lit - 1]) break;\n  for (cnt->off = SDEFL_OFF_MAX; cnt->off > 1; cnt->off--)\n    if (offlen[cnt->off - 1]) break;\n\n  total = (unsigned)(cnt->lit + cnt->off);\n  memcpy(lens, litlen, sizeof(unsigned char) * (size_t)cnt->lit);\n  memcpy(lens + cnt->lit, offlen, sizeof(unsigned char) * (size_t)cnt->off);\n  do {\n    unsigned len = lens[run_start];\n    unsigned run_end = run_start;\n    do run_end++; while (run_end != total && len == lens[run_end]);\n    if (!len) {\n      while ((run_end - run_start) >= 11) {\n        unsigned n = (run_end - run_start) - 11;\n        unsigned xbits =  n < 0x7f ? n : 0x7f;\n        freqs[18]++;\n        *at++ = 18u | (xbits << 5u);\n        run_start += 11 + xbits;\n      }\n      if ((run_end - run_start) >= 3) {\n        unsigned n = (run_end - run_start) - 3;\n        unsigned xbits =  n < 0x7 ? n : 0x7;\n        freqs[17]++;\n        *at++ = 17u | (xbits << 5u);\n        run_start += 3 + xbits;\n      }\n    } else if ((run_end - run_start) >= 4) {\n      freqs[len]++;\n      *at++ = len;\n      run_start++;\n      do {\n        unsigned xbits = (run_end - run_start) - 3;\n        xbits = xbits < 0x03 ? xbits : 0x03;\n        *at++ = 16 | (xbits << 5);\n        run_start += 3 + xbits;\n        freqs[16]++;\n      } while ((run_end - run_start) >= 3);\n    }\n    while (run_start != run_end) {\n      freqs[len]++;\n      *at++ = len;\n      run_start++;\n    }\n  } while (run_start != total);\n  cnt->items = (int)(at - items);\n}\nstruct sdefl_match_codest {\n  int ls, lc;\n  int dc, dx;\n};\nstatic void\nsdefl_match_codes(struct sdefl_match_codest *cod, int dist, int len) {\n  static const short dxmax[] = {0,6,12,24,48,96,192,384,768,1536,3072,6144,12288,24576};\n  static const unsigned char lslot[258+1] = {\n    0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12,\n    12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16,\n    16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18,\n    18, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20,\n    20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,\n    21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,\n    22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,\n    23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,\n    24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25,\n    25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,\n    25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26,\n    26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,\n    26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,\n    27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,\n    27, 27, 28\n  };\n  assert(len <= 258);\n  assert(dist <= 32768);\n  cod->ls = lslot[len];\n  cod->lc = 257 + cod->ls;\n  assert(cod->lc <= 285);\n\n  cod->dx = sdefl_ilog2(sdefl_npow2(dist) >> 2);\n  cod->dc = cod->dx ? ((cod->dx + 1) << 1) + (dist > dxmax[cod->dx]) : dist-1;\n}\nenum sdefl_blk_type {\n  SDEFL_BLK_UCOMPR,\n  SDEFL_BLK_DYN\n};\nstatic enum sdefl_blk_type\nsdefl_blk_type(const struct sdefl *s, int blk_len, int pre_item_len,\n               const unsigned *pre_freq, const unsigned char *pre_len) {\n  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};\n  static const unsigned char x_len_bits[] = {0,0,0,0,0,0,0,0, 1,1,1,1,2,2,2,2,\n    3,3,3,3,4,4,4,4, 5,5,5,5,0};\n  static const unsigned char x_off_bits[] = {0,0,0,0,1,1,2,2, 3,3,4,4,5,5,6,6,\n    7,7,8,8,9,9,10,10, 11,11,12,12,13,13};\n\n  int dyn_cost = 0;\n  int fix_cost = 0;\n  int sym = 0;\n\n  dyn_cost += 5 + 5 + 4 + (3 * pre_item_len);\n  for (sym = 0; sym < SDEFL_PRE_MAX; sym++)\n    dyn_cost += pre_freq[sym] * (x_pre_bits[sym] + pre_len[sym]);\n  for (sym = 0; sym < 256; sym++)\n    dyn_cost += s->freq.lit[sym] * s->cod.len.lit[sym];\n  dyn_cost += s->cod.len.lit[SDEFL_EOB];\n  for (sym = 257; sym < 286; sym++)\n    dyn_cost += s->freq.lit[sym] * (x_len_bits[sym - 257] + s->cod.len.lit[sym]);\n  for (sym = 0; sym < 30; sym++)\n    dyn_cost += s->freq.off[sym] * (x_off_bits[sym] + s->cod.len.off[sym]);\n\n  fix_cost += 8*(5 * sdefl_div_round_up(blk_len, SDEFL_RAW_BLK_SIZE) + blk_len + 1 + 2);\n  return (dyn_cost < fix_cost) ? SDEFL_BLK_DYN : SDEFL_BLK_UCOMPR;\n}\nstatic void\nsdefl_put16(unsigned char **dst, unsigned short x) {\n  unsigned char *val = *dst;\n  val[0] = (unsigned char)(x & 0xff);\n  val[1] = (unsigned char)(x >> 8);\n  *dst = val + 2;\n}\nstatic void\nsdefl_match(unsigned char **dst, struct sdefl *s, int dist, int len) {\n  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};\n  static const short lmin[] = {3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,\n      51,59,67,83,99,115,131,163,195,227,258};\n  static const short dmin[] = {1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,\n      385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577};\n\n  struct sdefl_match_codest cod;\n  sdefl_match_codes(&cod, dist, len);\n  sdefl_put(dst, s, (int)s->cod.word.lit[cod.lc], s->cod.len.lit[cod.lc]);\n  sdefl_put(dst, s, len - lmin[cod.ls], lxn[cod.ls]);\n  sdefl_put(dst, s, (int)s->cod.word.off[cod.dc], s->cod.len.off[cod.dc]);\n  sdefl_put(dst, s, dist - dmin[cod.dc], cod.dx);\n}\nstatic void\nsdefl_flush(unsigned char **dst, struct sdefl *s, int is_last,\n            const unsigned char *in, int blk_begin, int blk_end) {\n  int blk_len = blk_end - blk_begin;\n  int j, i = 0, item_cnt = 0;\n  struct sdefl_symcnt symcnt = {0};\n  unsigned codes[SDEFL_PRE_MAX];\n  unsigned char lens[SDEFL_PRE_MAX];\n  unsigned freqs[SDEFL_PRE_MAX] = {0};\n  unsigned items[SDEFL_SYM_MAX + SDEFL_OFF_MAX];\n  static const unsigned char perm[SDEFL_PRE_MAX] = {16,17,18,0,8,7,9,6,10,5,11,\n      4,12,3,13,2,14,1,15};\n\n  /* calculate huffman codes */\n  s->freq.lit[SDEFL_EOB]++;\n  sdefl_huff(s->cod.len.lit, s->cod.word.lit, s->freq.lit, SDEFL_SYM_MAX, SDEFL_LIT_LEN_CODES);\n  sdefl_huff(s->cod.len.off, s->cod.word.off, s->freq.off, SDEFL_OFF_MAX, SDEFL_OFF_CODES);\n  sdefl_precode(&symcnt, freqs, items, s->cod.len.lit, s->cod.len.off);\n  sdefl_huff(lens, codes, freqs, SDEFL_PRE_MAX, SDEFL_PRE_CODES);\n  for (item_cnt = SDEFL_PRE_MAX; item_cnt > 4; item_cnt--) {\n    if (lens[perm[item_cnt - 1]]){\n      break;\n    }\n  }\n  /* write block */\n  switch (sdefl_blk_type(s, blk_len, item_cnt, freqs, lens)) {\n  case SDEFL_BLK_UCOMPR: {\n    /* uncompressed blocks */\n    int n = sdefl_div_round_up(blk_len, SDEFL_RAW_BLK_SIZE);\n    for (i = 0; i < n; ++i) {\n      int fin = is_last && (i + 1 == n);\n      int amount = blk_len < SDEFL_RAW_BLK_SIZE ? blk_len : SDEFL_RAW_BLK_SIZE;\n      sdefl_put(dst, s, !!fin, 1); /* block */\n      sdefl_put(dst, s, 0x00, 2); /* stored block */\n      if (s->bitcnt) {\n        sdefl_put(dst, s, 0x00, 8 - s->bitcnt);\n      }\n      assert(s->bitcnt == 0);\n      sdefl_put16(dst, (unsigned short)amount);\n      sdefl_put16(dst, ~(unsigned short)amount);\n      memcpy(*dst, in + blk_begin + i * SDEFL_RAW_BLK_SIZE, amount);\n      *dst = *dst + amount;\n      blk_len -= amount;\n    }\n  } break;\n  case SDEFL_BLK_DYN: {\n    /* dynamic huffman block */\n    sdefl_put(dst, s, !!is_last, 1); /* block */\n    sdefl_put(dst, s, 0x02, 2); /* dynamic huffman */\n    sdefl_put(dst, s, symcnt.lit - 257, 5);\n    sdefl_put(dst, s, symcnt.off - 1, 5);\n    sdefl_put(dst, s, item_cnt - 4, 4);\n    for (i = 0; i < item_cnt; ++i) {\n      sdefl_put(dst, s, lens[perm[i]], 3);\n    }\n    for (i = 0; i < symcnt.items; ++i) {\n      unsigned sym = items[i] & 0x1F;\n      sdefl_put(dst, s, (int)codes[sym], lens[sym]);\n      if (sym < 16) continue;\n      if (sym == 16) sdefl_put(dst, s, items[i] >> 5, 2);\n      else if(sym == 17) sdefl_put(dst, s, items[i] >> 5, 3);\n      else sdefl_put(dst, s, items[i] >> 5, 7);\n    }\n    /* block sequences */\n    for (i = 0; i < s->seq_cnt; ++i) {\n      if (s->seq[i].off >= 0) {\n        for (j = 0; j < s->seq[i].len; ++j) {\n          int c = in[s->seq[i].off + j];\n          sdefl_put(dst, s, (int)s->cod.word.lit[c], s->cod.len.lit[c]);\n        }\n      } else {\n        sdefl_match(dst, s, -s->seq[i].off, s->seq[i].len);\n      }\n    }\n    sdefl_put(dst, s, (int)(s)->cod.word.lit[SDEFL_EOB], (s)->cod.len.lit[SDEFL_EOB]);\n  } break;}\n  memset(&s->freq, 0, sizeof(s->freq));\n  s->seq_cnt = 0;\n}\nstatic void\nsdefl_seq(struct sdefl *s, int off, int len) {\n  assert(s->seq_cnt + 2 < SDEFL_SEQ_SIZ);\n  s->seq[s->seq_cnt].off = off;\n  s->seq[s->seq_cnt].len = len;\n  s->seq_cnt++;\n}\nstatic void\nsdefl_reg_match(struct sdefl *s, int off, int len) {\n  struct sdefl_match_codest cod;\n  sdefl_match_codes(&cod, off, len);\n\n  assert(cod.lc < SDEFL_SYM_MAX);\n  assert(cod.dc < SDEFL_OFF_MAX);\n\n  s->freq.lit[cod.lc]++;\n  s->freq.off[cod.dc]++;\n}\nstruct sdefl_match {\n  int off;\n  int len;\n};\nstatic void\nsdefl_fnd(struct sdefl_match *m, const struct sdefl *s, int chain_len,\n          int max_match, const unsigned char *in, int p, int e) {\n  int i = s->tbl[sdefl_hash32(in + p)];\n  int limit = ((p - SDEFL_WIN_SIZ) < SDEFL_NIL) ? SDEFL_NIL : (p-SDEFL_WIN_SIZ);\n  (void)e; /* used in assertions */\n\n  assert(p < e);\n  assert(p + max_match <= e);\n  while (i > limit) {\n    assert(i + m->len < e);\n    assert(p + m->len < e);\n    assert(i + SDEFL_MIN_MATCH < e);\n    assert(p + SDEFL_MIN_MATCH < e);\n\n    if (in[i + m->len] == in[p + m->len] &&\n      (sdefl_uload32(&in[i]) == sdefl_uload32(&in[p]))) {\n      int n = SDEFL_MIN_MATCH;\n      while (n < max_match && in[i + n] == in[p + n]) {\n        assert(i + n < e);\n        assert(p + n < e);\n        n++;\n      }\n      if (n > m->len) {\n        m->len = n, m->off = p - i;\n        if (n == max_match)\n          break;\n      }\n    }\n    if (!(--chain_len)) break;\n    i = s->prv[i & SDEFL_WIN_MSK];\n  }\n}\nstatic int\nsdefl_compr(struct sdefl *s, unsigned char *out, const unsigned char *in,\n            int in_len, int lvl) {\n  unsigned char *q = out;\n  static const unsigned char pref[] = {8,10,14,24,30,48,65,96,130};\n  int max_chain = (lvl < 8) ? (1 << (lvl + 1)): (1 << 13);\n  int n, i = 0, litlen = 0;\n  for (n = 0; n < SDEFL_HASH_SIZ; ++n) {\n    s->tbl[n] = SDEFL_NIL;\n  }\n  do {int blk_begin = i;\n    int blk_end = ((i + SDEFL_BLK_MAX) < in_len) ? (i + SDEFL_BLK_MAX) : in_len;\n    while (i < blk_end) {\n      struct sdefl_match m = {0};\n      int left = blk_end - i;\n      int max_match = (left > SDEFL_MAX_MATCH) ? SDEFL_MAX_MATCH : left;\n      int nice_match = pref[lvl] < max_match ? pref[lvl] : max_match;\n      int run = 1, inc = 1, run_inc = 0;\n      if (max_match > SDEFL_MIN_MATCH) {\n        sdefl_fnd(&m, s, max_chain, max_match, in, i, in_len);\n      }\n      if (lvl >= 5 && m.len >= SDEFL_MIN_MATCH && m.len + 1 < nice_match){\n        struct sdefl_match m2 = {0};\n        sdefl_fnd(&m2, s, max_chain, m.len + 1, in, i + 1, in_len);\n        m.len = (m2.len > m.len) ? 0 : m.len;\n      }\n      if (m.len >= SDEFL_MIN_MATCH) {\n        if (litlen) {\n          sdefl_seq(s, i - litlen, litlen);\n          litlen = 0;\n        }\n        sdefl_seq(s, -m.off, m.len);\n        sdefl_reg_match(s, m.off, m.len);\n        if (lvl < 2 && m.len >= nice_match) {\n          inc = m.len;\n        } else {\n          run = m.len;\n        }\n      } else {\n        s->freq.lit[in[i]]++;\n        litlen++;\n      }\n      run_inc = run * inc;\n      if (in_len - (i + run_inc) > SDEFL_MIN_MATCH) {\n        while (run-- > 0) {\n          unsigned h = sdefl_hash32(&in[i]);\n          s->prv[i&SDEFL_WIN_MSK] = s->tbl[h];\n          s->tbl[h] = i, i += inc;\n          assert(i <= blk_end);\n        }\n      } else {\n        i += run_inc;\n        assert(i <= blk_end);\n      }\n    }\n    if (litlen) {\n      sdefl_seq(s, i - litlen, litlen);\n      litlen = 0;\n    }\n    sdefl_flush(&q, s, blk_end == in_len, in, blk_begin, blk_end);\n  } while (i < in_len);\n  if (s->bitcnt) {\n    sdefl_put(&q, s, 0x00, 8 - s->bitcnt);\n  }\n  assert(s->bitcnt == 0);\n  return (int)(q - out);\n}\nextern int\nsdeflate(struct sdefl *s, void *out, const void *in, int n, int lvl) {\n  s->bits = s->bitcnt = 0;\n  return sdefl_compr(s, (unsigned char*)out, (const unsigned char*)in, n, lvl);\n}\nstatic unsigned\nsdefl_adler32(unsigned adler32, const unsigned char *in, int in_len) {\n  #define SDEFL_ADLER_INIT (1)\n  const unsigned ADLER_MOD = 65521;\n  unsigned s1 = adler32 & 0xffff;\n  unsigned s2 = adler32 >> 16;\n  unsigned blk_len, i;\n\n  blk_len = in_len % 5552;\n  while (in_len) {\n    for (i = 0; i + 7 < blk_len; i += 8) {\n      s1 += in[0]; s2 += s1;\n      s1 += in[1]; s2 += s1;\n      s1 += in[2]; s2 += s1;\n      s1 += in[3]; s2 += s1;\n      s1 += in[4]; s2 += s1;\n      s1 += in[5]; s2 += s1;\n      s1 += in[6]; s2 += s1;\n      s1 += in[7]; s2 += s1;\n      in += 8;\n    }\n    for (; i < blk_len; ++i) {\n      s1 += *in++, s2 += s1;\n    }\n    s1 %= ADLER_MOD;\n    s2 %= ADLER_MOD;\n    in_len -= blk_len;\n    blk_len = 5552;\n  }\n  return (unsigned)(s2 << 16) + (unsigned)s1;\n}\nextern int\nzsdeflate(struct sdefl *s, void *out, const void *in, int n, int lvl) {\n  int p = 0;\n  unsigned a = 0;\n  unsigned char *q = (unsigned char*)out;\n\n  s->bits = s->bitcnt = 0;\n  sdefl_put(&q, s, 0x78, 8); /* deflate, 32k window */\n  sdefl_put(&q, s, 0x01, 8); /* fast compression */\n  q += sdefl_compr(s, q, (const unsigned char*)in, n, lvl);\n\n  /* append adler checksum */\n  a = sdefl_adler32(SDEFL_ADLER_INIT, (const unsigned char*)in, n);\n  for (p = 0; p < 4; ++p) {\n    sdefl_put(&q, s, (a >> 24) & 0xFF, 8);\n    a <<= 8;\n  }\n  return (int)(q - (unsigned char*)out);\n}\nextern int\nsdefl_bound(int len) {\n  int max_blocks = 1 + sdefl_div_round_up(len, SDEFL_RAW_BLK_SIZE);\n  int bound = 5 * max_blocks + len + 1 + 4 + 8 + 3;\n  return bound;\n}\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* SDEFL_IMPLEMENTATION */\n"
  },
  {
    "path": "gltf/README.md",
    "content": "# 📦 gltfpack\n\ngltfpack is a tool that can automatically optimize glTF files to reduce the download size and improve loading and rendering speed.\n\n## Installation\n\nYou 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.\n\n## Usage\n\nTo 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):\n\n```\ngltfpack -i scene.gltf -o scene.glb\n```\n\ngltfpack 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.\n\nBy 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+).\n\nWhen 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.\n\nFor 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).\n\ngltfpack 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`).\n\nWhen 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.\n\n## Decompression\n\nWhen 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:\n\n```js\nimport { MeshoptDecoder } from './meshopt_decoder.mjs';\n\n...\n\nvar loader = new GLTFLoader();\nloader.setMeshoptDecoder(MeshoptDecoder);\nloader.load('pirate.glb', function (gltf) { scene.add(gltf.scene); });\n```\n\nWhen using Three.js, this module can be imported from three.js repository from `examples/jsm/libs/meshopt_decoder.module.js`.\n\nNote 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.\n\n## Options\n\nBy 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`.\n\nThe following settings are frequently used to reduce the resulting data size:\n\n* `-cc`: produce compressed gltf/glb files (requires `EXT_meshopt_compression`)\n* `-tc`: convert all textures to KTX2 with BasisU supercompression (requires `KHR_texture_basisu` and may require `-tp` flag for compatibility with WebGL 1)\n* `-tw`: convert all textures to WebP (requires `EXT_texture_webp`)\n* `-mi`: use mesh instancing when serializing references to the same meshes (requires `EXT_mesh_gpu_instancing`)\n* `-si R`: simplify meshes targeting triangle/point count ratio R (default: 1; R should be between 0 and 1)\n\nThe following settings are frequently used to restrict some optimizations:\n\n* `-kn`: keep named nodes and meshes attached to named nodes so that named nodes can be transformed externally\n* `-km`: keep named materials and disable named material merging\n* `-ke`: keep extras data\n* `-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)\n\n## Extensions\n\ngltfpack supports most Khronos extensions and some multi-vendor extensions in the input scenes, with newer extensions added regularly. The following extensions are fully supported:\n\n- KHR_lights_punctual\n- KHR_materials_anisotropy\n- KHR_materials_clearcoat\n- KHR_materials_diffuse_transmission\n- KHR_materials_dispersion\n- KHR_materials_emissive_strength\n- KHR_materials_ior\n- KHR_materials_iridescence\n- KHR_materials_pbrSpecularGlossiness\n- KHR_materials_sheen\n- KHR_materials_specular\n- KHR_materials_transmission\n- KHR_materials_unlit\n- KHR_materials_variants\n- KHR_materials_volume\n- KHR_mesh_quantization\n- KHR_meshopt_compression\n- KHR_texture_basisu\n- KHR_texture_transform\n- EXT_mesh_gpu_instancing\n- EXT_meshopt_compression\n- EXT_texture_webp\n\nEven 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:\n\n- KHR_mesh_quantization (used by default unless disabled via `-noq`)\n- KHR_meshopt_compression (used when requested via `-ce khr` or `-cz`)\n- KHR_texture_transform (used by default when textures are present, unless disabled via `-noq` or `-vtf`)\n- KHR_texture_basisu (used when requested via `-tc` or `-tu`)\n- EXT_meshopt_compression (used when requested via `-c` or `-cc`)\n- EXT_mesh_gpu_instancing (used when requested via `-mi`)\n- EXT_texture_webp (used when requested via `-tw`)\n\ngltfpack 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.\n\n## Custom data\n\nglTF files may contain custom application-specific data stored outside of custom extensions. gltfpack has limited support for preserving this data.\n\nUnknown 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:\n\n- 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.\n- Instance attribute `_COLOR_0` is preserved as-is. This can be used to provide per-instance color tinting.\n\nAdditional 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.\n\nIn addition to `asset` extras, gltfpack unconditionally preserves morph target names specified via the `targetNames` property inside `extras` on meshes.\n\n## Building\n\ngltfpack 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:\n\n```\ngit clone -b gltfpack https://github.com/zeux/basis_universal\ngit clone https://github.com/webmproject/libwebp\ncmake . -DMESHOPT_BUILD_GLTFPACK=ON -DMESHOPT_GLTFPACK_BASISU_PATH=basis_universal -DMESHOPT_GLTFPACK_LIBWEBP_PATH=libwebp -DCMAKE_BUILD_TYPE=Release\ncmake --build . --target gltfpack --config Release\n```\n\n## License\n\ngltfpack is available to anybody free of charge, under the terms of MIT License (see LICENSE.md).\n"
  },
  {
    "path": "gltf/animation.cpp",
    "content": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <algorithm>\n\n#include <float.h>\n#include <math.h>\n#include <string.h>\n\nstatic float getDelta(const Attr& l, const Attr& r, cgltf_animation_path_type type)\n{\n\tswitch (type)\n\t{\n\tcase cgltf_animation_path_type_translation:\n\t\treturn 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]));\n\n\tcase cgltf_animation_path_type_rotation:\n\t\treturn 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])));\n\n\tcase cgltf_animation_path_type_scale:\n\t\treturn 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));\n\n\tcase cgltf_animation_path_type_weights:\n\t\treturn fabsf(l.f[0] - r.f[0]);\n\n\tdefault:\n\t\tassert(!\"Uknown animation path\");\n\t\treturn 0;\n\t}\n}\n\nstatic float getDeltaTolerance(cgltf_animation_path_type type)\n{\n\tswitch (type)\n\t{\n\tcase cgltf_animation_path_type_translation:\n\t\treturn 0.0001f; // 0.1mm linear\n\n\tcase cgltf_animation_path_type_rotation:\n\t\treturn 0.1f * (3.1415926f / 180.f); // 0.1 degrees\n\n\tcase cgltf_animation_path_type_scale:\n\t\treturn 0.001f; // 0.1% ratio\n\n\tcase cgltf_animation_path_type_weights:\n\t\treturn 0.001f; // 0.1% linear\n\n\tdefault:\n\t\tassert(!\"Uknown animation path\");\n\t\treturn 0;\n\t}\n}\n\nstatic Attr interpolateLinear(const Attr& l, const Attr& r, float t, cgltf_animation_path_type type)\n{\n\tif (type == cgltf_animation_path_type_rotation)\n\t{\n\t\t// Approximating slerp, https://zeux.io/2015/07/23/approximating-slerp/\n\t\t// We also handle quaternion double-cover\n\t\tfloat 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];\n\n\t\tfloat d = fabsf(ca);\n\t\tfloat A = 1.0904f + d * (-3.2452f + d * (3.55645f - d * 1.43519f));\n\t\tfloat B = 0.848013f + d * (-1.06021f + d * 0.215638f);\n\t\tfloat k = A * (t - 0.5f) * (t - 0.5f) + B;\n\t\tfloat ot = t + t * (t - 0.5f) * (t - 1) * k;\n\n\t\tfloat t0 = 1 - ot;\n\t\tfloat t1 = ca > 0 ? ot : -ot;\n\n\t\tAttr lerp = {{\n\t\t    l.f[0] * t0 + r.f[0] * t1,\n\t\t    l.f[1] * t0 + r.f[1] * t1,\n\t\t    l.f[2] * t0 + r.f[2] * t1,\n\t\t    l.f[3] * t0 + r.f[3] * t1,\n\t\t}};\n\n\t\tfloat 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]);\n\n\t\tif (len > 0.f)\n\t\t{\n\t\t\tlerp.f[0] /= len;\n\t\t\tlerp.f[1] /= len;\n\t\t\tlerp.f[2] /= len;\n\t\t\tlerp.f[3] /= len;\n\t\t}\n\n\t\treturn lerp;\n\t}\n\telse\n\t{\n\t\tAttr lerp = {{\n\t\t    l.f[0] * (1 - t) + r.f[0] * t,\n\t\t    l.f[1] * (1 - t) + r.f[1] * t,\n\t\t    l.f[2] * (1 - t) + r.f[2] * t,\n\t\t    l.f[3] * (1 - t) + r.f[3] * t,\n\t\t}};\n\n\t\treturn lerp;\n\t}\n}\n\nstatic Attr interpolateHermite(const Attr& v0, const Attr& t0, const Attr& v1, const Attr& t1, float t, float dt, cgltf_animation_path_type type)\n{\n\tfloat s0 = 1 + t * t * (2 * t - 3);\n\tfloat s1 = t + t * t * (t - 2);\n\tfloat s2 = 1 - s0;\n\tfloat s3 = t * t * (t - 1);\n\n\tfloat ts1 = dt * s1;\n\tfloat ts3 = dt * s3;\n\n\tAttr lerp = {{\n\t    s0 * v0.f[0] + ts1 * t0.f[0] + s2 * v1.f[0] + ts3 * t1.f[0],\n\t    s0 * v0.f[1] + ts1 * t0.f[1] + s2 * v1.f[1] + ts3 * t1.f[1],\n\t    s0 * v0.f[2] + ts1 * t0.f[2] + s2 * v1.f[2] + ts3 * t1.f[2],\n\t    s0 * v0.f[3] + ts1 * t0.f[3] + s2 * v1.f[3] + ts3 * t1.f[3],\n\t}};\n\n\tif (type == cgltf_animation_path_type_rotation)\n\t{\n\t\tfloat 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]);\n\n\t\tif (len > 0.f)\n\t\t{\n\t\t\tlerp.f[0] /= len;\n\t\t\tlerp.f[1] /= len;\n\t\t\tlerp.f[2] /= len;\n\t\t\tlerp.f[3] /= len;\n\t\t}\n\t}\n\n\treturn lerp;\n}\n\nstatic void resampleKeyframes(std::vector<Attr>& data, const std::vector<float>& input, const std::vector<Attr>& output, cgltf_animation_path_type type, cgltf_interpolation_type interpolation, size_t components, int frames, float mint, int freq)\n{\n\tsize_t cursor = 0;\n\n\tfor (int i = 0; i < frames; ++i)\n\t{\n\t\tfloat time = mint + float(i) / freq;\n\n\t\twhile (cursor + 1 < input.size())\n\t\t{\n\t\t\tfloat next_time = input[cursor + 1];\n\n\t\t\tif (next_time > time)\n\t\t\t\tbreak;\n\n\t\t\tcursor++;\n\t\t}\n\n\t\tif (cursor + 1 < input.size())\n\t\t{\n\t\t\tfloat cursor_time = input[cursor + 0];\n\t\t\tfloat next_time = input[cursor + 1];\n\n\t\t\tfloat range = next_time - cursor_time;\n\t\t\tfloat inv_range = (range == 0.f) ? 0.f : 1.f / (next_time - cursor_time);\n\t\t\tfloat t = std::max(0.f, std::min(1.f, (time - cursor_time) * inv_range));\n\n\t\t\tfor (size_t j = 0; j < components; ++j)\n\t\t\t{\n\t\t\t\tswitch (interpolation)\n\t\t\t\t{\n\t\t\t\tcase cgltf_interpolation_type_linear:\n\t\t\t\t{\n\t\t\t\t\tconst Attr& v0 = output[(cursor + 0) * components + j];\n\t\t\t\t\tconst Attr& v1 = output[(cursor + 1) * components + j];\n\t\t\t\t\tdata.push_back(interpolateLinear(v0, v1, t, type));\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\t\tcase cgltf_interpolation_type_step:\n\t\t\t\t{\n\t\t\t\t\tconst Attr& v = output[cursor * components + j];\n\t\t\t\t\tdata.push_back(v);\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\t\tcase cgltf_interpolation_type_cubic_spline:\n\t\t\t\t{\n\t\t\t\t\tconst Attr& v0 = output[(cursor * 3 + 1) * components + j];\n\t\t\t\t\tconst Attr& b0 = output[(cursor * 3 + 2) * components + j];\n\t\t\t\t\tconst Attr& a1 = output[(cursor * 3 + 3) * components + j];\n\t\t\t\t\tconst Attr& v1 = output[(cursor * 3 + 4) * components + j];\n\t\t\t\t\tdata.push_back(interpolateHermite(v0, b0, v1, a1, t, range, type));\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\tassert(!\"Unknown interpolation type\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsize_t offset = (interpolation == cgltf_interpolation_type_cubic_spline) ? cursor * 3 + 1 : cursor;\n\n\t\t\tfor (size_t j = 0; j < components; ++j)\n\t\t\t{\n\t\t\t\tconst Attr& v = output[offset * components + j];\n\t\t\t\tdata.push_back(v);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic float getMaxDelta(const std::vector<Attr>& data, cgltf_animation_path_type type, const Attr* value, size_t components)\n{\n\tassert(data.size() % components == 0);\n\n\tfloat result = 0;\n\n\tfor (size_t i = 0; i < data.size(); i += components)\n\t{\n\t\tfor (size_t j = 0; j < components; ++j)\n\t\t{\n\t\t\tfloat delta = getDelta(value[j], data[i + j], type);\n\n\t\t\tresult = (result < delta) ? delta : result;\n\t\t}\n\t}\n\n\treturn result;\n}\n\nstatic void getBaseTransform(Attr* result, size_t components, cgltf_animation_path_type type, cgltf_node* node)\n{\n\tswitch (type)\n\t{\n\tcase cgltf_animation_path_type_translation:\n\t\tmemcpy(result->f, node->translation, 3 * sizeof(float));\n\t\tbreak;\n\n\tcase cgltf_animation_path_type_rotation:\n\t\tmemcpy(result->f, node->rotation, 4 * sizeof(float));\n\t\tbreak;\n\n\tcase cgltf_animation_path_type_scale:\n\t\tmemcpy(result->f, node->scale, 3 * sizeof(float));\n\t\tbreak;\n\n\tcase cgltf_animation_path_type_weights:\n\t\tif (node->weights_count)\n\t\t{\n\t\t\tassert(node->weights_count == components);\n\t\t\tmemcpy(result->f, node->weights, components * sizeof(float));\n\t\t}\n\t\telse if (node->mesh && node->mesh->weights_count)\n\t\t{\n\t\t\tassert(node->mesh->weights_count == components);\n\t\t\tmemcpy(result->f, node->mesh->weights, components * sizeof(float));\n\t\t}\n\t\tbreak;\n\n\tdefault:\n\t\tassert(!\"Unknown animation path\");\n\t}\n}\n\nstatic float getWorldScale(cgltf_node* node)\n{\n\tfloat transform[16];\n\tcgltf_node_transform_world(node, transform);\n\n\t// 3x3 determinant computes scale^3\n\tfloat a0 = transform[5] * transform[10] - transform[6] * transform[9];\n\tfloat a1 = transform[4] * transform[10] - transform[6] * transform[8];\n\tfloat a2 = transform[4] * transform[9] - transform[5] * transform[8];\n\tfloat det = transform[0] * a0 - transform[1] * a1 + transform[2] * a2;\n\n\treturn powf(fabsf(det), 1.f / 3.f);\n}\n\nvoid processAnimation(Animation& animation, const Settings& settings)\n{\n\tfloat mint = FLT_MAX, maxt = 0;\n\n\tfor (size_t i = 0; i < animation.tracks.size(); ++i)\n\t{\n\t\tconst Track& track = animation.tracks[i];\n\t\tassert(!track.time.empty());\n\n\t\tmint = std::min(mint, track.time.front());\n\t\tmaxt = std::max(maxt, track.time.back());\n\t}\n\n\tanimation.start = mint = std::min(mint, maxt);\n\n\tif (settings.anim_freq)\n\t{\n\t\t// round the number of frames to nearest but favor the \"up\" direction\n\t\t// this means that at 100 Hz resampling, we will try to preserve the last frame <10ms\n\t\t// but if the last frame is <2ms we favor just removing this data\n\t\tint frames = 1 + int((maxt - mint) * settings.anim_freq + 0.8f);\n\n\t\tanimation.frames = frames;\n\t}\n\n\tstd::vector<Attr> base;\n\n\tfor (size_t i = 0; i < animation.tracks.size(); ++i)\n\t{\n\t\tTrack& track = animation.tracks[i];\n\n\t\tif (settings.anim_freq)\n\t\t{\n\t\t\tstd::vector<Attr> result;\n\t\t\tresampleKeyframes(result, track.time, track.data, track.path, track.interpolation, track.components, animation.frames, animation.start, settings.anim_freq);\n\n\t\t\ttrack.time.clear();\n\t\t\ttrack.data.swap(result);\n\t\t\ttrack.interpolation = track.interpolation == cgltf_interpolation_type_cubic_spline ? cgltf_interpolation_type_linear : track.interpolation;\n\t\t}\n\n\t\t// getMaxDelta assumes linear/step interpolation for now\n\t\tif (track.interpolation == cgltf_interpolation_type_cubic_spline)\n\t\t\tcontinue;\n\n\t\tfloat tolerance = getDeltaTolerance(track.path);\n\n\t\t// translation tracks use world space tolerance; in the future, we should compute all errors as linear using hierarchy\n\t\tif (track.node && track.node->parent && track.path == cgltf_animation_path_type_translation)\n\t\t{\n\t\t\tfloat scale = getWorldScale(track.node->parent);\n\t\t\ttolerance /= scale == 0.f ? 1.f : scale;\n\t\t}\n\n\t\tfloat deviation = getMaxDelta(track.data, track.path, &track.data[0], track.components);\n\n\t\tif (deviation <= tolerance)\n\t\t{\n\t\t\t// track is constant (equal to first keyframe), we only need the first keyframe\n\t\t\ttrack.constant = true;\n\t\t\ttrack.time.clear();\n\t\t\ttrack.data.resize(track.components);\n\n\t\t\t// track.dummy is true iff track redundantly sets up the value to be equal to default node transform\n\t\t\tbase.resize(track.components);\n\t\t\tgetBaseTransform(&base[0], track.components, track.path, track.node);\n\n\t\t\ttrack.dummy = getMaxDelta(track.data, track.path, &base[0], track.components) <= tolerance;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "gltf/cli.js",
    "content": "#!/usr/bin/env node\n// This file is part of gltfpack and is distributed under the terms of MIT License.\nimport { pack } from './library.js';\nimport fs from 'fs';\n\nvar args = process.argv.slice(2);\n\nvar iface = {\n\tread: function (path) {\n\t\treturn fs.readFileSync(path);\n\t},\n\twrite: function (path, data) {\n\t\tfs.writeFileSync(path, data);\n\t},\n};\n\npack(args, iface)\n\t.then(function (log) {\n\t\tprocess.stdout.write(log);\n\t\tprocess.exit(0);\n\t})\n\t.catch(function (err) {\n\t\tprocess.stderr.write(err.message);\n\t\tprocess.exit(1);\n\t});\n"
  },
  {
    "path": "gltf/encodebasis.cpp",
    "content": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#ifdef WITH_BASISU\n#include \"gltfpack.h\"\n\n#define BASISU_NO_ITERATOR_DEBUG_LEVEL\n\n#ifdef __clang__\n#pragma GCC diagnostic ignored \"-Wunknown-warning-option\"\n#pragma GCC diagnostic ignored \"-Warray-bounds\"\n#pragma GCC diagnostic ignored \"-Wc++17-extensions\"\n#pragma GCC diagnostic ignored \"-Wdeprecated-builtins\"\n#endif\n\n#ifdef __GNUC__\n#pragma GCC diagnostic ignored \"-Wclass-memaccess\"\n#pragma GCC diagnostic ignored \"-Wshadow\"\n#pragma GCC diagnostic ignored \"-Wunused-local-typedefs\"\n#pragma GCC diagnostic ignored \"-Wunused-value\"\n#endif\n\n#if defined(__GNUC__) && __GNUC__ >= 12\n#pragma GCC diagnostic ignored \"-Wc++17-extensions\"\n#endif\n\n#include \"encoder/basisu_comp.h\"\n\nstruct BasisSettings\n{\n\tint etc1s_l;\n\tint etc1s_q;\n\tint uastc_l;\n\tfloat uastc_q;\n};\n\nstatic const BasisSettings kBasisSettings[10] = {\n    {1, 1, 0, 4.f},\n    {1, 32, 0, 3.f},\n    {1, 64, 1, 2.f},\n    {1, 96, 1, 1.5f},\n    {1, 128, 1, 1.f}, // quality arguments aligned with basisu defaults\n    {1, 150, 1, 0.8f},\n    {1, 170, 1, 0.6f},\n    {1, 192, 1, 0.4f}, // gltfpack defaults\n    {1, 224, 2, 0.2f},\n    {1, 255, 2, 0.f},\n};\n\nstatic 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)\n{\n\tif (uastc)\n\t{\n\t\tstatic const uint32_t s_level_flags[basisu::TOTAL_PACK_UASTC_LEVELS] = {basisu::cPackUASTCLevelFastest, basisu::cPackUASTCLevelFaster, basisu::cPackUASTCLevelDefault, basisu::cPackUASTCLevelSlower, basisu::cPackUASTCLevelVerySlow};\n\n\t\tparams.m_uastc = true;\n\n#if BASISU_LIB_VERSION >= 160\n\t\tparams.m_pack_uastc_ldr_4x4_flags &= ~basisu::cPackUASTCLevelMask;\n\t\tparams.m_pack_uastc_ldr_4x4_flags |= s_level_flags[bs.uastc_l];\n\n\t\tparams.m_rdo_uastc_ldr_4x4 = bs.uastc_q > 0;\n\t\tparams.m_rdo_uastc_ldr_4x4_quality_scalar = bs.uastc_q;\n\t\tparams.m_rdo_uastc_ldr_4x4_dict_size = 1024;\n#else\n\t\tparams.m_pack_uastc_flags &= ~basisu::cPackUASTCLevelMask;\n\t\tparams.m_pack_uastc_flags |= s_level_flags[bs.uastc_l];\n\n\t\tparams.m_rdo_uastc = bs.uastc_q > 0;\n\t\tparams.m_rdo_uastc_quality_scalar = bs.uastc_q;\n\t\tparams.m_rdo_uastc_dict_size = 1024;\n#endif\n\t}\n\telse\n\t{\n#if BASISU_LIB_VERSION >= 200\n\t\tparams.m_etc1s_compression_level = bs.etc1s_l;\n\t\tparams.m_quality_level = bs.etc1s_q;\n\t\tparams.m_etc1s_max_endpoint_clusters = 0;\n\t\tparams.m_etc1s_max_selector_clusters = 0;\n#elif BASISU_LIB_VERSION >= 160\n\t\tparams.m_compression_level = bs.etc1s_l;\n\t\tparams.m_etc1s_quality_level = bs.etc1s_q;\n\t\tparams.m_etc1s_max_endpoint_clusters = 0;\n\t\tparams.m_etc1s_max_selector_clusters = 0;\n#else\n\t\tparams.m_compression_level = bs.etc1s_l;\n\t\tparams.m_quality_level = bs.etc1s_q;\n\t\tparams.m_max_endpoint_clusters = 0;\n\t\tparams.m_max_selector_clusters = 0;\n#endif\n\n\t\tparams.m_no_selector_rdo = info.normal_map;\n\t\tparams.m_no_endpoint_rdo = info.normal_map;\n\t}\n\n\tparams.m_perceptual = info.srgb;\n\n\tparams.m_mip_gen = true;\n\tparams.m_mip_srgb = info.srgb;\n\n\tparams.m_resample_width = width;\n\tparams.m_resample_height = height;\n\n\tparams.m_y_flip = settings.texture_flipy;\n\n\tparams.m_create_ktx2_file = true;\n\n#if BASISU_LIB_VERSION >= 200\n\tparams.m_ktx2_and_basis_srgb_transfer_function = info.srgb;\n#else\n\tparams.m_ktx2_srgb_transfer_func = info.srgb;\n#endif\n\n\tif (uastc)\n\t{\n\t\tparams.m_ktx2_uastc_supercompression = basist::KTX2_SS_ZSTANDARD;\n\t\tparams.m_ktx2_zstd_supercompression_level = 9;\n\t}\n\n\tparams.m_read_source_images = true;\n\n#if BASISU_LIB_VERSION >= 150\n\tparams.m_write_output_basis_or_ktx2_files = true;\n#else\n\tparams.m_write_output_basis_files = true;\n#endif\n\n\tparams.m_source_filenames.resize(1);\n\tparams.m_source_filenames[0] = input;\n\n\tparams.m_out_filename = output;\n\n\tparams.m_status_output = false;\n}\n\nstatic 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)\n{\n\tstd::string img_data;\n\tstd::string mime_type;\n\n\tif (!readImage(image, input_path, img_data, mime_type))\n\t\treturn \"error reading source file\";\n\n\tif (mime_type != \"image/png\" && mime_type != \"image/jpeg\")\n\t\treturn NULL;\n\n\tint width = 0, height = 0;\n\tif (!getDimensions(img_data, mime_type.c_str(), width, height))\n\t\treturn \"error parsing image header\";\n\n\tadjustDimensions(width, height, settings.texture_scale[info.kind], settings.texture_limit[info.kind], settings.texture_pow2);\n\n\ttemp_input = temp_prefix + mimeExtension(mime_type.c_str());\n\ttemp_output = temp_prefix + \".ktx2\";\n\n\tif (!writeFile(temp_input.c_str(), img_data))\n\t\treturn \"error writing temporary file\";\n\n\tint quality = settings.texture_quality[info.kind];\n\tbool uastc = settings.texture_mode[info.kind] == TextureMode_UASTC;\n\n\tconst BasisSettings& bs = kBasisSettings[quality - 1];\n\n\tfillParams(params, temp_input.c_str(), temp_output.c_str(), uastc, width, height, bs, info, settings);\n\n\treturn NULL;\n}\n\nvoid encodeImagesBasis(std::string* encoded, const cgltf_data* data, const std::vector<ImageInfo>& images, const char* input_path, const Settings& settings)\n{\n\tbasisu::basisu_encoder_init();\n\n\tbasisu::vector<basisu::basis_compressor_params> params(data->images_count);\n\tbasisu::vector<basisu::parallel_results> results(data->images_count);\n\n\tstd::string temp_prefix = getTempPrefix();\n\n\tstd::vector<std::string> temp_inputs(data->images_count);\n\tstd::vector<std::string> temp_outputs(data->images_count);\n\n\tfor (size_t i = 0; i < data->images_count; ++i)\n\t{\n\t\tconst cgltf_image& image = data->images[i];\n\t\tImageInfo info = images[i];\n\n\t\tif (settings.texture_mode[info.kind] == TextureMode_ETC1S || settings.texture_mode[info.kind] == TextureMode_UASTC)\n\t\t\tif (const char* error = prepareEncode(params[i], image, input_path, info, settings, temp_prefix + \"-\" + std::to_string(i), temp_inputs[i], temp_outputs[i]))\n\t\t\t\tencoded[i] = error;\n\t}\n\n\tuint32_t num_threads = settings.texture_jobs == 0 ? std::thread::hardware_concurrency() : settings.texture_jobs;\n\n\tbasisu::basis_parallel_compress(num_threads, params, results);\n\n\tfor (size_t i = 0; i < data->images_count; ++i)\n\t{\n\t\tif (params[i].m_source_filenames.empty())\n\t\t\t; // encoding was skipped or preparation resulted in an error\n\t\telse if (results[i].m_error_code == basisu::basis_compressor::cECFailedReadingSourceImages)\n\t\t\tencoded[i] = \"error decoding source image\";\n\t\telse if (results[i].m_error_code != basisu::basis_compressor::cECSuccess)\n\t\t\tencoded[i] = \"error encoding image\";\n\t\telse if (!readFile(temp_outputs[i].c_str(), encoded[i]))\n\t\t\tencoded[i] = \"error reading temporary file\";\n\t}\n\n\tfor (size_t i = 0; i < data->images_count; ++i)\n\t{\n\t\tif (!temp_inputs[i].empty())\n\t\t\tremoveFile(temp_inputs[i].c_str());\n\t\tif (!temp_outputs[i].empty())\n\t\t\tremoveFile(temp_outputs[i].c_str());\n\t}\n}\n#endif\n"
  },
  {
    "path": "gltf/encodewebp.cpp",
    "content": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#ifdef WITH_LIBWEBP\n#include \"gltfpack.h\"\n\n#include \"webp/decode.h\"\n#include \"webp/encode.h\"\n\n#ifndef WITH_LIBWEBP_BASIS\n#include \"imageio/image_dec.h\"\n#endif\n\n#include <atomic>\n#include <memory>\n#include <thread>\n\nstatic int writeWebP(const uint8_t* data, size_t data_size, const WebPPicture* picture)\n{\n\tstd::string* encoded = static_cast<std::string*>(picture->custom_ptr);\n\tencoded->append(reinterpret_cast<const char*>(data), data_size);\n\treturn 1;\n}\n\n// when gltfpack is built with Basis Universal, we have easy access to its jpeg/png decoders\n#ifdef WITH_LIBWEBP_BASIS\nnamespace pv_png\n{\nvoid* load_png(const void* pImage_buf, size_t buf_size, uint32_t desired_chans, uint32_t& width, uint32_t& height, uint32_t& num_chans);\n} // namespace pv_png\n\nnamespace jpgd\n{\nstatic const uint32_t cFlagLinearChromaFiltering = 1;\nunsigned 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);\n} // namespace jpgd\n\ntypedef int (*WebPImageReader)(const uint8_t* const data, size_t data_size, struct WebPPicture* const pic, int keep_alpha, struct Metadata* const metadata);\n\nstatic int readPngBasis(const uint8_t* const data, size_t data_size, struct WebPPicture* const pic, int keep_alpha, struct Metadata* const metadata)\n{\n\t(void)keep_alpha;\n\t(void)metadata;\n\n\tuint32_t width = 0, height = 0, channels = 0;\n\tvoid* img = pv_png::load_png(data, data_size, 4, width, height, channels);\n\tif (!img)\n\t\treturn 0;\n\n\tpic->width = width;\n\tpic->height = height;\n\tint ok = WebPPictureImportRGBA(pic, static_cast<uint8_t*>(img), width * 4);\n\tfree(img);\n\treturn ok;\n}\n\nstatic int readJpegBasis(const uint8_t* const data, size_t data_size, struct WebPPicture* const pic, int keep_alpha, struct Metadata* const metadata)\n{\n\t(void)keep_alpha;\n\t(void)metadata;\n\n\tint width = 0, height = 0, channels = 0;\n\tunsigned char* img = jpgd::decompress_jpeg_image_from_memory(data, int(data_size), &width, &height, &channels, 4, jpgd::cFlagLinearChromaFiltering);\n\tif (!img)\n\t\treturn 0;\n\n\tpic->width = width;\n\tpic->height = height;\n\tint ok = WebPPictureImportRGBA(pic, img, width * 4);\n\tfree(img);\n\treturn ok;\n}\n#endif\n\nstatic const char* encodeWebP(const cgltf_image& image, const char* input_path, const ImageInfo& info, const Settings& settings, std::string& encoded)\n{\n\tWebPConfig config;\n\tif (!WebPConfigInit(&config))\n\t\treturn \"error initializing WebP\";\n\n\tstd::string img_data;\n\tstd::string mime_type;\n\n\tif (!readImage(image, input_path, img_data, mime_type))\n\t\treturn \"error reading source file\";\n\n\tif (mime_type != \"image/png\" && mime_type != \"image/jpeg\")\n\t\treturn NULL;\n\n\tint width = 0, height = 0;\n\tif (!getDimensions(img_data, mime_type.c_str(), width, height))\n\t\treturn \"error parsing image header\";\n\n\tadjustDimensions(width, height, settings.texture_scale[info.kind], settings.texture_limit[info.kind], settings.texture_pow2);\n\n#ifdef WITH_LIBWEBP_BASIS\n\tWebPImageReader reader = (mime_type == \"image/jpeg\") ? readJpegBasis : readPngBasis;\n#else\n\tWebPInputFileFormat format = (mime_type == \"image/jpeg\") ? WEBP_JPEG_FORMAT : WEBP_PNG_FORMAT;\n\tWebPImageReader reader = WebPGetImageReader(format);\n\tif (!reader)\n\t\treturn \"unsupported image format\";\n#endif\n\n\tWebPPicture pic;\n\tif (!WebPPictureInit(&pic))\n\t\treturn \"error initializing picture\";\n\n\tstd::unique_ptr<WebPPicture, void (*)(WebPPicture*)> pic_storage(&pic, WebPPictureFree);\n\n\tif (!reader(reinterpret_cast<const uint8_t*>(img_data.data()), img_data.size(), &pic, 1, NULL))\n\t\treturn \"error decoding source image\";\n\n\tif ((width != pic.width || height != pic.height) && !WebPPictureRescale(&pic, width, height))\n\t\treturn \"error resizing image\";\n\n\tint quality = settings.texture_quality[info.kind];\n\n\tif (info.normal_map)\n\t\tconfig.quality = float(50 + quality * 5); // map 1-10 to 55-100\n\telse\n\t\tconfig.quality = float(20 + quality * 8); // map 1-10 to 28-100\n\n\tconfig.emulate_jpeg_size = 1; // for flatter quality curve\n\n\tpic.writer = writeWebP;\n\tpic.custom_ptr = &encoded;\n\tencoded.clear();\n\n\tif (!WebPEncode(&config, &pic))\n\t\treturn \"error encoding image\";\n\n\treturn NULL;\n}\n\nvoid encodeImagesWebP(std::string* encoded, const cgltf_data* data, const std::vector<ImageInfo>& images, const char* input_path, const Settings& settings)\n{\n\tstd::atomic<size_t> next_image{0};\n\n\tauto encode = [&]()\n\t{\n\t\tfor (;;)\n\t\t{\n\t\t\tsize_t i = next_image++;\n\t\t\tif (i >= data->images_count)\n\t\t\t\tbreak;\n\n\t\t\tconst cgltf_image& image = data->images[i];\n\t\t\tImageInfo info = images[i];\n\n\t\t\tif (settings.texture_mode[info.kind] == TextureMode_WebP)\n\t\t\t\tif (const char* error = encodeWebP(image, input_path, info, settings, encoded[i]))\n\t\t\t\t\tencoded[i] = error;\n\t\t}\n\t};\n\n\t// we use main thread as a worker as well\n\tsize_t worker_count = settings.texture_jobs == 0 ? std::thread::hardware_concurrency() : settings.texture_jobs;\n\tsize_t thread_count = worker_count > 0 ? worker_count - 1 : 0;\n\n\tstd::vector<std::thread> threads;\n\tfor (size_t i = 0; i < thread_count; ++i)\n\t\tthreads.emplace_back(encode);\n\n\tencode();\n\n\tfor (size_t i = 0; i < thread_count; ++i)\n\t\tthreads[i].join();\n}\n\n#endif\n"
  },
  {
    "path": "gltf/fileio.cpp",
    "content": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#ifdef _WIN32\n#include <process.h>\n#else\n#include <unistd.h>\n#endif\n\nstd::string getTempPrefix()\n{\n#if defined(_WIN32)\n\tconst char* temp_dir = getenv(\"TEMP\");\n\tstd::string path = temp_dir ? temp_dir : \".\";\n\tpath += \"\\\\gltfpack-temp\";\n\tpath += std::to_string(_getpid());\n\treturn path;\n#elif defined(__wasi__)\n\treturn \"gltfpack-temp\";\n#else\n\tstd::string path = \"/tmp/gltfpack-temp\";\n\tpath += std::to_string(getpid());\n\treturn path;\n#endif\n}\n\nstd::string getFullPath(const char* path, const char* base_path)\n{\n\tstd::string result = base_path;\n\n\tstd::string::size_type slash = result.find_last_of(\"/\\\\\");\n\tresult.erase(slash == std::string::npos ? 0 : slash + 1);\n\n\tresult += path;\n\n\treturn result;\n}\n\nstd::string getFileName(const char* path)\n{\n\tstd::string result = path;\n\n\tstd::string::size_type slash = result.find_last_of(\"/\\\\\");\n\tif (slash != std::string::npos)\n\t\tresult.erase(0, slash + 1);\n\n\tstd::string::size_type dot = result.find_last_of('.');\n\tif (dot != std::string::npos)\n\t\tresult.erase(dot);\n\n\treturn result;\n}\n\nstd::string getExtension(const char* path)\n{\n\tstd::string result = path;\n\n\tstd::string::size_type slash = result.find_last_of(\"/\\\\\");\n\tstd::string::size_type dot = result.find_last_of('.');\n\n\tif (slash != std::string::npos && dot != std::string::npos && dot < slash)\n\t\tdot = std::string::npos;\n\n\tresult.erase(0, dot);\n\n\tfor (size_t i = 0; i < result.length(); ++i)\n\t\tif (unsigned(result[i] - 'A') < 26)\n\t\t\tresult[i] = (result[i] - 'A') + 'a';\n\n\treturn result;\n}\n\nbool readFile(const char* path, std::string& data)\n{\n\tFILE* file = fopen(path, \"rb\");\n\tif (!file)\n\t\treturn false;\n\n\tfseek(file, 0, SEEK_END);\n\tlong length = ftell(file);\n\tfseek(file, 0, SEEK_SET);\n\n\tif (length <= 0)\n\t{\n\t\tfclose(file);\n\t\treturn false;\n\t}\n\n\tdata.resize(length);\n\tsize_t result = fread(&data[0], 1, data.size(), file);\n\tint rc = fclose(file);\n\n\treturn rc == 0 && result == data.size();\n}\n\nbool writeFile(const char* path, const std::string& data)\n{\n\tFILE* file = fopen(path, \"wb\");\n\tif (!file)\n\t\treturn false;\n\n\tsize_t result = fwrite(&data[0], 1, data.size(), file);\n\tint rc = fclose(file);\n\n\treturn rc == 0 && result == data.size();\n}\n\nvoid removeFile(const char* path)\n{\n\tremove(path);\n}\n"
  },
  {
    "path": "gltf/fuzz.dict",
    "content": "#\n# AFL dictionary for JSON\n# -----------------------\n#\n# Just the very basics.\n#\n# Inspired by a dictionary by Jakub Wilk <jwilk@jwilk.net>\n#\n\n\"0\"\n\",0\"\n\":0\"\n\"0:\"\n\"-1.2e+3\"\n\n\"true\"\n\"false\"\n\"null\"\n\n\"\\\"\\\"\"\n\",\\\"\\\"\"\n\":\\\"\\\"\"\n\"\\\"\\\":\"\n\n\"{}\"\n\",{}\"\n\":{}\"\n\"{\\\"\\\":0}\"\n\"{{}}\"\n\n\"[]\"\n\",[]\"\n\":[]\"\n\"[0]\"\n\"[[]]\"\n\n\"''\"\n\"\\\\\"\n\"\\\\b\"\n\"\\\\f\"\n\"\\\\n\"\n\"\\\\r\"\n\"\\\\t\"\n\"\\\\u0000\"\n\"\\\\x00\"\n\"\\\\0\"\n\"\\\\uD800\\\\uDC00\"\n\"\\\\uDBFF\\\\uDFFF\"\n\n\"\\\"\\\":0\"\n\"//\"\n\"/**/\"\n\n#\n# AFL dictionary for GLTF core\n# -----------------------\n\n\"5120\"\n\"5121\"\n\"5122\"\n\"5123\"\n\"5125\"\n\"5126\"\n\"\\\"BLEND\\\"\"\n\"\\\"CUBICSPLINE\\\"\"\n\"\\\"LINEAR\\\"\"\n\"\\\"MASK\\\"\"\n\"\\\"MAT2\\\"\"\n\"\\\"MAT3\\\"\"\n\"\\\"MAT4\\\"\"\n\"\\\"OPAQUE\\\"\"\n\"\\\"SCALAR\\\"\"\n\"\\\"STEP\\\"\"\n\"\\\"VEC2\\\"\"\n\"\\\"VEC3\\\"\"\n\"\\\"VEC4\\\"\"\n\"\\\"accessor\\\"\"\n\"\\\"accessors\\\"\"\n\"\\\"alphaCutoff\\\"\"\n\"\\\"alphaMode\\\"\"\n\"\\\"animations\\\"\"\n\"\\\"aspectRatio\\\"\"\n\"\\\"asset\\\"\"\n\"\\\"attributes\\\"\"\n\"\\\"baseColorFactor\\\"\"\n\"\\\"baseColorTexture\\\"\"\n\"\\\"bufferView\\\"\"\n\"\\\"bufferViews\\\"\"\n\"\\\"buffer\\\"\"\n\"\\\"buffers\\\"\"\n\"\\\"byteLength\\\"\"\n\"\\\"byteOffset\\\"\"\n\"\\\"byteStride\\\"\"\n\"\\\"camera\\\"\"\n\"\\\"cameras\\\"\"\n\"\\\"channel\\\"\"\n\"\\\"channels\\\"\"\n\"\\\"children\\\"\"\n\"\\\"componentType\\\"\"\n\"\\\"copyright\\\"\"\n\"\\\"count\\\"\"\n\"\\\"doubleSided\\\"\"\n\"\\\"emissiveFactor\\\"\"\n\"\\\"emissiveTexture\\\"\"\n\"\\\"extensionsRequired\\\"\"\n\"\\\"extensionsUsed\\\"\"\n\"\\\"extensions\\\"\"\n\"\\\"extras\\\"\"\n\"\\\"generator\\\"\"\n\"\\\"image\\\"\"\n\"\\\"images\\\"\"\n\"\\\"index\\\"\"\n\"\\\"indices\\\"\"\n\"\\\"input\\\"\"\n\"\\\"interpolation\\\"\"\n\"\\\"inverseBindMatrices\\\"\"\n\"\\\"joints\\\"\"\n\"\\\"magFilter\\\"\"\n\"\\\"material\\\"\"\n\"\\\"materials\\\"\"\n\"\\\"matrix\\\"\"\n\"\\\"max\\\"\"\n\"\\\"mesh\\\"\"\n\"\\\"meshes\\\"\"\n\"\\\"metallicFactor\\\"\"\n\"\\\"metallicRoughnessTexture\\\"\"\n\"\\\"mimeType\\\"\"\n\"\\\"minFilter\\\"\"\n\"\\\"minVersion\\\"\"\n\"\\\"min\\\"\"\n\"\\\"mode\\\"\"\n\"\\\"name\\\"\"\n\"\\\"node\\\"\"\n\"\\\"nodes\\\"\"\n\"\\\"normalTextureInfo\\\"\"\n\"\\\"normalTexture\\\"\"\n\"\\\"normalized\\\"\"\n\"\\\"occlusionTextureInfo\\\"\"\n\"\\\"occlusionTexture\\\"\"\n\"\\\"orthographic\\\"\"\n\"\\\"output\\\"\"\n\"\\\"path\\\"\"\n\"\\\"pbrMetallicRoughness\\\"\"\n\"\\\"perspective\\\"\"\n\"\\\"primitive\\\"\"\n\"\\\"primitives\\\"\"\n\"\\\"rotation\\\"\"\n\"\\\"roughnessFactor\\\"\"\n\"\\\"sampler\\\"\"\n\"\\\"samplers\\\"\"\n\"\\\"scale\\\"\"\n\"\\\"scene\\\"\"\n\"\\\"scenes\\\"\"\n\"\\\"skeleton\\\"\"\n\"\\\"skin\\\"\"\n\"\\\"skins\\\"\"\n\"\\\"source\\\"\"\n\"\\\"sparse\\\"\"\n\"\\\"strength\\\"\"\n\"\\\"target\\\"\"\n\"\\\"targets\\\"\"\n\"\\\"texCoord\\\"\"\n\"\\\"textureInfo\\\"\"\n\"\\\"texture\\\"\"\n\"\\\"textures\\\"\"\n\"\\\"translation\\\"\"\n\"\\\"type\\\"\"\n\"\\\"uri\\\"\"\n\"\\\"values\\\"\"\n\"\\\"version\\\"\"\n\"\\\"weights\\\"\"\n\"\\\"wrapS\\\"\"\n\"\\\"wrapT\\\"\"\n\"\\\"xmag\\\"\"\n\"\\\"yfov\\\"\"\n\"\\\"ymag\\\"\"\n\"\\\"zfar\\\"\"\n\"\\\"znear\\\"\"\n\n#\n# AFL dictionary for GLTF extensions\n# -----------------------\n\"\\\"KHR_materials_unlit\\\"\"\n\"\\\"KHR_texture_basisu\\\"\"\n\n\"\\\"KHR_materials_pbrSpecularGlossiness\\\"\"\n\"\\\"diffuseFactor\\\"\"\n\"\\\"diffuseTexture\\\"\"\n\"\\\"specularFactor\\\"\"\n\"\\\"glossinessFactor\\\"\"\n\"\\\"specularGlossinessTexture\\\"\"\n\n\"\\\"KHR_texture_transform\\\"\"\n\"\\\"offset\\\"\"\n\"\\\"rotation\\\"\"\n\"\\\"scale\\\"\"\n\"\\\"texCoord\\\"\"\n\n\"\\\"KHR_lights_punctual\\\"\"\n\"\\\"color\\\"\"\n\"\\\"intensity\\\"\"\n\"\\\"type\\\"\"\n\"\\\"range\\\"\"\n\"\\\"innerConeAngle\\\"\"\n\"\\\"outerConeAngle\\\"\"\n\n\"\\\"KHR_materials_transmission\\\"\"\n\"\\\"transmissionFactor\\\"\"\n\"\\\"transmissionTexture\\\"\"\n\n\"\\\"KHR_materials_volume\\\"\"\n\"\\\"thicknessFactor\\\"\"\n\"\\\"thicknessTexture\\\"\"\n\"\\\"attenuationColor\\\"\"\n\"\\\"attenuationDistance\\\"\"\n\n\"\\\"KHR_materials_sheen\\\"\"\n\"\\\"sheenColorFactor\\\"\"\n\"\\\"sheenColorTexture\\\"\"\n\"\\\"sheenRoughnessFactor\\\"\"\n\"\\\"sheenRoughnessTexture\\\"\"\n\n\"\\\"KHR_materials_emissive_strength\\\"\"\n\"\\\"emissiveStrength\"\\\"\"\n"
  },
  {
    "path": "gltf/gltfpack.cpp",
    "content": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <algorithm>\n#include <unordered_map>\n\n#include <locale.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#ifdef __wasi__\n#include <unistd.h>\n#endif\n\n#include \"../src/meshoptimizer.h\"\n\nstd::string getVersion()\n{\n\tchar result[32];\n\tsnprintf(result, sizeof(result), \"%d.%d\", MESHOPTIMIZER_VERSION / 1000, (MESHOPTIMIZER_VERSION % 1000) / 10);\n\treturn result;\n}\n\nstatic void finalizeBufferViews(std::string& json, std::vector<BufferView>& views, std::string& bin, std::string* fallback, size_t& fallback_size, const char* meshopt_ext, int attribute_level)\n{\n\tfor (size_t i = 0; i < views.size(); ++i)\n\t{\n\t\tBufferView& view = views[i];\n\n\t\tsize_t bin_offset = bin.size();\n\t\tsize_t fallback_offset = fallback_size;\n\n\t\tsize_t count = view.data.size() / view.stride;\n\n\t\tif (view.compression == BufferView::Compression_None)\n\t\t{\n\t\t\tbin += view.data;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tswitch (view.compression)\n\t\t\t{\n\t\t\tcase BufferView::Compression_Attribute:\n\t\t\t\tcompressVertexStream(bin, view.data, count, view.stride, attribute_level);\n\t\t\t\tbreak;\n\t\t\tcase BufferView::Compression_Index:\n\t\t\t\tcompressIndexStream(bin, view.data, count, view.stride);\n\t\t\t\tbreak;\n\t\t\tcase BufferView::Compression_IndexSequence:\n\t\t\t\tcompressIndexSequence(bin, view.data, count, view.stride);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tassert(!\"Unknown compression type\");\n\t\t\t}\n\n\t\t\tif (fallback)\n\t\t\t\t*fallback += view.data;\n\t\t\tfallback_size += view.data.size();\n\t\t}\n\n\t\tsize_t raw_offset = (view.compression != BufferView::Compression_None) ? fallback_offset : bin_offset;\n\n\t\tcomma(json);\n\t\twriteBufferView(json, view.kind, view.filter, count, view.stride, raw_offset, view.data.size(), view.compression, bin_offset, bin.size() - bin_offset, meshopt_ext);\n\n\t\t// record written bytes for statistics\n\t\tview.bytes = bin.size() - bin_offset;\n\n\t\t// align each bufferView by 4 bytes\n\t\tbin.resize((bin.size() + 3) & ~3);\n\t\tif (fallback)\n\t\t\tfallback->resize((fallback->size() + 3) & ~3);\n\t\tfallback_size = (fallback_size + 3) & ~3;\n\t}\n}\n\nstatic void printMeshStats(const std::vector<Mesh>& meshes, const char* name)\n{\n\tsize_t mesh_triangles = 0;\n\tsize_t mesh_vertices = 0;\n\tsize_t total_triangles = 0;\n\tsize_t total_instances = 0;\n\tsize_t total_draws = 0;\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tconst Mesh& mesh = meshes[i];\n\n\t\tsize_t triangles = mesh.type == cgltf_primitive_type_triangles ? mesh.indices.size() / 3 : 0;\n\n\t\tmesh_triangles += triangles;\n\t\tmesh_vertices += mesh.streams.empty() ? 0 : mesh.streams[0].data.size();\n\n\t\tsize_t instances = std::max(size_t(1), mesh.nodes.size() + mesh.instances.size());\n\n\t\ttotal_triangles += triangles * instances;\n\t\ttotal_instances += instances;\n\t\ttotal_draws += std::max(size_t(1), mesh.nodes.size());\n\t}\n\n\tprintf(\"%s: %d mesh primitives (%d triangles, %d vertices); %d draw calls (%d instances, %lld triangles)\\n\", name,\n\t    int(meshes.size()), int(mesh_triangles), int(mesh_vertices),\n\t    int(total_draws), int(total_instances), (long long)total_triangles);\n}\n\nstatic void printSceneStats(const std::vector<BufferView>& views, const std::vector<Mesh>& meshes, size_t node_offset, size_t mesh_offset, size_t material_offset, size_t json_size, size_t bin_size)\n{\n\tsize_t bytes[BufferView::Kind_Count] = {};\n\n\tfor (size_t i = 0; i < views.size(); ++i)\n\t{\n\t\tconst BufferView& view = views[i];\n\t\tbytes[view.kind] += view.bytes;\n\t}\n\n\tprintf(\"output: %d nodes, %d meshes (%d primitives), %d materials\\n\", int(node_offset), int(mesh_offset), int(meshes.size()), int(material_offset));\n\tprintf(\"output: JSON %d bytes, buffers %d bytes\\n\", int(json_size), int(bin_size));\n\tprintf(\"output: buffers: vertex %d bytes, index %d bytes, skin %d bytes, time %d bytes, keyframe %d bytes, instance %d bytes, image %d bytes\\n\",\n\t    int(bytes[BufferView::Kind_Vertex]), int(bytes[BufferView::Kind_Index]), int(bytes[BufferView::Kind_Skin]),\n\t    int(bytes[BufferView::Kind_Time]), int(bytes[BufferView::Kind_Keyframe]), int(bytes[BufferView::Kind_Instance]),\n\t    int(bytes[BufferView::Kind_Image]));\n}\n\nstatic void printAttributeStats(const std::vector<BufferView>& views, BufferView::Kind kind, const char* name)\n{\n\tfor (size_t i = 0; i < views.size(); ++i)\n\t{\n\t\tconst BufferView& view = views[i];\n\n\t\tif (view.kind != kind)\n\t\t\tcontinue;\n\n\t\tconst char* variant = \"unknown\";\n\n\t\tswitch (kind)\n\t\t{\n\t\tcase BufferView::Kind_Vertex:\n\t\t\tvariant = attributeType(cgltf_attribute_type(view.variant));\n\t\t\tbreak;\n\n\t\tcase BufferView::Kind_Index:\n\t\t\tvariant = \"index\";\n\t\t\tbreak;\n\n\t\tcase BufferView::Kind_Keyframe:\n\t\tcase BufferView::Kind_Instance:\n\t\t\tvariant = animationPath(cgltf_animation_path_type(view.variant));\n\t\t\tbreak;\n\n\t\tdefault:;\n\t\t}\n\n\t\tsize_t count = view.data.size() / view.stride;\n\n\t\tif (view.compression == BufferView::Compression_None)\n\t\t\tprintf(\"stats: %s %s: %d bytes (%.1f bits)\\n\",\n\t\t\t    name, variant, int(view.bytes), double(view.bytes) / double(count) * 8);\n\t\telse\n\t\t\tprintf(\"stats: %s %s: compressed %d bytes (%.1f bits), raw %d bytes (%d bits)\\n\",\n\t\t\t    name, variant,\n\t\t\t    int(view.bytes), double(view.bytes) / double(count) * 8,\n\t\t\t    int(view.data.size()), int(view.stride * 8));\n\t}\n}\n\nstatic void printImageStats(const std::vector<BufferView>& views, TextureKind kind, const char* name)\n{\n\tsize_t bytes = 0;\n\tsize_t count = 0;\n\n\tfor (size_t i = 0; i < views.size(); ++i)\n\t{\n\t\tconst BufferView& view = views[i];\n\n\t\tif (view.kind != BufferView::Kind_Image)\n\t\t\tcontinue;\n\n\t\tif (view.variant != -1 - kind)\n\t\t\tcontinue;\n\n\t\tcount += 1;\n\t\tbytes += view.data.size();\n\t}\n\n\tif (count)\n\t\tprintf(\"stats: image %s: %d bytes in %d images\\n\", name, int(bytes), int(count));\n}\n\nstatic bool printReport(const char* path, const std::vector<BufferView>& views, const std::vector<Mesh>& 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)\n{\n\tsize_t bytes[BufferView::Kind_Count] = {};\n\n\tfor (size_t i = 0; i < views.size(); ++i)\n\t{\n\t\tconst BufferView& view = views[i];\n\t\tbytes[view.kind] += view.bytes;\n\t}\n\n\tsize_t total_triangles = 0;\n\tsize_t total_instances = 0;\n\tsize_t total_draws = 0;\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tconst Mesh& mesh = meshes[i];\n\n\t\tsize_t triangles = mesh.type == cgltf_primitive_type_triangles ? mesh.indices.size() / 3 : 0;\n\t\tsize_t instances = std::max(size_t(1), mesh.nodes.size() + mesh.instances.size());\n\n\t\ttotal_triangles += triangles * instances;\n\t\ttotal_instances += instances;\n\t\ttotal_draws += std::max(size_t(1), mesh.nodes.size());\n\t}\n\n\tFILE* out = fopen(path, \"wb\");\n\tif (!out)\n\t\treturn false;\n\n\tfprintf(out, \"{\\n\");\n\tfprintf(out, \"\\t\\\"generator\\\": \\\"gltfpack %s\\\",\\n\", getVersion().c_str());\n\tfprintf(out, \"\\t\\\"scene\\\": {\\n\");\n\tfprintf(out, \"\\t\\t\\\"nodeCount\\\": %d,\\n\", int(node_count));\n\tfprintf(out, \"\\t\\t\\\"meshCount\\\": %d,\\n\", int(mesh_count));\n\tfprintf(out, \"\\t\\t\\\"materialCount\\\": %d,\\n\", int(material_count));\n\tfprintf(out, \"\\t\\t\\\"textureCount\\\": %d,\\n\", int(texture_count));\n\tfprintf(out, \"\\t\\t\\\"animationCount\\\": %d\\n\", int(animation_count));\n\tfprintf(out, \"\\t},\\n\");\n\tfprintf(out, \"\\t\\\"render\\\": {\\n\");\n\tfprintf(out, \"\\t\\t\\\"drawCount\\\": %d,\\n\", int(total_draws));\n\tfprintf(out, \"\\t\\t\\\"instanceCount\\\": %d,\\n\", int(total_instances));\n\tfprintf(out, \"\\t\\t\\\"triangleCount\\\": %lld\\n\", (long long)total_triangles);\n\tfprintf(out, \"\\t},\\n\");\n\tfprintf(out, \"\\t\\\"data\\\": {\\n\");\n\tfprintf(out, \"\\t\\t\\\"json\\\": %d,\\n\", int(json_size));\n\tfprintf(out, \"\\t\\t\\\"binary\\\": %d,\\n\", int(bin_size));\n\tfprintf(out, \"\\t\\t\\\"buffers\\\": {\\n\");\n\tfprintf(out, \"\\t\\t\\t\\\"vertex\\\": %d,\\n\", int(bytes[BufferView::Kind_Vertex]));\n\tfprintf(out, \"\\t\\t\\t\\\"index\\\": %d,\\n\", int(bytes[BufferView::Kind_Index]));\n\tfprintf(out, \"\\t\\t\\t\\\"animation\\\": %d,\\n\", int(bytes[BufferView::Kind_Time] + bytes[BufferView::Kind_Keyframe]));\n\tfprintf(out, \"\\t\\t\\t\\\"transform\\\": %d,\\n\", int(bytes[BufferView::Kind_Skin] + bytes[BufferView::Kind_Instance]));\n\tfprintf(out, \"\\t\\t\\t\\\"image\\\": %d\\n\", int(bytes[BufferView::Kind_Image]));\n\tfprintf(out, \"\\t\\t}\\n\");\n\tfprintf(out, \"\\t}\\n\");\n\tfprintf(out, \"}\\n\");\n\n\tint rc = fclose(out);\n\treturn rc == 0;\n}\n\nstatic bool canTransformMesh(const Mesh& mesh)\n{\n\t// volume thickness is specified in mesh coordinate space; to avoid modifying materials we prohibit transforming meshes with volume materials\n\tif (mesh.material && mesh.material->has_volume && mesh.material->volume.thickness_factor > 0.f)\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic void detachMesh(Mesh& mesh, cgltf_data* data, const std::vector<NodeInfo>& nodes, const Settings& settings)\n{\n\t// mesh is already instanced, skip\n\tif (!mesh.instances.empty())\n\t\treturn;\n\n\t// mesh is already world space, skip\n\tif (mesh.nodes.empty())\n\t\treturn;\n\n\t// note: when -kn is specified, we keep mesh-node attachment so that named nodes can be transformed\n\tif (settings.keep_nodes)\n\t\treturn;\n\n\t// we keep skinned meshes or meshes with morph targets as is\n\t// in theory we could transform both, but in practice transforming morph target meshes is more involved,\n\t// and reparenting skinned meshes leads to incorrect bounding box generated in three.js\n\tif (mesh.skin || mesh.targets)\n\t\treturn;\n\n\tbool any_animated = false;\n\tfor (size_t j = 0; j < mesh.nodes.size(); ++j)\n\t\tany_animated |= nodes[mesh.nodes[j] - data->nodes].animated;\n\n\t// animated meshes will be anchored to the same node that they used to be in to retain the animation\n\tif (any_animated)\n\t\treturn;\n\n\tint scene = nodes[mesh.nodes[0] - data->nodes].scene;\n\tbool any_other_scene = false;\n\tfor (size_t j = 0; j < mesh.nodes.size(); ++j)\n\t\tany_other_scene |= scene != nodes[mesh.nodes[j] - data->nodes].scene;\n\n\t// we only merge instances when all nodes have a single consistent scene\n\tif (scene < 0 || any_other_scene)\n\t\treturn;\n\n\t// we only merge multiple instances together if requested\n\t// this often makes the scenes faster to render by reducing the draw call count, but can result in larger files\n\tif (mesh.nodes.size() > 1 && !settings.mesh_merge && !settings.mesh_instancing)\n\t\treturn;\n\n\t// mesh has duplicate geometry; detaching it would increase the size due to unique world-space transforms\n\tif (mesh.nodes.size() == 1 && mesh.geometry_duplicate && !settings.mesh_merge)\n\t\treturn;\n\n\t// prefer instancing if possible, use merging otherwise\n\tif (mesh.nodes.size() > 1 && settings.mesh_instancing)\n\t{\n\t\tmesh.instances.resize(mesh.nodes.size());\n\n\t\tfor (size_t j = 0; j < mesh.nodes.size(); ++j)\n\t\t{\n\t\t\tInstance& obj = mesh.instances[j];\n\n\t\t\tcgltf_node_transform_world(mesh.nodes[j], obj.transform);\n\t\t\tobj.color[0] = obj.color[1] = obj.color[2] = obj.color[3] = 1.0f;\n\t\t}\n\n\t\tmesh.nodes.clear();\n\t\tmesh.scene = scene;\n\t}\n\telse if (canTransformMesh(mesh))\n\t{\n\t\tmergeMeshInstances(mesh);\n\n\t\tassert(mesh.nodes.empty());\n\t\tmesh.scene = scene;\n\t}\n}\n\nstatic bool isExtensionSupported(const ExtensionInfo* extensions, size_t count, const char* name)\n{\n\tfor (size_t i = 0; i < count; ++i)\n\t\tif (strcmp(extensions[i].name, name) == 0)\n\t\t\treturn true;\n\n\treturn false;\n}\n\nnamespace std\n{\ntemplate <>\nstruct hash<std::pair<uint64_t, uint64_t> >\n{\n\tsize_t operator()(const std::pair<uint64_t, uint64_t>& x) const\n\t{\n\t\treturn std::hash<uint64_t>()(x.first ^ x.second);\n\t}\n};\n} // namespace std\n\nstatic size_t process(cgltf_data* data, const char* input_path, const char* output_path, const char* report_path, std::vector<Mesh>& meshes, std::vector<Animation>& animations, const Settings& settings, std::string& json, std::string& bin, std::string& fallback, size_t& fallback_size, const char* meshopt_ext)\n{\n\tif (settings.verbose)\n\t{\n\t\tprintf(\"input: %d nodes, %d meshes (%d primitives), %d materials, %d skins, %d animations, %d images\\n\",\n\t\t    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));\n\t\tprintMeshStats(meshes, \"input\");\n\t}\n\n\tfor (size_t i = 0; i < animations.size(); ++i)\n\t\tprocessAnimation(animations[i], settings);\n\n\tstd::vector<NodeInfo> nodes(data->nodes_count);\n\n\tmarkScenes(data, nodes);\n\tmarkAnimated(data, nodes, animations);\n\n\tmergeMeshMaterials(data, meshes, settings);\n\tif (settings.mesh_dedup)\n\t\tdedupMeshes(meshes, settings);\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t\tdetachMesh(meshes[i], data, nodes, settings);\n\n\t// material information is required for mesh and image processing\n\tstd::vector<MaterialInfo> materials(data->materials_count);\n\tstd::vector<TextureInfo> textures(data->textures_count);\n\tstd::vector<ImageInfo> images(data->images_count);\n\n\t// mark materials that need to keep blending due to mesh vertex colors\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tMesh& mesh = meshes[i];\n\n\t\t// skip hasAlpha check unless it's required\n\t\tif ((((mesh.material && mesh.material->alpha_mode != cgltf_alpha_mode_opaque) || mesh.variants.size()) && hasVertexAlpha(mesh)) || hasInstanceAlpha(mesh.instances))\n\t\t{\n\t\t\tif (mesh.material)\n\t\t\t\tmaterials[mesh.material - data->materials].mesh_alpha = true;\n\n\t\t\tfor (size_t j = 0; j < mesh.variants.size(); ++j)\n\t\t\t\tmaterials[mesh.variants[j].material - data->materials].mesh_alpha = true;\n\t\t}\n\t}\n\n\tanalyzeMaterials(data, materials, textures, images);\n\n\tmergeTextures(data, textures);\n\n\toptimizeMaterials(data, materials, images, input_path);\n\n\t// streams need to be filtered before mesh merging (or processing) to make sure we can merge meshes with redundant streams\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tMesh& mesh = meshes[i];\n\t\tMaterialInfo mi = mesh.material ? materials[mesh.material - data->materials] : MaterialInfo();\n\n\t\t// merge material requirements across all variants\n\t\tfor (size_t j = 0; j < mesh.variants.size(); ++j)\n\t\t{\n\t\t\tMaterialInfo vi = materials[mesh.variants[j].material - data->materials];\n\n\t\t\tmi.needs_tangents |= vi.needs_tangents;\n\t\t\tmi.texture_set_mask |= vi.texture_set_mask;\n\t\t\tmi.unlit &= vi.unlit;\n\t\t}\n\n\t\tif (!settings.keep_attributes)\n\t\t\tfilterStreams(mesh, mi);\n\t}\n\n\tmergeMeshes(meshes, settings);\n\tfilterEmptyMeshes(meshes);\n\n\tmarkNeededNodes(data, nodes, meshes, animations, settings);\n\tmarkNeededMaterials(data, materials, meshes, settings);\n\n\tif (settings.simplify_scaled && settings.simplify_ratio < 1)\n\t\tcomputeMeshQuality(meshes);\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tMesh& mesh = meshes[i];\n\t\tprocessMesh(mesh, settings);\n\n\t\tif (mesh.geometry_duplicate)\n\t\t\thashMesh(mesh);\n\t}\n\n\tfilterEmptyMeshes(meshes); // some meshes may become empty after processing\n\n\tQuantizationPosition qp = prepareQuantizationPosition(meshes, settings);\n\n\tstd::vector<QuantizationTexture> qt_materials(materials.size());\n\tstd::vector<size_t> qt_meshes(meshes.size(), size_t(-1));\n\tprepareQuantizationTexture(data, qt_materials, qt_meshes, meshes, settings);\n\n\tQuantizationTexture qt_dummy = {};\n\tqt_dummy.bits = settings.tex_bits;\n\n\tstd::string json_images;\n\tstd::string json_samplers;\n\tstd::string json_textures;\n\tstd::string json_materials;\n\tstd::string json_accessors;\n\tstd::string json_meshes;\n\tstd::string json_nodes;\n\tstd::string json_skins;\n\tstd::vector<std::string> json_roots(data->scenes_count);\n\tstd::string json_animations;\n\tstd::string json_cameras;\n\tstd::string json_extensions;\n\n\tstd::vector<BufferView> views;\n\n\tbool ext_pbr_specular_glossiness = false;\n\tbool ext_clearcoat = false;\n\tbool ext_transmission = false;\n\tbool ext_ior = false;\n\tbool ext_specular = false;\n\tbool ext_sheen = false;\n\tbool ext_volume = false;\n\tbool ext_emissive_strength = false;\n\tbool ext_iridescence = false;\n\tbool ext_anisotropy = false;\n\tbool ext_dispersion = false;\n\tbool ext_diffuse_transmission = false;\n\tbool ext_unlit = false;\n\tbool ext_instancing = false;\n\tbool ext_texture_transform = false;\n\tbool ext_texture_basisu = false;\n\tbool ext_texture_webp = false;\n\n\tsize_t accr_offset = 0;\n\tsize_t node_offset = 0;\n\tsize_t mesh_offset = 0;\n\tsize_t texture_offset = 0;\n\tsize_t material_offset = 0;\n\n\tfor (size_t i = 0; i < data->samplers_count; ++i)\n\t{\n\t\tconst cgltf_sampler& sampler = data->samplers[i];\n\n\t\tcomma(json_samplers);\n\t\tappend(json_samplers, \"{\");\n\t\twriteSampler(json_samplers, sampler);\n\t\tappend(json_samplers, \"}\");\n\t}\n\n\tstd::vector<std::string> encoded_images(data->images_count);\n\n#ifdef WITH_BASISU\n\tif (data->images_count && settings.texture_ktx2)\n\t\tencodeImagesBasis(encoded_images.data(), data, images, input_path, settings);\n#endif\n\n#ifdef WITH_LIBWEBP\n\tif (data->images_count && settings.texture_webp)\n\t\tencodeImagesWebP(encoded_images.data(), data, images, input_path, settings);\n#endif\n\n\tfor (size_t i = 0; i < data->images_count; ++i)\n\t{\n\t\tconst cgltf_image& image = data->images[i];\n\n\t\tstd::string* encoded = !encoded_images[i].empty() ? &encoded_images[i] : NULL;\n\n\t\tcomma(json_images);\n\t\tappend(json_images, \"{\");\n\t\twriteImage(json_images, views, image, images[i], encoded, i, input_path, output_path, settings);\n\t\tappend(json_images, \"}\");\n\n\t\tif (encoded)\n\t\t\t*encoded = std::string(); // reclaim memory early\n\t}\n\n\tfor (size_t i = 0; i < data->textures_count; ++i)\n\t{\n\t\tconst cgltf_texture& texture = data->textures[i];\n\n\t\tif (!textures[i].keep)\n\t\t\tcontinue;\n\n\t\tcomma(json_textures);\n\t\tappend(json_textures, \"{\");\n\t\twriteTexture(json_textures, texture, texture.image ? &images[texture.image - data->images] : NULL, data, settings);\n\t\tappend(json_textures, \"}\");\n\n\t\tassert(textures[i].remap == int(texture_offset));\n\t\ttexture_offset++;\n\t\text_texture_basisu = ext_texture_basisu || texture.has_basisu;\n\t\text_texture_webp = ext_texture_webp || texture.has_webp;\n\t}\n\n\tfor (size_t i = 0; i < data->materials_count; ++i)\n\t{\n\t\tMaterialInfo& mi = materials[i];\n\n\t\tif (!mi.keep)\n\t\t\tcontinue;\n\n\t\tconst cgltf_material& material = data->materials[i];\n\n\t\tcomma(json_materials);\n\t\tappend(json_materials, \"{\");\n\t\twriteMaterial(json_materials, data, material, settings.quantize && !settings.pos_float ? &qp : NULL, settings.quantize && !settings.tex_float ? &qt_materials[i] : NULL, textures);\n\t\tif (settings.keep_extras)\n\t\t\twriteExtras(json_materials, material.extras);\n\t\tappend(json_materials, \"}\");\n\n\t\tmi.remap = int(material_offset);\n\t\tmaterial_offset++;\n\n\t\text_pbr_specular_glossiness = ext_pbr_specular_glossiness || material.has_pbr_specular_glossiness;\n\t\text_clearcoat = ext_clearcoat || material.has_clearcoat;\n\t\text_transmission = ext_transmission || material.has_transmission;\n\t\text_ior = ext_ior || material.has_ior;\n\t\text_specular = ext_specular || material.has_specular;\n\t\text_sheen = ext_sheen || material.has_sheen;\n\t\text_volume = ext_volume || material.has_volume;\n\t\text_emissive_strength = ext_emissive_strength || material.has_emissive_strength;\n\t\text_iridescence = ext_iridescence || material.has_iridescence;\n\t\text_diffuse_transmission = ext_diffuse_transmission || material.has_diffuse_transmission;\n\t\text_anisotropy = ext_anisotropy || material.has_anisotropy;\n\t\text_dispersion = ext_dispersion || material.has_dispersion;\n\t\text_unlit = ext_unlit || material.unlit;\n\t\text_texture_transform = ext_texture_transform || mi.uses_texture_transform;\n\t}\n\n\tstd::unordered_map<std::pair<uint64_t, uint64_t>, std::pair<size_t, size_t> > primitive_cache;\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tconst Mesh& mesh = meshes[i];\n\n\t\tcomma(json_meshes);\n\t\tappend(json_meshes, \"{\\\"primitives\\\":[\");\n\n\t\tsize_t pi = i;\n\t\tfor (; pi < meshes.size(); ++pi)\n\t\t{\n\t\t\tconst Mesh& prim = meshes[pi];\n\n\t\t\tif (prim.scene != mesh.scene || prim.skin != mesh.skin || prim.targets != mesh.targets)\n\t\t\t\tbreak;\n\n\t\t\tif (pi > i && (mesh.instances.size() || prim.instances.size()))\n\t\t\t\tbreak;\n\n\t\t\tif (!compareMeshNodes(mesh, prim))\n\t\t\t\tbreak;\n\n\t\t\tif (!compareMeshTargets(mesh, prim))\n\t\t\t\tbreak;\n\n\t\t\tconst QuantizationTexture& qt = qt_meshes[pi] == size_t(-1) ? qt_dummy : qt_materials[qt_meshes[pi]];\n\n\t\t\tcomma(json_meshes);\n\n\t\t\tif (prim.geometry_duplicate)\n\t\t\t{\n\t\t\t\tstd::pair<size_t, size_t>& primitive_json = primitive_cache[std::make_pair(prim.geometry_hash[0], prim.geometry_hash[1])];\n\n\t\t\t\tif (primitive_json.second)\n\t\t\t\t{\n\t\t\t\t\t// reuse previously written accessors\n\t\t\t\t\tjson_meshes.append(json_meshes, primitive_json.first, primitive_json.second);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tprimitive_json.first = json_meshes.size();\n\t\t\t\t\twriteMeshGeometry(json_meshes, views, json_accessors, accr_offset, prim, qp, qt, settings);\n\t\t\t\t\tprimitive_json.second = json_meshes.size() - primitive_json.first;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\twriteMeshGeometry(json_meshes, views, json_accessors, accr_offset, prim, qp, qt, settings);\n\t\t\t}\n\n\t\t\tif (prim.material)\n\t\t\t{\n\t\t\t\tMaterialInfo& mi = materials[prim.material - data->materials];\n\n\t\t\t\tassert(mi.keep);\n\t\t\t\tappend(json_meshes, \",\\\"material\\\":\");\n\t\t\t\tappend(json_meshes, size_t(mi.remap));\n\t\t\t}\n\n\t\t\tif (prim.variants.size())\n\t\t\t{\n\t\t\t\tappend(json_meshes, \",\\\"extensions\\\":{\\\"KHR_materials_variants\\\":{\\\"mappings\\\":[\");\n\n\t\t\t\tfor (size_t j = 0; j < prim.variants.size(); ++j)\n\t\t\t\t{\n\t\t\t\t\tconst cgltf_material_mapping& variant = prim.variants[j];\n\t\t\t\t\tMaterialInfo& mi = materials[variant.material - data->materials];\n\n\t\t\t\t\tassert(mi.keep);\n\t\t\t\t\tcomma(json_meshes);\n\t\t\t\t\tappend(json_meshes, \"{\\\"material\\\":\");\n\t\t\t\t\tappend(json_meshes, size_t(mi.remap));\n\t\t\t\t\tappend(json_meshes, \",\\\"variants\\\":[\");\n\t\t\t\t\tappend(json_meshes, size_t(variant.variant));\n\t\t\t\t\tappend(json_meshes, \"]}\");\n\t\t\t\t}\n\n\t\t\t\tappend(json_meshes, \"]}}\");\n\t\t\t}\n\n\t\t\tif (settings.keep_extras)\n\t\t\t\twriteExtras(json_meshes, prim.extras);\n\n\t\t\tappend(json_meshes, \"}\");\n\t\t}\n\n\t\tappend(json_meshes, \"]\");\n\n\t\tif (mesh.target_weights.size())\n\t\t{\n\t\t\tappend(json_meshes, \",\\\"weights\\\":\");\n\t\t\tappend(json_meshes, mesh.target_weights.data(), mesh.target_weights.size());\n\t\t}\n\n\t\tif (mesh.target_names.size())\n\t\t{\n\t\t\tappend(json_meshes, \",\\\"extras\\\":{\\\"targetNames\\\":[\");\n\t\t\tfor (size_t j = 0; j < mesh.target_names.size(); ++j)\n\t\t\t{\n\t\t\t\tcomma(json_meshes);\n\t\t\t\tappend(json_meshes, \"\\\"\");\n\t\t\t\tappend(json_meshes, mesh.target_names[j]);\n\t\t\t\tappend(json_meshes, \"\\\"\");\n\t\t\t}\n\t\t\tappend(json_meshes, \"]}\");\n\t\t}\n\n\t\tappend(json_meshes, \"}\");\n\n\t\tif (mesh.nodes.size())\n\t\t{\n\t\t\tfor (size_t j = 0; j < mesh.nodes.size(); ++j)\n\t\t\t{\n\t\t\t\tNodeInfo& ni = nodes[mesh.nodes[j] - data->nodes];\n\t\t\t\tassert(ni.keep);\n\n\t\t\t\t// if we don't use position quantization, prefer attaching the mesh to its node directly\n\t\t\t\tif (!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)))\n\t\t\t\t{\n\t\t\t\t\tni.has_mesh = true;\n\t\t\t\t\tni.mesh_index = mesh_offset;\n\t\t\t\t\tni.mesh_skin = mesh.skin;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tni.mesh_nodes.push_back(node_offset);\n\n\t\t\t\t\twriteMeshNode(json_nodes, mesh_offset, mesh.nodes[j], mesh.skin, data, settings.quantize && !settings.pos_float ? &qp : NULL);\n\n\t\t\t\t\tnode_offset++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (mesh.instances.size())\n\t\t{\n\t\t\tassert(mesh.scene >= 0);\n\t\t\tcomma(json_roots[mesh.scene]);\n\t\t\tappend(json_roots[mesh.scene], node_offset);\n\n\t\t\tbool has_color = false;\n\t\t\tfor (const Instance& instance : mesh.instances)\n\t\t\t\thas_color |= (instance.color[0] != 1.f || instance.color[1] != 1.f || instance.color[2] != 1.f || instance.color[3] != 1.f);\n\n\t\t\tsize_t instance_accr = writeInstances(views, json_accessors, accr_offset, mesh.instances, qp, has_color, settings);\n\n\t\t\tassert(!mesh.skin);\n\t\t\twriteMeshNodeInstanced(json_nodes, mesh_offset, instance_accr, has_color);\n\n\t\t\tnode_offset++;\n\t\t}\n\n\t\tif (mesh.nodes.empty() && mesh.instances.empty())\n\t\t{\n\t\t\tassert(mesh.scene >= 0);\n\t\t\tcomma(json_roots[mesh.scene]);\n\t\t\tappend(json_roots[mesh.scene], node_offset);\n\n\t\t\twriteMeshNode(json_nodes, mesh_offset, NULL, mesh.skin, data, settings.quantize && !settings.pos_float ? &qp : NULL);\n\n\t\t\tnode_offset++;\n\t\t}\n\n\t\tmesh_offset++;\n\t\text_instancing = ext_instancing || !mesh.instances.empty();\n\n\t\t// skip all meshes that we've written in this iteration\n\t\tassert(pi > i);\n\t\ti = pi - 1;\n\t}\n\n\tremapNodes(data, nodes, node_offset);\n\n\tfor (size_t i = 0; i < data->nodes_count; ++i)\n\t{\n\t\tNodeInfo& ni = nodes[i];\n\n\t\tif (!ni.keep)\n\t\t\tcontinue;\n\n\t\tconst cgltf_node& node = data->nodes[i];\n\n\t\tcomma(json_nodes);\n\t\tappend(json_nodes, \"{\");\n\t\twriteNode(json_nodes, node, nodes, data);\n\t\tif (settings.keep_extras)\n\t\t\twriteExtras(json_nodes, node.extras);\n\t\tappend(json_nodes, \"}\");\n\t}\n\n\tfor (size_t i = 0; i < data->scenes_count; ++i)\n\t{\n\t\tfor (size_t j = 0; j < data->scenes[i].nodes_count; ++j)\n\t\t{\n\t\t\tNodeInfo& ni = nodes[data->scenes[i].nodes[j] - data->nodes];\n\n\t\t\tif (ni.keep)\n\t\t\t{\n\t\t\t\tcomma(json_roots[i]);\n\t\t\t\tappend(json_roots[i], size_t(ni.remap));\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (size_t i = 0; i < data->skins_count; ++i)\n\t{\n\t\tconst cgltf_skin& skin = data->skins[i];\n\n\t\tsize_t matrix_accr = writeJointBindMatrices(views, json_accessors, accr_offset, skin, qp, settings);\n\n\t\twriteSkin(json_skins, skin, matrix_accr, nodes, data);\n\t}\n\n\tfor (size_t i = 0; i < animations.size(); ++i)\n\t{\n\t\tconst Animation& animation = animations[i];\n\n\t\twriteAnimation(json_animations, views, json_accessors, accr_offset, animation, i, data, nodes, settings);\n\t}\n\n\tfor (size_t i = 0; i < data->cameras_count; ++i)\n\t{\n\t\tconst cgltf_camera& camera = data->cameras[i];\n\n\t\twriteCamera(json_cameras, camera);\n\t}\n\n\tif (data->lights_count > 0)\n\t{\n\t\tcomma(json_extensions);\n\t\tappend(json_extensions, \"\\\"KHR_lights_punctual\\\":{\\\"lights\\\":[\");\n\n\t\tfor (size_t i = 0; i < data->lights_count; ++i)\n\t\t{\n\t\t\tconst cgltf_light& light = data->lights[i];\n\n\t\t\twriteLight(json_extensions, light);\n\t\t}\n\n\t\tappend(json_extensions, \"]}\");\n\t}\n\n\tif (data->variants_count > 0)\n\t{\n\t\tcomma(json_extensions);\n\t\tappend(json_extensions, \"\\\"KHR_materials_variants\\\":{\\\"variants\\\":[\");\n\n\t\tfor (size_t i = 0; i < data->variants_count; ++i)\n\t\t{\n\t\t\tconst cgltf_material_variant& variant = data->variants[i];\n\n\t\t\tcomma(json_extensions);\n\t\t\tappend(json_extensions, \"{\\\"name\\\":\\\"\");\n\t\t\tappend(json_extensions, variant.name);\n\t\t\tappend(json_extensions, \"\\\"}\");\n\t\t}\n\n\t\tappend(json_extensions, \"]}\");\n\t}\n\n\tappend(json, \"\\\"asset\\\":{\");\n\tappend(json, \"\\\"version\\\":\\\"2.0\\\",\\\"generator\\\":\\\"gltfpack \");\n\tappend(json, getVersion());\n\tappend(json, \"\\\"\");\n\twriteExtras(json, data->asset.extras);\n\tappend(json, \"}\");\n\n\tconst ExtensionInfo extensions[] = {\n\t    {\"KHR_mesh_quantization\", settings.quantize, true},\n\t    {meshopt_ext, settings.compress, !settings.fallback},\n\t    {\"KHR_texture_transform\", (settings.quantize && !settings.tex_float && !json_textures.empty()) || ext_texture_transform, false},\n\t    {\"KHR_materials_pbrSpecularGlossiness\", ext_pbr_specular_glossiness, false},\n\t    {\"KHR_materials_clearcoat\", ext_clearcoat, false},\n\t    {\"KHR_materials_transmission\", ext_transmission, false},\n\t    {\"KHR_materials_ior\", ext_ior, false},\n\t    {\"KHR_materials_specular\", ext_specular, false},\n\t    {\"KHR_materials_sheen\", ext_sheen, false},\n\t    {\"KHR_materials_volume\", ext_volume, false},\n\t    {\"KHR_materials_emissive_strength\", ext_emissive_strength, false},\n\t    {\"KHR_materials_iridescence\", ext_iridescence, false},\n\t    {\"KHR_materials_anisotropy\", ext_anisotropy, false},\n\t    {\"KHR_materials_dispersion\", ext_dispersion, false},\n\t    {\"KHR_materials_diffuse_transmission\", ext_diffuse_transmission, false},\n\t    {\"KHR_materials_unlit\", ext_unlit, false},\n\t    {\"KHR_materials_variants\", data->variants_count > 0, false},\n\t    {\"KHR_lights_punctual\", data->lights_count > 0, false},\n\t    {\"KHR_texture_basisu\", (!json_textures.empty() && settings.texture_ktx2) || ext_texture_basisu, true},\n\t    {\"EXT_texture_webp\", (!json_textures.empty() && settings.texture_webp) || ext_texture_webp, true},\n\t    {\"EXT_mesh_gpu_instancing\", ext_instancing, true},\n\t};\n\n\tfor (size_t i = 0; i < data->extensions_required_count; ++i)\n\t{\n\t\tconst char* ext = data->extensions_required[i];\n\n\t\tif (!isExtensionSupported(extensions, sizeof(extensions) / sizeof(extensions[0]), ext) && strstr(ext, \"_meshopt_compression\") == NULL)\n\t\t\tfprintf(stderr, \"Warning: required extension %s is not supported and will be skipped\\n\", ext);\n\t}\n\n\twriteExtensions(json, extensions, sizeof(extensions) / sizeof(extensions[0]));\n\n\t// buffers[] array to be inserted by the caller\n\tsize_t bufferspec_pos = json.size();\n\n\tstd::string json_views;\n\tfinalizeBufferViews(json_views, views, bin, settings.fallback ? &fallback : NULL, fallback_size, meshopt_ext, settings.compresskhr ? (settings.compressmore ? 3 : 2) : 0);\n\n\twriteArray(json, \"bufferViews\", json_views);\n\twriteArray(json, \"accessors\", json_accessors);\n\twriteArray(json, \"samplers\", json_samplers);\n\twriteArray(json, \"images\", json_images);\n\twriteArray(json, \"textures\", json_textures);\n\twriteArray(json, \"materials\", json_materials);\n\twriteArray(json, \"meshes\", json_meshes);\n\twriteArray(json, \"skins\", json_skins);\n\twriteArray(json, \"animations\", json_animations);\n\twriteArray(json, \"nodes\", json_nodes);\n\n\tif (!json_roots.empty())\n\t{\n\t\tappend(json, \",\\\"scenes\\\":[\");\n\n\t\tfor (size_t i = 0; i < data->scenes_count; ++i)\n\t\t\twriteScene(json, data->scenes[i], json_roots[i], settings);\n\n\t\tappend(json, \"]\");\n\t}\n\n\twriteArray(json, \"cameras\", json_cameras);\n\n\tif (data->scene)\n\t{\n\t\tappend(json, \",\\\"scene\\\":\");\n\t\tappend(json, size_t(data->scene - data->scenes));\n\t}\n\n\tif (!json_extensions.empty())\n\t{\n\t\tappend(json, \",\\\"extensions\\\":{\");\n\t\tappend(json, json_extensions);\n\t\tappend(json, \"}\");\n\t}\n\n\tif (settings.verbose)\n\t{\n\t\tprintMeshStats(meshes, \"output\");\n\t\tprintSceneStats(views, meshes, node_offset, mesh_offset, material_offset, json.size(), bin.size());\n\t}\n\n\tif (settings.verbose > 1)\n\t{\n\t\tprintAttributeStats(views, BufferView::Kind_Vertex, \"vertex\");\n\t\tprintAttributeStats(views, BufferView::Kind_Index, \"index\");\n\t\tprintAttributeStats(views, BufferView::Kind_Keyframe, \"keyframe\");\n\t\tprintAttributeStats(views, BufferView::Kind_Instance, \"instance\");\n\n\t\tprintImageStats(views, TextureKind_Generic, \"generic\");\n\t\tprintImageStats(views, TextureKind_Color, \"color\");\n\t\tprintImageStats(views, TextureKind_Normal, \"normal\");\n\t\tprintImageStats(views, TextureKind_Attrib, \"attrib\");\n\t}\n\n\tif (report_path)\n\t{\n\t\tif (!printReport(report_path, views, meshes, node_offset, mesh_offset, texture_offset, material_offset, animations.size(), json.size(), bin.size()))\n\t\t{\n\t\t\tfprintf(stderr, \"Warning: cannot save report to %s\\n\", report_path);\n\t\t}\n\t}\n\n\treturn bufferspec_pos;\n}\n\nstatic void writeU32(FILE* out, uint32_t data)\n{\n\tfwrite(&data, 4, 1, out);\n}\n\nstatic const char* getBaseName(const char* path)\n{\n\tconst char* slash = strrchr(path, '/');\n\tconst char* backslash = strrchr(path, '\\\\');\n\n\tconst char* rs = slash ? slash + 1 : path;\n\tconst char* bs = backslash ? backslash + 1 : path;\n\n\treturn std::max(rs, bs);\n}\n\nstatic 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)\n{\n\tstd::string json;\n\tappend(json, \"\\\"buffers\\\":[\");\n\tappend(json, \"{\");\n\tif (bin_path)\n\t{\n\t\tappend(json, \"\\\"uri\\\":\\\"\");\n\t\tappend(json, bin_path);\n\t\tappend(json, \"\\\"\");\n\t}\n\tcomma(json);\n\tappend(json, \"\\\"byteLength\\\":\");\n\tappend(json, bin_size);\n\tappend(json, \"}\");\n\tif (fallback_ref)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"{\");\n\t\tif (fallback_path)\n\t\t{\n\t\t\tappend(json, \"\\\"uri\\\":\\\"\");\n\t\t\tappend(json, fallback_path);\n\t\t\tappend(json, \"\\\"\");\n\t\t}\n\t\tcomma(json);\n\t\tappend(json, \"\\\"byteLength\\\":\");\n\t\tappend(json, fallback_size);\n\t\tappend(json, \",\\\"extensions\\\":{\");\n\t\tappend(json, \"\\\"\");\n\t\tappend(json, meshopt_ext);\n\t\tappend(json, \"\\\":{\");\n\t\tappend(json, \"\\\"fallback\\\":true\");\n\t\tappend(json, \"}}\");\n\t\tappend(json, \"}\");\n\t}\n\tappend(json, \"]\");\n\n\treturn json;\n}\n\nint gltfpack(const char* input, const char* output, const char* report, Settings settings)\n{\n\tcgltf_data* data = NULL;\n\tstd::vector<Mesh> meshes;\n\tstd::vector<Animation> animations;\n\n\tstd::string iext = getExtension(input);\n\tstd::string oext = output ? getExtension(output) : \"\";\n\n\tif (output)\n\t{\n\t\tif (oext != \".gltf\" && oext != \".glb\")\n\t\t{\n\t\t\tfprintf(stderr, \"Error: unsupported output extension '%s' (expected .gltf or .glb)\\n\", oext.c_str());\n\t\t\treturn 4;\n\t\t}\n\t}\n\n\tif (iext == \".gltf\" || iext == \".glb\")\n\t{\n\t\tconst char* error = NULL;\n\t\tdata = parseGltf(input, meshes, animations, &error);\n\n\t\tif (error)\n\t\t{\n\t\t\tfprintf(stderr, \"Error loading %s: %s\\n\", input, error);\n\t\t\treturn 2;\n\t\t}\n\t}\n\telse if (iext == \".obj\")\n\t{\n\t\tconst char* error = NULL;\n\t\tdata = parseObj(input, meshes, &error);\n\n\t\tif (!data)\n\t\t{\n\t\t\tfprintf(stderr, \"Error loading %s: %s\\n\", input, error);\n\t\t\treturn 2;\n\t\t}\n\t}\n\telse\n\t{\n\t\tfprintf(stderr, \"Error loading %s: unknown extension (expected .gltf or .glb or .obj)\\n\", input);\n\t\treturn 2;\n\t}\n\n#ifndef WITH_BASISU\n\tif (data->images_count && settings.texture_ktx2)\n\t{\n\t\tfprintf(stderr, \"Error: gltfpack was built without BasisU support, texture compression is not available\\n\");\n#ifdef __wasi__\n\t\tfprintf(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\");\n#endif\n\t\treturn 3;\n\t}\n#endif\n\n#ifndef WITH_LIBWEBP\n\tif (data->images_count && settings.texture_webp)\n\t{\n\t\tfprintf(stderr, \"Error: gltfpack was built without WebP support, texture compression is not available\\n\");\n#ifdef __wasi__\n\t\tfprintf(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\");\n#endif\n\t\treturn 3;\n\t}\n#endif\n\n\tif (oext == \".glb\")\n\t{\n\t\tsettings.texture_embed = true;\n\t}\n\n\tif (data->images_count && !settings.texture_ref && !settings.texture_embed)\n\t{\n\t\tfor (size_t i = 0; i < data->images_count; ++i)\n\t\t{\n\t\t\tconst char* uri = data->images[i].uri;\n\t\t\tif (!uri || strncmp(uri, \"data:\", 5) == 0)\n\t\t\t\tcontinue;\n\n\t\t\tfor (size_t j = 0; j < i; ++j)\n\t\t\t{\n\t\t\t\tconst char* urj = data->images[j].uri;\n\t\t\t\tif (!urj || strncmp(urj, \"data:\", 5) == 0)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif (strcmp(uri, urj) != 0 && strcmp(getBaseName(uri), getBaseName(urj)) == 0)\n\t\t\t\t{\n\t\t\t\t\tfprintf(stderr, \"Warning: images %s and %s share the same base name and will overwrite each other\\n\", uri, urj);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconst char* meshopt_ext = settings.compresskhr ? \"KHR_meshopt_compression\" : \"EXT_meshopt_compression\";\n\n\tstd::string json, bin, fallback;\n\tsize_t fallback_size = 0;\n\n\tjson += '{';\n\tsize_t bufferspec_pos = process(data, input, output, report, meshes, animations, settings, json, bin, fallback, fallback_size, meshopt_ext);\n\tjson += '}';\n\n\tcgltf_free(data);\n\n\tif (!output)\n\t{\n\t\treturn 0;\n\t}\n\n\tif (oext == \".gltf\")\n\t{\n\t\tstd::string binpath = output;\n\t\tbinpath.replace(binpath.size() - 5, 5, \".bin\");\n\n\t\tstd::string fbpath = output;\n\t\tfbpath.replace(fbpath.size() - 5, 5, \".fallback.bin\");\n\n\t\tFILE* outjson = fopen(output, \"wb\");\n\t\tFILE* outbin = fopen(binpath.c_str(), \"wb\");\n\t\tFILE* outfb = settings.fallback ? fopen(fbpath.c_str(), \"wb\") : NULL;\n\t\tif (!outjson || !outbin || (!outfb && settings.fallback))\n\t\t{\n\t\t\tfprintf(stderr, \"Error saving %s\\n\", output);\n\t\t\treturn 4;\n\t\t}\n\n\t\tstd::string bufferspec = getBufferSpec(getBaseName(binpath.c_str()), bin.size(), settings.fallback ? getBaseName(fbpath.c_str()) : NULL, fallback_size, settings.compress, meshopt_ext);\n\t\tjson.insert(bufferspec_pos, \",\" + bufferspec);\n\n\t\tfwrite(json.c_str(), json.size(), 1, outjson);\n\t\tfwrite(bin.c_str(), bin.size(), 1, outbin);\n\n\t\tif (settings.fallback)\n\t\t\tfwrite(fallback.c_str(), fallback.size(), 1, outfb);\n\n\t\tint rc = 0;\n\t\trc |= fclose(outjson);\n\t\trc |= fclose(outbin);\n\t\tif (outfb)\n\t\t\trc |= fclose(outfb);\n\n\t\tif (rc)\n\t\t{\n\t\t\tfprintf(stderr, \"Error saving %s\\n\", output);\n\t\t\treturn 4;\n\t\t}\n\t}\n\telse if (oext == \".glb\")\n\t{\n\t\tstd::string fbpath = output;\n\t\tfbpath.replace(fbpath.size() - 4, 4, \".fallback.bin\");\n\n\t\tFILE* out = fopen(output, \"wb\");\n\t\tFILE* outfb = settings.fallback ? fopen(fbpath.c_str(), \"wb\") : NULL;\n\t\tif (!out || (!outfb && settings.fallback))\n\t\t{\n\t\t\tfprintf(stderr, \"Error saving %s\\n\", output);\n\t\t\treturn 4;\n\t\t}\n\n\t\tstd::string bufferspec = getBufferSpec(NULL, bin.size(), settings.fallback ? getBaseName(fbpath.c_str()) : NULL, fallback_size, settings.compress, meshopt_ext);\n\t\tjson.insert(bufferspec_pos, \",\" + bufferspec);\n\n\t\twhile (json.size() % 4)\n\t\t\tjson.push_back(' ');\n\n\t\twhile (bin.size() % 4)\n\t\t\tbin.push_back('\\0');\n\n\t\twriteU32(out, 0x46546C67);\n\t\twriteU32(out, 2);\n\t\twriteU32(out, uint32_t(12 + 8 + json.size() + 8 + bin.size()));\n\n\t\twriteU32(out, uint32_t(json.size()));\n\t\twriteU32(out, 0x4E4F534A);\n\t\tfwrite(json.c_str(), json.size(), 1, out);\n\n\t\twriteU32(out, uint32_t(bin.size()));\n\t\twriteU32(out, 0x004E4942);\n\t\tfwrite(bin.c_str(), bin.size(), 1, out);\n\n\t\tif (settings.fallback)\n\t\t\tfwrite(fallback.c_str(), fallback.size(), 1, outfb);\n\n\t\tint rc = 0;\n\t\trc |= fclose(out);\n\t\tif (outfb)\n\t\t\trc |= fclose(outfb);\n\n\t\tif (rc)\n\t\t{\n\t\t\tfprintf(stderr, \"Error saving %s\\n\", output);\n\t\t\treturn 4;\n\t\t}\n\t}\n\telse\n\t{\n\t\tfprintf(stderr, \"Error saving %s: unknown extension (expected .gltf or .glb)\\n\", output);\n\t\treturn 4;\n\t}\n\n\treturn 0;\n}\n\nSettings defaults()\n{\n\tSettings settings = {};\n\tsettings.quantize = true;\n\tsettings.pos_bits = 14;\n\tsettings.tex_bits = 12;\n\tsettings.nrm_bits = 8;\n\tsettings.col_bits = 8;\n\tsettings.trn_bits = 16;\n\tsettings.rot_bits = 12;\n\tsettings.scl_bits = 16;\n\tsettings.anim_freq = 30;\n\tsettings.mesh_dedup = true;\n\tsettings.simplify_ratio = 1.f;\n\tsettings.simplify_error = 1e-2f;\n\tsettings.simplify_attributes = true;\n\tsettings.simplify_scaled = true;\n\n\tfor (int kind = 0; kind < TextureKind__Count; ++kind)\n\t{\n\t\tsettings.texture_mode[kind] = TextureMode_Raw;\n\t\tsettings.texture_scale[kind] = 1.f;\n\t\tsettings.texture_quality[kind] = 8;\n\t}\n\n\treturn settings;\n}\n\ntemplate <typename T>\nT clamp(T v, T min, T max)\n{\n\treturn v < min ? min : (v > max ? max : v);\n}\n\nunsigned int textureMask(const char* arg)\n{\n\tunsigned int result = 0;\n\n\twhile (arg)\n\t{\n\t\tconst char* comma = strchr(arg, ',');\n\t\tsize_t seg = comma ? comma - arg - 1 : strlen(arg);\n\n\t\tif (strncmp(arg, \"color\", seg) == 0)\n\t\t\tresult |= 1 << TextureKind_Color;\n\t\telse if (strncmp(arg, \"normal\", seg) == 0)\n\t\t\tresult |= 1 << TextureKind_Normal;\n\t\telse if (strncmp(arg, \"attrib\", seg) == 0)\n\t\t\tresult |= 1 << TextureKind_Attrib;\n\t\telse\n\t\t\tfprintf(stderr, \"Warning: unrecognized texture class %.*s\\n\", int(seg), arg);\n\n\t\targ = comma ? comma + 1 : NULL;\n\t}\n\n\treturn result;\n}\n\ntemplate <typename T>\nvoid applySetting(T (&data)[TextureKind__Count], T value, unsigned int mask = ~0u)\n{\n\tfor (int kind = 0; kind < TextureKind__Count; ++kind)\n\t\tif (mask & (1 << kind))\n\t\t\tdata[kind] = value;\n}\n\n#ifndef GLTFFUZZ\nint main(int argc, char** argv)\n{\n#ifndef __wasi__\n\tsetlocale(LC_ALL, \"C\"); // disable locale specific convention for number parsing/printing\n#endif\n\n\tmeshopt_encodeVertexVersion(0);\n\tmeshopt_encodeIndexVersion(1);\n\n\tSettings settings = defaults();\n\n\tconst char* input = NULL;\n\tconst char* output = NULL;\n\tconst char* report = NULL;\n\tbool help = false;\n\tbool test = false;\n\tbool require_texc = false;\n\n\tstd::vector<const char*> testinputs;\n\n\tfor (int i = 1; i < argc; ++i)\n\t{\n\t\tconst char* arg = argv[i];\n\n\t\tif (strcmp(arg, \"-vp\") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))\n\t\t{\n\t\t\tsettings.pos_bits = clamp(atoi(argv[++i]), 1, 16);\n\t\t}\n\t\telse if (strcmp(arg, \"-vt\") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))\n\t\t{\n\t\t\tsettings.tex_bits = clamp(atoi(argv[++i]), 1, 16);\n\t\t}\n\t\telse if (strcmp(arg, \"-vn\") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))\n\t\t{\n\t\t\tsettings.nrm_bits = clamp(atoi(argv[++i]), 1, 16);\n\t\t}\n\t\telse if (strcmp(arg, \"-vc\") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))\n\t\t{\n\t\t\tsettings.col_bits = clamp(atoi(argv[++i]), 1, 16);\n\t\t}\n\t\telse if (strcmp(arg, \"-vpi\") == 0)\n\t\t{\n\t\t\tsettings.pos_float = false;\n\t\t\tsettings.pos_normalized = false;\n\t\t}\n\t\telse if (strcmp(arg, \"-vpn\") == 0)\n\t\t{\n\t\t\tsettings.pos_float = false;\n\t\t\tsettings.pos_normalized = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-vpf\") == 0)\n\t\t{\n\t\t\tsettings.pos_float = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-vtf\") == 0)\n\t\t{\n\t\t\tsettings.tex_float = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-vnf\") == 0)\n\t\t{\n\t\t\tsettings.nrm_float = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-vi\") == 0)\n\t\t{\n\t\t\tsettings.mesh_interleaved = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-at\") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))\n\t\t{\n\t\t\tsettings.trn_bits = clamp(atoi(argv[++i]), 1, 24);\n\t\t}\n\t\telse if (strcmp(arg, \"-ar\") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))\n\t\t{\n\t\t\tsettings.rot_bits = clamp(atoi(argv[++i]), 4, 16);\n\t\t}\n\t\telse if (strcmp(arg, \"-as\") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))\n\t\t{\n\t\t\tsettings.scl_bits = clamp(atoi(argv[++i]), 1, 24);\n\t\t}\n\t\telse if (strcmp(arg, \"-af\") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))\n\t\t{\n\t\t\tsettings.anim_freq = clamp(atoi(argv[++i]), 0, 100);\n\t\t}\n\t\telse if (strcmp(arg, \"-ac\") == 0)\n\t\t{\n\t\t\tsettings.anim_const = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-kn\") == 0)\n\t\t{\n\t\t\tsettings.keep_nodes = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-km\") == 0)\n\t\t{\n\t\t\tsettings.keep_materials = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-ke\") == 0)\n\t\t{\n\t\t\tsettings.keep_extras = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-kv\") == 0)\n\t\t{\n\t\t\tsettings.keep_attributes = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-mdd\") == 0)\n\t\t{\n\t\t\tfprintf(stderr, \"Warning: option -mdd disables mesh deduplication and is temporary; avoid production usage\\n\");\n\t\t\tsettings.mesh_dedup = false;\n\t\t}\n\t\telse if (strcmp(arg, \"-mm\") == 0)\n\t\t{\n\t\t\tsettings.mesh_merge = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-mi\") == 0)\n\t\t{\n\t\t\tsettings.mesh_instancing = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-si\") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))\n\t\t{\n\t\t\tsettings.simplify_ratio = clamp(float(atof(argv[++i])), 0.f, 1.f);\n\t\t}\n\t\telse if (strcmp(arg, \"-se\") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))\n\t\t{\n\t\t\tsettings.simplify_error = clamp(float(atof(argv[++i])), 0.f, 1.f);\n\t\t}\n\t\telse if (strcmp(arg, \"-sa\") == 0)\n\t\t{\n\t\t\tsettings.simplify_aggressive = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-slb\") == 0)\n\t\t{\n\t\t\tsettings.simplify_lock_borders = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-sv\") == 0)\n\t\t{\n\t\t\tfprintf(stderr, \"Warning: attribute aware simplification is enabled by default; option -sv is only provided for compatibility and may be removed in the future\\n\");\n\t\t}\n\t\telse if (strcmp(arg, \"-svd\") == 0)\n\t\t{\n\t\t\tfprintf(stderr, \"Warning: option -svd disables attribute aware simplification and is temporary; avoid production usage\\n\");\n\t\t\tsettings.simplify_attributes = false;\n\t\t}\n\t\telse if (strcmp(arg, \"-ssd\") == 0)\n\t\t{\n\t\t\tfprintf(stderr, \"Warning: option -ssd disables scaled simplification error and is temporary; avoid production usage\\n\");\n\t\t\tsettings.simplify_scaled = false;\n\t\t}\n\t\telse if (strcmp(arg, \"-sp\") == 0)\n\t\t{\n\t\t\tsettings.simplify_permissive = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-tu\") == 0)\n\t\t{\n\t\t\tsettings.texture_ktx2 = true;\n\n\t\t\tunsigned int mask = ~0u;\n\t\t\tif (i + 1 < argc && isalpha(argv[i + 1][0]))\n\t\t\t\tmask = textureMask(argv[++i]);\n\n\t\t\tapplySetting(settings.texture_mode, TextureMode_UASTC, mask);\n\t\t}\n\t\telse if (strcmp(arg, \"-tc\") == 0)\n\t\t{\n\t\t\tsettings.texture_ktx2 = true;\n\n\t\t\tunsigned int mask = ~0u;\n\t\t\tif (i + 1 < argc && isalpha(argv[i + 1][0]))\n\t\t\t\tmask = textureMask(argv[++i]);\n\n\t\t\tapplySetting(settings.texture_mode, TextureMode_ETC1S, mask);\n\t\t}\n\t\telse if (strcmp(arg, \"-tw\") == 0)\n\t\t{\n\t\t\tsettings.texture_webp = true;\n\n\t\t\tunsigned int mask = ~0u;\n\t\t\tif (i + 1 < argc && isalpha(argv[i + 1][0]))\n\t\t\t\tmask = textureMask(argv[++i]);\n\n\t\t\tapplySetting(settings.texture_mode, TextureMode_WebP, mask);\n\t\t}\n\t\telse if (strcmp(arg, \"-tq\") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))\n\t\t{\n\t\t\trequire_texc = true;\n\n\t\t\tint quality = clamp(atoi(argv[++i]), 1, 10);\n\t\t\tapplySetting(settings.texture_quality, quality);\n\t\t}\n\t\telse if (strcmp(arg, \"-tq\") == 0 && i + 2 < argc && isalpha(argv[i + 1][0]) && isdigit(argv[i + 2][0]))\n\t\t{\n\t\t\trequire_texc = true;\n\n\t\t\tunsigned int mask = textureMask(argv[++i]);\n\t\t\tint quality = clamp(atoi(argv[++i]), 1, 10);\n\t\t\tapplySetting(settings.texture_quality, quality, mask);\n\t\t}\n\t\telse if (strcmp(arg, \"-ts\") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))\n\t\t{\n\t\t\trequire_texc = true;\n\n\t\t\tfloat scale = clamp(float(atof(argv[++i])), 0.f, 1.f);\n\t\t\tapplySetting(settings.texture_scale, scale);\n\t\t}\n\t\telse if (strcmp(arg, \"-ts\") == 0 && i + 2 < argc && isalpha(argv[i + 1][0]) && isdigit(argv[i + 2][0]))\n\t\t{\n\t\t\trequire_texc = true;\n\n\t\t\tunsigned int mask = textureMask(argv[++i]);\n\t\t\tfloat scale = clamp(float(atof(argv[++i])), 0.f, 1.f);\n\t\t\tapplySetting(settings.texture_scale, scale, mask);\n\t\t}\n\t\telse if (strcmp(arg, \"-tl\") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))\n\t\t{\n\t\t\trequire_texc = true;\n\n\t\t\tint limit = atoi(argv[++i]);\n\t\t\tapplySetting(settings.texture_limit, limit);\n\t\t}\n\t\telse if (strcmp(arg, \"-tl\") == 0 && i + 2 < argc && isalpha(argv[i + 1][0]) && isdigit(argv[i + 2][0]))\n\t\t{\n\t\t\trequire_texc = true;\n\n\t\t\tunsigned int mask = textureMask(argv[++i]);\n\t\t\tint limit = atoi(argv[++i]);\n\t\t\tapplySetting(settings.texture_limit, limit, mask);\n\t\t}\n\t\telse if (strcmp(arg, \"-tp\") == 0)\n\t\t{\n\t\t\trequire_texc = true;\n\n\t\t\tsettings.texture_pow2 = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-tfy\") == 0)\n\t\t{\n\t\t\trequire_texc = true;\n\n\t\t\tsettings.texture_flipy = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-tr\") == 0)\n\t\t{\n\t\t\tsettings.texture_ref = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-tj\") == 0 && i + 1 < argc && isdigit(argv[i + 1][0]))\n\t\t{\n\t\t\tsettings.texture_jobs = clamp(atoi(argv[++i]), 0, 128);\n\t\t}\n\t\telse if (strcmp(arg, \"-noq\") == 0)\n\t\t{\n\t\t\t// TODO: Warn if -noq is used and suggest -vpf instead; use -noqq to silence\n\t\t\tsettings.quantize = false;\n\t\t}\n\t\telse if (strcmp(arg, \"-i\") == 0 && i + 1 < argc && !input)\n\t\t{\n\t\t\tinput = argv[++i];\n\t\t}\n\t\telse if (strcmp(arg, \"-o\") == 0 && i + 1 < argc && !output)\n\t\t{\n\t\t\toutput = argv[++i];\n\t\t}\n\t\telse if (strcmp(arg, \"-r\") == 0 && i + 1 < argc && !report)\n\t\t{\n\t\t\treport = argv[++i];\n\t\t}\n\t\telse if (strcmp(arg, \"-c\") == 0)\n\t\t{\n\t\t\tsettings.compress = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-cc\") == 0)\n\t\t{\n\t\t\tsettings.compress = true;\n\t\t\tsettings.compressmore = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-cf\") == 0)\n\t\t{\n\t\t\tsettings.compress = true;\n\t\t\tsettings.fallback = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-cz\") == 0)\n\t\t{\n\t\t\tsettings.compress = true;\n\t\t\tsettings.compressmore = true;\n\t\t\tsettings.compresskhr = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-ce\") == 0 && i + 1 < argc && (strcmp(argv[i + 1], \"khr\") == 0 || strcmp(argv[i + 1], \"ext\") == 0))\n\t\t{\n\t\t\tsettings.compress = true;\n\t\t\tsettings.compresskhr = strcmp(argv[++i], \"khr\") == 0;\n\t\t}\n\t\telse if (strcmp(arg, \"-v\") == 0)\n\t\t{\n\t\t\tsettings.verbose = 1;\n\t\t}\n\t\telse if (strcmp(arg, \"-vv\") == 0)\n\t\t{\n\t\t\tsettings.verbose = 2;\n\t\t}\n\t\telse if (strcmp(arg, \"-h\") == 0)\n\t\t{\n\t\t\thelp = true;\n\t\t}\n\t\telse if (strcmp(arg, \"-test\") == 0)\n\t\t{\n\t\t\ttest = true;\n\t\t}\n\t\telse if (arg[0] == '-')\n\t\t{\n\t\t\tfprintf(stderr, \"Unrecognized option %s\\n\", arg);\n\t\t\treturn 1;\n\t\t}\n\t\telse if (test)\n\t\t{\n\t\t\ttestinputs.push_back(arg);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfprintf(stderr, \"Expected option, got %s instead\\n\", arg);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\t// shortcut for gltfpack -v\n\tif (settings.verbose && argc == 2)\n\t{\n\t\tprintf(\"gltfpack %s\\n\", getVersion().c_str());\n\t\treturn 0;\n\t}\n\n\tif (test)\n\t{\n\t\tfor (size_t i = 0; i < testinputs.size(); ++i)\n\t\t{\n\t\t\tconst char* path = testinputs[i];\n\n\t\t\tprintf(\"%s\\n\", path);\n\t\t\tgltfpack(path, NULL, NULL, settings);\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\tif (!input || !output || help)\n\t{\n\t\tfprintf(stderr, \"gltfpack %s\\n\", getVersion().c_str());\n\t\tfprintf(stderr, \"Usage: gltfpack [options] -i input -o output\\n\");\n\n\t\tif (help)\n\t\t{\n\t\t\tfprintf(stderr, \"\\nBasics:\\n\");\n\t\t\tfprintf(stderr, \"\\t-i file: input file to process, .obj/.gltf/.glb\\n\");\n\t\t\tfprintf(stderr, \"\\t-o file: output file path, .gltf/.glb\\n\");\n\t\t\tfprintf(stderr, \"\\t-c: produce compressed gltf/glb files (-cc/-cz for higher compression ratio)\\n\");\n\t\t\tfprintf(stderr, \"\\nTextures:\\n\");\n\t\t\tfprintf(stderr, \"\\t-tc: convert all textures to KTX2 with BasisU supercompression\\n\");\n\t\t\tfprintf(stderr, \"\\t-tu: use UASTC when encoding textures (much higher quality and much larger size)\\n\");\n\t\t\tfprintf(stderr, \"\\t-tw: convert all textures to WebP\\n\");\n\t\t\tfprintf(stderr, \"\\t-tq N: set texture encoding quality (default: 8; N should be between 1 and 10)\\n\");\n\t\t\tfprintf(stderr, \"\\t-ts R: scale texture dimensions by the ratio R (default: 1; R should be between 0 and 1)\\n\");\n\t\t\tfprintf(stderr, \"\\t-tl N: limit texture dimensions to N pixels (default: 0 = no limit)\\n\");\n\t\t\tfprintf(stderr, \"\\t-tp: resize textures to nearest power of 2 to conform to WebGL1 restrictions\\n\");\n\t\t\tfprintf(stderr, \"\\t-tfy: flip textures along Y axis during BasisU supercompression\\n\");\n\t\t\tfprintf(stderr, \"\\t-tj N: use N threads when compressing textures\\n\");\n\t\t\tfprintf(stderr, \"\\t-tr: keep referring to original texture paths instead of copying/embedding images\\n\");\n\t\t\tfprintf(stderr, \"\\tTexture classes:\\n\");\n\t\t\tfprintf(stderr, \"\\t-tc C: use ETC1S when encoding textures of class C\\n\");\n\t\t\tfprintf(stderr, \"\\t-tu C: use UASTC when encoding textures of class C\\n\");\n\t\t\tfprintf(stderr, \"\\t-tw C: use WebP when encoding textures of class C\\n\");\n\t\t\tfprintf(stderr, \"\\t-tq C N: set texture encoding quality for class C\\n\");\n\t\t\tfprintf(stderr, \"\\t-ts C R: scale texture dimensions for class C\\n\");\n\t\t\tfprintf(stderr, \"\\t-tl C N: limit texture dimensions for class C\\n\");\n\t\t\tfprintf(stderr, \"\\t... where C is a comma-separated list (no spaces) with valid values color,normal,attrib\\n\");\n\t\t\tfprintf(stderr, \"\\nSimplification:\\n\");\n\t\t\tfprintf(stderr, \"\\t-si R: simplify meshes targeting triangle/point count ratio R (default: 1; R should be between 0 and 1)\\n\");\n\t\t\tfprintf(stderr, \"\\t-se E: limit simplification error to E (default: 0.01 = 1%% deviation; E should be between 0 and 1)\\n\");\n\t\t\tfprintf(stderr, \"\\t-sp: use permissive simplification mode to allow simplification across attribute discontinuities\\n\");\n\t\t\tfprintf(stderr, \"\\t-sa: aggressively simplify to the target ratio disregarding quality\\n\");\n\t\t\tfprintf(stderr, \"\\t-slb: lock border vertices during simplification to avoid gaps on connected meshes\\n\");\n\t\t\tfprintf(stderr, \"\\nVertex precision:\\n\");\n\t\t\tfprintf(stderr, \"\\t-vp N: use N-bit quantization for positions (default: 14; N should be between 1 and 16)\\n\");\n\t\t\tfprintf(stderr, \"\\t-vt N: use N-bit quantization for texture coordinates (default: 12; N should be between 1 and 16)\\n\");\n\t\t\tfprintf(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\");\n\t\t\tfprintf(stderr, \"\\t-vc N: use N-bit quantization for colors (default: 8; N should be between 1 and 16)\\n\");\n\t\t\tfprintf(stderr, \"\\nVertex positions:\\n\");\n\t\t\tfprintf(stderr, \"\\t-vpi: use integer attributes for positions (default)\\n\");\n\t\t\tfprintf(stderr, \"\\t-vpn: use normalized attributes for positions\\n\");\n\t\t\tfprintf(stderr, \"\\t-vpf: use floating point attributes for positions\\n\");\n\t\t\tfprintf(stderr, \"\\nVertex attributes:\\n\");\n\t\t\tfprintf(stderr, \"\\t-vtf: use floating point attributes for texture coordinates\\n\");\n\t\t\tfprintf(stderr, \"\\t-vnf: use floating point attributes for normals\\n\");\n\t\t\tfprintf(stderr, \"\\t-vi: use interleaved vertex attributes (reduces compression efficiency)\\n\");\n\t\t\tfprintf(stderr, \"\\t-kv: keep source vertex attributes even if they aren't used\\n\");\n\t\t\tfprintf(stderr, \"\\nAnimations:\\n\");\n\t\t\tfprintf(stderr, \"\\t-at N: use N-bit quantization for translations (default: 16; N should be between 1 and 24)\\n\");\n\t\t\tfprintf(stderr, \"\\t-ar N: use N-bit quantization for rotations (default: 12; N should be between 4 and 16)\\n\");\n\t\t\tfprintf(stderr, \"\\t-as N: use N-bit quantization for scale (default: 16; N should be between 1 and 24)\\n\");\n\t\t\tfprintf(stderr, \"\\t-af N: resample animations at N Hz (default: 30; use 0 to disable)\\n\");\n\t\t\tfprintf(stderr, \"\\t-ac: keep constant animation tracks even if they don't modify the node transform\\n\");\n\t\t\tfprintf(stderr, \"\\nScene:\\n\");\n\t\t\tfprintf(stderr, \"\\t-kn: keep named nodes and meshes attached to named nodes so that named nodes can be transformed externally\\n\");\n\t\t\tfprintf(stderr, \"\\t-km: keep named materials and disable named material merging\\n\");\n\t\t\tfprintf(stderr, \"\\t-ke: keep extras data\\n\");\n\t\t\tfprintf(stderr, \"\\t-mm: merge instances of the same mesh together when possible\\n\");\n\t\t\tfprintf(stderr, \"\\t-mi: use EXT_mesh_gpu_instancing when serializing multiple mesh instances\\n\");\n\t\t\tfprintf(stderr, \"\\nMiscellaneous:\\n\");\n\t\t\tfprintf(stderr, \"\\t-cf: produce compressed gltf/glb files with fallback for loaders that don't support compression\\n\");\n\t\t\tfprintf(stderr, \"\\t-ce ext|khr: use EXT or KHR version of meshopt compression extension for compression\\n\");\n\t\t\tfprintf(stderr, \"\\t-noq: disable quantization; produces much larger glTF files with no extensions\\n\");\n\t\t\tfprintf(stderr, \"\\t-v: verbose output (when used with other options)\\n\");\n\t\t\tfprintf(stderr, \"\\t-v: print version (when used without other options)\\n\");\n\t\t\tfprintf(stderr, \"\\t-r file: output a JSON report to file\\n\");\n\t\t\tfprintf(stderr, \"\\t-h: display this help and exit\\n\");\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfprintf(stderr, \"\\nBasics:\\n\");\n\t\t\tfprintf(stderr, \"\\t-i file: input file to process, .obj/.gltf/.glb\\n\");\n\t\t\tfprintf(stderr, \"\\t-o file: output file path, .gltf/.glb\\n\");\n\t\t\tfprintf(stderr, \"\\t-c: produce compressed gltf/glb files (-cc/-cz for higher compression ratio)\\n\");\n\t\t\tfprintf(stderr, \"\\t-tc: convert all textures to KTX2 with BasisU supercompression\\n\");\n\t\t\tfprintf(stderr, \"\\t-tw: convert all textures to WebP\\n\");\n\t\t\tfprintf(stderr, \"\\t-si R: simplify meshes targeting triangle/point count ratio R (between 0 and 1)\\n\");\n\t\t\tfprintf(stderr, \"\\nRun gltfpack -h to display a full list of options\\n\");\n\t\t}\n\n\t\treturn 1;\n\t}\n\n\tif (require_texc && !settings.texture_ktx2 && !settings.texture_webp)\n\t{\n\t\tfprintf(stderr, \"Texture processing is only supported when texture compression is enabled via -tc/-tu/-tw\\n\");\n\t\treturn 1;\n\t}\n\n\tif (settings.texture_ref && (settings.texture_ktx2 || settings.texture_webp))\n\t{\n\t\tfprintf(stderr, \"Option -tr currently can not be used together with texture compression\\n\");\n\t\treturn 1;\n\t}\n\n\tif (settings.fallback && settings.compressmore)\n\t{\n\t\tfprintf(stderr, \"Option -cf can not be used together with -cc\\n\");\n\t\treturn 1;\n\t}\n\n\tif (settings.fallback && (settings.pos_float || settings.tex_float || settings.nrm_float))\n\t{\n\t\tfprintf(stderr, \"Option -cf can not be used together with -vpf, -vtf or -vnf\\n\");\n\t\treturn 1;\n\t}\n\n\tif (settings.keep_nodes && (settings.mesh_merge || settings.mesh_instancing))\n\t\tfprintf(stderr, \"Warning: option -kn disables mesh merge (-mm) and mesh instancing (-mi) optimizations\\n\");\n\n\treturn gltfpack(input, output, report, settings);\n}\n#endif\n\n#ifdef __wasi__\nextern \"C\" int pack(int argc, char** argv)\n{\n\tchdir(\"/gltfpack-$pwd\");\n\n\tint result = main(argc, argv);\n\tfflush(NULL);\n\treturn result;\n}\n#endif\n\n#ifdef GLTFFUZZ\nextern \"C\" int LLVMFuzzerTestOneInput(const uint8_t* buffer, size_t size)\n{\n\tSettings settings = defaults();\n\n\tsettings.texture_embed = true;\n\n\tstd::vector<Mesh> meshes;\n\tstd::vector<Animation> animations;\n\n\tconst char* error = NULL;\n\tcgltf_data* data = parseGlb(buffer, size, meshes, animations, &error);\n\n\t// this is a difficult tradeoff\n\t// returning 0 on files that fail to parse means that fuzzing is more incremental: files with errors are put into the corpus,\n\t// and the subsequent mutations may lead to discovering more interesting inputs, including valid ones that are difficult to find otherwise.\n\t// however, this leads to most of the corpus being invalid, and we very rarely get useful coverage for actual gltfpack processing.\n\t// for now we just focus on valid files, as we expect cgltf parser itself to be bulletproof as it's fuzzed separately.\n\tif (error)\n\t\treturn -1;\n\n\tstd::string json, bin, fallback;\n\tsize_t fallback_size = 0;\n\tprocess(data, NULL, NULL, NULL, meshes, animations, settings, json, bin, fallback, fallback_size, NULL);\n\n\tcgltf_free(data);\n\n\treturn 0;\n}\n#endif\n"
  },
  {
    "path": "gltf/gltfpack.h",
    "content": "/**\n * gltfpack - version 1.0\n *\n * Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\n * Report bugs and download new versions at https://github.com/zeux/meshoptimizer\n *\n * This application is distributed under the MIT License. See notice at the end of this file.\n */\n#pragma once\n\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#ifndef _CRT_NONSTDC_NO_WARNINGS\n#define _CRT_NONSTDC_NO_WARNINGS\n#endif\n\n#include \"../extern/cgltf.h\"\n\n#include <assert.h>\n\n#include <string>\n#include <vector>\n\nstruct Attr\n{\n\tfloat f[4];\n};\n\nstruct Stream\n{\n\tcgltf_attribute_type type;\n\tint index;\n\tint target; // 0 = base mesh, 1+ = morph target\n\n\tconst char* custom_name; // only valid for cgltf_attribute_type_custom\n\n\tstd::vector<Attr> data;\n};\n\nstruct Instance\n{\n\tfloat transform[16];\n\tfloat color[4];\n};\n\nstruct Mesh\n{\n\tint scene;\n\tstd::vector<cgltf_node*> nodes;\n\tstd::vector<Instance> instances;\n\n\tcgltf_material* material;\n\tcgltf_skin* skin;\n\n\tcgltf_extras extras;\n\n\tcgltf_primitive_type type;\n\n\tstd::vector<Stream> streams;\n\tstd::vector<unsigned int> indices;\n\n\tbool geometry_duplicate;\n\tuint64_t geometry_hash[2];\n\n\tsize_t targets;\n\tstd::vector<float> target_weights;\n\tstd::vector<const char*> target_names;\n\n\tstd::vector<cgltf_material_mapping> variants;\n\n\tfloat quality;\n};\n\nstruct Track\n{\n\tcgltf_node* node;\n\tcgltf_animation_path_type path;\n\n\tbool constant;\n\tbool dummy;\n\n\tsize_t components; // 1 unless path is cgltf_animation_path_type_weights\n\n\tcgltf_interpolation_type interpolation;\n\n\tstd::vector<float> time; // empty for resampled or constant animations\n\tstd::vector<Attr> data;\n};\n\nstruct Animation\n{\n\tconst char* name;\n\n\tfloat start;\n\tint frames;\n\n\tstd::vector<Track> tracks;\n};\n\nenum TextureKind\n{\n\tTextureKind_Generic,\n\tTextureKind_Color,\n\tTextureKind_Normal,\n\tTextureKind_Attrib,\n\n\tTextureKind__Count\n};\n\nenum TextureMode\n{\n\tTextureMode_Raw,\n\tTextureMode_ETC1S,\n\tTextureMode_UASTC,\n\tTextureMode_WebP,\n};\n\nstruct Settings\n{\n\tint pos_bits;\n\tint tex_bits;\n\tint nrm_bits;\n\tint col_bits;\n\n\tbool pos_normalized;\n\tbool pos_float;\n\tbool tex_float;\n\tbool nrm_float;\n\n\tint trn_bits;\n\tint rot_bits;\n\tint scl_bits;\n\n\tint anim_freq;\n\tbool anim_const;\n\n\tbool keep_nodes;\n\tbool keep_materials;\n\tbool keep_extras;\n\tbool keep_attributes;\n\n\tbool mesh_dedup;\n\tbool mesh_merge;\n\tbool mesh_instancing;\n\tbool mesh_interleaved;\n\n\tfloat simplify_ratio;\n\tfloat simplify_error;\n\tbool simplify_aggressive;\n\tbool simplify_lock_borders;\n\tbool simplify_attributes;\n\tbool simplify_scaled;\n\tbool simplify_permissive;\n\n\tbool texture_ktx2;\n\tbool texture_webp;\n\tbool texture_embed;\n\tbool texture_ref;\n\n\tbool texture_pow2;\n\tbool texture_flipy;\n\tfloat texture_scale[TextureKind__Count];\n\tint texture_limit[TextureKind__Count];\n\n\tTextureMode texture_mode[TextureKind__Count];\n\tint texture_quality[TextureKind__Count];\n\n\tint texture_jobs;\n\n\tbool quantize;\n\n\tbool compress;\n\tbool compressmore;\n\tbool compresskhr;\n\tbool fallback;\n\n\tint verbose;\n};\n\nstruct QuantizationPosition\n{\n\tfloat offset[3];\n\tfloat scale;\n\tint bits;\n\tbool normalized;\n\n\tfloat node_scale; // computed from scale/bits/normalized\n};\n\nstruct QuantizationTexture\n{\n\tfloat offset[2];\n\tfloat scale[2];\n\tint bits;\n\tbool normalized;\n};\n\nstruct StreamFormat\n{\n\tenum Filter\n\t{\n\t\tFilter_None,\n\t\tFilter_Oct,\n\t\tFilter_Quat,\n\t\tFilter_Exp,\n\t\tFilter_Color,\n\t};\n\n\tcgltf_type type;\n\tcgltf_component_type component_type;\n\tbool normalized;\n\tsize_t stride;\n\tFilter filter;\n};\n\nstruct NodeInfo\n{\n\tint scene;\n\n\tbool keep;\n\tbool animated;\n\n\tunsigned int animated_path_mask;\n\n\tint remap;\n\n\tstd::vector<size_t> mesh_nodes;\n\n\tbool has_mesh;\n\tsize_t mesh_index;\n\tcgltf_skin* mesh_skin;\n};\n\nstruct MaterialInfo\n{\n\tbool keep;\n\n\tbool uses_texture_transform;\n\tbool needs_tangents;\n\tbool unlit;\n\tbool mesh_alpha;\n\n\tunsigned int texture_set_mask;\n\n\tint remap;\n};\n\nstruct TextureInfo\n{\n\tbool keep;\n\n\tint remap;\n};\n\nstruct ImageInfo\n{\n\tTextureKind kind;\n\tbool normal_map;\n\tbool srgb;\n\n\tint channels;\n};\n\nstruct ExtensionInfo\n{\n\tconst char* name;\n\n\tbool used;\n\tbool required;\n};\n\nstruct BufferView\n{\n\tenum Kind\n\t{\n\t\tKind_Vertex,\n\t\tKind_Index,\n\t\tKind_Skin,\n\t\tKind_Time,\n\t\tKind_Keyframe,\n\t\tKind_Instance,\n\t\tKind_Image,\n\t\tKind_Count\n\t};\n\n\tenum Compression\n\t{\n\t\tCompression_None = -1,\n\t\tCompression_Attribute,\n\t\tCompression_Index,\n\t\tCompression_IndexSequence,\n\t};\n\n\tKind kind;\n\tStreamFormat::Filter filter;\n\tCompression compression;\n\tsize_t stride;\n\tint variant;\n\n\tstd::string data;\n\n\tsize_t bytes;\n};\n\nstd::string getTempPrefix();\n\nstd::string getFullPath(const char* path, const char* base_path);\nstd::string getFileName(const char* path);\nstd::string getExtension(const char* path);\n\nbool readFile(const char* path, std::string& data);\nbool writeFile(const char* path, const std::string& data);\nvoid removeFile(const char* path);\n\ncgltf_data* parseObj(const char* path, std::vector<Mesh>& meshes, const char** error);\ncgltf_data* parseGltf(const char* path, std::vector<Mesh>& meshes, std::vector<Animation>& animations, const char** error);\n\ncgltf_data* parseGlb(const void* buffer, size_t size, std::vector<Mesh>& meshes, std::vector<Animation>& animations, const char** error);\n\nbool areExtrasEqual(const cgltf_extras& lhs, const cgltf_extras& rhs);\n\nvoid processAnimation(Animation& animation, const Settings& settings);\nvoid processMesh(Mesh& mesh, const Settings& settings);\n\nbool compareMeshTargets(const Mesh& lhs, const Mesh& rhs);\nbool compareMeshVariants(const Mesh& lhs, const Mesh& rhs);\nbool compareMeshNodes(const Mesh& lhs, const Mesh& rhs);\n\nvoid hashMesh(Mesh& mesh);\nvoid dedupMeshes(std::vector<Mesh>& meshes, const Settings& settings);\nvoid mergeMeshInstances(Mesh& mesh);\nvoid mergeMeshes(std::vector<Mesh>& meshes, const Settings& settings);\nvoid filterEmptyMeshes(std::vector<Mesh>& meshes);\nvoid filterStreams(Mesh& mesh, const MaterialInfo& mi);\n\nvoid mergeMeshMaterials(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settings);\nvoid markNeededMaterials(cgltf_data* data, std::vector<MaterialInfo>& materials, const std::vector<Mesh>& meshes, const Settings& settings);\n\nvoid mergeTextures(cgltf_data* data, std::vector<TextureInfo>& textures);\n\nbool hasValidTransform(const cgltf_texture_view& view);\n\nvoid analyzeMaterials(cgltf_data* data, std::vector<MaterialInfo>& materials, std::vector<TextureInfo>& textures, std::vector<ImageInfo>& images);\nvoid optimizeMaterials(cgltf_data* data, std::vector<MaterialInfo>& materials, std::vector<ImageInfo>& images, const char* input_path);\n\nbool readImage(const cgltf_image& image, const char* input_path, std::string& data, std::string& mime_type);\nbool hasAlpha(const std::string& data, const char* mime_type);\nbool getDimensions(const std::string& data, const char* mime_type, int& width, int& height);\nvoid adjustDimensions(int& width, int& height, float scale, int limit, bool pow2);\nconst char* mimeExtension(const char* mime_type);\n\n#ifdef WITH_BASISU\nvoid encodeImagesBasis(std::string* encoded, const cgltf_data* data, const std::vector<ImageInfo>& images, const char* input_path, const Settings& settings);\n#endif\n\n#ifdef WITH_LIBWEBP\nvoid encodeImagesWebP(std::string* encoded, const cgltf_data* data, const std::vector<ImageInfo>& images, const char* input_path, const Settings& settings);\n#endif\n\nvoid markScenes(cgltf_data* data, std::vector<NodeInfo>& nodes);\nvoid markAnimated(cgltf_data* data, std::vector<NodeInfo>& nodes, const std::vector<Animation>& animations);\nvoid markNeededNodes(cgltf_data* data, std::vector<NodeInfo>& nodes, const std::vector<Mesh>& meshes, const std::vector<Animation>& animations, const Settings& settings);\nvoid remapNodes(cgltf_data* data, std::vector<NodeInfo>& nodes, size_t& node_offset);\nvoid decomposeTransform(float translation[3], float rotation[4], float scale[3], const float* transform);\n\nvoid computeMeshQuality(std::vector<Mesh>& meshes);\n\nbool hasVertexAlpha(const Mesh& mesh);\nbool hasInstanceAlpha(const std::vector<Instance>& instances);\n\nQuantizationPosition prepareQuantizationPosition(const std::vector<Mesh>& meshes, const Settings& settings);\nvoid prepareQuantizationTexture(cgltf_data* data, std::vector<QuantizationTexture>& result, std::vector<size_t>& indices, const std::vector<Mesh>& meshes, const Settings& settings);\nvoid getPositionBounds(float min[3], float max[3], const Stream& stream, const QuantizationPosition& qp, const Settings& settings);\n\nStreamFormat writeVertexStream(std::string& bin, const Stream& stream, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings, bool filters = true);\nStreamFormat writeIndexStream(std::string& bin, const std::vector<unsigned int>& stream);\nStreamFormat writeTimeStream(std::string& bin, const std::vector<float>& data);\nStreamFormat writeKeyframeStream(std::string& bin, cgltf_animation_path_type type, const std::vector<Attr>& data, const Settings& settings, bool has_tangents = false);\n\nvoid compressVertexStream(std::string& bin, const std::string& data, size_t count, size_t stride, int level);\nvoid compressIndexStream(std::string& bin, const std::string& data, size_t count, size_t stride);\nvoid compressIndexSequence(std::string& bin, const std::string& data, size_t count, size_t stride);\n\nsize_t getBufferView(std::vector<BufferView>& views, BufferView::Kind kind, StreamFormat::Filter filter, BufferView::Compression compression, size_t stride, int variant = 0);\n\nvoid comma(std::string& s);\nvoid append(std::string& s, size_t v);\nvoid append(std::string& s, float v);\nvoid append(std::string& s, const char* v);\nvoid append(std::string& s, const std::string& v);\nvoid append(std::string& s, const float* data, size_t count);\nvoid appendJson(std::string& s, const char* data);\n\nconst char* attributeType(cgltf_attribute_type type);\nconst char* animationPath(cgltf_animation_path_type type);\n\nvoid writeMaterial(std::string& json, const cgltf_data* data, const cgltf_material& material, const QuantizationPosition* qp, const QuantizationTexture* qt, std::vector<TextureInfo>& textures);\nvoid 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);\nvoid writeSampler(std::string& json, const cgltf_sampler& sampler);\nvoid writeImage(std::string& json, std::vector<BufferView>& 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);\nvoid writeTexture(std::string& json, const cgltf_texture& texture, const ImageInfo* info, cgltf_data* data, const Settings& settings);\nvoid writeMeshAttributes(std::string& json, std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, int target, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings);\nsize_t writeMeshIndices(std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const std::vector<unsigned int>& indices, cgltf_primitive_type type, const Settings& settings);\nvoid writeMeshGeometry(std::string& json, std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings);\nsize_t writeJointBindMatrices(std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const cgltf_skin& skin, const QuantizationPosition& qp, const Settings& settings);\nsize_t writeInstances(std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const std::vector<Instance>& instances, const QuantizationPosition& qp, bool has_color, const Settings& settings);\nvoid writeMeshNode(std::string& json, size_t mesh_offset, cgltf_node* node, cgltf_skin* skin, cgltf_data* data, const QuantizationPosition* qp);\nvoid writeMeshNodeInstanced(std::string& json, size_t mesh_offset, size_t accr_offset, bool has_color);\nvoid writeSkin(std::string& json, const cgltf_skin& skin, size_t matrix_accr, const std::vector<NodeInfo>& nodes, cgltf_data* data);\nvoid writeNode(std::string& json, const cgltf_node& node, const std::vector<NodeInfo>& nodes, cgltf_data* data);\nvoid writeAnimation(std::string& json, std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const Animation& animation, size_t i, cgltf_data* data, const std::vector<NodeInfo>& nodes, const Settings& settings);\nvoid writeCamera(std::string& json, const cgltf_camera& camera);\nvoid writeLight(std::string& json, const cgltf_light& light);\nvoid writeArray(std::string& json, const char* name, const std::string& contents);\nvoid writeExtensions(std::string& json, const ExtensionInfo* extensions, size_t count);\nvoid writeExtras(std::string& json, const cgltf_extras& extras);\nvoid writeScene(std::string& json, const cgltf_scene& scene, const std::string& roots, const Settings& settings);\n\n/**\n * Copyright (c) 2016-2026 Arseny Kapoulkine\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n */\n"
  },
  {
    "path": "gltf/gltfpack.manifest",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n  <assemblyIdentity type=\"win32\" name=\"...\" version=\"6.0.0.0\"/>\n  <application>\n    <windowsSettings>\n      <activeCodePage xmlns=\"http://schemas.microsoft.com/SMI/2019/WindowsSettings\">UTF-8</activeCodePage>\n    </windowsSettings>\n  </application>\n</assembly>\n"
  },
  {
    "path": "gltf/image.cpp",
    "content": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\nstatic const char* kMimeTypes[][2] = {\n    {\"image/jpeg\", \".jpg\"},\n    {\"image/jpeg\", \".jpeg\"},\n    {\"image/png\", \".png\"},\n    {\"image/ktx2\", \".ktx2\"},\n    {\"image/webp\", \".webp\"},\n};\n\nstatic const char* inferMimeType(const char* path)\n{\n\tstd::string ext = getExtension(path);\n\n\tfor (size_t i = 0; i < sizeof(kMimeTypes) / sizeof(kMimeTypes[0]); ++i)\n\t\tif (ext == kMimeTypes[i][1])\n\t\t\treturn kMimeTypes[i][0];\n\n\treturn \"\";\n}\n\nstatic bool parseDataUri(const char* uri, std::string& mime_type, std::string& result)\n{\n\tif (strncmp(uri, \"data:\", 5) == 0)\n\t{\n\t\tconst char* comma = strchr(uri, ',');\n\n\t\tif (comma && comma - uri >= 7 && strncmp(comma - 7, \";base64\", 7) == 0)\n\t\t{\n\t\t\tconst char* base64 = comma + 1;\n\t\t\tsize_t base64_size = strlen(base64);\n\t\t\tsize_t size = base64_size - base64_size / 4;\n\n\t\t\tif (base64_size >= 2)\n\t\t\t{\n\t\t\t\tsize -= base64[base64_size - 2] == '=';\n\t\t\t\tsize -= base64[base64_size - 1] == '=';\n\t\t\t}\n\n\t\t\tvoid* data = NULL;\n\n\t\t\tcgltf_options options = {};\n\t\t\tcgltf_result res = cgltf_load_buffer_base64(&options, size, base64, &data);\n\n\t\t\tif (res != cgltf_result_success)\n\t\t\t\treturn false;\n\n\t\t\tmime_type = std::string(uri + 5, comma - 7);\n\t\t\tresult = std::string(static_cast<const char*>(data), size);\n\n\t\t\tfree(data);\n\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nstatic std::string fixupMimeType(const std::string& data, const std::string& mime_type)\n{\n\tif (mime_type == \"image/jpeg\" && data.compare(0, 8, \"\\x89\\x50\\x4e\\x47\\x0d\\x0a\\x1a\\x0a\") == 0)\n\t\treturn \"image/png\";\n\n\tif (mime_type == \"image/png\" && data.compare(0, 3, \"\\xff\\xd8\\xff\") == 0)\n\t\treturn \"image/jpeg\";\n\n\treturn mime_type;\n}\n\nbool readImage(const cgltf_image& image, const char* input_path, std::string& data, std::string& mime_type)\n{\n\tif (image.uri && parseDataUri(image.uri, mime_type, data))\n\t{\n\t\tmime_type = fixupMimeType(data, mime_type);\n\t\treturn true;\n\t}\n\telse if (image.buffer_view && image.buffer_view->buffer->data && image.mime_type)\n\t{\n\t\tconst cgltf_buffer_view* view = image.buffer_view;\n\n\t\tdata.assign(static_cast<const char*>(view->buffer->data) + view->offset, view->size);\n\n\t\tmime_type = image.mime_type;\n\t\tmime_type = fixupMimeType(data, image.mime_type);\n\t\treturn true;\n\t}\n\telse if (image.uri && *image.uri && input_path)\n\t{\n\t\tstd::string path = image.uri;\n\n\t\tcgltf_decode_uri(&path[0]);\n\t\tpath.resize(strlen(&path[0]));\n\n\t\tif (!readFile(getFullPath(path.c_str(), input_path).c_str(), data))\n\t\t\treturn false;\n\n\t\tmime_type = image.mime_type ? image.mime_type : inferMimeType(path.c_str());\n\t\tmime_type = fixupMimeType(data, mime_type);\n\t\treturn true;\n\t}\n\telse\n\t{\n\t\treturn false;\n\t}\n}\n\nstatic int readInt16BE(const std::string& data, size_t offset)\n{\n\treturn (unsigned char)data[offset] * 256 + (unsigned char)data[offset + 1];\n}\n\nstatic int readInt32BE(const std::string& data, size_t offset)\n{\n\treturn (unsigned((unsigned char)data[offset]) << 24) |\n\t       (unsigned((unsigned char)data[offset + 1]) << 16) |\n\t       (unsigned((unsigned char)data[offset + 2]) << 8) |\n\t       unsigned((unsigned char)data[offset + 3]);\n}\n\nstatic int readInt32LE(const std::string& data, size_t offset)\n{\n\treturn unsigned((unsigned char)data[offset]) |\n\t       (unsigned((unsigned char)data[offset + 1]) << 8) |\n\t       (unsigned((unsigned char)data[offset + 2]) << 16) |\n\t       (unsigned((unsigned char)data[offset + 3]) << 24);\n}\n\n// https://en.wikipedia.org/wiki/PNG#File_format\nstatic bool getDimensionsPng(const std::string& data, int& width, int& height)\n{\n\tif (data.size() < 8 + 8 + 13 + 4)\n\t\treturn false;\n\n\tconst char* signature = \"\\x89\\x50\\x4e\\x47\\x0d\\x0a\\x1a\\x0a\";\n\tif (data.compare(0, 8, signature) != 0)\n\t\treturn false;\n\n\tif (data.compare(12, 4, \"IHDR\") != 0)\n\t\treturn false;\n\n\twidth = readInt32BE(data, 16);\n\theight = readInt32BE(data, 20);\n\n\treturn true;\n}\n\n// https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format#File_format_structure\nstatic bool getDimensionsJpeg(const std::string& data, int& width, int& height)\n{\n\tsize_t offset = 0;\n\n\t// note, this can stop parsing before reaching the end but we stop at SOF anyway\n\twhile (offset + 4 <= data.size())\n\t{\n\t\tif (data[offset] != '\\xff')\n\t\t\treturn false;\n\n\t\tchar marker = data[offset + 1];\n\n\t\tif (marker == '\\xff')\n\t\t{\n\t\t\toffset++;\n\t\t\tcontinue; // padding\n\t\t}\n\n\t\t// d0..d9 correspond to SOI, RSTn, EOI\n\t\tif (marker == 0 || unsigned(marker - '\\xd0') <= 9)\n\t\t{\n\t\t\toffset += 2;\n\t\t\tcontinue; // no payload\n\t\t}\n\n\t\t// c0..c1 correspond to SOF0, SOF1\n\t\tif (marker == '\\xc0' || marker == '\\xc2')\n\t\t{\n\t\t\tif (offset + 10 > data.size())\n\t\t\t\treturn false;\n\n\t\t\twidth = readInt16BE(data, offset + 7);\n\t\t\theight = readInt16BE(data, offset + 5);\n\n\t\t\treturn true;\n\t\t}\n\n\t\toffset += 2 + readInt16BE(data, offset + 2);\n\t}\n\n\treturn false;\n}\n\n// https://en.wikipedia.org/wiki/PNG#File_format\nstatic bool hasTransparencyPng(const std::string& data)\n{\n\tif (data.size() < 8 + 8 + 13 + 4)\n\t\treturn false;\n\n\tconst char* signature = \"\\x89\\x50\\x4e\\x47\\x0d\\x0a\\x1a\\x0a\";\n\tif (data.compare(0, 8, signature) != 0)\n\t\treturn false;\n\n\tif (data.compare(12, 4, \"IHDR\") != 0)\n\t\treturn false;\n\n\tint ctype = data[25];\n\n\tif (ctype != 3)\n\t\treturn ctype == 4 || ctype == 6;\n\n\tsize_t offset = 8; // reparse IHDR chunk for simplicity\n\n\twhile (offset + 12 <= data.size())\n\t{\n\t\tint length = readInt32BE(data, offset);\n\n\t\tif (length < 0)\n\t\t\treturn false;\n\n\t\tif (data.compare(offset + 4, 4, \"tRNS\") == 0)\n\t\t\treturn true;\n\n\t\toffset += 12 + length;\n\t}\n\n\treturn false;\n}\n\n// https://github.khronos.org/KTX-Specification/ktxspec.v2.html\nstatic bool hasTransparencyKtx2(const std::string& data)\n{\n\tif (data.size() < 12 + 17 * 4)\n\t\treturn false;\n\n\tconst char* signature = \"\\xabKTX 20\\xbb\\r\\n\\x1a\\n\";\n\tif (data.compare(0, 12, signature) != 0)\n\t\treturn false;\n\n\tint dfdOffset = readInt32LE(data, 48);\n\tint dfdLength = readInt32LE(data, 52);\n\n\tif (dfdLength < 4 + 24 + 16 || unsigned(dfdOffset) > data.size() || unsigned(dfdLength) > data.size() - dfdOffset)\n\t\treturn false;\n\n\tconst int KDF_DF_MODEL_ETC1S = 163;\n\tconst int KDF_DF_MODEL_UASTC = 166;\n\tconst int KHR_DF_CHANNEL_UASTC_RGBA = 3;\n\tconst int KHR_DF_CHANNEL_ETC1S_RGB = 0;\n\tconst int KHR_DF_CHANNEL_ETC1S_AAA = 15;\n\n\tint colorModel = readInt32LE(data, dfdOffset + 12) & 0xff;\n\tint channelType1 = (readInt32LE(data, dfdOffset + 28) >> 24) & 0xf;\n\tint channelType2 = dfdLength >= 4 + 24 + 16 * 2 ? (readInt32LE(data, dfdOffset + 44) >> 24) & 0xf : channelType1;\n\n\tif (colorModel == KDF_DF_MODEL_ETC1S)\n\t{\n\t\treturn channelType1 == KHR_DF_CHANNEL_ETC1S_RGB && channelType2 == KHR_DF_CHANNEL_ETC1S_AAA;\n\t}\n\telse if (colorModel == KDF_DF_MODEL_UASTC)\n\t{\n\t\treturn channelType1 == KHR_DF_CHANNEL_UASTC_RGBA;\n\t}\n\n\treturn false;\n}\n\n// https://developers.google.com/speed/webp/docs/riff_container\nstatic bool hasTransparencyWebP(const std::string& data)\n{\n\tif (data.size() < 12 + 4 + 12)\n\t\treturn false;\n\n\tif (data.compare(0, 4, \"RIFF\") != 0)\n\t\treturn false;\n\tif (data.compare(8, 4, \"WEBP\") != 0)\n\t\treturn false;\n\n\t// WebP data may use VP8L, VP8X or VP8 format, but VP8 does not support transparency\n\tif (data.compare(12, 4, \"VP8L\") == 0)\n\t{\n\t\tif (unsigned(data[20]) != 0x2f)\n\t\t\treturn false;\n\n\t\t// width (14) | height (14) | alpha_is_used (1) | version_number(3)\n\t\tunsigned int header = readInt32LE(data, 21);\n\t\treturn (header & (1 << 28)) != 0;\n\t}\n\telse if (data.compare(12, 4, \"VP8X\") == 0)\n\t{\n\t\t// zero (2) | icc (1) | alpha (1) | exif (1) | xmp (1) | animation (1) | zero (1)\n\t\tunsigned char header = data[20];\n\t\treturn (header & (1 << 4)) != 0;\n\t}\n\n\treturn false;\n}\n\nbool hasAlpha(const std::string& data, const char* mime_type)\n{\n\tif (strcmp(mime_type, \"image/png\") == 0)\n\t\treturn hasTransparencyPng(data);\n\telse if (strcmp(mime_type, \"image/ktx2\") == 0)\n\t\treturn hasTransparencyKtx2(data);\n\telse if (strcmp(mime_type, \"image/webp\") == 0)\n\t\treturn hasTransparencyWebP(data);\n\telse\n\t\treturn false;\n}\n\nbool getDimensions(const std::string& data, const char* mime_type, int& width, int& height)\n{\n\tif (strcmp(mime_type, \"image/png\") == 0)\n\t\treturn getDimensionsPng(data, width, height);\n\tif (strcmp(mime_type, \"image/jpeg\") == 0)\n\t\treturn getDimensionsJpeg(data, width, height);\n\n\treturn false;\n}\n\nstatic int roundPow2(int value)\n{\n\tint result = 1;\n\n\twhile (result < value)\n\t\tresult <<= 1;\n\n\t// to prevent odd texture sizes from increasing the size too much, we round to nearest power of 2 above a certain size\n\tif (value > 128 && result * 3 / 4 > value)\n\t\tresult >>= 1;\n\n\treturn result;\n}\n\nstatic int roundBlock(int value, bool pow2)\n{\n\tif (value == 0)\n\t\treturn 4;\n\n\tif (pow2 && value > 4)\n\t\treturn roundPow2(value);\n\n\treturn (value + 3) & ~3;\n}\n\nvoid adjustDimensions(int& width, int& height, float scale, int limit, bool pow2)\n{\n\twidth = int(width * scale);\n\theight = int(height * scale);\n\n\tif (limit && (width > limit || height > limit))\n\t{\n\t\tfloat limit_scale = float(limit) / float(width > height ? width : height);\n\n\t\twidth = int(width * limit_scale);\n\t\theight = int(height * limit_scale);\n\t}\n\n\twidth = roundBlock(width, pow2);\n\theight = roundBlock(height, pow2);\n}\n\nconst char* mimeExtension(const char* mime_type)\n{\n\tfor (size_t i = 0; i < sizeof(kMimeTypes) / sizeof(kMimeTypes[0]); ++i)\n\t\tif (strcmp(kMimeTypes[i][0], mime_type) == 0)\n\t\t\treturn kMimeTypes[i][1];\n\n\treturn \".raw\";\n}\n"
  },
  {
    "path": "gltf/json.cpp",
    "content": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <float.h>\n#include <math.h>\n#include <stdio.h>\n\nvoid comma(std::string& s)\n{\n\tchar ch = s.empty() ? 0 : s[s.size() - 1];\n\n\tif (ch != 0 && ch != '[' && ch != '{')\n\t\ts += \",\";\n}\n\nvoid append(std::string& s, size_t v)\n{\n\tchar buf[32];\n\tsnprintf(buf, sizeof(buf), \"%zu\", v);\n\ts += buf;\n}\n\nvoid append(std::string& s, float v)\n{\n\t// sanitize +-inf to +-FLT_MAX and NaN to FLT_MAX\n\t// 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\n\tfloat sv = fabsf(v) < FLT_MAX ? v : (v < 0 ? -FLT_MAX : FLT_MAX);\n\n\tchar buf[64];\n\tsnprintf(buf, sizeof(buf), \"%.9g\", sv);\n\ts += buf;\n}\n\nvoid append(std::string& s, const char* v)\n{\n\ts += v;\n}\n\nvoid append(std::string& s, const std::string& v)\n{\n\ts += v;\n}\n\nvoid append(std::string& s, const float* data, size_t count)\n{\n\ts += '[';\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tif (i != 0)\n\t\t\ts += ',';\n\t\tappend(s, data[i]);\n\t}\n\ts += ']';\n}\n\nvoid appendJson(std::string& s, const char* data)\n{\n\tenum State\n\t{\n\t\tNone,\n\t\tEscape,\n\t\tQuoted\n\t} state = None;\n\n\tfor (const char* it = data; *it; ++it)\n\t{\n\t\tchar ch = *it;\n\n\t\t// whitespace outside of quoted strings can be ignored\n\t\tif (state != None || !isspace(ch))\n\t\t\ts += ch;\n\n\t\t// the finite automata tracks whether we're inside a quoted string\n\t\tswitch (state)\n\t\t{\n\t\tcase None:\n\t\t\tstate = (ch == '\"') ? Quoted : None;\n\t\t\tbreak;\n\n\t\tcase Quoted:\n\t\t\tstate = (ch == '\"') ? None : (ch == '\\\\' ? Escape : Quoted);\n\t\t\tbreak;\n\n\t\tcase Escape:\n\t\t\tstate = Quoted;\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tassert(!\"Unexpected parsing state\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "gltf/library.js",
    "content": "// This file is part of gltfpack and is distributed under the terms of MIT License.\n\n/**\n * Initialize the library with the Wasm module (library.wasm)\n *\n * @param wasm Promise with contents of library.wasm\n *\n * Note: this is called automatically in node.js\n */\nfunction init(wasm) {\n\tif (ready) {\n\t\tthrow new Error('init must be called once');\n\t}\n\n\tready = Promise.resolve(wasm)\n\t\t.then(function (buffer) {\n\t\t\treturn WebAssembly.instantiate(buffer, { wasi_snapshot_preview1: wasi });\n\t\t})\n\t\t.then(function (result) {\n\t\t\tinstance = result.instance;\n\t\t\tinstance.exports.__wasm_call_ctors();\n\t\t});\n}\n\n/**\n * Pack the requested glTF data using the requested command line and access interface.\n *\n * @param args An array of strings with the input arguments; the paths for input and output files are interpreted by the interface\n * @param iface An interface to the system that will be used to service file requests and other system calls\n * @return Promise that indicates completion of the operation\n *\n * iface should contain the following methods:\n * read(path): Given a path, return a Uint8Array with the contents of that path\n * write(path, data): Write the specified Uint8Array to the provided path\n */\nfunction pack(args, iface) {\n\tif (!ready) {\n\t\tthrow new Error('init must be called before pack');\n\t}\n\n\tvar argv = args.slice();\n\targv.unshift('gltfpack');\n\n\treturn ready.then(function () {\n\t\tvar buf = uploadArgv(argv);\n\n\t\toutput.position = 0;\n\t\toutput.size = 0;\n\n\t\tfs_interface = iface;\n\t\tvar result = instance.exports.pack(argv.length, buf);\n\t\tfs_interface = undefined;\n\n\t\tinstance.exports.free(buf);\n\n\t\tvar log = getString(output.data.buffer, 0, output.size);\n\n\t\tif (result != 0) {\n\t\t\tthrow new Error(log);\n\t\t} else {\n\t\t\treturn log;\n\t\t}\n\t});\n}\n\n// Library implementation (here be dragons)\nvar WASI_EBADF = 8;\nvar WASI_EINVAL = 28;\nvar WASI_EIO = 29;\nvar WASI_ENOSYS = 52;\n\nvar ready;\nvar instance;\nvar fs_interface;\n\nvar output = { data: new Uint8Array(), position: 0, size: 0 };\nvar fds = { 1: output, 2: output, 3: { mount: '/', path: '/' }, 4: { mount: '/gltfpack-$pwd', path: '' } };\n\nvar wasi = {\n\tproc_exit: function (rval) {},\n\n\tfd_close: function (fd) {\n\t\tif (!fds[fd]) {\n\t\t\treturn WASI_EBADF;\n\t\t}\n\n\t\ttry {\n\t\t\tif (fds[fd].close) {\n\t\t\t\tfds[fd].close();\n\t\t\t}\n\t\t\tfds[fd] = undefined;\n\t\t\treturn 0;\n\t\t} catch (err) {\n\t\t\tfds[fd] = undefined;\n\t\t\treturn WASI_EIO;\n\t\t}\n\t},\n\n\tfd_fdstat_get: function (fd, stat) {\n\t\tif (!fds[fd]) {\n\t\t\treturn WASI_EBADF;\n\t\t}\n\n\t\tvar heap = getHeap();\n\t\theap.setUint8(stat + 0, fds[fd].path !== undefined ? 3 : 4);\n\t\theap.setUint16(stat + 2, 0, true);\n\t\theap.setUint32(stat + 8, 0, true);\n\t\theap.setUint32(stat + 12, 0, true);\n\t\theap.setUint32(stat + 16, 0, true);\n\t\theap.setUint32(stat + 20, 0, true);\n\t\treturn 0;\n\t},\n\n\tpath_open32: function (parent_fd, dirflags, path, path_len, oflags, fs_rights_base, fs_rights_inheriting, fdflags, opened_fd) {\n\t\tif (!fds[parent_fd] || fds[parent_fd].path === undefined) {\n\t\t\treturn WASI_EBADF;\n\t\t}\n\n\t\tvar heap = getHeap();\n\n\t\tvar file = {};\n\t\tfile.name = fds[parent_fd].path + getString(heap.buffer, path, path_len);\n\t\tfile.position = 0;\n\n\t\tif (oflags & 1) {\n\t\t\tfile.data = new Uint8Array(4096);\n\t\t\tfile.size = 0;\n\t\t\tfile.close = function () {\n\t\t\t\tfs_interface.write(file.name, new Uint8Array(file.data.buffer, 0, file.size));\n\t\t\t};\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tfile.data = fs_interface.read(file.name);\n\n\t\t\t\tif (!file.data) {\n\t\t\t\t\treturn WASI_EIO;\n\t\t\t\t}\n\n\t\t\t\tfile.size = file.data.length;\n\t\t\t} catch (err) {\n\t\t\t\treturn WASI_EIO;\n\t\t\t}\n\t\t}\n\n\t\tvar fd = nextFd();\n\t\tfds[fd] = file;\n\n\t\theap.setUint32(opened_fd, fd, true);\n\t\treturn 0;\n\t},\n\n\tpath_filestat_get: function (parent_fd, flags, path, path_len, buf) {\n\t\tif (!fds[parent_fd] || fds[parent_fd].path === undefined) {\n\t\t\treturn WASI_EBADF;\n\t\t}\n\n\t\tvar heap = getHeap();\n\t\tvar name = getString(heap.buffer, path, path_len);\n\n\t\tvar heap = getHeap();\n\t\tfor (var i = 0; i < 64; ++i) heap.setUint8(buf + i, 0);\n\n\t\theap.setUint8(buf + 16, name == '.' ? 3 : 4);\n\t\treturn 0;\n\t},\n\n\tfd_prestat_get: function (fd, buf) {\n\t\tif (!fds[fd] || fds[fd].path === undefined) {\n\t\t\treturn WASI_EBADF;\n\t\t}\n\n\t\tvar path_buf = stringBuffer(fds[fd].mount);\n\n\t\tvar heap = getHeap();\n\t\theap.setUint8(buf, 0);\n\t\theap.setUint32(buf + 4, path_buf.length, true);\n\t\treturn 0;\n\t},\n\n\tfd_prestat_dir_name: function (fd, path, path_len) {\n\t\tif (!fds[fd] || fds[fd].path === undefined) {\n\t\t\treturn WASI_EBADF;\n\t\t}\n\n\t\tvar path_buf = stringBuffer(fds[fd].mount);\n\n\t\tif (path_len != path_buf.length) {\n\t\t\treturn WASI_EINVAL;\n\t\t}\n\n\t\tvar heap = getHeap();\n\t\tnew Uint8Array(heap.buffer).set(path_buf, path);\n\t\treturn 0;\n\t},\n\n\tpath_remove_directory: function (parent_fd, path, path_len) {\n\t\treturn WASI_EINVAL;\n\t},\n\n\tfd_fdstat_set_flags: function (fd, flags) {\n\t\treturn WASI_ENOSYS;\n\t},\n\n\tfd_seek32: function (fd, offset, whence, newoffset) {\n\t\tif (!fds[fd]) {\n\t\t\treturn WASI_EBADF;\n\t\t}\n\n\t\tvar newposition;\n\n\t\tswitch (whence) {\n\t\t\tcase 0:\n\t\t\t\tnewposition = offset;\n\t\t\t\tbreak;\n\n\t\t\tcase 1:\n\t\t\t\tnewposition = fds[fd].position + offset;\n\t\t\t\tbreak;\n\n\t\t\tcase 2:\n\t\t\t\tnewposition = fds[fd].size;\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\treturn WASI_EINVAL;\n\t\t}\n\n\t\tif (newposition > fds[fd].size) {\n\t\t\treturn WASI_EINVAL;\n\t\t}\n\n\t\tfds[fd].position = newposition;\n\n\t\tvar heap = getHeap();\n\t\theap.setUint32(newoffset, newposition, true);\n\t\treturn 0;\n\t},\n\n\tfd_read: function (fd, iovs, iovs_len, nread) {\n\t\tif (!fds[fd]) {\n\t\t\treturn WASI_EBADF;\n\t\t}\n\n\t\tvar heap = getHeap();\n\t\tvar read = 0;\n\n\t\tfor (var i = 0; i < iovs_len; ++i) {\n\t\t\tvar buf = heap.getUint32(iovs + 8 * i + 0, true);\n\t\t\tvar buf_len = heap.getUint32(iovs + 8 * i + 4, true);\n\n\t\t\tvar readi = Math.min(fds[fd].size - fds[fd].position, buf_len);\n\n\t\t\tnew Uint8Array(heap.buffer).set(fds[fd].data.subarray(fds[fd].position, fds[fd].position + readi), buf);\n\n\t\t\tfds[fd].position += readi;\n\t\t\tread += readi;\n\t\t}\n\n\t\theap.setUint32(nread, read, true);\n\t\treturn 0;\n\t},\n\n\tfd_write: function (fd, iovs, iovs_len, nwritten) {\n\t\tif (!fds[fd]) {\n\t\t\treturn WASI_EBADF;\n\t\t}\n\n\t\tvar heap = getHeap();\n\t\tvar written = 0;\n\n\t\tfor (var i = 0; i < iovs_len; ++i) {\n\t\t\tvar buf = heap.getUint32(iovs + 8 * i + 0, true);\n\t\t\tvar buf_len = heap.getUint32(iovs + 8 * i + 4, true);\n\n\t\t\tif (fds[fd].position + buf_len > fds[fd].data.length) {\n\t\t\t\tfds[fd].data = growArray(fds[fd].data, fds[fd].position + buf_len);\n\t\t\t}\n\n\t\t\tfds[fd].data.set(new Uint8Array(heap.buffer, buf, buf_len), fds[fd].position);\n\t\t\tfds[fd].position += buf_len;\n\t\t\tfds[fd].size = Math.max(fds[fd].position, fds[fd].size);\n\n\t\t\twritten += buf_len;\n\t\t}\n\n\t\theap.setUint32(nwritten, written, true);\n\t\treturn 0;\n\t},\n};\n\nfunction nextFd() {\n\tfor (var i = 1; ; ++i) {\n\t\tif (fds[i] === undefined) {\n\t\t\treturn i;\n\t\t}\n\t}\n}\n\nfunction getHeap() {\n\treturn new DataView(instance.exports.memory.buffer);\n}\n\nfunction getString(buffer, offset, length) {\n\treturn new TextDecoder().decode(new Uint8Array(buffer, offset, length));\n}\n\nfunction stringBuffer(string) {\n\treturn new TextEncoder().encode(string);\n}\n\nfunction growArray(data, len) {\n\tvar new_length = Math.max(1, data.length);\n\twhile (new_length < len) {\n\t\tnew_length *= 2;\n\t}\n\n\tvar new_data = new Uint8Array(new_length);\n\tnew_data.set(data);\n\n\treturn new_data;\n}\n\nfunction uploadArgv(argv) {\n\tvar buf_size = argv.length * 4;\n\tfor (var i = 0; i < argv.length; ++i) {\n\t\tbuf_size += stringBuffer(argv[i]).length + 1;\n\t}\n\n\tvar buf = instance.exports.malloc(buf_size);\n\tvar argp = buf + argv.length * 4;\n\n\tvar heap = getHeap();\n\n\tfor (var i = 0; i < argv.length; ++i) {\n\t\tvar item = stringBuffer(argv[i]);\n\n\t\theap.setUint32(buf + i * 4, argp, true);\n\t\tnew Uint8Array(heap.buffer).set(item, argp);\n\t\theap.setUint8(argp + item.length, 0);\n\n\t\targp += item.length + 1;\n\t}\n\n\treturn buf;\n}\n\n// Automatic initialization for node.js\nif (typeof process !== 'undefined' && process.release && process.release.name === 'node') {\n\tinit(\n\t\timport('node:fs').then(function (fs) {\n\t\t\treturn fs.readFileSync(new URL('./library.wasm', import.meta.url));\n\t\t})\n\t);\n}\n\nexport { init, pack };\n"
  },
  {
    "path": "gltf/material.cpp",
    "content": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <string.h>\n\nstatic bool areTexturesEqual(const cgltf_texture& lhs, const cgltf_texture& rhs)\n{\n\tif (lhs.image != rhs.image)\n\t\treturn false;\n\n\tif (lhs.sampler != rhs.sampler)\n\t\treturn false;\n\n\tif (lhs.basisu_image != rhs.basisu_image)\n\t\treturn false;\n\n\tif (lhs.webp_image != rhs.webp_image)\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool areTextureViewsEqual(const cgltf_texture_view& lhs, const cgltf_texture_view& rhs)\n{\n\tif (lhs.has_transform != rhs.has_transform)\n\t\treturn false;\n\n\tif (lhs.has_transform)\n\t{\n\t\tconst cgltf_texture_transform& lt = lhs.transform;\n\t\tconst cgltf_texture_transform& rt = rhs.transform;\n\n\t\tif (memcmp(lt.offset, rt.offset, sizeof(cgltf_float) * 2) != 0)\n\t\t\treturn false;\n\n\t\tif (lt.rotation != rt.rotation)\n\t\t\treturn false;\n\n\t\tif (memcmp(lt.scale, rt.scale, sizeof(cgltf_float) * 2) != 0)\n\t\t\treturn false;\n\n\t\tif (lt.texcoord != rt.texcoord)\n\t\t\treturn false;\n\t}\n\n\tif (lhs.texture != rhs.texture && (!lhs.texture || !rhs.texture || !areTexturesEqual(*lhs.texture, *rhs.texture)))\n\t\treturn false;\n\n\tif (lhs.texcoord != rhs.texcoord)\n\t\treturn false;\n\n\tif (lhs.scale != rhs.scale)\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool areMaterialComponentsEqual(const cgltf_pbr_metallic_roughness& lhs, const cgltf_pbr_metallic_roughness& rhs)\n{\n\tif (!areTextureViewsEqual(lhs.base_color_texture, rhs.base_color_texture))\n\t\treturn false;\n\n\tif (!areTextureViewsEqual(lhs.metallic_roughness_texture, rhs.metallic_roughness_texture))\n\t\treturn false;\n\n\tif (memcmp(lhs.base_color_factor, rhs.base_color_factor, sizeof(cgltf_float) * 4) != 0)\n\t\treturn false;\n\n\tif (lhs.metallic_factor != rhs.metallic_factor)\n\t\treturn false;\n\n\tif (lhs.roughness_factor != rhs.roughness_factor)\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool areMaterialComponentsEqual(const cgltf_pbr_specular_glossiness& lhs, const cgltf_pbr_specular_glossiness& rhs)\n{\n\tif (!areTextureViewsEqual(lhs.diffuse_texture, rhs.diffuse_texture))\n\t\treturn false;\n\n\tif (!areTextureViewsEqual(lhs.specular_glossiness_texture, rhs.specular_glossiness_texture))\n\t\treturn false;\n\n\tif (memcmp(lhs.diffuse_factor, rhs.diffuse_factor, sizeof(cgltf_float) * 4) != 0)\n\t\treturn false;\n\n\tif (memcmp(lhs.specular_factor, rhs.specular_factor, sizeof(cgltf_float) * 3) != 0)\n\t\treturn false;\n\n\tif (lhs.glossiness_factor != rhs.glossiness_factor)\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool areMaterialComponentsEqual(const cgltf_clearcoat& lhs, const cgltf_clearcoat& rhs)\n{\n\tif (!areTextureViewsEqual(lhs.clearcoat_texture, rhs.clearcoat_texture))\n\t\treturn false;\n\n\tif (!areTextureViewsEqual(lhs.clearcoat_roughness_texture, rhs.clearcoat_roughness_texture))\n\t\treturn false;\n\n\tif (!areTextureViewsEqual(lhs.clearcoat_normal_texture, rhs.clearcoat_normal_texture))\n\t\treturn false;\n\n\tif (lhs.clearcoat_factor != rhs.clearcoat_factor)\n\t\treturn false;\n\n\tif (lhs.clearcoat_roughness_factor != rhs.clearcoat_roughness_factor)\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool areMaterialComponentsEqual(const cgltf_transmission& lhs, const cgltf_transmission& rhs)\n{\n\tif (!areTextureViewsEqual(lhs.transmission_texture, rhs.transmission_texture))\n\t\treturn false;\n\n\tif (lhs.transmission_factor != rhs.transmission_factor)\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool areMaterialComponentsEqual(const cgltf_ior& lhs, const cgltf_ior& rhs)\n{\n\tif (lhs.ior != rhs.ior)\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool areMaterialComponentsEqual(const cgltf_specular& lhs, const cgltf_specular& rhs)\n{\n\tif (!areTextureViewsEqual(lhs.specular_texture, rhs.specular_texture))\n\t\treturn false;\n\n\tif (!areTextureViewsEqual(lhs.specular_color_texture, rhs.specular_color_texture))\n\t\treturn false;\n\n\tif (lhs.specular_factor != rhs.specular_factor)\n\t\treturn false;\n\n\tif (memcmp(lhs.specular_color_factor, rhs.specular_color_factor, sizeof(cgltf_float) * 3) != 0)\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool areMaterialComponentsEqual(const cgltf_sheen& lhs, const cgltf_sheen& rhs)\n{\n\tif (!areTextureViewsEqual(lhs.sheen_color_texture, rhs.sheen_color_texture))\n\t\treturn false;\n\n\tif (memcmp(lhs.sheen_color_factor, rhs.sheen_color_factor, sizeof(cgltf_float) * 3) != 0)\n\t\treturn false;\n\n\tif (!areTextureViewsEqual(lhs.sheen_roughness_texture, rhs.sheen_roughness_texture))\n\t\treturn false;\n\n\tif (lhs.sheen_roughness_factor != rhs.sheen_roughness_factor)\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool areMaterialComponentsEqual(const cgltf_volume& lhs, const cgltf_volume& rhs)\n{\n\tif (!areTextureViewsEqual(lhs.thickness_texture, rhs.thickness_texture))\n\t\treturn false;\n\n\tif (lhs.thickness_factor != rhs.thickness_factor)\n\t\treturn false;\n\n\tif (memcmp(lhs.attenuation_color, rhs.attenuation_color, sizeof(cgltf_float) * 3) != 0)\n\t\treturn false;\n\n\tif (lhs.attenuation_distance != rhs.attenuation_distance)\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool areMaterialComponentsEqual(const cgltf_emissive_strength& lhs, const cgltf_emissive_strength& rhs)\n{\n\tif (lhs.emissive_strength != rhs.emissive_strength)\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool areMaterialComponentsEqual(const cgltf_iridescence& lhs, const cgltf_iridescence& rhs)\n{\n\tif (lhs.iridescence_factor != rhs.iridescence_factor)\n\t\treturn false;\n\n\tif (!areTextureViewsEqual(lhs.iridescence_texture, rhs.iridescence_texture))\n\t\treturn false;\n\n\tif (lhs.iridescence_ior != rhs.iridescence_ior)\n\t\treturn false;\n\n\tif (lhs.iridescence_thickness_min != rhs.iridescence_thickness_min)\n\t\treturn false;\n\n\tif (lhs.iridescence_thickness_max != rhs.iridescence_thickness_max)\n\t\treturn false;\n\n\tif (!areTextureViewsEqual(lhs.iridescence_thickness_texture, rhs.iridescence_thickness_texture))\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool areMaterialComponentsEqual(const cgltf_anisotropy& lhs, const cgltf_anisotropy& rhs)\n{\n\tif (lhs.anisotropy_strength != rhs.anisotropy_strength)\n\t\treturn false;\n\n\tif (lhs.anisotropy_rotation != rhs.anisotropy_rotation)\n\t\treturn false;\n\n\tif (!areTextureViewsEqual(lhs.anisotropy_texture, rhs.anisotropy_texture))\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool areMaterialComponentsEqual(const cgltf_dispersion& lhs, const cgltf_dispersion& rhs)\n{\n\tif (lhs.dispersion != rhs.dispersion)\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool areMaterialComponentsEqual(const cgltf_diffuse_transmission& lhs, const cgltf_diffuse_transmission& rhs)\n{\n\tif (lhs.diffuse_transmission_factor != rhs.diffuse_transmission_factor)\n\t\treturn false;\n\n\tif (memcmp(lhs.diffuse_transmission_color_factor, rhs.diffuse_transmission_color_factor, sizeof(cgltf_float) * 3) != 0)\n\t\treturn false;\n\n\tif (!areTextureViewsEqual(lhs.diffuse_transmission_texture, rhs.diffuse_transmission_texture))\n\t\treturn false;\n\n\tif (!areTextureViewsEqual(lhs.diffuse_transmission_color_texture, rhs.diffuse_transmission_color_texture))\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool areMaterialsEqual(const cgltf_material& lhs, const cgltf_material& rhs, const Settings& settings)\n{\n\tif (lhs.has_pbr_metallic_roughness != rhs.has_pbr_metallic_roughness)\n\t\treturn false;\n\n\tif (lhs.has_pbr_metallic_roughness && !areMaterialComponentsEqual(lhs.pbr_metallic_roughness, rhs.pbr_metallic_roughness))\n\t\treturn false;\n\n\tif (lhs.has_pbr_specular_glossiness != rhs.has_pbr_specular_glossiness)\n\t\treturn false;\n\n\tif (lhs.has_pbr_specular_glossiness && !areMaterialComponentsEqual(lhs.pbr_specular_glossiness, rhs.pbr_specular_glossiness))\n\t\treturn false;\n\n\tif (lhs.has_clearcoat != rhs.has_clearcoat)\n\t\treturn false;\n\n\tif (lhs.has_clearcoat && !areMaterialComponentsEqual(lhs.clearcoat, rhs.clearcoat))\n\t\treturn false;\n\n\tif (lhs.has_transmission != rhs.has_transmission)\n\t\treturn false;\n\n\tif (lhs.has_transmission && !areMaterialComponentsEqual(lhs.transmission, rhs.transmission))\n\t\treturn false;\n\n\tif (lhs.has_ior != rhs.has_ior)\n\t\treturn false;\n\n\tif (lhs.has_ior && !areMaterialComponentsEqual(lhs.ior, rhs.ior))\n\t\treturn false;\n\n\tif (lhs.has_specular != rhs.has_specular)\n\t\treturn false;\n\n\tif (lhs.has_specular && !areMaterialComponentsEqual(lhs.specular, rhs.specular))\n\t\treturn false;\n\n\tif (lhs.has_sheen != rhs.has_sheen)\n\t\treturn false;\n\n\tif (lhs.has_sheen && !areMaterialComponentsEqual(lhs.sheen, rhs.sheen))\n\t\treturn false;\n\n\tif (lhs.has_volume != rhs.has_volume)\n\t\treturn false;\n\n\tif (lhs.has_volume && !areMaterialComponentsEqual(lhs.volume, rhs.volume))\n\t\treturn false;\n\n\tif (lhs.has_emissive_strength != rhs.has_emissive_strength)\n\t\treturn false;\n\n\tif (lhs.has_emissive_strength && !areMaterialComponentsEqual(lhs.emissive_strength, rhs.emissive_strength))\n\t\treturn false;\n\n\tif (lhs.has_iridescence != rhs.has_iridescence)\n\t\treturn false;\n\n\tif (lhs.has_iridescence && !areMaterialComponentsEqual(lhs.iridescence, rhs.iridescence))\n\t\treturn false;\n\n\tif (lhs.has_anisotropy != rhs.has_anisotropy)\n\t\treturn false;\n\n\tif (lhs.has_anisotropy && !areMaterialComponentsEqual(lhs.anisotropy, rhs.anisotropy))\n\t\treturn false;\n\n\tif (lhs.has_dispersion != rhs.has_dispersion)\n\t\treturn false;\n\n\tif (lhs.has_dispersion && !areMaterialComponentsEqual(lhs.dispersion, rhs.dispersion))\n\t\treturn false;\n\n\tif (lhs.has_diffuse_transmission != rhs.has_diffuse_transmission)\n\t\treturn false;\n\n\tif (lhs.has_diffuse_transmission && !areMaterialComponentsEqual(lhs.diffuse_transmission, rhs.diffuse_transmission))\n\t\treturn false;\n\n\tif (!areTextureViewsEqual(lhs.normal_texture, rhs.normal_texture))\n\t\treturn false;\n\n\tif (!areTextureViewsEqual(lhs.occlusion_texture, rhs.occlusion_texture))\n\t\treturn false;\n\n\tif (!areTextureViewsEqual(lhs.emissive_texture, rhs.emissive_texture))\n\t\treturn false;\n\n\tif (memcmp(lhs.emissive_factor, rhs.emissive_factor, sizeof(cgltf_float) * 3) != 0)\n\t\treturn false;\n\n\tif (lhs.alpha_mode != rhs.alpha_mode)\n\t\treturn false;\n\n\tif (lhs.alpha_cutoff != rhs.alpha_cutoff)\n\t\treturn false;\n\n\tif (lhs.double_sided != rhs.double_sided)\n\t\treturn false;\n\n\tif (lhs.unlit != rhs.unlit)\n\t\treturn false;\n\n\tif (settings.keep_extras && !areExtrasEqual(lhs.extras, rhs.extras))\n\t\treturn false;\n\n\treturn true;\n}\n\nvoid mergeMeshMaterials(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settings)\n{\n\tstd::vector<cgltf_material*> material_remap(data->materials_count);\n\n\tfor (size_t i = 0; i < data->materials_count; ++i)\n\t{\n\t\tmaterial_remap[i] = &data->materials[i];\n\n\t\tif (settings.keep_materials && data->materials[i].name && *data->materials[i].name)\n\t\t\tcontinue;\n\n\t\tassert(areMaterialsEqual(data->materials[i], data->materials[i], settings));\n\n\t\tfor (size_t j = 0; j < i; ++j)\n\t\t{\n\t\t\tif (settings.keep_materials && data->materials[j].name && *data->materials[j].name)\n\t\t\t\tcontinue;\n\n\t\t\tif (areMaterialsEqual(data->materials[i], data->materials[j], settings))\n\t\t\t{\n\t\t\t\tmaterial_remap[i] = &data->materials[j];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tMesh& mesh = meshes[i];\n\n\t\tif (mesh.material)\n\t\t\tmesh.material = material_remap[mesh.material - data->materials];\n\n\t\tfor (size_t j = 0; j < mesh.variants.size(); ++j)\n\t\t\tmesh.variants[j].material = material_remap[mesh.variants[j].material - data->materials];\n\t}\n}\n\nvoid markNeededMaterials(cgltf_data* data, std::vector<MaterialInfo>& materials, const std::vector<Mesh>& meshes, const Settings& settings)\n{\n\t// mark all used materials as kept\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tconst Mesh& mesh = meshes[i];\n\n\t\tif (mesh.material)\n\t\t{\n\t\t\tMaterialInfo& mi = materials[mesh.material - data->materials];\n\n\t\t\tmi.keep = true;\n\t\t}\n\n\t\tfor (size_t j = 0; j < mesh.variants.size(); ++j)\n\t\t{\n\t\t\tMaterialInfo& mi = materials[mesh.variants[j].material - data->materials];\n\n\t\t\tmi.keep = true;\n\t\t}\n\t}\n\n\t// mark all named materials as kept if requested\n\tif (settings.keep_materials)\n\t{\n\t\tfor (size_t i = 0; i < data->materials_count; ++i)\n\t\t{\n\t\t\tcgltf_material& material = data->materials[i];\n\n\t\t\tif (material.name && *material.name)\n\t\t\t{\n\t\t\t\tmaterials[i].keep = true;\n\t\t\t}\n\t\t}\n\t}\n}\n\nbool hasValidTransform(const cgltf_texture_view& view)\n{\n\tif (view.has_transform)\n\t{\n\t\tif (view.transform.offset[0] != 0.0f || view.transform.offset[1] != 0.0f ||\n\t\t    view.transform.scale[0] != 1.0f || view.transform.scale[1] != 1.0f ||\n\t\t    view.transform.rotation != 0.0f)\n\t\t\treturn true;\n\n\t\tif (view.transform.has_texcoord && view.transform.texcoord != view.texcoord)\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic const cgltf_image* getTextureImage(const cgltf_texture* texture)\n{\n\tif (texture && texture->image)\n\t\treturn texture->image;\n\n\tif (texture && texture->basisu_image)\n\t\treturn texture->basisu_image;\n\n\tif (texture && texture->webp_image)\n\t\treturn texture->webp_image;\n\n\treturn NULL;\n}\n\nstatic void analyzeMaterialTexture(const cgltf_texture_view& view, TextureKind kind, MaterialInfo& mi, cgltf_data* data, std::vector<TextureInfo>& textures, std::vector<ImageInfo>& images)\n{\n\tmi.uses_texture_transform |= hasValidTransform(view);\n\n\tif (view.texture)\n\t{\n\t\ttextures[view.texture - data->textures].keep = true;\n\n\t\tmi.texture_set_mask |= 1u << view.texcoord;\n\t\tmi.needs_tangents |= (kind == TextureKind_Normal);\n\t}\n\n\tif (const cgltf_image* image = getTextureImage(view.texture))\n\t{\n\t\tImageInfo& info = images[image - data->images];\n\n\t\tif (info.kind == TextureKind_Generic)\n\t\t\tinfo.kind = kind;\n\t\telse if (info.kind > kind) // this is useful to keep color textures that have attrib data in alpha tagged as color\n\t\t\tinfo.kind = kind;\n\n\t\tinfo.normal_map |= (kind == TextureKind_Normal);\n\t\tinfo.srgb |= (kind == TextureKind_Color);\n\t}\n}\n\nstatic void analyzeMaterial(const cgltf_material& material, MaterialInfo& mi, cgltf_data* data, std::vector<TextureInfo>& textures, std::vector<ImageInfo>& images)\n{\n\tif (material.has_pbr_metallic_roughness)\n\t{\n\t\tanalyzeMaterialTexture(material.pbr_metallic_roughness.base_color_texture, TextureKind_Color, mi, data, textures, images);\n\t\tanalyzeMaterialTexture(material.pbr_metallic_roughness.metallic_roughness_texture, TextureKind_Attrib, mi, data, textures, images);\n\t}\n\n\tif (material.has_pbr_specular_glossiness)\n\t{\n\t\tanalyzeMaterialTexture(material.pbr_specular_glossiness.diffuse_texture, TextureKind_Color, mi, data, textures, images);\n\t\tanalyzeMaterialTexture(material.pbr_specular_glossiness.specular_glossiness_texture, TextureKind_Attrib, mi, data, textures, images);\n\t}\n\n\tif (material.has_clearcoat)\n\t{\n\t\tanalyzeMaterialTexture(material.clearcoat.clearcoat_texture, TextureKind_Attrib, mi, data, textures, images);\n\t\tanalyzeMaterialTexture(material.clearcoat.clearcoat_roughness_texture, TextureKind_Attrib, mi, data, textures, images);\n\t\tanalyzeMaterialTexture(material.clearcoat.clearcoat_normal_texture, TextureKind_Normal, mi, data, textures, images);\n\t}\n\n\tif (material.has_transmission)\n\t{\n\t\tanalyzeMaterialTexture(material.transmission.transmission_texture, TextureKind_Attrib, mi, data, textures, images);\n\t}\n\n\tif (material.has_specular)\n\t{\n\t\tanalyzeMaterialTexture(material.specular.specular_texture, TextureKind_Attrib, mi, data, textures, images);\n\t\tanalyzeMaterialTexture(material.specular.specular_color_texture, TextureKind_Color, mi, data, textures, images);\n\t}\n\n\tif (material.has_sheen)\n\t{\n\t\tanalyzeMaterialTexture(material.sheen.sheen_color_texture, TextureKind_Color, mi, data, textures, images);\n\t\tanalyzeMaterialTexture(material.sheen.sheen_roughness_texture, TextureKind_Attrib, mi, data, textures, images);\n\t}\n\n\tif (material.has_volume)\n\t{\n\t\tanalyzeMaterialTexture(material.volume.thickness_texture, TextureKind_Attrib, mi, data, textures, images);\n\t}\n\n\tif (material.has_iridescence)\n\t{\n\t\tanalyzeMaterialTexture(material.iridescence.iridescence_texture, TextureKind_Attrib, mi, data, textures, images);\n\t\tanalyzeMaterialTexture(material.iridescence.iridescence_thickness_texture, TextureKind_Attrib, mi, data, textures, images);\n\t}\n\n\tif (material.has_anisotropy)\n\t{\n\t\tanalyzeMaterialTexture(material.anisotropy.anisotropy_texture, TextureKind_Normal, mi, data, textures, images);\n\t}\n\n\tif (material.has_diffuse_transmission)\n\t{\n\t\tanalyzeMaterialTexture(material.diffuse_transmission.diffuse_transmission_texture, TextureKind_Attrib, mi, data, textures, images);\n\t\tanalyzeMaterialTexture(material.diffuse_transmission.diffuse_transmission_color_texture, TextureKind_Color, mi, data, textures, images);\n\t}\n\n\tanalyzeMaterialTexture(material.normal_texture, TextureKind_Normal, mi, data, textures, images);\n\tanalyzeMaterialTexture(material.occlusion_texture, TextureKind_Attrib, mi, data, textures, images);\n\tanalyzeMaterialTexture(material.emissive_texture, TextureKind_Color, mi, data, textures, images);\n\n\tif (material.unlit)\n\t\tmi.unlit = true;\n}\n\nvoid analyzeMaterials(cgltf_data* data, std::vector<MaterialInfo>& materials, std::vector<TextureInfo>& textures, std::vector<ImageInfo>& images)\n{\n\tfor (size_t i = 0; i < data->materials_count; ++i)\n\t{\n\t\tanalyzeMaterial(data->materials[i], materials[i], data, textures, images);\n\t}\n}\n\nstatic int getChannels(const cgltf_image& image, ImageInfo& info, const char* input_path)\n{\n\tif (info.channels)\n\t\treturn info.channels;\n\n\tstd::string img_data;\n\tstd::string mime_type;\n\tif (readImage(image, input_path, img_data, mime_type))\n\t\tinfo.channels = hasAlpha(img_data, mime_type.c_str()) ? 4 : 3;\n\telse\n\t\tinfo.channels = -1;\n\n\treturn info.channels;\n}\n\nstatic bool shouldKeepAlpha(const cgltf_texture_view& color, float alpha, cgltf_data* data, const char* input_path, std::vector<ImageInfo>& images)\n{\n\tif (alpha != 1.f)\n\t\treturn true;\n\n\tconst cgltf_image* image = getTextureImage(color.texture);\n\n\treturn image && getChannels(*image, images[image - data->images], input_path) == 4;\n}\n\nvoid optimizeMaterials(cgltf_data* data, std::vector<MaterialInfo>& materials, std::vector<ImageInfo>& images, const char* input_path)\n{\n\tfor (size_t i = 0; i < data->materials_count; ++i)\n\t{\n\t\t// remove BLEND/MASK from materials that don't have alpha information\n\t\tcgltf_material& material = data->materials[i];\n\n\t\tif (material.alpha_mode != cgltf_alpha_mode_opaque && !materials[i].mesh_alpha)\n\t\t{\n\t\t\tif (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))\n\t\t\t\tcontinue;\n\n\t\t\tif (material.has_pbr_specular_glossiness && shouldKeepAlpha(material.pbr_specular_glossiness.diffuse_texture, material.pbr_specular_glossiness.diffuse_factor[3], data, input_path, images))\n\t\t\t\tcontinue;\n\n\t\t\tmaterial.alpha_mode = cgltf_alpha_mode_opaque;\n\t\t\tmaterial.alpha_cutoff = 0.5f; // reset to default to avoid writing it to output\n\t\t}\n\t}\n}\n\nvoid mergeTextures(cgltf_data* data, std::vector<TextureInfo>& textures)\n{\n\tsize_t offset = 0;\n\n\tfor (size_t i = 0; i < textures.size(); ++i)\n\t{\n\t\tTextureInfo& info = textures[i];\n\n\t\tif (!info.keep)\n\t\t\tcontinue;\n\n\t\tfor (size_t j = 0; j < i; ++j)\n\t\t\tif (textures[j].keep && areTexturesEqual(data->textures[i], data->textures[j]))\n\t\t\t{\n\t\t\t\tinfo.keep = false;\n\t\t\t\tinfo.remap = textures[j].remap;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\tif (info.keep)\n\t\t{\n\t\t\tinfo.remap = int(offset);\n\t\t\toffset++;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "gltf/mesh.cpp",
    "content": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <algorithm>\n#include <unordered_map>\n\n#include <math.h>\n#include <stdint.h>\n#include <string.h>\n\n#include \"../src/meshoptimizer.h\"\n\nstatic float inverseTranspose(float* result, const float* transform)\n{\n\tfloat m[4][4] = {};\n\tmemcpy(m, transform, 16 * sizeof(float));\n\n\tfloat det =\n\t    m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2]) -\n\t    m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) +\n\t    m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);\n\n\tfloat invdet = (det == 0.f) ? 0.f : 1.f / det;\n\n\tfloat r[4][4] = {};\n\n\tr[0][0] = (m[1][1] * m[2][2] - m[2][1] * m[1][2]) * invdet;\n\tr[1][0] = (m[0][2] * m[2][1] - m[0][1] * m[2][2]) * invdet;\n\tr[2][0] = (m[0][1] * m[1][2] - m[0][2] * m[1][1]) * invdet;\n\tr[0][1] = (m[1][2] * m[2][0] - m[1][0] * m[2][2]) * invdet;\n\tr[1][1] = (m[0][0] * m[2][2] - m[0][2] * m[2][0]) * invdet;\n\tr[2][1] = (m[1][0] * m[0][2] - m[0][0] * m[1][2]) * invdet;\n\tr[0][2] = (m[1][0] * m[2][1] - m[2][0] * m[1][1]) * invdet;\n\tr[1][2] = (m[2][0] * m[0][1] - m[0][0] * m[2][1]) * invdet;\n\tr[2][2] = (m[0][0] * m[1][1] - m[1][0] * m[0][1]) * invdet;\n\n\tr[3][3] = 1.f;\n\n\tmemcpy(result, r, 16 * sizeof(float));\n\n\treturn det;\n}\n\nstatic void transformPosition(float* res, const float* ptr, const float* transform)\n{\n\tfloat x = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8] + transform[12];\n\tfloat y = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9] + transform[13];\n\tfloat z = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10] + transform[14];\n\n\tres[0] = x;\n\tres[1] = y;\n\tres[2] = z;\n}\n\nstatic void transformNormal(float* res, const float* ptr, const float* transform)\n{\n\tfloat x = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8];\n\tfloat y = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9];\n\tfloat z = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10];\n\n\tfloat l = sqrtf(x * x + y * y + z * z);\n\tfloat s = (l == 0.f) ? 0.f : 1 / l;\n\n\tres[0] = x * s;\n\tres[1] = y * s;\n\tres[2] = z * s;\n}\n\nstatic Stream* getStream(Mesh& mesh, cgltf_attribute_type type, int index = 0)\n{\n\tfor (size_t i = 0; i < mesh.streams.size(); ++i)\n\t\tif (mesh.streams[i].type == type && mesh.streams[i].index == index)\n\t\t\treturn &mesh.streams[i];\n\n\treturn NULL;\n}\n\n// assumes mesh & target are structurally identical\nstatic void transformMesh(Mesh& target, const Mesh& mesh, const cgltf_node* node)\n{\n\tassert(target.streams.size() == mesh.streams.size());\n\tassert(target.indices.size() == mesh.indices.size());\n\n\tfloat transform[16];\n\tcgltf_node_transform_world(node, transform);\n\n\tfloat transforminvt[16];\n\tfloat det = inverseTranspose(transforminvt, transform);\n\n\tfor (size_t si = 0; si < mesh.streams.size(); ++si)\n\t{\n\t\tconst Stream& source = mesh.streams[si];\n\t\tStream& stream = target.streams[si];\n\n\t\tassert(source.type == stream.type);\n\t\tassert(source.data.size() == stream.data.size());\n\n\t\tif (stream.type == cgltf_attribute_type_position)\n\t\t{\n\t\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t\t\ttransformPosition(stream.data[i].f, source.data[i].f, transform);\n\t\t}\n\t\telse if (stream.type == cgltf_attribute_type_normal)\n\t\t{\n\t\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t\t\ttransformNormal(stream.data[i].f, source.data[i].f, transforminvt);\n\t\t}\n\t\telse if (stream.type == cgltf_attribute_type_tangent)\n\t\t{\n\t\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t\t\ttransformNormal(stream.data[i].f, source.data[i].f, transform);\n\t\t}\n\t}\n\n\t// copy indices so that we can modify them below\n\ttarget.indices = mesh.indices;\n\n\tif (det < 0 && mesh.type == cgltf_primitive_type_triangles)\n\t{\n\t\t// negative scale means we need to flip face winding\n\t\tfor (size_t i = 0; i < target.indices.size(); i += 3)\n\t\t\tstd::swap(target.indices[i + 0], target.indices[i + 1]);\n\t}\n}\n\nbool compareMeshTargets(const Mesh& lhs, const Mesh& rhs)\n{\n\tif (lhs.targets != rhs.targets)\n\t\treturn false;\n\n\tif (lhs.target_weights.size() != rhs.target_weights.size())\n\t\treturn false;\n\n\tfor (size_t i = 0; i < lhs.target_weights.size(); ++i)\n\t\tif (lhs.target_weights[i] != rhs.target_weights[i])\n\t\t\treturn false;\n\n\tif (lhs.target_names.size() != rhs.target_names.size())\n\t\treturn false;\n\n\tfor (size_t i = 0; i < lhs.target_names.size(); ++i)\n\t\tif (strcmp(lhs.target_names[i], rhs.target_names[i]) != 0)\n\t\t\treturn false;\n\n\treturn true;\n}\n\nbool compareMeshVariants(const Mesh& lhs, const Mesh& rhs)\n{\n\tif (lhs.variants.size() != rhs.variants.size())\n\t\treturn false;\n\n\tfor (size_t i = 0; i < lhs.variants.size(); ++i)\n\t{\n\t\tif (lhs.variants[i].variant != rhs.variants[i].variant)\n\t\t\treturn false;\n\n\t\tif (lhs.variants[i].material != rhs.variants[i].material)\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nbool compareMeshNodes(const Mesh& lhs, const Mesh& rhs)\n{\n\tif (lhs.nodes.size() != rhs.nodes.size())\n\t\treturn false;\n\n\tfor (size_t i = 0; i < lhs.nodes.size(); ++i)\n\t\tif (lhs.nodes[i] != rhs.nodes[i])\n\t\t\treturn false;\n\n\treturn true;\n}\n\nstatic bool compareInstances(const Instance& lhs, const Instance& rhs)\n{\n\treturn memcmp(&lhs, &rhs, sizeof(Instance)) == 0;\n}\n\nstatic bool canMergeMeshNodes(cgltf_node* lhs, cgltf_node* rhs, const Settings& settings)\n{\n\tif (lhs == rhs)\n\t\treturn true;\n\n\tif (lhs->parent != rhs->parent)\n\t\treturn false;\n\n\tbool lhs_transform = lhs->has_translation | lhs->has_rotation | lhs->has_scale | lhs->has_matrix | (!!lhs->weights);\n\tbool rhs_transform = rhs->has_translation | rhs->has_rotation | rhs->has_scale | rhs->has_matrix | (!!rhs->weights);\n\n\tif (lhs_transform || rhs_transform)\n\t\treturn false;\n\n\tif (settings.keep_nodes)\n\t{\n\t\tif (lhs->name && *lhs->name)\n\t\t\treturn false;\n\n\t\tif (rhs->name && *rhs->name)\n\t\t\treturn false;\n\t}\n\n\t// we can merge nodes that don't have transforms of their own and have the same parent\n\t// this is helpful when instead of splitting mesh into primitives, DCCs split mesh into mesh nodes\n\treturn true;\n}\n\nstatic bool canMergeMeshes(const Mesh& lhs, const Mesh& rhs, const Settings& settings)\n{\n\tif (lhs.scene != rhs.scene)\n\t\treturn false;\n\n\tif (lhs.nodes.size() != rhs.nodes.size())\n\t\treturn false;\n\n\tfor (size_t i = 0; i < lhs.nodes.size(); ++i)\n\t\tif (!canMergeMeshNodes(lhs.nodes[i], rhs.nodes[i], settings))\n\t\t\treturn false;\n\n\tif (lhs.instances.size() != rhs.instances.size())\n\t\treturn false;\n\n\tfor (size_t i = 0; i < lhs.instances.size(); ++i)\n\t\tif (!compareInstances(lhs.instances[i], rhs.instances[i]))\n\t\t\treturn false;\n\n\tif (lhs.material != rhs.material)\n\t\treturn false;\n\n\tif (lhs.skin != rhs.skin)\n\t\treturn false;\n\n\tif (lhs.type != rhs.type)\n\t\treturn false;\n\n\tif (!compareMeshTargets(lhs, rhs))\n\t\treturn false;\n\n\tif (!compareMeshVariants(lhs, rhs))\n\t\treturn false;\n\n\tif (lhs.indices.empty() != rhs.indices.empty())\n\t\treturn false;\n\n\tif (lhs.streams.size() != rhs.streams.size())\n\t\treturn false;\n\n\tif (settings.keep_extras && !areExtrasEqual(lhs.extras, rhs.extras))\n\t\treturn false;\n\n\tfor (size_t i = 0; i < lhs.streams.size(); ++i)\n\t\tif (lhs.streams[i].type != rhs.streams[i].type || lhs.streams[i].index != rhs.streams[i].index || lhs.streams[i].target != rhs.streams[i].target)\n\t\t\treturn false;\n\n\treturn true;\n}\n\nstatic void mergeMeshes(Mesh& target, const Mesh& mesh)\n{\n\tassert(target.streams.size() == mesh.streams.size());\n\n\tsize_t vertex_offset = target.streams[0].data.size();\n\tsize_t index_offset = target.indices.size();\n\n\tfor (size_t i = 0; i < target.streams.size(); ++i)\n\t\ttarget.streams[i].data.insert(target.streams[i].data.end(), mesh.streams[i].data.begin(), mesh.streams[i].data.end());\n\n\ttarget.indices.resize(target.indices.size() + mesh.indices.size());\n\n\tsize_t index_count = mesh.indices.size();\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t\ttarget.indices[index_offset + i] = unsigned(vertex_offset + mesh.indices[i]);\n}\n\nstatic void hashUpdate(uint64_t hash[2], const void* data, size_t size)\n{\n#define ROTL64(x, r) (((x) << (r)) | ((x) >> (64 - (r))))\n\n\t// MurMurHash3 128-bit\n\tconst uint64_t c1 = 0x87c37b91114253d5ull;\n\tconst uint64_t c2 = 0x4cf5ad432745937full;\n\n\tuint64_t h1 = hash[0], h2 = hash[1];\n\n\tsize_t offset = 0;\n\n\t// body\n\tfor (; offset + 16 <= size; offset += 16)\n\t{\n\t\tuint64_t k1, k2;\n\t\tmemcpy(&k1, static_cast<const char*>(data) + offset + 0, 8);\n\t\tmemcpy(&k2, static_cast<const char*>(data) + offset + 8, 8);\n\n\t\tk1 *= c1, k1 = ROTL64(k1, 31), k1 *= c2;\n\t\th1 ^= k1, h1 = ROTL64(h1, 27), h1 += h2;\n\t\th1 = h1 * 5 + 0x52dce729;\n\t\tk2 *= c2, k2 = ROTL64(k2, 33), k2 *= c1;\n\t\th2 ^= k2, h2 = ROTL64(h2, 31), h2 += h1;\n\t\th2 = h2 * 5 + 0x38495ab5;\n\t}\n\n\t// tail\n\tif (offset < size)\n\t{\n\t\tuint64_t tail[2] = {};\n\t\tmemcpy(tail, static_cast<const char*>(data) + offset, size - offset);\n\n\t\tuint64_t k1 = tail[0], k2 = tail[1];\n\n\t\tk1 *= c1, k1 = ROTL64(k1, 31), k1 *= c2;\n\t\th1 ^= k1;\n\t\tk2 *= c2, k2 = ROTL64(k2, 33), k2 *= c1;\n\t\th2 ^= k2;\n\t}\n\n\th1 ^= size;\n\th2 ^= size;\n\n\thash[0] = h1;\n\thash[1] = h2;\n\n#undef ROTL64\n}\n\nvoid hashMesh(Mesh& mesh)\n{\n\tmesh.geometry_hash[0] = mesh.geometry_hash[1] = 41;\n\n\tfor (size_t i = 0; i < mesh.streams.size(); ++i)\n\t{\n\t\tconst Stream& stream = mesh.streams[i];\n\n\t\tint meta[3] = {stream.type, stream.index, stream.target};\n\t\thashUpdate(mesh.geometry_hash, meta, sizeof(meta));\n\n\t\tif (stream.custom_name)\n\t\t\thashUpdate(mesh.geometry_hash, stream.custom_name, strlen(stream.custom_name));\n\n\t\thashUpdate(mesh.geometry_hash, stream.data.data(), stream.data.size() * sizeof(Attr));\n\t}\n\n\tif (!mesh.indices.empty())\n\t\thashUpdate(mesh.geometry_hash, mesh.indices.data(), mesh.indices.size() * sizeof(unsigned int));\n\n\tint meta[4] = {int(mesh.streams.size()), mesh.streams.empty() ? 0 : int(mesh.streams[0].data.size()), int(mesh.indices.size()), mesh.type};\n\thashUpdate(mesh.geometry_hash, meta, sizeof(meta));\n}\n\nstatic bool canDedupMesh(const Mesh& mesh, const Settings& settings)\n{\n\t// empty mesh\n\tif (mesh.streams.empty())\n\t\treturn false;\n\n\t// world-space mesh\n\tif (mesh.nodes.empty() && mesh.instances.empty())\n\t\treturn false;\n\n\t// has extras\n\tif (settings.keep_extras && mesh.extras.data)\n\t\treturn false;\n\n\t// to simplify dedup we ignore complex target setups for now\n\tif (!mesh.target_weights.empty() || !mesh.target_names.empty() || !mesh.variants.empty())\n\t\treturn false;\n\n\treturn true;\n}\n\nvoid dedupMeshes(std::vector<Mesh>& meshes, const Settings& settings)\n{\n\tstd::unordered_map<uint64_t, int> hashes;\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tMesh& mesh = meshes[i];\n\n\t\thashMesh(mesh);\n\t\thashes[mesh.geometry_hash[0] ^ mesh.geometry_hash[1]]++;\n\t}\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tMesh& target = meshes[i];\n\n\t\tif (!canDedupMesh(target, settings))\n\t\t\tcontinue;\n\n\t\tif (hashes[target.geometry_hash[0] ^ target.geometry_hash[1]] <= 1)\n\t\t\tcontinue;\n\n\t\tfor (size_t j = i + 1; j < meshes.size(); ++j)\n\t\t{\n\t\t\tMesh& mesh = meshes[j];\n\n\t\t\tif (mesh.geometry_hash[0] != target.geometry_hash[0] || mesh.geometry_hash[1] != target.geometry_hash[1])\n\t\t\t\tcontinue;\n\n\t\t\tif (!canDedupMesh(mesh, settings))\n\t\t\t\tcontinue;\n\n\t\t\tif (mesh.scene != target.scene || mesh.material != target.material || mesh.skin != target.skin)\n\t\t\t{\n\t\t\t\t// mark both meshes as having duplicate geometry; we don't use this in dedupMeshes but it's useful later in the pipeline\n\t\t\t\ttarget.geometry_duplicate = true;\n\t\t\t\tmesh.geometry_duplicate = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// basic sanity test; these should be included in geometry hash\n\t\t\tassert(mesh.streams.size() == target.streams.size());\n\t\t\tassert(mesh.streams[0].data.size() == target.streams[0].data.size());\n\t\t\tassert(mesh.indices.size() == target.indices.size());\n\n\t\t\ttarget.nodes.insert(target.nodes.end(), mesh.nodes.begin(), mesh.nodes.end());\n\t\t\ttarget.instances.insert(target.instances.end(), mesh.instances.begin(), mesh.instances.end());\n\n\t\t\tmesh.streams.clear();\n\t\t\tmesh.indices.clear();\n\t\t\tmesh.nodes.clear();\n\t\t\tmesh.instances.clear();\n\t\t}\n\t}\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tMesh& target = meshes[i];\n\t\tif (target.nodes.size() <= 1)\n\t\t\tcontinue;\n\n\t\tstd::sort(target.nodes.begin(), target.nodes.end());\n\t\ttarget.nodes.erase(std::unique(target.nodes.begin(), target.nodes.end()), target.nodes.end());\n\t}\n}\n\nvoid mergeMeshInstances(Mesh& mesh)\n{\n\tif (mesh.nodes.empty())\n\t\treturn;\n\n\t// fast-path: for single instance meshes we transform in-place\n\tif (mesh.nodes.size() == 1)\n\t{\n\t\ttransformMesh(mesh, mesh, mesh.nodes[0]);\n\t\tmesh.nodes.clear();\n\t\treturn;\n\t}\n\n\tMesh base = mesh;\n\tMesh transformed = base;\n\n\tfor (size_t i = 0; i < mesh.streams.size(); ++i)\n\t{\n\t\tmesh.streams[i].data.clear();\n\t\tmesh.streams[i].data.reserve(base.streams[i].data.size() * mesh.nodes.size());\n\t}\n\n\tmesh.indices.clear();\n\tmesh.indices.reserve(base.indices.size() * mesh.nodes.size());\n\n\tfor (size_t i = 0; i < mesh.nodes.size(); ++i)\n\t{\n\t\ttransformMesh(transformed, base, mesh.nodes[i]);\n\t\tmergeMeshes(mesh, transformed);\n\t}\n\n\tmesh.nodes.clear();\n}\n\nvoid mergeMeshes(std::vector<Mesh>& meshes, const Settings& settings)\n{\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tMesh& target = meshes[i];\n\n\t\tif (target.streams.empty())\n\t\t\tcontinue;\n\n\t\tsize_t target_vertices = target.streams[0].data.size();\n\t\tsize_t target_indices = target.indices.size();\n\n\t\tsize_t last_merged = i;\n\n\t\tfor (size_t j = i + 1; j < meshes.size(); ++j)\n\t\t{\n\t\t\tMesh& mesh = meshes[j];\n\n\t\t\tif (!mesh.streams.empty() && canMergeMeshes(target, mesh, settings))\n\t\t\t{\n\t\t\t\ttarget_vertices += mesh.streams[0].data.size();\n\t\t\t\ttarget_indices += mesh.indices.size();\n\t\t\t\tlast_merged = j;\n\t\t\t}\n\t\t}\n\n\t\tfor (size_t j = 0; j < target.streams.size(); ++j)\n\t\t\ttarget.streams[j].data.reserve(target_vertices);\n\n\t\ttarget.indices.reserve(target_indices);\n\n\t\tfor (size_t j = i + 1; j <= last_merged; ++j)\n\t\t{\n\t\t\tMesh& mesh = meshes[j];\n\n\t\t\tif (!mesh.streams.empty() && canMergeMeshes(target, mesh, settings))\n\t\t\t{\n\t\t\t\tmergeMeshes(target, mesh);\n\n\t\t\t\tmesh.streams.clear();\n\t\t\t\tmesh.indices.clear();\n\t\t\t\tmesh.nodes.clear();\n\t\t\t\tmesh.instances.clear();\n\t\t\t}\n\t\t}\n\n\t\tassert(target.streams[0].data.size() == target_vertices);\n\t\tassert(target.indices.size() == target_indices);\n\t}\n}\n\nvoid filterEmptyMeshes(std::vector<Mesh>& meshes)\n{\n\tsize_t write = 0;\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tMesh& mesh = meshes[i];\n\n\t\tif (mesh.streams.empty())\n\t\t\tcontinue;\n\n\t\tif (mesh.streams[0].data.empty())\n\t\t\tcontinue;\n\n\t\tif (mesh.type != cgltf_primitive_type_points && mesh.indices.empty())\n\t\t\tcontinue;\n\n\t\t// the following code is roughly equivalent to meshes[write] = std::move(mesh)\n\t\tstd::vector<Stream> streams;\n\t\tstreams.swap(mesh.streams);\n\n\t\tstd::vector<unsigned int> indices;\n\t\tindices.swap(mesh.indices);\n\n\t\tmeshes[write] = mesh;\n\t\tmeshes[write].streams.swap(streams);\n\t\tmeshes[write].indices.swap(indices);\n\n\t\twrite++;\n\t}\n\n\tmeshes.resize(write);\n}\n\nstatic bool isConstant(const std::vector<Attr>& data, const Attr& value, float tolerance = 0.01f)\n{\n\tfor (size_t i = 0; i < data.size(); ++i)\n\t{\n\t\tconst Attr& a = data[i];\n\n\t\tif (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)\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid filterStreams(Mesh& mesh, const MaterialInfo& mi)\n{\n\tbool morph_normal = false;\n\tbool morph_tangent = false;\n\tint keep_texture_set = -1;\n\n\tfor (size_t i = 0; i < mesh.streams.size(); ++i)\n\t{\n\t\tStream& stream = mesh.streams[i];\n\n\t\tif (stream.target)\n\t\t{\n\t\t\tmorph_normal = morph_normal || (stream.type == cgltf_attribute_type_normal && !isConstant(stream.data, {0, 0, 0, 0}));\n\t\t\tmorph_tangent = morph_tangent || (stream.type == cgltf_attribute_type_tangent && !isConstant(stream.data, {0, 0, 0, 0}));\n\t\t}\n\n\t\tif (stream.type == cgltf_attribute_type_texcoord && stream.index < 32 && (mi.texture_set_mask & (1u << stream.index)) != 0)\n\t\t{\n\t\t\tkeep_texture_set = std::max(keep_texture_set, stream.index);\n\t\t}\n\t}\n\n\tsize_t write = 0;\n\n\tfor (size_t i = 0; i < mesh.streams.size(); ++i)\n\t{\n\t\tStream& stream = mesh.streams[i];\n\n\t\tif (stream.type == cgltf_attribute_type_texcoord && stream.index > keep_texture_set)\n\t\t\tcontinue;\n\n\t\tif (stream.type == cgltf_attribute_type_normal && mi.unlit)\n\t\t\tcontinue;\n\n\t\tif (stream.type == cgltf_attribute_type_tangent && !mi.needs_tangents)\n\t\t\tcontinue;\n\n\t\tif ((stream.type == cgltf_attribute_type_joints || stream.type == cgltf_attribute_type_weights) && !mesh.skin)\n\t\t\tcontinue;\n\n\t\tif (stream.type == cgltf_attribute_type_color && isConstant(stream.data, {1, 1, 1, 1}))\n\t\t\tcontinue;\n\n\t\tif (stream.target && stream.type == cgltf_attribute_type_normal && !morph_normal)\n\t\t\tcontinue;\n\n\t\tif (stream.target && stream.type == cgltf_attribute_type_tangent && !morph_tangent)\n\t\t\tcontinue;\n\n\t\tif (mesh.type == cgltf_primitive_type_points && stream.type == cgltf_attribute_type_normal && !stream.data.empty() && isConstant(stream.data, stream.data[0]))\n\t\t\tcontinue;\n\n\t\t// the following code is roughly equivalent to streams[write] = std::move(stream)\n\t\tstd::vector<Attr> data;\n\t\tdata.swap(stream.data);\n\n\t\tmesh.streams[write] = stream;\n\t\tmesh.streams[write].data.swap(data);\n\n\t\twrite++;\n\t}\n\n\tmesh.streams.resize(write);\n}\n\nstruct QuantizedTBN\n{\n\tint8_t nx, ny, nz, nw;\n\tint8_t tx, ty, tz, tw;\n};\n\nstatic void quantizeTBN(QuantizedTBN* target, size_t offset, const Attr* source, size_t size, int bits)\n{\n\tint8_t* target8 = reinterpret_cast<int8_t*>(target) + offset;\n\n\tfor (size_t i = 0; i < size; ++i)\n\t{\n\t\ttarget8[i * sizeof(QuantizedTBN) + 0] = int8_t(meshopt_quantizeSnorm(source[i].f[0], bits));\n\t\ttarget8[i * sizeof(QuantizedTBN) + 1] = int8_t(meshopt_quantizeSnorm(source[i].f[1], bits));\n\t\ttarget8[i * sizeof(QuantizedTBN) + 2] = int8_t(meshopt_quantizeSnorm(source[i].f[2], bits));\n\t\ttarget8[i * sizeof(QuantizedTBN) + 3] = int8_t(meshopt_quantizeSnorm(source[i].f[3], bits));\n\t}\n}\n\nstatic void reindexMesh(Mesh& mesh, bool quantize_tbn)\n{\n\tsize_t total_vertices = mesh.streams[0].data.size();\n\tsize_t total_indices = mesh.indices.size();\n\n\tstd::vector<QuantizedTBN> qtbn;\n\n\tstd::vector<meshopt_Stream> streams;\n\tfor (size_t i = 0; i < mesh.streams.size(); ++i)\n\t{\n\t\tconst Stream& attr = mesh.streams[i];\n\t\tif (attr.target)\n\t\t\tcontinue;\n\n\t\tassert(attr.data.size() == total_vertices);\n\n\t\tif (quantize_tbn && (attr.type == cgltf_attribute_type_normal || attr.type == cgltf_attribute_type_tangent))\n\t\t{\n\t\t\tif (qtbn.empty())\n\t\t\t{\n\t\t\t\tqtbn.resize(total_vertices);\n\n\t\t\t\tmeshopt_Stream stream = {&qtbn[0], sizeof(QuantizedTBN), sizeof(QuantizedTBN)};\n\t\t\t\tstreams.push_back(stream);\n\t\t\t}\n\n\t\t\tsize_t offset = attr.type == cgltf_attribute_type_normal ? offsetof(QuantizedTBN, nx) : offsetof(QuantizedTBN, tx);\n\t\t\tquantizeTBN(&qtbn[0], offset, &attr.data[0], total_vertices, /* bits= */ 8);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmeshopt_Stream stream = {&attr.data[0], sizeof(Attr), sizeof(Attr)};\n\t\t\tstreams.push_back(stream);\n\t\t}\n\t}\n\n\tif (streams.empty())\n\t\treturn;\n\n\tstd::vector<unsigned int> remap(total_vertices);\n\tsize_t unique_vertices = meshopt_generateVertexRemapMulti(&remap[0], &mesh.indices[0], total_indices, total_vertices, &streams[0], streams.size());\n\tassert(unique_vertices <= total_vertices);\n\n\tmeshopt_remapIndexBuffer(&mesh.indices[0], &mesh.indices[0], total_indices, &remap[0]);\n\n\tfor (size_t i = 0; i < mesh.streams.size(); ++i)\n\t{\n\t\tassert(mesh.streams[i].data.size() == total_vertices);\n\n\t\tmeshopt_remapVertexBuffer(&mesh.streams[i].data[0], &mesh.streams[i].data[0], total_vertices, sizeof(Attr), &remap[0]);\n\t\tmesh.streams[i].data.resize(unique_vertices);\n\t}\n}\n\nstatic void filterTriangles(Mesh& mesh)\n{\n\tassert(mesh.type == cgltf_primitive_type_triangles);\n\n\tunsigned int* indices = &mesh.indices[0];\n\tsize_t total_indices = mesh.indices.size();\n\n\tsize_t write = 0;\n\n\tfor (size_t i = 0; i < total_indices; i += 3)\n\t{\n\t\tunsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2];\n\n\t\tif (a != b && a != c && b != c)\n\t\t{\n\t\t\tindices[write + 0] = a;\n\t\t\tindices[write + 1] = b;\n\t\t\tindices[write + 2] = c;\n\t\t\twrite += 3;\n\t\t}\n\t}\n\n\tmesh.indices.resize(write);\n}\n\nstatic void simplifyAttributes(std::vector<float>& attrs, float* attrw, size_t stride, Mesh& mesh)\n{\n\tassert(stride >= 6); // normal + color\n\n\tsize_t vertex_count = mesh.streams[0].data.size();\n\n\tattrs.resize(vertex_count * stride);\n\tfloat* data = attrs.data();\n\n\tif (const Stream* attr = getStream(mesh, cgltf_attribute_type_normal))\n\t{\n\t\tconst Attr* a = attr->data.data();\n\n\t\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\t{\n\t\t\tdata[i * stride + 0] = a[i].f[0];\n\t\t\tdata[i * stride + 1] = a[i].f[1];\n\t\t\tdata[i * stride + 2] = a[i].f[2];\n\t\t}\n\n\t\tattrw[0] = attrw[1] = attrw[2] = 0.5f;\n\t}\n\n\tif (const Stream* attr = getStream(mesh, cgltf_attribute_type_color))\n\t{\n\t\tconst Attr* a = attr->data.data();\n\n\t\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\t{\n\t\t\tdata[i * stride + 3] = a[i].f[0] * a[i].f[3];\n\t\t\tdata[i * stride + 4] = a[i].f[1] * a[i].f[3];\n\t\t\tdata[i * stride + 5] = a[i].f[2] * a[i].f[3];\n\t\t}\n\n\t\tattrw[3] = attrw[4] = attrw[5] = 1.0f;\n\t}\n}\n\nstatic void simplifyProtect(std::vector<unsigned char>& locks, Mesh& mesh, size_t presplit_vertices)\n{\n\tconst Stream* positions = getStream(mesh, cgltf_attribute_type_position);\n\tassert(positions);\n\n\tsize_t vertex_count = positions->data.size();\n\n\tlocks.resize(vertex_count);\n\tunsigned char* data = locks.data();\n\n\tstd::vector<unsigned int> remap(vertex_count);\n\tmeshopt_generatePositionRemap(&remap[0], positions->data[0].f, vertex_count, sizeof(Attr));\n\n\t// protect UV discontinuities\n\tif (Stream* attr = getStream(mesh, cgltf_attribute_type_texcoord))\n\t{\n\t\tAttr* a = attr->data.data();\n\n\t\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\t{\n\t\t\tunsigned int r = remap[i];\n\n\t\t\tif (r != i && (a[i].f[0] != a[r].f[0] || a[i].f[1] != a[r].f[1]))\n\t\t\t\tdata[i] |= meshopt_SimplifyVertex_Protect;\n\t\t}\n\t}\n\t// protect all vertices that were artificially split on the UV mirror edges\n\tfor (size_t i = presplit_vertices; i < vertex_count; ++i)\n\t\tdata[i] |= meshopt_SimplifyVertex_Protect;\n}\n\nstatic void simplifyUvSplit(Mesh& mesh, std::vector<unsigned int>& remap)\n{\n\tassert(mesh.type == cgltf_primitive_type_triangles);\n\tassert(!mesh.indices.empty());\n\n\tconst Stream* uv = getStream(mesh, cgltf_attribute_type_texcoord);\n\tif (!uv)\n\t\treturn;\n\n\tsize_t vertex_count = uv->data.size();\n\n\tstd::vector<unsigned char> uvsign(mesh.indices.size() / 3);\n\tstd::vector<unsigned char> flipseam(vertex_count);\n\n\tfor (size_t i = 0; i < mesh.indices.size(); i += 3)\n\t{\n\t\tunsigned int a = mesh.indices[i + 0];\n\t\tunsigned int b = mesh.indices[i + 1];\n\t\tunsigned int c = mesh.indices[i + 2];\n\n\t\tconst Attr& va = uv->data[a];\n\t\tconst Attr& vb = uv->data[b];\n\t\tconst Attr& vc = uv->data[c];\n\n\t\tfloat 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]);\n\t\tunsigned char flag = uvarea > 0 ? 1 : (uvarea < 0 ? 2 : 0);\n\n\t\tuvsign[i / 3] = flag;\n\t\tflipseam[a] |= flag;\n\t\tflipseam[b] |= flag;\n\t\tflipseam[c] |= flag;\n\t}\n\n\tstd::vector<unsigned int> split(vertex_count);\n\tsize_t splits = 0;\n\n\tremap.resize(vertex_count);\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tremap[i] = unsigned(i);\n\n\t\tif (flipseam[i] == 3)\n\t\t{\n\t\t\tassert(remap.size() == vertex_count + splits);\n\t\t\tremap.push_back(unsigned(i));\n\n\t\t\tsplit[i] = unsigned(vertex_count + splits);\n\t\t\tsplits++;\n\n\t\t\tfor (size_t k = 0; k < mesh.streams.size(); ++k)\n\t\t\t\tmesh.streams[k].data.push_back(mesh.streams[k].data[i]);\n\t\t}\n\t}\n\n\tfor (size_t i = 0; i < mesh.indices.size(); i += 3)\n\t{\n\t\tunsigned int a = mesh.indices[i + 0];\n\t\tunsigned int b = mesh.indices[i + 1];\n\t\tunsigned int c = mesh.indices[i + 2];\n\n\t\tunsigned char sign = uvsign[i / 3];\n\n\t\tif (flipseam[a] == 3 && sign == 2)\n\t\t\tmesh.indices[i + 0] = split[a];\n\t\tif (flipseam[b] == 3 && sign == 2)\n\t\t\tmesh.indices[i + 1] = split[b];\n\t\tif (flipseam[c] == 3 && sign == 2)\n\t\t\tmesh.indices[i + 2] = split[c];\n\t}\n}\n\nstatic void simplifyMesh(Mesh& mesh, float threshold, float error, bool attributes, bool aggressive, bool lock_borders, bool permissive)\n{\n\tassert(mesh.type == cgltf_primitive_type_triangles);\n\n\tif (mesh.indices.empty())\n\t\treturn;\n\n\tconst Stream* positions = getStream(mesh, cgltf_attribute_type_position);\n\tif (!positions)\n\t\treturn;\n\n\tsize_t presplit_vertices = positions->data.size();\n\n\tstd::vector<unsigned int> uvremap;\n\tif (attributes)\n\t\tsimplifyUvSplit(mesh, uvremap);\n\n\tsize_t vertex_count = positions->data.size();\n\n\tsize_t target_index_count = size_t(double(size_t(mesh.indices.size() / 3)) * threshold) * 3;\n\tfloat target_error = error;\n\tfloat target_error_aggressive = 1e-1f;\n\n\tunsigned int options = 0;\n\tif (lock_borders)\n\t\toptions |= meshopt_SimplifyLockBorder;\n\telse\n\t\toptions |= meshopt_SimplifyPrune;\n\n\tif (permissive)\n\t\toptions |= meshopt_SimplifyPermissive;\n\n\tif (mesh.targets || getStream(mesh, cgltf_attribute_type_weights))\n\t\toptions |= meshopt_SimplifyRegularize;\n\n\tstd::vector<unsigned int> indices(mesh.indices.size());\n\n\tfloat attrw[6] = {};\n\tstd::vector<float> attrs;\n\tif (attributes)\n\t\tsimplifyAttributes(attrs, attrw, sizeof(attrw) / sizeof(attrw[0]), mesh);\n\n\tstd::vector<unsigned char> locks;\n\tif (attributes && permissive)\n\t\tsimplifyProtect(locks, mesh, presplit_vertices);\n\n\tif (attributes)\n\t\tindices.resize(meshopt_simplifyWithAttributes(&indices[0], &mesh.indices[0], mesh.indices.size(), positions->data[0].f, vertex_count, sizeof(Attr),\n\t\t    attrs.data(), sizeof(attrw), attrw, sizeof(attrw) / sizeof(attrw[0]), permissive ? locks.data() : NULL, target_index_count, target_error, options));\n\telse\n\t\tindices.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));\n\n\tmesh.indices.swap(indices);\n\n\t// Note: if the simplifier got stuck, we can try to reindex without normals/tangents and retry\n\t// For now we simply fall back to aggressive simplifier instead\n\n\t// 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\n\tif (aggressive && mesh.indices.size() > target_index_count)\n\t{\n\t\tindices.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));\n\t\tmesh.indices.swap(indices);\n\t}\n\n\tif (uvremap.size() && mesh.indices.size())\n\t\tmeshopt_remapIndexBuffer(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), &uvremap[0]);\n}\n\nstatic void optimizeMesh(Mesh& mesh, bool compressmore)\n{\n\tassert(mesh.type == cgltf_primitive_type_triangles);\n\n\tif (mesh.indices.empty())\n\t\treturn;\n\n\tsize_t vertex_count = mesh.streams[0].data.size();\n\n\tif (compressmore)\n\t\tmeshopt_optimizeVertexCacheStrip(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), vertex_count);\n\telse\n\t\tmeshopt_optimizeVertexCache(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), vertex_count);\n\n\tstd::vector<unsigned int> remap(vertex_count);\n\tsize_t unique_vertices = meshopt_optimizeVertexFetchRemap(&remap[0], &mesh.indices[0], mesh.indices.size(), vertex_count);\n\tassert(unique_vertices <= vertex_count);\n\n\tmeshopt_remapIndexBuffer(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), &remap[0]);\n\n\tfor (size_t i = 0; i < mesh.streams.size(); ++i)\n\t{\n\t\tassert(mesh.streams[i].data.size() == vertex_count);\n\n\t\tmeshopt_remapVertexBuffer(&mesh.streams[i].data[0], &mesh.streams[i].data[0], vertex_count, sizeof(Attr), &remap[0]);\n\t\tmesh.streams[i].data.resize(unique_vertices);\n\t}\n}\n\nstruct BoneInfluence\n{\n\tfloat i;\n\tfloat w;\n};\n\nstruct BoneInfluenceWeightPredicate\n{\n\tbool operator()(const BoneInfluence& lhs, const BoneInfluence& rhs) const\n\t{\n\t\treturn lhs.w > rhs.w;\n\t}\n};\n\nstatic void filterBones(Mesh& mesh)\n{\n\tconst int kMaxGroups = 8;\n\n\tstd::pair<Stream*, Stream*> groups[kMaxGroups];\n\tint group_count = 0;\n\n\t// gather all joint/weight groups; each group contains 4 bone influences\n\tfor (int i = 0; i < kMaxGroups; ++i)\n\t{\n\t\tStream* jg = getStream(mesh, cgltf_attribute_type_joints, int(i));\n\t\tStream* wg = getStream(mesh, cgltf_attribute_type_weights, int(i));\n\n\t\tif (!jg || !wg)\n\t\t\tbreak;\n\n\t\tgroups[group_count++] = std::make_pair(jg, wg);\n\t}\n\n\tif (group_count == 0)\n\t\treturn;\n\n\t// weights below cutoff can't be represented in quantized 8-bit storage\n\tconst float weight_cutoff = 0.5f / 255.f;\n\n\tsize_t vertex_count = mesh.streams[0].data.size();\n\n\tBoneInfluence inf[kMaxGroups * 4] = {};\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tint count = 0;\n\n\t\t// gather all bone influences for this vertex\n\t\tfor (int j = 0; j < group_count; ++j)\n\t\t{\n\t\t\tconst Attr& ja = groups[j].first->data[i];\n\t\t\tconst Attr& wa = groups[j].second->data[i];\n\n\t\t\tfor (int k = 0; k < 4; ++k)\n\t\t\t\tif (wa.f[k] > weight_cutoff)\n\t\t\t\t{\n\t\t\t\t\tinf[count].i = ja.f[k];\n\t\t\t\t\tinf[count].w = wa.f[k];\n\t\t\t\t\tcount++;\n\t\t\t\t}\n\t\t}\n\n\t\t// pick top 4 influences; this also sorts resulting influences by weight which helps renderers that use influence subset in shader LODs\n\t\tstd::sort(inf, inf + count, BoneInfluenceWeightPredicate());\n\n\t\t// copy the top 4 influences back into stream 0 - we will remove other streams at the end\n\t\tAttr& ja = groups[0].first->data[i];\n\t\tAttr& wa = groups[0].second->data[i];\n\n\t\tfor (int k = 0; k < 4; ++k)\n\t\t{\n\t\t\tif (k < count)\n\t\t\t{\n\t\t\t\tja.f[k] = inf[k].i;\n\t\t\t\twa.f[k] = inf[k].w;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tja.f[k] = 0.f;\n\t\t\t\twa.f[k] = 0.f;\n\t\t\t}\n\t\t}\n\t}\n\n\t// remove redundant weight/joint streams\n\tfor (size_t i = 0; i < mesh.streams.size();)\n\t{\n\t\tStream& s = mesh.streams[i];\n\n\t\tif ((s.type == cgltf_attribute_type_joints || s.type == cgltf_attribute_type_weights) && s.index > 0)\n\t\t\tmesh.streams.erase(mesh.streams.begin() + i);\n\t\telse\n\t\t\t++i;\n\t}\n}\n\nstatic void simplifyPointMesh(Mesh& mesh, float threshold)\n{\n\tassert(mesh.type == cgltf_primitive_type_points);\n\n\tif (threshold >= 1)\n\t\treturn;\n\n\tconst Stream* positions = getStream(mesh, cgltf_attribute_type_position);\n\tif (!positions)\n\t\treturn;\n\n\tconst Stream* colors = getStream(mesh, cgltf_attribute_type_color);\n\n\tsize_t vertex_count = mesh.streams[0].data.size();\n\n\tsize_t target_vertex_count = size_t(double(vertex_count) * threshold);\n\n\tconst float color_weight = 1;\n\n\tstd::vector<unsigned int> indices(target_vertex_count);\n\tif (target_vertex_count)\n\t\tindices.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));\n\n\tstd::vector<Attr> scratch(indices.size());\n\n\tfor (size_t i = 0; i < mesh.streams.size(); ++i)\n\t{\n\t\tstd::vector<Attr>& data = mesh.streams[i].data;\n\n\t\tassert(data.size() == vertex_count);\n\n\t\tfor (size_t j = 0; j < indices.size(); ++j)\n\t\t\tscratch[j] = data[indices[j]];\n\n\t\tdata = scratch;\n\t}\n}\n\nstatic void sortPointMesh(Mesh& mesh)\n{\n\tassert(mesh.type == cgltf_primitive_type_points);\n\n\tconst Stream* positions = getStream(mesh, cgltf_attribute_type_position);\n\tif (!positions)\n\t\treturn;\n\n\t// 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\n\tif (getStream(mesh, cgltf_attribute_type_custom))\n\t\treturn;\n\n\tsize_t vertex_count = mesh.streams[0].data.size();\n\n\tstd::vector<unsigned int> remap(vertex_count);\n\tmeshopt_spatialSortRemap(&remap[0], positions->data[0].f, vertex_count, sizeof(Attr));\n\n\tfor (size_t i = 0; i < mesh.streams.size(); ++i)\n\t{\n\t\tassert(mesh.streams[i].data.size() == vertex_count);\n\n\t\tmeshopt_remapVertexBuffer(&mesh.streams[i].data[0], &mesh.streams[i].data[0], vertex_count, sizeof(Attr), &remap[0]);\n\t}\n}\n\nvoid processMesh(Mesh& mesh, const Settings& settings)\n{\n\tswitch (mesh.type)\n\t{\n\tcase cgltf_primitive_type_points:\n\t\tassert(mesh.indices.empty());\n\t\tsimplifyPointMesh(mesh, settings.simplify_ratio);\n\t\tsortPointMesh(mesh);\n\t\tbreak;\n\n\tcase cgltf_primitive_type_lines:\n\t\tbreak;\n\n\tcase cgltf_primitive_type_triangles:\n\t\tfilterBones(mesh);\n\t\treindexMesh(mesh, settings.quantize && !settings.nrm_float);\n\t\tfilterTriangles(mesh);\n\n\t\tif (settings.simplify_ratio < 1)\n\t\t{\n\t\t\tfloat error = settings.simplify_scaled ? settings.simplify_error / mesh.quality : settings.simplify_error;\n\t\t\tsimplifyMesh(mesh, settings.simplify_ratio, error, settings.simplify_attributes, settings.simplify_aggressive, settings.simplify_lock_borders, settings.simplify_permissive);\n\t\t}\n\n\t\toptimizeMesh(mesh, settings.compressmore);\n\t\tbreak;\n\n\tdefault:\n\t\tassert(!\"Unknown primitive type\");\n\t}\n}\n\nstatic float getScale(const float* transform)\n{\n\tfloat translation[3], rotation[4], scale[3];\n\tdecomposeTransform(translation, rotation, scale, transform);\n\n\tfloat sx = fabsf(scale[0]), sy = fabsf(scale[1]), sz = fabsf(scale[2]);\n\treturn std::max(std::max(sx, sy), sz);\n}\n\nvoid computeMeshQuality(std::vector<Mesh>& meshes)\n{\n\tstd::vector<float> scales(meshes.size(), 1.f);\n\tfloat maxscale = 0.f;\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tMesh& mesh = meshes[i];\n\n\t\tconst Stream* positions = getStream(mesh, cgltf_attribute_type_position);\n\t\tif (!positions)\n\t\t\tcontinue;\n\n\t\tfloat geometry_scale = meshopt_simplifyScale(positions->data[0].f, positions->data.size(), sizeof(Attr));\n\t\tfloat node_maxscale = 0.f;\n\n\t\tfor (cgltf_node* node : mesh.nodes)\n\t\t{\n\t\t\tfloat transform[16];\n\t\t\tcgltf_node_transform_world(node, transform);\n\n\t\t\tnode_maxscale = std::max(node_maxscale, getScale(transform));\n\t\t}\n\n\t\tfor (const Instance& xf : mesh.instances)\n\t\t\tnode_maxscale = std::max(node_maxscale, getScale(xf.transform));\n\n\t\tscales[i] = node_maxscale == 0.f ? geometry_scale : node_maxscale * geometry_scale;\n\t\tmaxscale = std::max(maxscale, scales[i]);\n\t}\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t\tmeshes[i].quality = (scales[i] == 0.f || maxscale == 0.f) ? 1.f : scales[i] / maxscale;\n}\n\nbool hasVertexAlpha(const Mesh& mesh)\n{\n\tconst Stream* color = getStream(const_cast<Mesh&>(mesh), cgltf_attribute_type_color);\n\tif (!color)\n\t\treturn false;\n\n\tfor (size_t i = 0; i < color->data.size(); ++i)\n\t\tif (color->data[i].f[3] < 1.f)\n\t\t\treturn true;\n\n\treturn false;\n}\n\nbool hasInstanceAlpha(const std::vector<Instance>& instances)\n{\n\tfor (const Instance& instance : instances)\n\t\tif (instance.color[3] < 1.f)\n\t\t\treturn true;\n\n\treturn false;\n}\n"
  },
  {
    "path": "gltf/node.cpp",
    "content": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <math.h>\n#include <string.h>\n\nvoid markScenes(cgltf_data* data, std::vector<NodeInfo>& nodes)\n{\n\tfor (size_t i = 0; i < nodes.size(); ++i)\n\t\tnodes[i].scene = -1;\n\n\tfor (size_t i = 0; i < data->scenes_count; ++i)\n\t\tfor (size_t j = 0; j < data->scenes[i].nodes_count; ++j)\n\t\t{\n\t\t\tNodeInfo& ni = nodes[data->scenes[i].nodes[j] - data->nodes];\n\n\t\t\tif (ni.scene >= 0)\n\t\t\t\tni.scene = -2; // multiple scenes\n\t\t\telse\n\t\t\t\tni.scene = int(i);\n\t\t}\n\n\tfor (size_t i = 0; i < data->nodes_count; ++i)\n\t{\n\t\tcgltf_node* root = &data->nodes[i];\n\t\twhile (root->parent)\n\t\t\troot = root->parent;\n\n\t\tnodes[i].scene = nodes[root - data->nodes].scene;\n\t}\n}\n\nvoid markAnimated(cgltf_data* data, std::vector<NodeInfo>& nodes, const std::vector<Animation>& animations)\n{\n\tfor (size_t i = 0; i < animations.size(); ++i)\n\t{\n\t\tconst Animation& animation = animations[i];\n\n\t\tfor (size_t j = 0; j < animation.tracks.size(); ++j)\n\t\t{\n\t\t\tconst Track& track = animation.tracks[j];\n\n\t\t\t// mark nodes that have animation tracks that change their base transform as animated\n\t\t\tif (!track.dummy)\n\t\t\t{\n\t\t\t\tNodeInfo& ni = nodes[track.node - data->nodes];\n\n\t\t\t\tni.animated_path_mask |= (1 << track.path);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (size_t i = 0; i < data->nodes_count; ++i)\n\t{\n\t\tNodeInfo& ni = nodes[i];\n\n\t\tfor (cgltf_node* node = &data->nodes[i]; node; node = node->parent)\n\t\t\tni.animated |= nodes[node - data->nodes].animated_path_mask != 0;\n\t}\n}\n\nvoid markNeededNodes(cgltf_data* data, std::vector<NodeInfo>& nodes, const std::vector<Mesh>& meshes, const std::vector<Animation>& animations, const Settings& settings)\n{\n\t// mark all joints as kept\n\tfor (size_t i = 0; i < data->skins_count; ++i)\n\t{\n\t\tconst cgltf_skin& skin = data->skins[i];\n\n\t\t// for now we keep all joints directly referenced by the skin and the entire ancestry tree; we keep names for joints as well\n\t\tfor (size_t j = 0; j < skin.joints_count; ++j)\n\t\t{\n\t\t\tNodeInfo& ni = nodes[skin.joints[j] - data->nodes];\n\n\t\t\tni.keep = true;\n\t\t}\n\t}\n\n\t// mark all animated nodes as kept\n\tfor (size_t i = 0; i < animations.size(); ++i)\n\t{\n\t\tconst Animation& animation = animations[i];\n\n\t\tfor (size_t j = 0; j < animation.tracks.size(); ++j)\n\t\t{\n\t\t\tconst Track& track = animation.tracks[j];\n\n\t\t\tif (settings.anim_const || !track.dummy)\n\t\t\t{\n\t\t\t\tNodeInfo& ni = nodes[track.node - data->nodes];\n\n\t\t\t\tni.keep = true;\n\t\t\t}\n\t\t}\n\t}\n\n\t// mark all mesh nodes as kept\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tconst Mesh& mesh = meshes[i];\n\n\t\tfor (size_t j = 0; j < mesh.nodes.size(); ++j)\n\t\t{\n\t\t\tNodeInfo& ni = nodes[mesh.nodes[j] - data->nodes];\n\n\t\t\tni.keep = true;\n\t\t}\n\t}\n\n\t// mark all light/camera nodes as kept\n\tfor (size_t i = 0; i < data->nodes_count; ++i)\n\t{\n\t\tconst cgltf_node& node = data->nodes[i];\n\n\t\tif (node.light || node.camera)\n\t\t{\n\t\t\tnodes[i].keep = true;\n\t\t}\n\t}\n\n\t// mark all named nodes as needed (if -kn is specified)\n\tif (settings.keep_nodes)\n\t{\n\t\tfor (size_t i = 0; i < data->nodes_count; ++i)\n\t\t{\n\t\t\tconst cgltf_node& node = data->nodes[i];\n\n\t\t\tif (node.name && *node.name)\n\t\t\t{\n\t\t\t\tnodes[i].keep = true;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid remapNodes(cgltf_data* data, std::vector<NodeInfo>& nodes, size_t& node_offset)\n{\n\t// to keep a node, we currently need to keep the entire ancestry chain\n\tfor (size_t i = 0; i < data->nodes_count; ++i)\n\t{\n\t\tif (!nodes[i].keep)\n\t\t\tcontinue;\n\n\t\tfor (cgltf_node* node = &data->nodes[i]; node; node = node->parent)\n\t\t\tnodes[node - data->nodes].keep = true;\n\t}\n\n\t// generate sequential indices for all nodes; they aren't sorted topologically\n\tfor (size_t i = 0; i < data->nodes_count; ++i)\n\t{\n\t\tNodeInfo& ni = nodes[i];\n\n\t\tif (ni.keep)\n\t\t{\n\t\t\tni.remap = int(node_offset);\n\n\t\t\tnode_offset++;\n\t\t}\n\t}\n}\n\nvoid decomposeTransform(float translation[3], float rotation[4], float scale[3], const float* transform)\n{\n\tfloat m[4][4] = {};\n\tmemcpy(m, transform, 16 * sizeof(float));\n\n\t// extract translation from last row\n\ttranslation[0] = m[3][0];\n\ttranslation[1] = m[3][1];\n\ttranslation[2] = m[3][2];\n\n\t// compute determinant to determine handedness\n\tfloat det =\n\t    m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2]) -\n\t    m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) +\n\t    m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);\n\n\tfloat sign = (det < 0.f) ? -1.f : 1.f;\n\n\t// recover scale from axis lengths\n\tscale[0] = sqrtf(m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]) * sign;\n\tscale[1] = sqrtf(m[1][0] * m[1][0] + m[1][1] * m[1][1] + m[1][2] * m[1][2]) * sign;\n\tscale[2] = sqrtf(m[2][0] * m[2][0] + m[2][1] * m[2][1] + m[2][2] * m[2][2]) * sign;\n\n\t// normalize axes to get a pure rotation matrix\n\tfloat rsx = (scale[0] == 0.f) ? 0.f : 1.f / scale[0];\n\tfloat rsy = (scale[1] == 0.f) ? 0.f : 1.f / scale[1];\n\tfloat rsz = (scale[2] == 0.f) ? 0.f : 1.f / scale[2];\n\n\tfloat r00 = m[0][0] * rsx, r10 = m[1][0] * rsy, r20 = m[2][0] * rsz;\n\tfloat r01 = m[0][1] * rsx, r11 = m[1][1] * rsy, r21 = m[2][1] * rsz;\n\tfloat r02 = m[0][2] * rsx, r12 = m[1][2] * rsy, r22 = m[2][2] * rsz;\n\n\t// \"branchless\" version of Mike Day's matrix to quaternion conversion\n\tint qc = r22 < 0 ? (r00 > r11 ? 0 : 1) : (r00 < -r11 ? 2 : 3);\n\tfloat qs1 = qc & 2 ? -1.f : 1.f;\n\tfloat qs2 = qc & 1 ? -1.f : 1.f;\n\tfloat qs3 = (qc - 1) & 2 ? -1.f : 1.f;\n\n\tfloat qt = 1.f - qs3 * r00 - qs2 * r11 - qs1 * r22;\n\tfloat qs = 0.5f / sqrtf(qt);\n\n\trotation[qc ^ 0] = qs * qt;\n\trotation[qc ^ 1] = qs * (r01 + qs1 * r10);\n\trotation[qc ^ 2] = qs * (r20 + qs2 * r02);\n\trotation[qc ^ 3] = qs * (r12 + qs3 * r21);\n}\n"
  },
  {
    "path": "gltf/package.json",
    "content": "{\n\t\"name\": \"gltfpack\",\n\t\"version\": \"1.0.0\",\n\t\"description\": \"A command-line tool that can optimize glTF files for size and speed\",\n\t\"author\": \"Arseny Kapoulkine\",\n\t\"license\": \"MIT\",\n\t\"bugs\": \"https://github.com/zeux/meshoptimizer/issues\",\n\t\"homepage\": \"https://github.com/zeux/meshoptimizer\",\n\t\"keywords\": [\n\t\t\"gltf\"\n\t],\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"https://github.com/zeux/meshoptimizer\"\n\t},\n\t\"type\": \"module\",\n\t\"bin\": \"./cli.js\",\n\t\"main\": \"./library.js\",\n\t\"files\": [\n\t\t\"*.js\",\n\t\t\"*.wasm\"\n\t],\n\t\"scripts\": {\n\t\t\"prepublishOnly\": \"node cli.js -v\"\n\t},\n\t\"engines\": {\n\t\t\"node\": \">=18\"\n\t}\n}\n"
  },
  {
    "path": "gltf/parsegltf.cpp",
    "content": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"../src/meshoptimizer.h\"\n\nstatic const size_t kMaxStreams = 16;\n\nstatic const char* getError(cgltf_result result, cgltf_data* data)\n{\n\tswitch (result)\n\t{\n\tcase cgltf_result_file_not_found:\n\t\treturn data ? \"resource not found\" : \"file not found\";\n\n\tcase cgltf_result_io_error:\n\t\treturn \"I/O error\";\n\n\tcase cgltf_result_invalid_json:\n\t\treturn \"invalid JSON\";\n\n\tcase cgltf_result_invalid_gltf:\n\t\treturn \"invalid GLTF\";\n\n\tcase cgltf_result_out_of_memory:\n\t\treturn \"out of memory\";\n\n\tcase cgltf_result_legacy_gltf:\n\t\treturn \"legacy GLTF\";\n\n\tcase cgltf_result_data_too_short:\n\t\treturn data ? \"buffer too short\" : \"not a GLTF file\";\n\n\tcase cgltf_result_unknown_format:\n\t\treturn data ? \"unknown resource format\" : \"not a GLTF file\";\n\n\tdefault:\n\t\treturn \"unknown error\";\n\t}\n}\n\nstatic void readAccessor(std::vector<float>& data, const cgltf_accessor* accessor)\n{\n\tassert(accessor->type == cgltf_type_scalar);\n\n\tdata.resize(accessor->count);\n\tcgltf_accessor_unpack_floats(accessor, &data[0], data.size());\n}\n\nstatic void readAccessor(std::vector<Attr>& data, const cgltf_accessor* accessor)\n{\n\tsize_t components = cgltf_num_components(accessor->type);\n\n\tstd::vector<float> temp(accessor->count * components);\n\tcgltf_accessor_unpack_floats(accessor, &temp[0], temp.size());\n\n\tdata.resize(accessor->count);\n\n\tfor (size_t i = 0; i < accessor->count; ++i)\n\t{\n\t\tfor (size_t k = 0; k < components && k < 4; ++k)\n\t\t\tdata[i].f[k] = temp[i * components + k];\n\t}\n}\n\nstatic void readAccessor(std::vector<Attr>& data, const cgltf_accessor* accessor, const std::vector<unsigned int>& sparse)\n{\n\tdata.resize(sparse.size());\n\n\tfor (size_t i = 0; i < sparse.size(); ++i)\n\t\tcgltf_accessor_read_float(accessor, sparse[i], &data[i].f[0], 4);\n}\n\nstatic void fixupIndices(std::vector<unsigned int>& indices, cgltf_primitive_type& type)\n{\n\tif (type == cgltf_primitive_type_line_loop)\n\t{\n\t\tstd::vector<unsigned int> result;\n\t\tresult.reserve(indices.size() * 2 + 2);\n\n\t\tfor (size_t i = 1; i <= indices.size(); ++i)\n\t\t{\n\t\t\tresult.push_back(indices[i - 1]);\n\t\t\tresult.push_back(indices[i % indices.size()]);\n\t\t}\n\n\t\tindices.swap(result);\n\t\ttype = cgltf_primitive_type_lines;\n\t}\n\telse if (type == cgltf_primitive_type_line_strip)\n\t{\n\t\tstd::vector<unsigned int> result;\n\t\tresult.reserve(indices.size() * 2);\n\n\t\tfor (size_t i = 1; i < indices.size(); ++i)\n\t\t{\n\t\t\tresult.push_back(indices[i - 1]);\n\t\t\tresult.push_back(indices[i]);\n\t\t}\n\n\t\tindices.swap(result);\n\t\ttype = cgltf_primitive_type_lines;\n\t}\n\telse if (type == cgltf_primitive_type_triangle_strip)\n\t{\n\t\tstd::vector<unsigned int> result;\n\t\tresult.reserve(indices.size() * 3);\n\n\t\tfor (size_t i = 2; i < indices.size(); ++i)\n\t\t{\n\t\t\tint flip = i & 1;\n\n\t\t\tresult.push_back(indices[i - 2 + flip]);\n\t\t\tresult.push_back(indices[i - 1 - flip]);\n\t\t\tresult.push_back(indices[i]);\n\t\t}\n\n\t\tindices.swap(result);\n\t\ttype = cgltf_primitive_type_triangles;\n\t}\n\telse if (type == cgltf_primitive_type_triangle_fan)\n\t{\n\t\tstd::vector<unsigned int> result;\n\t\tresult.reserve(indices.size() * 3);\n\n\t\tfor (size_t i = 2; i < indices.size(); ++i)\n\t\t{\n\t\t\tresult.push_back(indices[0]);\n\t\t\tresult.push_back(indices[i - 1]);\n\t\t\tresult.push_back(indices[i]);\n\t\t}\n\n\t\tindices.swap(result);\n\t\ttype = cgltf_primitive_type_triangles;\n\t}\n\telse if (type == cgltf_primitive_type_lines)\n\t{\n\t\t// glTF files don't require that line index count is divisible by 2, but it is obviously critical for scenes to render\n\t\tindices.resize(indices.size() / 2 * 2);\n\t}\n\telse if (type == cgltf_primitive_type_triangles)\n\t{\n\t\t// glTF files don't require that triangle index count is divisible by 3, but it is obviously critical for scenes to render\n\t\tindices.resize(indices.size() / 3 * 3);\n\t}\n}\n\nstatic bool isIdAttribute(const char* name)\n{\n\treturn strcmp(name, \"_ID\") == 0 ||\n\t       strcmp(name, \"_BATCHID\") == 0 ||\n\t       strncmp(name, \"_FEATURE_ID_\", 12) == 0;\n}\n\nstatic void parseMeshesGltf(cgltf_data* data, std::vector<Mesh>& meshes, std::vector<std::pair<size_t, size_t> >& mesh_remap)\n{\n\tsize_t total_primitives = 0;\n\n\tfor (size_t mi = 0; mi < data->meshes_count; ++mi)\n\t\ttotal_primitives += data->meshes[mi].primitives_count;\n\n\tmeshes.reserve(total_primitives);\n\tmesh_remap.resize(data->meshes_count);\n\n\tfor (size_t mi = 0; mi < data->meshes_count; ++mi)\n\t{\n\t\tconst cgltf_mesh& mesh = data->meshes[mi];\n\n\t\tsize_t remap_offset = meshes.size();\n\n\t\tfor (size_t pi = 0; pi < mesh.primitives_count; ++pi)\n\t\t{\n\t\t\tconst cgltf_primitive& primitive = mesh.primitives[pi];\n\n\t\t\tif (primitive.type == cgltf_primitive_type_points && primitive.indices)\n\t\t\t{\n\t\t\t\tfprintf(stderr, \"Warning: ignoring primitive %d of mesh %d because indexed points are not supported\\n\", int(pi), int(mi));\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tmeshes.push_back(Mesh());\n\t\t\tMesh& result = meshes.back();\n\n\t\t\tresult.scene = -1;\n\t\t\tresult.material = primitive.material;\n\t\t\tresult.extras = primitive.extras;\n\t\t\tresult.type = primitive.type;\n\n\t\t\tresult.streams.reserve(primitive.attributes_count);\n\n\t\t\tsize_t vertex_count = primitive.attributes_count ? primitive.attributes[0].data->count : 0;\n\n\t\t\tif (primitive.indices)\n\t\t\t{\n\t\t\t\tresult.indices.resize(primitive.indices->count);\n\t\t\t\tif (!result.indices.empty())\n\t\t\t\t\tcgltf_accessor_unpack_indices(primitive.indices, &result.indices[0], sizeof(unsigned int), result.indices.size());\n\n\t\t\t\tfor (size_t i = 0; i < result.indices.size(); ++i)\n\t\t\t\t\tassert(result.indices[i] < vertex_count);\n\t\t\t}\n\t\t\telse if (primitive.type != cgltf_primitive_type_points)\n\t\t\t{\n\t\t\t\t// note, while we could generate a good index buffer here, mesh will be reindexed during processing\n\t\t\t\tresult.indices.resize(vertex_count);\n\t\t\t\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\t\t\t\tresult.indices[i] = unsigned(i);\n\t\t\t}\n\n\t\t\t// convert line loops and line/triangle strips to lists\n\t\t\tfixupIndices(result.indices, result.type);\n\n\t\t\tstd::vector<unsigned int> sparse;\n\n\t\t\t// if the index data is very sparse, switch to deindexing on the fly to avoid the excessive cost of reading large accessors\n\t\t\tif (!result.indices.empty() && result.indices.size() < vertex_count / 2)\n\t\t\t{\n\t\t\t\tsparse = result.indices;\n\n\t\t\t\t// mesh will be reindexed during processing\n\t\t\t\tfor (size_t i = 0; i < result.indices.size(); ++i)\n\t\t\t\t\tresult.indices[i] = unsigned(i);\n\t\t\t}\n\n\t\t\tfor (size_t ai = 0; ai < primitive.attributes_count; ++ai)\n\t\t\t{\n\t\t\t\tconst cgltf_attribute& attr = primitive.attributes[ai];\n\n\t\t\t\tif (attr.type == cgltf_attribute_type_invalid || (attr.type == cgltf_attribute_type_custom && !isIdAttribute(attr.name)))\n\t\t\t\t{\n\t\t\t\t\tfprintf(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));\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (result.streams.size() == kMaxStreams)\n\t\t\t\t{\n\t\t\t\t\tfprintf(stderr, \"Warning: ignoring attribute %s in primitive %d of mesh %d (limit %d reached)\\n\", attr.name, int(pi), int(mi), int(kMaxStreams));\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tresult.streams.push_back(Stream());\n\t\t\t\tStream& s = result.streams.back();\n\n\t\t\t\ts.type = attr.type;\n\t\t\t\ts.index = attr.index;\n\n\t\t\t\tif (attr.type == cgltf_attribute_type_custom)\n\t\t\t\t\ts.custom_name = attr.name;\n\n\t\t\t\tif (sparse.empty())\n\t\t\t\t\treadAccessor(s.data, attr.data);\n\t\t\t\telse\n\t\t\t\t\treadAccessor(s.data, attr.data, sparse);\n\n\t\t\t\tif (attr.type == cgltf_attribute_type_color && attr.data->type == cgltf_type_vec3)\n\t\t\t\t{\n\t\t\t\t\tfor (size_t i = 0; i < s.data.size(); ++i)\n\t\t\t\t\t\ts.data[i].f[3] = 1.0f;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (size_t ti = 0; ti < primitive.targets_count; ++ti)\n\t\t\t{\n\t\t\t\tconst cgltf_morph_target& target = primitive.targets[ti];\n\n\t\t\t\tfor (size_t ai = 0; ai < target.attributes_count; ++ai)\n\t\t\t\t{\n\t\t\t\t\tconst cgltf_attribute& attr = target.attributes[ai];\n\n\t\t\t\t\tif (attr.type == cgltf_attribute_type_invalid || attr.type == cgltf_attribute_type_custom)\n\t\t\t\t\t{\n\t\t\t\t\t\tfprintf(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));\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tresult.streams.push_back(Stream());\n\t\t\t\t\tStream& s = result.streams.back();\n\n\t\t\t\t\ts.type = attr.type;\n\t\t\t\t\ts.index = attr.index;\n\t\t\t\t\ts.target = int(ti + 1);\n\n\t\t\t\t\tif (sparse.empty())\n\t\t\t\t\t\treadAccessor(s.data, attr.data);\n\t\t\t\t\telse\n\t\t\t\t\t\treadAccessor(s.data, attr.data, sparse);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresult.targets = primitive.targets_count;\n\t\t\tresult.target_weights.assign(mesh.weights, mesh.weights + mesh.weights_count);\n\t\t\tresult.target_names.assign(mesh.target_names, mesh.target_names + mesh.target_names_count);\n\n\t\t\tresult.variants.assign(primitive.mappings, primitive.mappings + primitive.mappings_count);\n\t\t}\n\n\t\tmesh_remap[mi] = std::make_pair(remap_offset, meshes.size());\n\t}\n}\n\nstatic void parseMeshInstancesGltf(std::vector<Instance>& instances, cgltf_node* node, size_t ni)\n{\n\tcgltf_accessor* translation = NULL;\n\tcgltf_accessor* rotation = NULL;\n\tcgltf_accessor* scale = NULL;\n\tcgltf_accessor* color = NULL;\n\n\tfor (size_t i = 0; i < node->mesh_gpu_instancing.attributes_count; ++i)\n\t{\n\t\tconst cgltf_attribute& attr = node->mesh_gpu_instancing.attributes[i];\n\n\t\tif (strcmp(attr.name, \"TRANSLATION\") == 0 && attr.data->type == cgltf_type_vec3)\n\t\t\ttranslation = attr.data;\n\t\telse if (strcmp(attr.name, \"ROTATION\") == 0 && attr.data->type == cgltf_type_vec4)\n\t\t\trotation = attr.data;\n\t\telse if (strcmp(attr.name, \"SCALE\") == 0 && attr.data->type == cgltf_type_vec3)\n\t\t\tscale = attr.data;\n\t\telse if (strcmp(attr.name, \"_COLOR_0\") == 0 && (attr.data->type == cgltf_type_vec3 || attr.data->type == cgltf_type_vec4))\n\t\t\tcolor = attr.data;\n\t\telse\n\t\t\tfprintf(stderr, \"Warning: ignoring %s instance attribute %s in node %d\\n\", *attr.name == '_' ? \"custom\" : \"unknown\", attr.name, int(ni));\n\t}\n\n\tsize_t count = node->mesh_gpu_instancing.attributes[0].data->count;\n\n\tinstances.reserve(instances.size() + count);\n\n\tcgltf_node instance = {};\n\tinstance.parent = node;\n\tinstance.has_translation = translation != NULL;\n\tinstance.has_rotation = rotation != NULL;\n\tinstance.has_scale = scale != NULL;\n\tinstance.rotation[3] = 1.f;\n\tinstance.scale[0] = 1.f;\n\tinstance.scale[1] = 1.f;\n\tinstance.scale[2] = 1.f;\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tif (translation)\n\t\t\tcgltf_accessor_read_float(translation, i, instance.translation, 4);\n\t\tif (rotation)\n\t\t\tcgltf_accessor_read_float(rotation, i, instance.rotation, 4);\n\t\tif (scale)\n\t\t\tcgltf_accessor_read_float(scale, i, instance.scale, 4);\n\n\t\tInstance obj = {};\n\t\tcgltf_node_transform_world(&instance, obj.transform);\n\n\t\tobj.color[0] = obj.color[1] = obj.color[2] = obj.color[3] = 1.0f;\n\n\t\tif (color)\n\t\t\tcgltf_accessor_read_float(color, i, obj.color, 4);\n\n\t\tinstances.push_back(obj);\n\t}\n}\n\nstatic void parseMeshNodesGltf(cgltf_data* data, std::vector<Mesh>& meshes, const std::vector<std::pair<size_t, size_t> >& mesh_remap)\n{\n\tfor (size_t i = 0; i < data->nodes_count; ++i)\n\t{\n\t\tcgltf_node& node = data->nodes[i];\n\t\tif (!node.mesh)\n\t\t\tcontinue;\n\n\t\tstd::pair<size_t, size_t> range = mesh_remap[node.mesh - data->meshes];\n\n\t\tfor (size_t mi = range.first; mi < range.second; ++mi)\n\t\t{\n\t\t\tMesh* mesh = &meshes[mi];\n\n\t\t\tif (mesh->skin != node.skin && (!mesh->nodes.empty() || !mesh->instances.empty()))\n\t\t\t{\n\t\t\t\t// this should be extremely rare - if the same mesh is used with different skins, we need to duplicate it\n\t\t\t\t// in this case we don't spend any effort on keeping the number of duplicates to the minimum, because this\n\t\t\t\t// should really never happen.\n\t\t\t\tmeshes.push_back(*mesh);\n\t\t\t\tmesh = &meshes.back();\n\t\t\t}\n\n\t\t\tif (node.has_mesh_gpu_instancing)\n\t\t\t{\n\t\t\t\tmesh->scene = 0; // we need to assign scene index since instances are attached to a scene; for now we assume 0\n\t\t\t\tparseMeshInstancesGltf(mesh->instances, &node, i);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tmesh->skin = node.skin;\n\t\t\t\tmesh->nodes.push_back(&node);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tMesh& mesh = meshes[i];\n\n\t\t// because the rest of gltfpack assumes that empty nodes array = world-space mesh, we need to filter unused meshes\n\t\tif (mesh.nodes.empty() && mesh.instances.empty())\n\t\t{\n\t\t\tmesh.streams.clear();\n\t\t\tmesh.indices.clear();\n\t\t}\n\t}\n}\n\nstatic void parseAnimationsGltf(cgltf_data* data, std::vector<Animation>& animations)\n{\n\tanimations.reserve(data->animations_count);\n\n\tfor (size_t i = 0; i < data->animations_count; ++i)\n\t{\n\t\tconst cgltf_animation& animation = data->animations[i];\n\n\t\tanimations.push_back(Animation());\n\t\tAnimation& result = animations.back();\n\n\t\tresult.name = animation.name;\n\n\t\tresult.tracks.reserve(animation.channels_count);\n\n\t\tfor (size_t j = 0; j < animation.channels_count; ++j)\n\t\t{\n\t\t\tconst cgltf_animation_channel& channel = animation.channels[j];\n\n\t\t\tif (!channel.target_node)\n\t\t\t{\n\t\t\t\tfprintf(stderr, \"Warning: ignoring channel %d of animation %d (%s) because it has no target node\\n\", int(j), int(i), animation.name ? animation.name : \"\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tresult.tracks.push_back(Track());\n\t\t\tTrack& track = result.tracks.back();\n\n\t\t\ttrack.node = channel.target_node;\n\t\t\ttrack.path = channel.target_path;\n\n\t\t\ttrack.components = (channel.target_path == cgltf_animation_path_type_weights) ? track.node->mesh->primitives[0].targets_count : 1;\n\n\t\t\ttrack.interpolation = channel.sampler->interpolation;\n\n\t\t\treadAccessor(track.time, channel.sampler->input);\n\t\t\treadAccessor(track.data, channel.sampler->output);\n\t\t}\n\n\t\tif (result.tracks.empty())\n\t\t{\n\t\t\tfprintf(stderr, \"Warning: ignoring animation %d (%s) because it has no valid tracks\\n\", int(i), animation.name ? animation.name : \"\");\n\t\t\tanimations.pop_back();\n\t\t}\n\t}\n}\n\nstatic bool requiresExtension(cgltf_data* data, const char* name)\n{\n\tfor (size_t i = 0; i < data->extensions_required_count; ++i)\n\t\tif (strcmp(data->extensions_required[i], name) == 0)\n\t\t\treturn true;\n\n\treturn false;\n}\n\nstatic bool needsDummyBuffers(cgltf_data* data)\n{\n\tfor (size_t i = 0; i < data->accessors_count; ++i)\n\t{\n\t\tcgltf_accessor* accessor = &data->accessors[i];\n\n\t\tif (accessor->buffer_view && accessor->buffer_view->data == NULL && accessor->buffer_view->buffer->data == NULL)\n\t\t\treturn true;\n\n\t\tif (accessor->is_sparse)\n\t\t{\n\t\t\tcgltf_accessor_sparse* sparse = &accessor->sparse;\n\n\t\t\tif (sparse->indices_buffer_view->buffer->data == NULL)\n\t\t\t\treturn true;\n\t\t\tif (sparse->values_buffer_view->buffer->data == NULL)\n\t\t\t\treturn true;\n\t\t}\n\t}\n\n\tfor (size_t i = 0; i < data->images_count; ++i)\n\t{\n\t\tcgltf_image* image = &data->images[i];\n\n\t\tif (image->buffer_view && image->buffer_view->buffer->data == NULL)\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic void freeFile(cgltf_data* data)\n{\n\tdata->json = NULL;\n\tdata->bin = NULL;\n\n\tfree(data->file_data);\n\tdata->file_data = NULL;\n}\n\nstatic bool freeUnusedBuffers(cgltf_data* data)\n{\n\tstd::vector<char> used(data->buffers_count);\n\n\tfor (size_t i = 0; i < data->skins_count; ++i)\n\t{\n\t\tconst cgltf_skin& skin = data->skins[i];\n\n\t\tif (skin.inverse_bind_matrices && skin.inverse_bind_matrices->buffer_view)\n\t\t{\n\t\t\tassert(skin.inverse_bind_matrices->buffer_view->buffer);\n\t\t\tused[skin.inverse_bind_matrices->buffer_view->buffer - data->buffers] = 1;\n\t\t}\n\t}\n\n\tfor (size_t i = 0; i < data->images_count; ++i)\n\t{\n\t\tconst cgltf_image& image = data->images[i];\n\n\t\tif (image.buffer_view)\n\t\t{\n\t\t\tassert(image.buffer_view->buffer);\n\t\t\tused[image.buffer_view->buffer - data->buffers] = 1;\n\t\t}\n\t}\n\n\tbool free_bin = false;\n\n\tfor (size_t i = 0; i < data->buffers_count; ++i)\n\t{\n\t\tcgltf_buffer& buffer = data->buffers[i];\n\n\t\tif (!used[i] && buffer.data)\n\t\t{\n\t\t\tif (buffer.data != data->bin)\n\t\t\t\tfree(buffer.data);\n\t\t\telse\n\t\t\t\tfree_bin = true;\n\n\t\t\tbuffer.data = NULL;\n\t\t}\n\t}\n\n\treturn free_bin;\n}\n\nstatic cgltf_result decompressMeshopt(cgltf_data* data)\n{\n\tfor (size_t i = 0; i < data->buffer_views_count; ++i)\n\t{\n\t\tif (!data->buffer_views[i].has_meshopt_compression)\n\t\t\tcontinue;\n\t\tcgltf_meshopt_compression* mc = &data->buffer_views[i].meshopt_compression;\n\n\t\tconst unsigned char* source = (const unsigned char*)mc->buffer->data;\n\t\tif (!source)\n\t\t\treturn cgltf_result_invalid_gltf;\n\t\tsource += mc->offset;\n\n\t\tvoid* result = malloc(mc->count * mc->stride);\n\t\tif (!result)\n\t\t\treturn cgltf_result_out_of_memory;\n\n\t\tdata->buffer_views[i].data = result;\n\n\t\tint rc = -1;\n\n\t\tswitch (mc->mode)\n\t\t{\n\t\tcase cgltf_meshopt_compression_mode_attributes:\n\t\t\trc = meshopt_decodeVertexBuffer(result, mc->count, mc->stride, source, mc->size);\n\t\t\tbreak;\n\n\t\tcase cgltf_meshopt_compression_mode_triangles:\n\t\t\trc = meshopt_decodeIndexBuffer(result, mc->count, mc->stride, source, mc->size);\n\t\t\tbreak;\n\n\t\tcase cgltf_meshopt_compression_mode_indices:\n\t\t\trc = meshopt_decodeIndexSequence(result, mc->count, mc->stride, source, mc->size);\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\treturn cgltf_result_invalid_gltf;\n\t\t}\n\n\t\tif (rc != 0)\n\t\t\treturn cgltf_result_io_error;\n\n\t\tswitch (mc->filter)\n\t\t{\n\t\tcase cgltf_meshopt_compression_filter_octahedral:\n\t\t\tmeshopt_decodeFilterOct(result, mc->count, mc->stride);\n\t\t\tbreak;\n\n\t\tcase cgltf_meshopt_compression_filter_quaternion:\n\t\t\tmeshopt_decodeFilterQuat(result, mc->count, mc->stride);\n\t\t\tbreak;\n\n\t\tcase cgltf_meshopt_compression_filter_exponential:\n\t\t\tmeshopt_decodeFilterExp(result, mc->count, mc->stride);\n\t\t\tbreak;\n\n\t\tcase cgltf_meshopt_compression_filter_color:\n\t\t\tmeshopt_decodeFilterColor(result, mc->count, mc->stride);\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn cgltf_result_success;\n}\n\nstatic cgltf_data* parseGltf(cgltf_data* data, cgltf_result result, std::vector<Mesh>& meshes, std::vector<Animation>& animations, const char** error)\n{\n\t*error = NULL;\n\n\tif (result != cgltf_result_success)\n\t\t*error = getError(result, data);\n\telse if (requiresExtension(data, \"KHR_draco_mesh_compression\"))\n\t\t*error = \"file requires Draco mesh compression support\";\n\telse if (needsDummyBuffers(data))\n\t\t*error = \"buffer has no data\";\n\n\tif (*error)\n\t{\n\t\tcgltf_free(data);\n\t\treturn NULL;\n\t}\n\n\tif (requiresExtension(data, \"KHR_mesh_quantization\"))\n\t\tfprintf(stderr, \"Warning: file uses quantized geometry; repacking may result in increased quantization error\\n\");\n\tif (requiresExtension(data, \"EXT_mesh_gpu_instancing\") && data->scenes_count > 1)\n\t\tfprintf(stderr, \"Warning: file uses instancing and has more than one scene; results may be incorrect\\n\");\n\n\tstd::vector<std::pair<size_t, size_t> > mesh_remap;\n\n\tparseMeshesGltf(data, meshes, mesh_remap);\n\tparseMeshNodesGltf(data, meshes, mesh_remap);\n\tparseAnimationsGltf(data, animations);\n\n\tbool free_bin = freeUnusedBuffers(data);\n\n\tif (data->bin && free_bin)\n\t\tfreeFile(data);\n\n\treturn data;\n}\n\ncgltf_data* parseGltf(const char* path, std::vector<Mesh>& meshes, std::vector<Animation>& animations, const char** error)\n{\n\tcgltf_data* data = NULL;\n\n\tcgltf_options options = {};\n\tcgltf_result result = cgltf_parse_file(&options, path, &data);\n\n\tif (result == cgltf_result_success && !data->bin)\n\t\tfreeFile(data);\n\n\tresult = (result == cgltf_result_success) ? cgltf_load_buffers(&options, data, path) : result;\n\tresult = (result == cgltf_result_success) ? cgltf_validate(data) : result;\n\tresult = (result == cgltf_result_success) ? decompressMeshopt(data) : result;\n\n\treturn parseGltf(data, result, meshes, animations, error);\n}\n\ncgltf_data* parseGlb(const void* buffer, size_t size, std::vector<Mesh>& meshes, std::vector<Animation>& animations, const char** error)\n{\n\tcgltf_data* data = NULL;\n\n\tcgltf_options options = {};\n\toptions.type = cgltf_file_type_glb;\n\n\tcgltf_result result = cgltf_parse(&options, buffer, size, &data);\n\n\tresult = (result == cgltf_result_success) ? cgltf_load_buffers(&options, data, NULL) : result;\n\tresult = (result == cgltf_result_success) ? cgltf_validate(data) : result;\n\tresult = (result == cgltf_result_success) ? decompressMeshopt(data) : result;\n\n\treturn parseGltf(data, result, meshes, animations, error);\n}\n\nbool areExtrasEqual(const cgltf_extras& lhs, const cgltf_extras& rhs)\n{\n\tif (lhs.data && rhs.data)\n\t\treturn strcmp(lhs.data, rhs.data) == 0;\n\telse\n\t\treturn lhs.data == rhs.data;\n}\n"
  },
  {
    "path": "gltf/parselib.cpp",
    "content": "#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#define CGLTF_IMPLEMENTATION\n#include \"../extern/cgltf.h\"\n\n#define FAST_OBJ_IMPLEMENTATION\n#include \"../extern/fast_obj.h\"\n\n#undef CGLTF_IMPLEMENTATION\n#undef FAST_OBJ_IMPLEMENTATION\n"
  },
  {
    "path": "gltf/parseobj.cpp",
    "content": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include \"../extern/fast_obj.h\"\n#include \"../src/meshoptimizer.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\nstatic void defaultFree(void*, void* p)\n{\n\tfree(p);\n}\n\nstatic int textureIndex(const std::vector<unsigned int>& textures, unsigned int name)\n{\n\tfor (size_t i = 0; i < textures.size(); ++i)\n\t\tif (textures[i] == name)\n\t\t\treturn int(i);\n\n\treturn -1;\n}\n\nstatic void fixupUri(char* uri)\n{\n\t// Some .obj paths come with back slashes, that are invalid as URI separators and won't open on macOS/Linux when embedding textures\n\tfor (char* s = uri; *s; ++s)\n\t\tif (*s == '\\\\')\n\t\t\t*s = '/';\n}\n\nstatic void parseMaterialsObj(fastObjMesh* obj, cgltf_data* data)\n{\n\t// every texture in obj has a unique id (1+); we convert it to a 0-based index\n\t// this effectively extracts only used textures out of the obj, as we don't remove unused textures later in the processing\n\tstd::vector<unsigned int> textures;\n\n\tfor (unsigned int mi = 0; mi < obj->material_count; ++mi)\n\t{\n\t\tfastObjMaterial& om = obj->materials[mi];\n\n\t\tif (om.map_Kd && textureIndex(textures, om.map_Kd) < 0)\n\t\t\ttextures.push_back(om.map_Kd);\n\t}\n\n\tdata->images = (cgltf_image*)calloc(textures.size(), sizeof(cgltf_image));\n\tdata->images_count = textures.size();\n\n\tfor (size_t i = 0; i < textures.size(); ++i)\n\t{\n\t\tunsigned int id = textures[i];\n\t\tdata->images[i].uri = strdup(obj->textures[id].name);\n\t\tfixupUri(data->images[i].uri);\n\t}\n\n\tdata->textures = (cgltf_texture*)calloc(textures.size(), sizeof(cgltf_texture));\n\tdata->textures_count = textures.size();\n\n\tfor (size_t i = 0; i < textures.size(); ++i)\n\t{\n\t\tdata->textures[i].image = &data->images[i];\n\t}\n\n\tdata->materials = (cgltf_material*)calloc(obj->material_count, sizeof(cgltf_material));\n\tdata->materials_count = obj->material_count;\n\n\tfor (unsigned int mi = 0; mi < obj->material_count; ++mi)\n\t{\n\t\tconst fastObjMaterial& om = obj->materials[mi];\n\t\tcgltf_material& gm = data->materials[mi];\n\n\t\tif (om.name)\n\t\t\tgm.name = strdup(om.name);\n\n\t\tgm.has_pbr_metallic_roughness = true;\n\n\t\tgm.pbr_metallic_roughness.base_color_factor[0] = 1.0f;\n\t\tgm.pbr_metallic_roughness.base_color_factor[1] = 1.0f;\n\t\tgm.pbr_metallic_roughness.base_color_factor[2] = 1.0f;\n\t\tgm.pbr_metallic_roughness.base_color_factor[3] = 1.0f;\n\n\t\tgm.pbr_metallic_roughness.metallic_factor = 0.0f;\n\t\tgm.pbr_metallic_roughness.roughness_factor = 1.0f;\n\n\t\tgm.alpha_cutoff = 0.5f;\n\n\t\tif (om.map_Kd)\n\t\t{\n\t\t\tgm.pbr_metallic_roughness.base_color_texture.texture = &data->textures[textureIndex(textures, om.map_Kd)];\n\t\t\tgm.pbr_metallic_roughness.base_color_texture.scale = 1.0f;\n\n\t\t\tgm.alpha_mode = (om.illum == 4 || om.illum == 6 || om.illum == 7 || om.illum == 9) ? cgltf_alpha_mode_mask : cgltf_alpha_mode_opaque;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tgm.pbr_metallic_roughness.base_color_factor[0] = om.Kd[0];\n\t\t\tgm.pbr_metallic_roughness.base_color_factor[1] = om.Kd[1];\n\t\t\tgm.pbr_metallic_roughness.base_color_factor[2] = om.Kd[2];\n\t\t}\n\n\t\tif (om.map_d)\n\t\t{\n\t\t\tif (om.map_Kd && strcmp(obj->textures[om.map_Kd].name, obj->textures[om.map_d].name) != 0)\n\t\t\t\tfprintf(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);\n\n\t\t\tgm.alpha_mode = cgltf_alpha_mode_blend;\n\t\t}\n\t\telse if (om.d < 1.0f)\n\t\t{\n\t\t\tgm.pbr_metallic_roughness.base_color_factor[3] = om.d;\n\n\t\t\tgm.alpha_mode = cgltf_alpha_mode_blend;\n\t\t}\n\t}\n}\n\nstatic void parseNodesObj(fastObjMesh* obj, cgltf_data* data)\n{\n\tdata->nodes = (cgltf_node*)calloc(obj->object_count, sizeof(cgltf_node));\n\tdata->nodes_count = obj->object_count;\n\n\tfor (unsigned int oi = 0; oi < obj->object_count; ++oi)\n\t{\n\t\tconst fastObjGroup& og = obj->objects[oi];\n\t\tcgltf_node* node = &data->nodes[oi];\n\n\t\tif (og.name)\n\t\t\tnode->name = strdup(og.name);\n\n\t\tnode->rotation[3] = 1.0f;\n\t\tnode->scale[0] = 1.0f;\n\t\tnode->scale[1] = 1.0f;\n\t\tnode->scale[2] = 1.0f;\n\t}\n\n\tdata->scenes = (cgltf_scene*)calloc(1, sizeof(cgltf_scene));\n\tdata->scenes_count = 1;\n\n\tdata->scene = data->scenes;\n\n\tdata->scenes->nodes = (cgltf_node**)calloc(obj->object_count, sizeof(cgltf_node*));\n\tdata->scenes->nodes_count = obj->object_count;\n\n\tfor (unsigned int oi = 0; oi < obj->object_count; ++oi)\n\t\tdata->scenes->nodes[oi] = &data->nodes[oi];\n}\n\nstatic 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)\n{\n\tstd::vector<unsigned int> remap(face_vertex_count);\n\tsize_t unique_vertices = meshopt_generateVertexRemap(remap.data(), NULL, face_vertex_count, &obj->indices[face_vertex_offset], face_vertex_count, sizeof(fastObjIndex));\n\n\tint pos_stream = 0;\n\tint nrm_stream = obj->normal_count > 1 ? 1 : -1;\n\tint tex_stream = obj->texcoord_count > 1 ? 1 + (nrm_stream >= 0) : -1;\n\tint col_stream = obj->color_count > 1 ? 1 + (nrm_stream >= 0) + (tex_stream >= 0) : -1;\n\n\tmesh.streams.resize(1 + (nrm_stream >= 0) + (tex_stream >= 0) + (col_stream >= 0));\n\n\tmesh.streams[pos_stream].type = cgltf_attribute_type_position;\n\tmesh.streams[pos_stream].data.resize(unique_vertices);\n\n\tif (nrm_stream >= 0)\n\t{\n\t\tmesh.streams[nrm_stream].type = cgltf_attribute_type_normal;\n\t\tmesh.streams[nrm_stream].data.resize(unique_vertices);\n\t}\n\n\tif (tex_stream >= 0)\n\t{\n\t\tmesh.streams[tex_stream].type = cgltf_attribute_type_texcoord;\n\t\tmesh.streams[tex_stream].data.resize(unique_vertices);\n\t}\n\n\tif (col_stream >= 0)\n\t{\n\t\tmesh.streams[col_stream].type = cgltf_attribute_type_color;\n\t\tmesh.streams[col_stream].data.resize(unique_vertices);\n\t}\n\n\tmesh.indices.resize(index_count);\n\n\tfor (unsigned int vi = 0; vi < face_vertex_count; ++vi)\n\t{\n\t\tunsigned int target = remap[vi];\n\t\t// TODO: this fills every target vertex multiple times\n\n\t\tfastObjIndex ii = obj->indices[face_vertex_offset + vi];\n\n\t\tAttr p = {{obj->positions[ii.p * 3 + 0], obj->positions[ii.p * 3 + 1], obj->positions[ii.p * 3 + 2]}};\n\t\tmesh.streams[pos_stream].data[target] = p;\n\n\t\tif (nrm_stream >= 0)\n\t\t{\n\t\t\tAttr n = {{obj->normals[ii.n * 3 + 0], obj->normals[ii.n * 3 + 1], obj->normals[ii.n * 3 + 2]}};\n\t\t\tmesh.streams[nrm_stream].data[target] = n;\n\t\t}\n\n\t\tif (tex_stream >= 0)\n\t\t{\n\t\t\tAttr t = {{obj->texcoords[ii.t * 2 + 0], 1.f - obj->texcoords[ii.t * 2 + 1]}};\n\t\t\tmesh.streams[tex_stream].data[target] = t;\n\t\t}\n\n\t\tif (col_stream >= 0)\n\t\t{\n\t\t\tAttr c = {{obj->colors[ii.p * 3 + 0], obj->colors[ii.p * 3 + 1], obj->colors[ii.p * 3 + 2]}};\n\t\t\tmesh.streams[col_stream].data[target] = c;\n\t\t}\n\t}\n\n\tunsigned int vertex_offset = 0;\n\tunsigned int index_offset = 0;\n\n\tfor (unsigned int fi = 0; fi < face_count; ++fi)\n\t{\n\t\tunsigned int face_vertices = obj->face_vertices[face_offset + fi];\n\n\t\tif (mesh.type == cgltf_primitive_type_lines)\n\t\t{\n\t\t\tfor (unsigned int vi = 1; vi < face_vertices; ++vi)\n\t\t\t{\n\t\t\t\tsize_t to = index_offset + (vi - 1) * 2;\n\n\t\t\t\tmesh.indices[to + 0] = remap[vertex_offset + vi - 1];\n\t\t\t\tmesh.indices[to + 1] = remap[vertex_offset + vi];\n\t\t\t}\n\n\t\t\tvertex_offset += face_vertices;\n\t\t\tindex_offset += (face_vertices - 1) * 2;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor (unsigned int vi = 2; vi < face_vertices; ++vi)\n\t\t\t{\n\t\t\t\tsize_t to = index_offset + (vi - 2) * 3;\n\n\t\t\t\tmesh.indices[to + 0] = remap[vertex_offset];\n\t\t\t\tmesh.indices[to + 1] = remap[vertex_offset + vi - 1];\n\t\t\t\tmesh.indices[to + 2] = remap[vertex_offset + vi];\n\t\t\t}\n\n\t\t\tvertex_offset += face_vertices;\n\t\t\tindex_offset += (face_vertices - 2) * 3;\n\t\t}\n\t}\n\n\tassert(vertex_offset == face_vertex_count);\n\tassert(index_offset == index_count);\n}\n\nstatic void parseMeshGroupObj(fastObjMesh* obj, const fastObjGroup& og, cgltf_data* data, cgltf_node* node, std::vector<Mesh>& meshes)\n{\n\tunsigned int face_vertex_offset = og.index_offset;\n\tunsigned int face_end_offset = og.face_offset + og.face_count;\n\n\tfor (unsigned int face_offset = og.face_offset; face_offset < face_end_offset;)\n\t{\n\t\tunsigned int mi = obj->face_materials[face_offset];\n\t\tunsigned char isl = obj->face_lines ? obj->face_lines[face_offset] : 0;\n\n\t\tunsigned int face_count = 0;\n\t\tunsigned int face_vertex_count = 0;\n\t\tunsigned int index_count = 0;\n\n\t\tfor (unsigned int fj = face_offset; fj < face_end_offset && obj->face_materials[fj] == mi && (!obj->face_lines || obj->face_lines[fj] == isl); ++fj)\n\t\t{\n\t\t\tface_count += 1;\n\t\t\tface_vertex_count += obj->face_vertices[fj];\n\t\t\tindex_count += isl ? (obj->face_vertices[fj] - 1) * 2 : (obj->face_vertices[fj] - 2) * 3;\n\t\t}\n\n\t\tmeshes.push_back(Mesh());\n\t\tMesh& mesh = meshes.back();\n\n\t\tif (data->materials_count)\n\t\t{\n\t\t\tassert(mi < data->materials_count);\n\t\t\tmesh.material = &data->materials[mi];\n\t\t}\n\n\t\tmesh.type = isl ? cgltf_primitive_type_lines : cgltf_primitive_type_triangles;\n\t\tmesh.targets = 0;\n\n\t\tif (node)\n\t\t\tmesh.nodes.push_back(node);\n\n\t\tparseMeshObj(obj, face_offset, face_vertex_offset, face_count, face_vertex_count, index_count, mesh);\n\n\t\tface_offset += face_count;\n\t\tface_vertex_offset += face_vertex_count;\n\t}\n}\n\ncgltf_data* parseObj(const char* path, std::vector<Mesh>& meshes, const char** error)\n{\n\tfastObjMesh* obj = fast_obj_read(path);\n\n\tif (!obj)\n\t{\n\t\t*error = \"file not found\";\n\t\treturn NULL;\n\t}\n\n\tint fallback_materials = 0;\n\tfor (unsigned int mi = 0; mi < obj->material_count; ++mi)\n\t\tfallback_materials += obj->materials[mi].fallback;\n\n\tif (fallback_materials)\n\t\tfprintf(stderr, \"Warning: %d/%d materials could not be loaded from mtllib\\n\", fallback_materials, obj->material_count);\n\n\tcgltf_data* data = (cgltf_data*)calloc(1, sizeof(cgltf_data));\n\tdata->memory.free_func = defaultFree;\n\n\tparseMaterialsObj(obj, data);\n\n\tparseNodesObj(obj, data);\n\tassert(data->nodes_count == obj->object_count);\n\n\tfor (unsigned int oi = 0; oi < obj->object_count; ++oi)\n\t\tparseMeshGroupObj(obj, obj->objects[oi], data, &data->nodes[oi], meshes);\n\n\tfast_obj_destroy(obj);\n\n\treturn data;\n}\n"
  },
  {
    "path": "gltf/stream.cpp",
    "content": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <algorithm>\n\n#include <float.h>\n#include <limits.h>\n#include <math.h>\n#include <stdint.h>\n\n#include \"../src/meshoptimizer.h\"\n\nstruct Bounds\n{\n\tAttr min, max;\n\n\tBounds()\n\t{\n\t\tmin.f[0] = min.f[1] = min.f[2] = min.f[3] = +FLT_MAX;\n\t\tmax.f[0] = max.f[1] = max.f[2] = max.f[3] = -FLT_MAX;\n\t}\n\n\tbool isValid() const\n\t{\n\t\treturn min.f[0] <= max.f[0] && min.f[1] <= max.f[1] && min.f[2] <= max.f[2] && min.f[3] <= max.f[3];\n\t}\n\n\tfloat getExtent() const\n\t{\n\t\treturn std::max(max.f[0] - min.f[0], std::max(max.f[1] - min.f[1], max.f[2] - min.f[2]));\n\t}\n\n\tvoid merge(const Bounds& other)\n\t{\n\t\tfor (int k = 0; k < 4; ++k)\n\t\t{\n\t\t\tmin.f[k] = std::min(min.f[k], other.min.f[k]);\n\t\t\tmax.f[k] = std::max(max.f[k], other.max.f[k]);\n\t\t}\n\t}\n};\n\nstatic Bounds computeBounds(const Mesh& mesh, cgltf_attribute_type type)\n{\n\tBounds b;\n\tAttr pad = {};\n\n\tfor (size_t j = 0; j < mesh.streams.size(); ++j)\n\t{\n\t\tconst Stream& s = mesh.streams[j];\n\n\t\tif (s.type == type)\n\t\t{\n\t\t\tif (s.target == 0)\n\t\t\t{\n\t\t\t\tfor (size_t k = 0; k < s.data.size(); ++k)\n\t\t\t\t{\n\t\t\t\t\tconst Attr& a = s.data[k];\n\n\t\t\t\t\tb.min.f[0] = std::min(b.min.f[0], a.f[0]);\n\t\t\t\t\tb.min.f[1] = std::min(b.min.f[1], a.f[1]);\n\t\t\t\t\tb.min.f[2] = std::min(b.min.f[2], a.f[2]);\n\t\t\t\t\tb.min.f[3] = std::min(b.min.f[3], a.f[3]);\n\n\t\t\t\t\tb.max.f[0] = std::max(b.max.f[0], a.f[0]);\n\t\t\t\t\tb.max.f[1] = std::max(b.max.f[1], a.f[1]);\n\t\t\t\t\tb.max.f[2] = std::max(b.max.f[2], a.f[2]);\n\t\t\t\t\tb.max.f[3] = std::max(b.max.f[3], a.f[3]);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfor (size_t k = 0; k < s.data.size(); ++k)\n\t\t\t\t{\n\t\t\t\t\tconst Attr& a = s.data[k];\n\n\t\t\t\t\tpad.f[0] = std::max(pad.f[0], fabsf(a.f[0]));\n\t\t\t\t\tpad.f[1] = std::max(pad.f[1], fabsf(a.f[1]));\n\t\t\t\t\tpad.f[2] = std::max(pad.f[2], fabsf(a.f[2]));\n\t\t\t\t\tpad.f[3] = std::max(pad.f[3], fabsf(a.f[3]));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (int k = 0; k < 4; ++k)\n\t{\n\t\tb.min.f[k] -= pad.f[k];\n\t\tb.max.f[k] += pad.f[k];\n\t}\n\n\treturn b;\n}\n\nstatic float computeUvArea(const Mesh& mesh)\n{\n\tif (mesh.indices.empty() || mesh.type != cgltf_primitive_type_triangles)\n\t\treturn 0.f;\n\n\tfloat result = 0.f;\n\n\tfor (size_t j = 0; j < mesh.streams.size(); ++j)\n\t{\n\t\tconst Stream& s = mesh.streams[j];\n\n\t\tif (s.type != cgltf_attribute_type_texcoord)\n\t\t\tcontinue;\n\n\t\tfloat uvarea = 0.f;\n\n\t\tfor (size_t i = 0; i < mesh.indices.size(); i += 3)\n\t\t{\n\t\t\tunsigned int a = mesh.indices[i + 0];\n\t\t\tunsigned int b = mesh.indices[i + 1];\n\t\t\tunsigned int c = mesh.indices[i + 2];\n\n\t\t\tconst Attr& va = s.data[a];\n\t\t\tconst Attr& vb = s.data[b];\n\t\t\tconst Attr& vc = s.data[c];\n\n\t\t\tuvarea += 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]));\n\t\t}\n\n\t\tresult = std::max(result, uvarea / float(mesh.indices.size() / 3));\n\t}\n\n\treturn result;\n}\n\nQuantizationPosition prepareQuantizationPosition(const std::vector<Mesh>& meshes, const Settings& settings)\n{\n\tQuantizationPosition result = {};\n\n\tresult.bits = settings.pos_bits;\n\tresult.normalized = settings.pos_normalized;\n\n\tstd::vector<Bounds> bounds(meshes.size());\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t\tbounds[i] = computeBounds(meshes[i], cgltf_attribute_type_position);\n\n\tBounds b;\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t\tb.merge(bounds[i]);\n\n\tif (b.isValid())\n\t{\n\t\tresult.offset[0] = b.min.f[0];\n\t\tresult.offset[1] = b.min.f[1];\n\t\tresult.offset[2] = b.min.f[2];\n\t\tresult.scale = b.getExtent();\n\t}\n\n\tif (b.isValid() && settings.quantize && !settings.pos_float)\n\t{\n\t\tfloat error = result.scale * 0.5f / (1 << (result.bits - 1));\n\t\tfloat max_rel_error = 0;\n\n\t\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t\t\tif (bounds[i].isValid() && bounds[i].getExtent() > 1e-2f)\n\t\t\t\tmax_rel_error = std::max(max_rel_error, error / bounds[i].getExtent());\n\n\t\tif (max_rel_error > 5e-2f)\n\t\t\tfprintf(stderr, \"Warning: position data has significant error (%.0f%%); consider using floating-point quantization (-vpf) or more bits (-vp N)\\n\", max_rel_error * 100);\n\t}\n\n\tresult.node_scale = result.scale / float((1 << result.bits) - 1) * (result.normalized ? 65535.f : 1.f);\n\n\treturn result;\n}\n\nstatic size_t follow(std::vector<size_t>& parents, size_t index)\n{\n\twhile (index != parents[index])\n\t{\n\t\tsize_t parent = parents[index];\n\n\t\tparents[index] = parents[parent];\n\t\tindex = parent;\n\t}\n\n\treturn index;\n}\n\nvoid prepareQuantizationTexture(cgltf_data* data, std::vector<QuantizationTexture>& result, std::vector<size_t>& indices, const std::vector<Mesh>& meshes, const Settings& settings)\n{\n\t// use union-find to associate each material with a canonical material\n\t// this is necessary because any set of materials that are used on the same mesh must use the same quantization\n\tstd::vector<size_t> parents(result.size());\n\n\tfor (size_t i = 0; i < parents.size(); ++i)\n\t\tparents[i] = i;\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tconst Mesh& mesh = meshes[i];\n\n\t\tif (!mesh.material && mesh.variants.empty())\n\t\t\tcontinue;\n\n\t\tsize_t root = follow(parents, (mesh.material ? mesh.material : mesh.variants[0].material) - data->materials);\n\n\t\tfor (size_t j = 0; j < mesh.variants.size(); ++j)\n\t\t{\n\t\t\tsize_t var = follow(parents, mesh.variants[j].material - data->materials);\n\n\t\t\tparents[var] = root;\n\t\t}\n\n\t\tindices[i] = root;\n\t}\n\n\t// compute canonical material bounds based on meshes that use them\n\tstd::vector<Bounds> bounds(result.size());\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tconst Mesh& mesh = meshes[i];\n\n\t\tif (!mesh.material && mesh.variants.empty())\n\t\t\tcontinue;\n\n\t\tindices[i] = follow(parents, indices[i]);\n\n\t\tBounds mb = computeBounds(mesh, cgltf_attribute_type_texcoord);\n\t\tbounds[indices[i]].merge(mb);\n\t}\n\n\t// detect potential precision issues and warn about them\n\tif (settings.quantize && !settings.tex_float)\n\t{\n\t\tfloat max_rel_error = 0;\n\n\t\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t\t{\n\t\t\tconst Mesh& mesh = meshes[i];\n\n\t\t\tif (!mesh.material && mesh.variants.empty())\n\t\t\t\tcontinue;\n\n\t\t\tconst Bounds& b = bounds[indices[i]];\n\t\t\tif (!b.isValid())\n\t\t\t\tcontinue;\n\n\t\t\tfloat scale = std::max(b.max.f[0] - b.min.f[0], b.max.f[1] - b.min.f[1]);\n\t\t\tfloat error = scale * 0.5f / (1 << (settings.tex_bits - 1));\n\n\t\t\tif (error < 1e-3f)\n\t\t\t\tcontinue;\n\n\t\t\tfloat uvarea = computeUvArea(mesh);\n\t\t\tfloat rel_error = uvarea > 0 ? error / sqrtf(uvarea) : 0.f;\n\n\t\t\tmax_rel_error = std::max(max_rel_error, rel_error);\n\t\t}\n\n\t\tif (max_rel_error > 1e-1f)\n\t\t\tfprintf(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);\n\t}\n\n\t// update all material data using canonical bounds\n\tfor (size_t i = 0; i < result.size(); ++i)\n\t{\n\t\tQuantizationTexture& qt = result[i];\n\n\t\tqt.bits = settings.tex_bits;\n\t\tqt.normalized = true;\n\n\t\tconst Bounds& b = bounds[follow(parents, i)];\n\n\t\tif (b.isValid())\n\t\t{\n\t\t\tqt.offset[0] = b.min.f[0];\n\t\t\tqt.offset[1] = b.min.f[1];\n\t\t\tqt.scale[0] = b.max.f[0] - b.min.f[0];\n\t\t\tqt.scale[1] = b.max.f[1] - b.min.f[1];\n\t\t}\n\t}\n}\n\nvoid getPositionBounds(float min[3], float max[3], const Stream& stream, const QuantizationPosition& qp, const Settings& settings)\n{\n\tassert(stream.type == cgltf_attribute_type_position);\n\tassert(stream.data.size() > 0);\n\n\tmin[0] = min[1] = min[2] = FLT_MAX;\n\tmax[0] = max[1] = max[2] = -FLT_MAX;\n\n\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t{\n\t\tconst Attr& a = stream.data[i];\n\n\t\tfor (int k = 0; k < 3; ++k)\n\t\t{\n\t\t\tmin[k] = std::min(min[k], a.f[k]);\n\t\t\tmax[k] = std::max(max[k], a.f[k]);\n\t\t}\n\t}\n\n\tif (settings.quantize)\n\t{\n\t\tif (settings.pos_float)\n\t\t{\n\t\t\tfor (int k = 0; k < 3; ++k)\n\t\t\t{\n\t\t\t\tmin[k] = meshopt_quantizeFloat(min[k], qp.bits);\n\t\t\t\tmax[k] = meshopt_quantizeFloat(max[k], qp.bits);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfloat pos_rscale = qp.scale == 0.f ? 0.f : 1.f / qp.scale * (stream.target > 0 && qp.normalized ? 32767.f / 65535.f : 1.f);\n\n\t\t\tfor (int k = 0; k < 3; ++k)\n\t\t\t{\n\t\t\t\tif (stream.target == 0)\n\t\t\t\t{\n\t\t\t\t\tmin[k] = float(meshopt_quantizeUnorm((min[k] - qp.offset[k]) * pos_rscale, qp.bits));\n\t\t\t\t\tmax[k] = float(meshopt_quantizeUnorm((max[k] - qp.offset[k]) * pos_rscale, qp.bits));\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tmin[k] = (min[k] >= 0.f ? 1.f : -1.f) * float(meshopt_quantizeUnorm(fabsf(min[k]) * pos_rscale, qp.bits));\n\t\t\t\t\tmax[k] = (max[k] >= 0.f ? 1.f : -1.f) * float(meshopt_quantizeUnorm(fabsf(max[k]) * pos_rscale, qp.bits));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void renormalizeWeights(uint8_t (&w)[4])\n{\n\tint sum = w[0] + w[1] + w[2] + w[3];\n\n\tif (sum == 255)\n\t\treturn;\n\n\t// we assume that the total error is limited to 0.5/component = 2\n\t// this means that it's acceptable to adjust the max. component to compensate for the error\n\tint max = 0;\n\n\tfor (int k = 1; k < 4; ++k)\n\t\tif (w[k] > w[max])\n\t\t\tmax = k;\n\n\tw[max] += uint8_t(255 - sum);\n}\n\nstatic void encodeSnorm(void* destination, size_t count, size_t stride, int bits, const float* data)\n{\n\tassert(stride == 4 || stride == 8);\n\tassert(bits >= 1 && bits <= 16);\n\n\tsigned char* d8 = static_cast<signed char*>(destination);\n\tshort* d16 = static_cast<short*>(destination);\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tconst float* v = &data[i * 4];\n\n\t\tint fx = meshopt_quantizeSnorm(v[0], bits);\n\t\tint fy = meshopt_quantizeSnorm(v[1], bits);\n\t\tint fz = meshopt_quantizeSnorm(v[2], bits);\n\t\tint fw = meshopt_quantizeSnorm(v[3], bits);\n\n\t\tif (stride == 4)\n\t\t{\n\t\t\td8[i * 4 + 0] = (signed char)(fx);\n\t\t\td8[i * 4 + 1] = (signed char)(fy);\n\t\t\td8[i * 4 + 2] = (signed char)(fz);\n\t\t\td8[i * 4 + 3] = (signed char)(fw);\n\t\t}\n\t\telse\n\t\t{\n\t\t\td16[i * 4 + 0] = short(fx);\n\t\t\td16[i * 4 + 1] = short(fy);\n\t\t\td16[i * 4 + 2] = short(fz);\n\t\t\td16[i * 4 + 3] = short(fw);\n\t\t}\n\t}\n}\n\nstatic int quantizeColor(float v, int bytebits, int bits)\n{\n\tint result = meshopt_quantizeUnorm(v, bytebits);\n\n\t// replicate the top bit into the low significant bits\n\tconst int mask = (1 << (bytebits - bits)) - 1;\n\n\treturn (result & ~mask) | (mask & -(result >> (bytebits - 1)));\n}\n\nstatic void encodeColor(void* destination, size_t count, size_t stride, int bits, const float* data)\n{\n\tassert(stride == 4 || stride == 8);\n\tassert(bits >= 2 && bits <= 16);\n\n\tunsigned char* d8 = static_cast<unsigned char*>(destination);\n\tunsigned short* d16 = static_cast<unsigned short*>(destination);\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tconst float* c = &data[i * 4];\n\n\t\tif (stride == 4)\n\t\t{\n\t\t\td8[i * 4 + 0] = uint8_t(quantizeColor(c[0], 8, bits));\n\t\t\td8[i * 4 + 1] = uint8_t(quantizeColor(c[1], 8, bits));\n\t\t\td8[i * 4 + 2] = uint8_t(quantizeColor(c[2], 8, bits));\n\t\t\td8[i * 4 + 3] = uint8_t(quantizeColor(c[3], 8, bits));\n\t\t}\n\t\telse\n\t\t{\n\t\t\td16[i * 4 + 0] = uint16_t(quantizeColor(c[0], 16, bits));\n\t\t\td16[i * 4 + 1] = uint16_t(quantizeColor(c[1], 16, bits));\n\t\t\td16[i * 4 + 2] = uint16_t(quantizeColor(c[2], 16, bits));\n\t\t\td16[i * 4 + 3] = uint16_t(quantizeColor(c[3], 16, bits));\n\t\t}\n\t}\n}\n\nstatic StreamFormat writeVertexStreamRaw(std::string& bin, const Stream& stream, cgltf_type type, size_t components)\n{\n\tassert(components >= 1 && components <= 4);\n\n\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t{\n\t\tconst Attr& a = stream.data[i];\n\n\t\tbin.append(reinterpret_cast<const char*>(a.f), sizeof(float) * components);\n\t}\n\n\tStreamFormat format = {type, cgltf_component_type_r_32f, false, sizeof(float) * components};\n\treturn format;\n}\n\nstatic StreamFormat writeVertexStreamFloat(std::string& bin, const Stream& stream, cgltf_type type, int components, bool expf, int bits, meshopt_EncodeExpMode mode)\n{\n\tassert(components >= 1 && components <= 4);\n\n\tStreamFormat::Filter filter = expf ? StreamFormat::Filter_Exp : StreamFormat::Filter_None;\n\n\tif (filter == StreamFormat::Filter_Exp)\n\t{\n\t\tsize_t offset = bin.size();\n\t\tsize_t stride = sizeof(float) * components;\n\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t\tbin.append(reinterpret_cast<const char*>(stream.data[i].f), stride);\n\n\t\tmeshopt_encodeFilterExp(&bin[offset], stream.data.size(), stride, bits + 1, reinterpret_cast<const float*>(&bin[offset]), mode);\n\t}\n\telse\n\t{\n\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t{\n\t\t\tconst Attr& a = stream.data[i];\n\n\t\t\tfloat v[4];\n\t\t\tfor (int k = 0; k < components; ++k)\n\t\t\t\tv[k] = meshopt_quantizeFloat(a.f[k], bits);\n\t\t\tbin.append(reinterpret_cast<const char*>(v), sizeof(float) * components);\n\t\t}\n\t}\n\n\tStreamFormat format = {type, cgltf_component_type_r_32f, false, sizeof(float) * components, filter};\n\treturn format;\n}\n\nStreamFormat writeVertexStream(std::string& bin, const Stream& stream, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings, bool filters)\n{\n\tif (stream.type == cgltf_attribute_type_position)\n\t{\n\t\tif (!settings.quantize)\n\t\t\treturn writeVertexStreamRaw(bin, stream, cgltf_type_vec3, 3);\n\n\t\tif (settings.pos_float)\n\t\t\treturn writeVertexStreamFloat(bin, stream, cgltf_type_vec3, 3, settings.compress && filters, qp.bits,\n\t\t\t    settings.compressmore ? meshopt_EncodeExpSharedComponent : meshopt_EncodeExpSeparate);\n\n\t\tif (stream.target == 0)\n\t\t{\n\t\t\tfloat pos_rscale = qp.scale == 0.f ? 0.f : 1.f / qp.scale;\n\n\t\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t\t{\n\t\t\t\tconst Attr& a = stream.data[i];\n\n\t\t\t\tuint16_t v[4] = {\n\t\t\t\t    uint16_t(meshopt_quantizeUnorm((a.f[0] - qp.offset[0]) * pos_rscale, qp.bits)),\n\t\t\t\t    uint16_t(meshopt_quantizeUnorm((a.f[1] - qp.offset[1]) * pos_rscale, qp.bits)),\n\t\t\t\t    uint16_t(meshopt_quantizeUnorm((a.f[2] - qp.offset[2]) * pos_rscale, qp.bits)),\n\t\t\t\t    0};\n\t\t\t\tbin.append(reinterpret_cast<const char*>(v), sizeof(v));\n\t\t\t}\n\n\t\t\tStreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_16u, qp.normalized, 8};\n\t\t\treturn format;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfloat pos_rscale = qp.scale == 0.f ? 0.f : 1.f / qp.scale * (qp.normalized ? 32767.f / 65535.f : 1.f);\n\n\t\t\tint maxv = 0;\n\n\t\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t\t{\n\t\t\t\tconst Attr& a = stream.data[i];\n\n\t\t\t\tmaxv = std::max(maxv, meshopt_quantizeUnorm(fabsf(a.f[0]) * pos_rscale, qp.bits));\n\t\t\t\tmaxv = std::max(maxv, meshopt_quantizeUnorm(fabsf(a.f[1]) * pos_rscale, qp.bits));\n\t\t\t\tmaxv = std::max(maxv, meshopt_quantizeUnorm(fabsf(a.f[2]) * pos_rscale, qp.bits));\n\t\t\t}\n\n\t\t\tif (maxv <= 127 && !qp.normalized)\n\t\t\t{\n\t\t\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t\t\t{\n\t\t\t\t\tconst Attr& a = stream.data[i];\n\n\t\t\t\t\tint8_t v[4] = {\n\t\t\t\t\t    int8_t((a.f[0] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[0]) * pos_rscale, qp.bits)),\n\t\t\t\t\t    int8_t((a.f[1] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[1]) * pos_rscale, qp.bits)),\n\t\t\t\t\t    int8_t((a.f[2] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[2]) * pos_rscale, qp.bits)),\n\t\t\t\t\t    0};\n\t\t\t\t\tbin.append(reinterpret_cast<const char*>(v), sizeof(v));\n\t\t\t\t}\n\n\t\t\t\tStreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_8, false, 4};\n\t\t\t\treturn format;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t\t\t{\n\t\t\t\t\tconst Attr& a = stream.data[i];\n\n\t\t\t\t\tint16_t v[4] = {\n\t\t\t\t\t    int16_t((a.f[0] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[0]) * pos_rscale, qp.bits)),\n\t\t\t\t\t    int16_t((a.f[1] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[1]) * pos_rscale, qp.bits)),\n\t\t\t\t\t    int16_t((a.f[2] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[2]) * pos_rscale, qp.bits)),\n\t\t\t\t\t    0};\n\t\t\t\t\tbin.append(reinterpret_cast<const char*>(v), sizeof(v));\n\t\t\t\t}\n\n\t\t\t\tStreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_16, qp.normalized, 8};\n\t\t\t\treturn format;\n\t\t\t}\n\t\t}\n\t}\n\telse if (stream.type == cgltf_attribute_type_texcoord)\n\t{\n\t\tif (!settings.quantize)\n\t\t\treturn writeVertexStreamRaw(bin, stream, cgltf_type_vec2, 2);\n\n\t\t// expand the encoded range to ensure it covers [0..1) interval\n\t\t// this can slightly reduce precision but we should not need more precision inside 0..1, and this significantly improves compressed size when using encodeExpOne\n\t\tif (settings.tex_float)\n\t\t\treturn writeVertexStreamFloat(bin, stream, cgltf_type_vec2, 2, settings.compress && filters, qt.bits, meshopt_EncodeExpClamped);\n\n\t\tfloat uv_rscale[2] = {\n\t\t    qt.scale[0] == 0.f ? 0.f : 1.f / qt.scale[0],\n\t\t    qt.scale[1] == 0.f ? 0.f : 1.f / qt.scale[1],\n\t\t};\n\n\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t{\n\t\t\tconst Attr& a = stream.data[i];\n\n\t\t\tuint16_t v[2] = {\n\t\t\t    uint16_t(meshopt_quantizeUnorm((a.f[0] - qt.offset[0]) * uv_rscale[0], qt.bits)),\n\t\t\t    uint16_t(meshopt_quantizeUnorm((a.f[1] - qt.offset[1]) * uv_rscale[1], qt.bits)),\n\t\t\t};\n\t\t\tbin.append(reinterpret_cast<const char*>(v), sizeof(v));\n\t\t}\n\n\t\tStreamFormat format = {cgltf_type_vec2, cgltf_component_type_r_16u, qt.normalized, 4};\n\t\treturn format;\n\t}\n\telse if (stream.type == cgltf_attribute_type_normal)\n\t{\n\t\tif (!settings.quantize)\n\t\t\treturn writeVertexStreamRaw(bin, stream, cgltf_type_vec3, 3);\n\n\t\t// expand the encoded range to ensure it covers [0..1) interval\n\t\tif (settings.nrm_float)\n\t\t\treturn writeVertexStreamFloat(bin, stream, cgltf_type_vec3, 3, settings.compress && filters, settings.nrm_bits,\n\t\t\t    (settings.compressmore || stream.target) ? meshopt_EncodeExpSharedComponent : meshopt_EncodeExpClamped);\n\n\t\tbool oct = filters && settings.compressmore && stream.target == 0;\n\t\tint bits = settings.nrm_bits;\n\n\t\tStreamFormat::Filter filter = oct ? StreamFormat::Filter_Oct : StreamFormat::Filter_None;\n\n\t\tsize_t offset = bin.size();\n\t\tsize_t stride = bits > 8 ? 8 : 4;\n\t\tbin.resize(bin.size() + stream.data.size() * stride);\n\n\t\tif (oct)\n\t\t\tmeshopt_encodeFilterOct(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f);\n\t\telse\n\t\t\tencodeSnorm(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f);\n\n\t\tcgltf_component_type component_type = bits > 8 ? cgltf_component_type_r_16 : cgltf_component_type_r_8;\n\t\tStreamFormat format = {cgltf_type_vec3, component_type, true, stride, filter};\n\t\treturn format;\n\t}\n\telse if (stream.type == cgltf_attribute_type_tangent)\n\t{\n\t\tif (!settings.quantize)\n\t\t\treturn writeVertexStreamRaw(bin, stream, cgltf_type_vec4, 4);\n\n\t\tbool oct = filters && settings.compressmore && stream.target == 0;\n\t\tint bits = (settings.nrm_bits > 8) ? 8 : settings.nrm_bits;\n\n\t\tStreamFormat::Filter filter = oct ? StreamFormat::Filter_Oct : StreamFormat::Filter_None;\n\n\t\tsize_t offset = bin.size();\n\t\tsize_t stride = 4;\n\t\tbin.resize(bin.size() + stream.data.size() * stride);\n\n\t\tif (oct)\n\t\t\tmeshopt_encodeFilterOct(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f);\n\t\telse\n\t\t\tencodeSnorm(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f);\n\n\t\tcgltf_type type = (stream.target == 0) ? cgltf_type_vec4 : cgltf_type_vec3;\n\t\tStreamFormat format = {type, cgltf_component_type_r_8, true, 4, filter};\n\t\treturn format;\n\t}\n\telse if (stream.type == cgltf_attribute_type_color)\n\t{\n\t\tbool col = filters && settings.compresskhr && settings.compressmore;\n\t\tint bits = settings.col_bits;\n\n\t\tStreamFormat::Filter filter = col ? StreamFormat::Filter_Color : StreamFormat::Filter_None;\n\n\t\tsize_t offset = bin.size();\n\t\tsize_t stride = bits > 8 ? 8 : 4;\n\t\tbin.resize(bin.size() + stream.data.size() * stride);\n\n\t\tif (col)\n\t\t\tmeshopt_encodeFilterColor(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f);\n\t\telse\n\t\t\tencodeColor(&bin[offset], stream.data.size(), stride, bits, stream.data[0].f);\n\n\t\tif (bits > 8)\n\t\t{\n\t\t\tStreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_16u, true, 8, filter};\n\t\t\treturn format;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tStreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_8u, true, 4, filter};\n\t\t\treturn format;\n\t\t}\n\t}\n\telse if (stream.type == cgltf_attribute_type_weights)\n\t{\n\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t{\n\t\t\tconst Attr& a = stream.data[i];\n\n\t\t\tfloat ws = a.f[0] + a.f[1] + a.f[2] + a.f[3];\n\t\t\tfloat wsi = (ws == 0.f) ? 0.f : 1.f / ws;\n\n\t\t\tuint8_t v[4] = {\n\t\t\t    uint8_t(meshopt_quantizeUnorm(a.f[0] * wsi, 8)),\n\t\t\t    uint8_t(meshopt_quantizeUnorm(a.f[1] * wsi, 8)),\n\t\t\t    uint8_t(meshopt_quantizeUnorm(a.f[2] * wsi, 8)),\n\t\t\t    uint8_t(meshopt_quantizeUnorm(a.f[3] * wsi, 8))};\n\n\t\t\tif (wsi != 0.f)\n\t\t\t\trenormalizeWeights(v);\n\n\t\t\tbin.append(reinterpret_cast<const char*>(v), sizeof(v));\n\t\t}\n\n\t\tStreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_8u, true, 4};\n\t\treturn format;\n\t}\n\telse if (stream.type == cgltf_attribute_type_joints)\n\t{\n\t\tunsigned int maxj = 0;\n\n\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t\tmaxj = std::max(maxj, unsigned(stream.data[i].f[0]));\n\n\t\tassert(maxj <= 65535);\n\n\t\tif (maxj <= 255)\n\t\t{\n\t\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t\t{\n\t\t\t\tconst Attr& a = stream.data[i];\n\n\t\t\t\tuint8_t v[4] = {\n\t\t\t\t    uint8_t(a.f[0]),\n\t\t\t\t    uint8_t(a.f[1]),\n\t\t\t\t    uint8_t(a.f[2]),\n\t\t\t\t    uint8_t(a.f[3])};\n\t\t\t\tbin.append(reinterpret_cast<const char*>(v), sizeof(v));\n\t\t\t}\n\n\t\t\tStreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_8u, false, 4};\n\t\t\treturn format;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t\t{\n\t\t\t\tconst Attr& a = stream.data[i];\n\n\t\t\t\tuint16_t v[4] = {\n\t\t\t\t    uint16_t(a.f[0]),\n\t\t\t\t    uint16_t(a.f[1]),\n\t\t\t\t    uint16_t(a.f[2]),\n\t\t\t\t    uint16_t(a.f[3])};\n\t\t\t\tbin.append(reinterpret_cast<const char*>(v), sizeof(v));\n\t\t\t}\n\n\t\t\tStreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_16u, false, 8};\n\t\t\treturn format;\n\t\t}\n\t}\n\telse if (stream.type == cgltf_attribute_type_custom)\n\t{\n\t\t// note: _custom is equivalent to _ID, as such the data contains scalar integers\n\t\tif (!settings.compressmore || !filters)\n\t\t\treturn writeVertexStreamRaw(bin, stream, cgltf_type_scalar, 1);\n\n\t\tunsigned int maxv = 0;\n\n\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t\tmaxv = std::max(maxv, unsigned(stream.data[i].f[0]));\n\n\t\t// exp encoding uses a signed mantissa with only 23 significant bits; input glTF encoding may encode indices losslessly up to 2^24\n\t\tif (maxv >= (1 << 23))\n\t\t\treturn writeVertexStreamRaw(bin, stream, cgltf_type_scalar, 1);\n\n\t\tfor (size_t i = 0; i < stream.data.size(); ++i)\n\t\t{\n\t\t\tconst Attr& a = stream.data[i];\n\n\t\t\tuint32_t id = uint32_t(a.f[0]);\n\t\t\tuint32_t v = id; // exp encoding of integers in [0..2^23-1] range is equivalent to the integer itself\n\n\t\t\tbin.append(reinterpret_cast<const char*>(&v), sizeof(v));\n\t\t}\n\n\t\tStreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_32f, false, 4, StreamFormat::Filter_Exp};\n\t\treturn format;\n\t}\n\telse\n\t{\n\t\treturn writeVertexStreamRaw(bin, stream, cgltf_type_vec4, 4);\n\t}\n}\n\nStreamFormat writeIndexStream(std::string& bin, const std::vector<unsigned int>& stream)\n{\n\tunsigned int maxi = 0;\n\tfor (size_t i = 0; i < stream.size(); ++i)\n\t\tmaxi = std::max(maxi, stream[i]);\n\n\t// save 16-bit indices if we can; note that we can't use restart index (65535)\n\tif (maxi < 65535)\n\t{\n\t\tfor (size_t i = 0; i < stream.size(); ++i)\n\t\t{\n\t\t\tuint16_t v[1] = {uint16_t(stream[i])};\n\t\t\tbin.append(reinterpret_cast<const char*>(v), sizeof(v));\n\t\t}\n\n\t\tStreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_16u, false, 2};\n\t\treturn format;\n\t}\n\telse\n\t{\n\t\tfor (size_t i = 0; i < stream.size(); ++i)\n\t\t{\n\t\t\tuint32_t v[1] = {stream[i]};\n\t\t\tbin.append(reinterpret_cast<const char*>(v), sizeof(v));\n\t\t}\n\n\t\tStreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_32u, false, 4};\n\t\treturn format;\n\t}\n}\n\nStreamFormat writeTimeStream(std::string& bin, const std::vector<float>& data)\n{\n\tfor (size_t i = 0; i < data.size(); ++i)\n\t{\n\t\tfloat v[1] = {data[i]};\n\t\tbin.append(reinterpret_cast<const char*>(v), sizeof(v));\n\t}\n\n\tStreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_32f, false, 4};\n\treturn format;\n}\n\nStreamFormat writeKeyframeStream(std::string& bin, cgltf_animation_path_type type, const std::vector<Attr>& data, const Settings& settings, bool has_tangents)\n{\n\tif (type == cgltf_animation_path_type_rotation)\n\t{\n\t\tStreamFormat::Filter filter = settings.compressmore && !has_tangents ? StreamFormat::Filter_Quat : StreamFormat::Filter_None;\n\n\t\tsize_t offset = bin.size();\n\t\tsize_t stride = 8;\n\t\tbin.resize(bin.size() + data.size() * stride);\n\n\t\tif (filter == StreamFormat::Filter_Quat)\n\t\t\tmeshopt_encodeFilterQuat(&bin[offset], data.size(), stride, settings.rot_bits, data[0].f);\n\t\telse\n\t\t\tencodeSnorm(&bin[offset], data.size(), stride, 16, data[0].f);\n\n\t\tStreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_16, true, 8, filter};\n\t\treturn format;\n\t}\n\telse if (type == cgltf_animation_path_type_weights)\n\t{\n\t\tfor (size_t i = 0; i < data.size(); ++i)\n\t\t{\n\t\t\tconst Attr& a = data[i];\n\n\t\t\tuint8_t v[1] = {uint8_t(meshopt_quantizeUnorm(a.f[0], 8))};\n\t\t\tbin.append(reinterpret_cast<const char*>(v), sizeof(v));\n\t\t}\n\n\t\tStreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_8u, true, 1};\n\t\treturn format;\n\t}\n\telse if (type == cgltf_animation_path_type_translation || type == cgltf_animation_path_type_scale)\n\t{\n\t\tStreamFormat::Filter filter = settings.compressmore ? StreamFormat::Filter_Exp : StreamFormat::Filter_None;\n\t\tint bits = (type == cgltf_animation_path_type_translation) ? settings.trn_bits : settings.scl_bits;\n\n\t\tsize_t offset = bin.size();\n\n\t\tfor (size_t i = 0; i < data.size(); ++i)\n\t\t{\n\t\t\tconst Attr& a = data[i];\n\n\t\t\tfloat v[3] = {a.f[0], a.f[1], a.f[2]};\n\t\t\tbin.append(reinterpret_cast<const char*>(v), sizeof(v));\n\t\t}\n\n\t\tif (filter == StreamFormat::Filter_Exp)\n\t\t\tmeshopt_encodeFilterExp(&bin[offset], data.size(), 12, bits, reinterpret_cast<const float*>(&bin[offset]), meshopt_EncodeExpSharedVector);\n\n\t\tStreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_32f, false, 12, filter};\n\t\treturn format;\n\t}\n\telse\n\t{\n\t\tfor (size_t i = 0; i < data.size(); ++i)\n\t\t{\n\t\t\tconst Attr& a = data[i];\n\n\t\t\tfloat v[4] = {a.f[0], a.f[1], a.f[2], a.f[3]};\n\t\t\tbin.append(reinterpret_cast<const char*>(v), sizeof(v));\n\t\t}\n\n\t\tStreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_32f, false, 16};\n\t\treturn format;\n\t}\n}\n\nvoid compressVertexStream(std::string& bin, const std::string& data, size_t count, size_t stride, int level)\n{\n\tassert(data.size() == count * stride);\n\n\tstd::vector<unsigned char> compressed(meshopt_encodeVertexBufferBound(count, stride));\n\tsize_t size = meshopt_encodeVertexBufferLevel(&compressed[0], compressed.size(), data.c_str(), count, stride, level, level > 0);\n\n\tbin.append(reinterpret_cast<const char*>(&compressed[0]), size);\n}\n\nvoid compressIndexStream(std::string& bin, const std::string& data, size_t count, size_t stride)\n{\n\tassert(stride == 2 || stride == 4);\n\tassert(data.size() == count * stride);\n\tassert(count % 3 == 0);\n\n\tstd::vector<unsigned char> compressed(meshopt_encodeIndexBufferBound(count, count));\n\tsize_t size = 0;\n\n\tif (stride == 2)\n\t\tsize = meshopt_encodeIndexBuffer(&compressed[0], compressed.size(), reinterpret_cast<const uint16_t*>(data.c_str()), count);\n\telse\n\t\tsize = meshopt_encodeIndexBuffer(&compressed[0], compressed.size(), reinterpret_cast<const uint32_t*>(data.c_str()), count);\n\n\tbin.append(reinterpret_cast<const char*>(&compressed[0]), size);\n}\n\nvoid compressIndexSequence(std::string& bin, const std::string& data, size_t count, size_t stride)\n{\n\tassert(stride == 2 || stride == 4);\n\tassert(data.size() == count * stride);\n\n\tstd::vector<unsigned char> compressed(meshopt_encodeIndexSequenceBound(count, count));\n\tsize_t size = 0;\n\n\tif (stride == 2)\n\t\tsize = meshopt_encodeIndexSequence(&compressed[0], compressed.size(), reinterpret_cast<const uint16_t*>(data.c_str()), count);\n\telse\n\t\tsize = meshopt_encodeIndexSequence(&compressed[0], compressed.size(), reinterpret_cast<const uint32_t*>(data.c_str()), count);\n\n\tbin.append(reinterpret_cast<const char*>(&compressed[0]), size);\n}\n"
  },
  {
    "path": "gltf/wasistubs.cpp",
    "content": "#ifdef __wasi__\n#include <stdlib.h>\n#include <string.h>\n\n#include <wasi/api.h>\n\nextern \"C\" void __cxa_throw(void* ptr, void* type, void* destructor)\n{\n\tabort();\n}\n\nextern \"C\" void* __cxa_allocate_exception(size_t thrown_size)\n{\n\tabort();\n}\n\nextern \"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)\n    __attribute__((\n        __import_module__(\"wasi_snapshot_preview1\"),\n        __import_name__(\"path_open32\"),\n        __warn_unused_result__));\n\nextern \"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)\n{\n\treturn __wasi_path_open32(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);\n}\n\nextern \"C\" int32_t __wasi_fd_seek32(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3)\n    __attribute__((\n        __import_module__(\"wasi_snapshot_preview1\"),\n        __import_name__(\"fd_seek32\"),\n        __warn_unused_result__));\n\nextern \"C\" int32_t __imported_wasi_snapshot_preview1_fd_seek(int32_t arg0, int64_t arg1, int32_t arg2, int32_t arg3)\n{\n\t*(uint64_t*)arg3 = 0;\n\treturn __wasi_fd_seek32(arg0, arg1, arg2, arg3);\n}\n\nextern \"C\" int32_t __imported_wasi_snapshot_preview1_clock_time_get(int32_t arg0, int64_t arg1, int32_t arg2)\n{\n\treturn __WASI_ERRNO_NOSYS;\n}\n\n#endif\n"
  },
  {
    "path": "gltf/wasistubs.txt",
    "content": "__wasi_path_open32\n__wasi_fd_seek32\n"
  },
  {
    "path": "gltf/write.cpp",
    "content": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <float.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\nstatic const char* componentType(cgltf_component_type type)\n{\n\tswitch (type)\n\t{\n\tcase cgltf_component_type_r_8:\n\t\treturn \"5120\";\n\tcase cgltf_component_type_r_8u:\n\t\treturn \"5121\";\n\tcase cgltf_component_type_r_16:\n\t\treturn \"5122\";\n\tcase cgltf_component_type_r_16u:\n\t\treturn \"5123\";\n\tcase cgltf_component_type_r_32u:\n\t\treturn \"5125\";\n\tcase cgltf_component_type_r_32f:\n\t\treturn \"5126\";\n\tdefault:\n\t\treturn \"0\";\n\t}\n}\n\nstatic const char* shapeType(cgltf_type type)\n{\n\tswitch (type)\n\t{\n\tcase cgltf_type_scalar:\n\t\treturn \"SCALAR\";\n\tcase cgltf_type_vec2:\n\t\treturn \"VEC2\";\n\tcase cgltf_type_vec3:\n\t\treturn \"VEC3\";\n\tcase cgltf_type_vec4:\n\t\treturn \"VEC4\";\n\tcase cgltf_type_mat2:\n\t\treturn \"MAT2\";\n\tcase cgltf_type_mat3:\n\t\treturn \"MAT3\";\n\tcase cgltf_type_mat4:\n\t\treturn \"MAT4\";\n\tdefault:\n\t\treturn \"\";\n\t}\n}\n\nconst char* attributeType(cgltf_attribute_type type)\n{\n\tswitch (type)\n\t{\n\tcase cgltf_attribute_type_position:\n\t\treturn \"POSITION\";\n\tcase cgltf_attribute_type_normal:\n\t\treturn \"NORMAL\";\n\tcase cgltf_attribute_type_tangent:\n\t\treturn \"TANGENT\";\n\tcase cgltf_attribute_type_texcoord:\n\t\treturn \"TEXCOORD\";\n\tcase cgltf_attribute_type_color:\n\t\treturn \"COLOR\";\n\tcase cgltf_attribute_type_joints:\n\t\treturn \"JOINTS\";\n\tcase cgltf_attribute_type_weights:\n\t\treturn \"WEIGHTS\";\n\tcase cgltf_attribute_type_custom:\n\t\treturn \"CUSTOM\";\n\tdefault:\n\t\treturn \"ATTRIBUTE\";\n\t}\n}\n\nconst char* animationPath(cgltf_animation_path_type type)\n{\n\tswitch (type)\n\t{\n\tcase cgltf_animation_path_type_translation:\n\t\treturn \"translation\";\n\tcase cgltf_animation_path_type_rotation:\n\t\treturn \"rotation\";\n\tcase cgltf_animation_path_type_scale:\n\t\treturn \"scale\";\n\tcase cgltf_animation_path_type_weights:\n\t\treturn \"weights\";\n\tdefault:\n\t\treturn \"\";\n\t}\n}\n\nstatic const char* lightType(cgltf_light_type type)\n{\n\tswitch (type)\n\t{\n\tcase cgltf_light_type_directional:\n\t\treturn \"directional\";\n\tcase cgltf_light_type_point:\n\t\treturn \"point\";\n\tcase cgltf_light_type_spot:\n\t\treturn \"spot\";\n\tdefault:\n\t\treturn \"\";\n\t}\n}\n\nstatic const char* alphaMode(cgltf_alpha_mode mode)\n{\n\tswitch (mode)\n\t{\n\tcase cgltf_alpha_mode_opaque:\n\t\treturn \"OPAQUE\";\n\tcase cgltf_alpha_mode_mask:\n\t\treturn \"MASK\";\n\tcase cgltf_alpha_mode_blend:\n\t\treturn \"BLEND\";\n\tdefault:\n\t\treturn \"\";\n\t}\n}\n\nstatic const char* interpolationType(cgltf_interpolation_type type)\n{\n\tswitch (type)\n\t{\n\tcase cgltf_interpolation_type_linear:\n\t\treturn \"LINEAR\";\n\tcase cgltf_interpolation_type_step:\n\t\treturn \"STEP\";\n\tcase cgltf_interpolation_type_cubic_spline:\n\t\treturn \"CUBICSPLINE\";\n\tdefault:\n\t\treturn \"\";\n\t}\n}\n\nstatic const char* compressionMode(BufferView::Compression mode)\n{\n\tswitch (mode)\n\t{\n\tcase BufferView::Compression_Attribute:\n\t\treturn \"ATTRIBUTES\";\n\n\tcase BufferView::Compression_Index:\n\t\treturn \"TRIANGLES\";\n\n\tcase BufferView::Compression_IndexSequence:\n\t\treturn \"INDICES\";\n\n\tdefault:\n\t\treturn \"\";\n\t}\n}\n\nstatic const char* compressionFilter(StreamFormat::Filter filter)\n{\n\tswitch (filter)\n\t{\n\tcase StreamFormat::Filter_None:\n\t\treturn \"NONE\";\n\n\tcase StreamFormat::Filter_Oct:\n\t\treturn \"OCTAHEDRAL\";\n\n\tcase StreamFormat::Filter_Quat:\n\t\treturn \"QUATERNION\";\n\n\tcase StreamFormat::Filter_Exp:\n\t\treturn \"EXPONENTIAL\";\n\n\tcase StreamFormat::Filter_Color:\n\t\treturn \"COLOR\";\n\n\tdefault:\n\t\treturn \"\";\n\t}\n}\n\nstatic void writeTextureInfo(std::string& json, const cgltf_data* data, const cgltf_texture_view& view, const QuantizationTexture* qt, std::vector<TextureInfo>& textures, const char* scale = NULL)\n{\n\tassert(view.texture);\n\n\tbool has_transform = false;\n\tcgltf_texture_transform transform = {};\n\ttransform.scale[0] = transform.scale[1] = 1.f;\n\n\tif (hasValidTransform(view))\n\t{\n\t\ttransform = view.transform;\n\t\thas_transform = true;\n\t}\n\n\tif (qt)\n\t{\n\t\ttransform.offset[0] += qt->offset[0];\n\t\ttransform.offset[1] += qt->offset[1];\n\t\ttransform.scale[0] *= qt->scale[0] / float((1 << qt->bits) - 1) * (qt->normalized ? 65535.f : 1.f);\n\t\ttransform.scale[1] *= qt->scale[1] / float((1 << qt->bits) - 1) * (qt->normalized ? 65535.f : 1.f);\n\t\thas_transform = true;\n\t}\n\n\tappend(json, \"{\\\"index\\\":\");\n\tappend(json, size_t(textures[view.texture - data->textures].remap));\n\tif (view.texcoord != 0)\n\t{\n\t\tappend(json, \",\\\"texCoord\\\":\");\n\t\tappend(json, size_t(view.texcoord));\n\t}\n\tif (scale && view.scale != 1)\n\t{\n\t\tappend(json, \",\\\"\");\n\t\tappend(json, scale);\n\t\tappend(json, \"\\\":\");\n\t\tappend(json, view.scale);\n\t}\n\tif (has_transform)\n\t{\n\t\tappend(json, \",\\\"extensions\\\":{\\\"KHR_texture_transform\\\":{\");\n\t\tappend(json, \"\\\"offset\\\":\");\n\t\tappend(json, transform.offset, 2);\n\t\tappend(json, \",\\\"scale\\\":\");\n\t\tappend(json, transform.scale, 2);\n\t\tif (transform.rotation != 0.f)\n\t\t{\n\t\t\tappend(json, \",\\\"rotation\\\":\");\n\t\t\tappend(json, transform.rotation);\n\t\t}\n\t\tappend(json, \"}}\");\n\t}\n\tappend(json, \"}\");\n}\n\nstatic const float white[4] = {1, 1, 1, 1};\nstatic const float black[4] = {0, 0, 0, 0};\n\nstatic void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_pbr_metallic_roughness& pbr, const QuantizationTexture* qt, std::vector<TextureInfo>& textures)\n{\n\tcomma(json);\n\tappend(json, \"\\\"pbrMetallicRoughness\\\":{\");\n\tif (memcmp(pbr.base_color_factor, white, 16) != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"baseColorFactor\\\":\");\n\t\tappend(json, pbr.base_color_factor, 4);\n\t}\n\tif (pbr.base_color_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"baseColorTexture\\\":\");\n\t\twriteTextureInfo(json, data, pbr.base_color_texture, qt, textures);\n\t}\n\tif (pbr.metallic_factor != 1)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"metallicFactor\\\":\");\n\t\tappend(json, pbr.metallic_factor);\n\t}\n\tif (pbr.roughness_factor != 1)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"roughnessFactor\\\":\");\n\t\tappend(json, pbr.roughness_factor);\n\t}\n\tif (pbr.metallic_roughness_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"metallicRoughnessTexture\\\":\");\n\t\twriteTextureInfo(json, data, pbr.metallic_roughness_texture, qt, textures);\n\t}\n\tappend(json, \"}\");\n}\n\nstatic void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_pbr_specular_glossiness& pbr, const QuantizationTexture* qt, std::vector<TextureInfo>& textures)\n{\n\tcomma(json);\n\tappend(json, \"\\\"KHR_materials_pbrSpecularGlossiness\\\":{\");\n\tif (pbr.diffuse_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"diffuseTexture\\\":\");\n\t\twriteTextureInfo(json, data, pbr.diffuse_texture, qt, textures);\n\t}\n\tif (pbr.specular_glossiness_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"specularGlossinessTexture\\\":\");\n\t\twriteTextureInfo(json, data, pbr.specular_glossiness_texture, qt, textures);\n\t}\n\tif (memcmp(pbr.diffuse_factor, white, 16) != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"diffuseFactor\\\":\");\n\t\tappend(json, pbr.diffuse_factor, 4);\n\t}\n\tif (memcmp(pbr.specular_factor, white, 12) != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"specularFactor\\\":\");\n\t\tappend(json, pbr.specular_factor, 3);\n\t}\n\tif (pbr.glossiness_factor != 1)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"glossinessFactor\\\":\");\n\t\tappend(json, pbr.glossiness_factor);\n\t}\n\tappend(json, \"}\");\n}\n\nstatic void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_clearcoat& cc, const QuantizationTexture* qt, std::vector<TextureInfo>& textures)\n{\n\tcomma(json);\n\tappend(json, \"\\\"KHR_materials_clearcoat\\\":{\");\n\tif (cc.clearcoat_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"clearcoatTexture\\\":\");\n\t\twriteTextureInfo(json, data, cc.clearcoat_texture, qt, textures);\n\t}\n\tif (cc.clearcoat_roughness_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"clearcoatRoughnessTexture\\\":\");\n\t\twriteTextureInfo(json, data, cc.clearcoat_roughness_texture, qt, textures);\n\t}\n\tif (cc.clearcoat_normal_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"clearcoatNormalTexture\\\":\");\n\t\twriteTextureInfo(json, data, cc.clearcoat_normal_texture, qt, textures, \"scale\");\n\t}\n\tif (cc.clearcoat_factor != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"clearcoatFactor\\\":\");\n\t\tappend(json, cc.clearcoat_factor);\n\t}\n\tif (cc.clearcoat_factor != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"clearcoatRoughnessFactor\\\":\");\n\t\tappend(json, cc.clearcoat_roughness_factor);\n\t}\n\tappend(json, \"}\");\n}\n\nstatic void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_transmission& tm, const QuantizationTexture* qt, std::vector<TextureInfo>& textures)\n{\n\tcomma(json);\n\tappend(json, \"\\\"KHR_materials_transmission\\\":{\");\n\tif (tm.transmission_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"transmissionTexture\\\":\");\n\t\twriteTextureInfo(json, data, tm.transmission_texture, qt, textures);\n\t}\n\tif (tm.transmission_factor != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"transmissionFactor\\\":\");\n\t\tappend(json, tm.transmission_factor);\n\t}\n\tappend(json, \"}\");\n}\n\nstatic void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_ior& tm)\n{\n\t(void)data;\n\n\tcomma(json);\n\tappend(json, \"\\\"KHR_materials_ior\\\":{\");\n\tappend(json, \"\\\"ior\\\":\");\n\tappend(json, tm.ior);\n\tappend(json, \"}\");\n}\n\nstatic void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_specular& tm, const QuantizationTexture* qt, std::vector<TextureInfo>& textures)\n{\n\tcomma(json);\n\tappend(json, \"\\\"KHR_materials_specular\\\":{\");\n\tif (tm.specular_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"specularTexture\\\":\");\n\t\twriteTextureInfo(json, data, tm.specular_texture, qt, textures);\n\t}\n\tif (tm.specular_color_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"specularColorTexture\\\":\");\n\t\twriteTextureInfo(json, data, tm.specular_color_texture, qt, textures);\n\t}\n\tif (tm.specular_factor != 1)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"specularFactor\\\":\");\n\t\tappend(json, tm.specular_factor);\n\t}\n\tif (memcmp(tm.specular_color_factor, white, 12) != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"specularColorFactor\\\":\");\n\t\tappend(json, tm.specular_color_factor, 3);\n\t}\n\tappend(json, \"}\");\n}\n\nstatic void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_sheen& tm, const QuantizationTexture* qt, std::vector<TextureInfo>& textures)\n{\n\tcomma(json);\n\tappend(json, \"\\\"KHR_materials_sheen\\\":{\");\n\tif (tm.sheen_color_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"sheenColorTexture\\\":\");\n\t\twriteTextureInfo(json, data, tm.sheen_color_texture, qt, textures);\n\t}\n\tif (tm.sheen_roughness_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"sheenRoughnessTexture\\\":\");\n\t\twriteTextureInfo(json, data, tm.sheen_roughness_texture, qt, textures);\n\t}\n\tif (memcmp(tm.sheen_color_factor, black, 12) != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"sheenColorFactor\\\":\");\n\t\tappend(json, tm.sheen_color_factor, 3);\n\t}\n\tif (tm.sheen_roughness_factor != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"sheenRoughnessFactor\\\":\");\n\t\tappend(json, tm.sheen_roughness_factor);\n\t}\n\tappend(json, \"}\");\n}\n\nstatic void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_volume& tm, const QuantizationPosition* qp, const QuantizationTexture* qt, std::vector<TextureInfo>& textures)\n{\n\tcomma(json);\n\tappend(json, \"\\\"KHR_materials_volume\\\":{\");\n\tif (tm.thickness_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"thicknessTexture\\\":\");\n\t\twriteTextureInfo(json, data, tm.thickness_texture, qt, textures);\n\t}\n\tif (tm.thickness_factor != 0)\n\t{\n\t\t// thickness is in mesh coordinate space which is rescaled by quantization\n\t\tcomma(json);\n\t\tappend(json, \"\\\"thicknessFactor\\\":\");\n\t\tappend(json, tm.thickness_factor / (qp ? qp->node_scale : 1.f));\n\t}\n\tif (memcmp(tm.attenuation_color, white, 12) != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"attenuationColor\\\":\");\n\t\tappend(json, tm.attenuation_color, 3);\n\t}\n\tif (tm.attenuation_distance != FLT_MAX)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"attenuationDistance\\\":\");\n\t\tappend(json, tm.attenuation_distance);\n\t}\n\tappend(json, \"}\");\n}\n\nstatic void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_emissive_strength& tm)\n{\n\t(void)data;\n\n\tcomma(json);\n\tappend(json, \"\\\"KHR_materials_emissive_strength\\\":{\");\n\tif (tm.emissive_strength != 1)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"emissiveStrength\\\":\");\n\t\tappend(json, tm.emissive_strength);\n\t}\n\tappend(json, \"}\");\n}\n\nstatic void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_iridescence& tm, const QuantizationTexture* qt, std::vector<TextureInfo>& textures)\n{\n\tcomma(json);\n\tappend(json, \"\\\"KHR_materials_iridescence\\\":{\");\n\tif (tm.iridescence_factor != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"iridescenceFactor\\\":\");\n\t\tappend(json, tm.iridescence_factor);\n\t}\n\tif (tm.iridescence_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"iridescenceTexture\\\":\");\n\t\twriteTextureInfo(json, data, tm.iridescence_texture, qt, textures);\n\t}\n\tif (tm.iridescence_ior != 1.3f)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"iridescenceIor\\\":\");\n\t\tappend(json, tm.iridescence_ior);\n\t}\n\tif (tm.iridescence_thickness_min != 100.f)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"iridescenceThicknessMinimum\\\":\");\n\t\tappend(json, tm.iridescence_thickness_min);\n\t}\n\tif (tm.iridescence_thickness_max != 400.f)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"iridescenceThicknessMaximum\\\":\");\n\t\tappend(json, tm.iridescence_thickness_max);\n\t}\n\tif (tm.iridescence_thickness_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"iridescenceThicknessTexture\\\":\");\n\t\twriteTextureInfo(json, data, tm.iridescence_thickness_texture, qt, textures);\n\t}\n\tappend(json, \"}\");\n}\n\nstatic void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_anisotropy& tm, const QuantizationTexture* qt, std::vector<TextureInfo>& textures)\n{\n\tcomma(json);\n\tappend(json, \"\\\"KHR_materials_anisotropy\\\":{\");\n\tif (tm.anisotropy_strength != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"anisotropyStrength\\\":\");\n\t\tappend(json, tm.anisotropy_strength);\n\t}\n\tif (tm.anisotropy_rotation != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"anisotropyRotation\\\":\");\n\t\tappend(json, tm.anisotropy_rotation);\n\t}\n\tif (tm.anisotropy_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"anisotropyTexture\\\":\");\n\t\twriteTextureInfo(json, data, tm.anisotropy_texture, qt, textures);\n\t}\n\tappend(json, \"}\");\n}\n\nstatic void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_dispersion& tm)\n{\n\t(void)data;\n\n\tcomma(json);\n\tappend(json, \"\\\"KHR_materials_dispersion\\\":{\");\n\tappend(json, \"\\\"dispersion\\\":\");\n\tappend(json, tm.dispersion);\n\tappend(json, \"}\");\n}\n\nstatic void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_diffuse_transmission& tm, const QuantizationTexture* qt, std::vector<TextureInfo>& textures)\n{\n\tcomma(json);\n\tappend(json, \"\\\"KHR_materials_diffuse_transmission\\\":{\");\n\tif (tm.diffuse_transmission_factor != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"diffuseTransmissionFactor\\\":\");\n\t\tappend(json, tm.diffuse_transmission_factor);\n\t}\n\tif (tm.diffuse_transmission_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"diffuseTransmissionTexture\\\":\");\n\t\twriteTextureInfo(json, data, tm.diffuse_transmission_texture, qt, textures);\n\t}\n\tif (memcmp(tm.diffuse_transmission_color_factor, white, sizeof(float) * 3) != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"diffuseTransmissionColorFactor\\\":\");\n\t\tappend(json, tm.diffuse_transmission_color_factor, 3);\n\t}\n\tif (tm.diffuse_transmission_color_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"diffuseTransmissionColorTexture\\\":\");\n\t\twriteTextureInfo(json, data, tm.diffuse_transmission_color_texture, qt, textures);\n\t}\n\tappend(json, \"}\");\n}\n\nvoid writeMaterial(std::string& json, const cgltf_data* data, const cgltf_material& material, const QuantizationPosition* qp, const QuantizationTexture* qt, std::vector<TextureInfo>& textures)\n{\n\tif (material.name && *material.name)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"name\\\":\\\"\");\n\t\tappend(json, material.name);\n\t\tappend(json, \"\\\"\");\n\t}\n\n\tif (material.has_pbr_metallic_roughness)\n\t{\n\t\twriteMaterialComponent(json, data, material.pbr_metallic_roughness, qt, textures);\n\t}\n\n\tif (material.normal_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"normalTexture\\\":\");\n\t\twriteTextureInfo(json, data, material.normal_texture, qt, textures, \"scale\");\n\t}\n\n\tif (material.occlusion_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"occlusionTexture\\\":\");\n\t\twriteTextureInfo(json, data, material.occlusion_texture, qt, textures, \"strength\");\n\t}\n\n\tif (material.emissive_texture.texture)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"emissiveTexture\\\":\");\n\t\twriteTextureInfo(json, data, material.emissive_texture, qt, textures);\n\t}\n\n\tif (memcmp(material.emissive_factor, black, 12) != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"emissiveFactor\\\":\");\n\t\tappend(json, material.emissive_factor, 3);\n\t}\n\n\tif (material.alpha_mode != cgltf_alpha_mode_opaque)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"alphaMode\\\":\\\"\");\n\t\tappend(json, alphaMode(material.alpha_mode));\n\t\tappend(json, \"\\\"\");\n\t}\n\n\tif (material.alpha_cutoff != 0.5f)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"alphaCutoff\\\":\");\n\t\tappend(json, material.alpha_cutoff);\n\t}\n\n\tif (material.double_sided)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"doubleSided\\\":true\");\n\t}\n\n\tif (material.has_pbr_specular_glossiness || material.has_clearcoat || material.has_transmission || material.has_ior || material.has_specular ||\n\t    material.has_sheen || material.has_volume || material.has_emissive_strength || material.has_iridescence || material.has_anisotropy ||\n\t    material.has_dispersion || material.has_diffuse_transmission || material.unlit)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"extensions\\\":{\");\n\n\t\tif (material.has_pbr_specular_glossiness)\n\t\t\twriteMaterialComponent(json, data, material.pbr_specular_glossiness, qt, textures);\n\n\t\tif (material.has_clearcoat)\n\t\t\twriteMaterialComponent(json, data, material.clearcoat, qt, textures);\n\n\t\tif (material.has_transmission)\n\t\t\twriteMaterialComponent(json, data, material.transmission, qt, textures);\n\n\t\tif (material.has_ior)\n\t\t\twriteMaterialComponent(json, data, material.ior);\n\n\t\tif (material.has_specular)\n\t\t\twriteMaterialComponent(json, data, material.specular, qt, textures);\n\n\t\tif (material.has_sheen)\n\t\t\twriteMaterialComponent(json, data, material.sheen, qt, textures);\n\n\t\tif (material.has_volume)\n\t\t\twriteMaterialComponent(json, data, material.volume, qp, qt, textures);\n\n\t\tif (material.has_emissive_strength)\n\t\t\twriteMaterialComponent(json, data, material.emissive_strength);\n\n\t\tif (material.has_iridescence)\n\t\t\twriteMaterialComponent(json, data, material.iridescence, qt, textures);\n\n\t\tif (material.has_anisotropy)\n\t\t\twriteMaterialComponent(json, data, material.anisotropy, qt, textures);\n\n\t\tif (material.has_dispersion)\n\t\t\twriteMaterialComponent(json, data, material.dispersion);\n\n\t\tif (material.has_diffuse_transmission)\n\t\t\twriteMaterialComponent(json, data, material.diffuse_transmission, qt, textures);\n\n\t\tif (material.unlit)\n\t\t{\n\t\t\tcomma(json);\n\t\t\tappend(json, \"\\\"KHR_materials_unlit\\\":{}\");\n\t\t}\n\n\t\tappend(json, \"}\");\n\t}\n}\n\nsize_t getBufferView(std::vector<BufferView>& views, BufferView::Kind kind, StreamFormat::Filter filter, BufferView::Compression compression, size_t stride, int variant)\n{\n\tif (variant >= 0)\n\t{\n\t\tfor (size_t i = 0; i < views.size(); ++i)\n\t\t{\n\t\t\tBufferView& v = views[i];\n\n\t\t\tif (v.kind == kind && v.filter == filter && v.compression == compression && v.stride == stride && v.variant == variant)\n\t\t\t\treturn i;\n\t\t}\n\t}\n\n\tBufferView view = {kind, filter, compression, stride, variant};\n\tviews.push_back(view);\n\n\treturn views.size() - 1;\n}\n\nvoid 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)\n{\n\tassert(bin_size == count * stride);\n\n\t// when compression is enabled, we store uncompressed data in buffer 1 and compressed data in buffer 0\n\t// when compression is disabled, we store uncompressed data in buffer 0\n\tsize_t buffer = compression != BufferView::Compression_None ? 1 : 0;\n\n\tappend(json, \"{\\\"buffer\\\":\");\n\tappend(json, buffer);\n\tappend(json, \",\\\"byteOffset\\\":\");\n\tappend(json, bin_offset);\n\tappend(json, \",\\\"byteLength\\\":\");\n\tappend(json, bin_size);\n\tif (kind == BufferView::Kind_Vertex)\n\t{\n\t\tappend(json, \",\\\"byteStride\\\":\");\n\t\tappend(json, stride);\n\t}\n\tif (kind == BufferView::Kind_Vertex || kind == BufferView::Kind_Index)\n\t{\n\t\tappend(json, \",\\\"target\\\":\");\n\t\tappend(json, (kind == BufferView::Kind_Vertex) ? \"34962\" : \"34963\");\n\t}\n\tif (compression != BufferView::Compression_None)\n\t{\n\t\tappend(json, \",\\\"extensions\\\":{\");\n\t\tappend(json, \"\\\"\");\n\t\tappend(json, meshopt_ext);\n\t\tappend(json, \"\\\":{\");\n\t\tappend(json, \"\\\"buffer\\\":0\");\n\t\tappend(json, \",\\\"byteOffset\\\":\");\n\t\tappend(json, size_t(compressed_offset));\n\t\tappend(json, \",\\\"byteLength\\\":\");\n\t\tappend(json, size_t(compressed_size));\n\t\tappend(json, \",\\\"byteStride\\\":\");\n\t\tappend(json, stride);\n\t\tappend(json, \",\\\"mode\\\":\\\"\");\n\t\tappend(json, compressionMode(compression));\n\t\tappend(json, \"\\\"\");\n\t\tif (filter != StreamFormat::Filter_None)\n\t\t{\n\t\t\tappend(json, \",\\\"filter\\\":\\\"\");\n\t\t\tappend(json, compressionFilter(filter));\n\t\t\tappend(json, \"\\\"\");\n\t\t}\n\t\tappend(json, \",\\\"count\\\":\");\n\t\tappend(json, count);\n\t\tappend(json, \"}}\");\n\t}\n\tappend(json, \"}\");\n}\n\nstatic 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)\n{\n\tappend(json, \"{\\\"bufferView\\\":\");\n\tappend(json, view);\n\tappend(json, \",\\\"byteOffset\\\":\");\n\tappend(json, offset);\n\tappend(json, \",\\\"componentType\\\":\");\n\tappend(json, componentType(component_type));\n\tappend(json, \",\\\"count\\\":\");\n\tappend(json, count);\n\tappend(json, \",\\\"type\\\":\\\"\");\n\tappend(json, shapeType(type));\n\tappend(json, \"\\\"\");\n\n\tif (normalized)\n\t{\n\t\tappend(json, \",\\\"normalized\\\":true\");\n\t}\n\n\tif (min && max)\n\t{\n\t\tassert(numminmax);\n\n\t\tappend(json, \",\\\"min\\\":\");\n\t\tappend(json, min, numminmax);\n\t\tappend(json, \",\\\"max\\\":\");\n\t\tappend(json, max, numminmax);\n\t}\n\n\tappend(json, \"}\");\n}\n\nstatic void writeEmbeddedImage(std::string& json, std::vector<BufferView>& views, const char* data, size_t size, const char* mime_type, TextureKind kind)\n{\n\tsize_t view = getBufferView(views, BufferView::Kind_Image, StreamFormat::Filter_None, BufferView::Compression_None, 1, -1 - kind);\n\n\tassert(views[view].data.empty());\n\tviews[view].data.assign(data, size);\n\n\tappend(json, \"\\\"bufferView\\\":\");\n\tappend(json, view);\n\tappend(json, \",\\\"mimeType\\\":\\\"\");\n\tappend(json, mime_type);\n\tappend(json, \"\\\"\");\n}\n\nstatic std::string decodeUri(const char* uri)\n{\n\tstd::string result = uri;\n\n\tif (!result.empty())\n\t{\n\t\tcgltf_decode_uri(&result[0]);\n\t\tresult.resize(strlen(result.c_str()));\n\t}\n\n\treturn result;\n}\n\nvoid writeSampler(std::string& json, const cgltf_sampler& sampler)\n{\n\tif (sampler.mag_filter != cgltf_filter_type_undefined)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"magFilter\\\":\");\n\t\tappend(json, size_t(sampler.mag_filter));\n\t}\n\tif (sampler.min_filter != cgltf_filter_type_undefined)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"minFilter\\\":\");\n\t\tappend(json, size_t(sampler.min_filter));\n\t}\n\tif (sampler.wrap_s != cgltf_wrap_mode_repeat)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"wrapS\\\":\");\n\t\tappend(json, size_t(sampler.wrap_s));\n\t}\n\tif (sampler.wrap_t != cgltf_wrap_mode_repeat)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"wrapT\\\":\");\n\t\tappend(json, size_t(sampler.wrap_t));\n\t}\n}\n\nstatic void writeImageError(std::string& json, const char* action, size_t index, const char* uri, const char* reason)\n{\n\tappend(json, \"\\\"uri\\\":\\\"\");\n\tappend(json, \"data:image/png;base64,ERR/\");\n\tappend(json, \"\\\"\");\n\n\tfprintf(stderr, \"Warning: unable to %s image %d (%s), skipping%s%s%s\\n\", action, int(index), uri ? uri : \"embedded\", reason ? \" (\" : \"\", reason ? reason : \"\", reason ? \")\" : \"\");\n}\n\nstatic void writeImageData(std::string& json, std::vector<BufferView>& views, size_t index, const char* uri, const char* mime_type, const std::string& contents, const char* output_path, TextureKind kind, bool embed)\n{\n\tbool dataUri = uri && strncmp(uri, \"data:\", 5) == 0;\n\n\tif (!embed && uri && !dataUri && output_path)\n\t{\n\t\tstd::string file_name = getFileName(uri) + mimeExtension(mime_type);\n\t\tstd::string file_path = getFullPath(decodeUri(file_name.c_str()).c_str(), output_path);\n\n\t\tif (writeFile(file_path.c_str(), contents))\n\t\t{\n\t\t\tappend(json, \"\\\"uri\\\":\\\"\");\n\t\t\tappend(json, file_name);\n\t\t\tappend(json, \"\\\"\");\n\t\t}\n\t\telse\n\t\t{\n\t\t\twriteImageError(json, \"save\", int(index), uri, file_path.c_str());\n\t\t}\n\t}\n\telse\n\t{\n\t\twriteEmbeddedImage(json, views, contents.c_str(), contents.size(), mime_type, kind);\n\t}\n}\n\nvoid writeImage(std::string& json, std::vector<BufferView>& 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)\n{\n\tif (image.name && *image.name)\n\t{\n\t\tappend(json, \"\\\"name\\\":\\\"\");\n\t\tappend(json, image.name);\n\t\tappend(json, \"\\\",\");\n\t}\n\n\tif (encoded)\n\t{\n\t\tconst char* mime_type = (settings.texture_mode[info.kind] == TextureMode_WebP) ? \"image/webp\" : \"image/ktx2\";\n\n\t\t// image was pre-encoded via encodeImages (which might have failed!)\n\t\tif (encoded->compare(0, 5, \"error\") == 0)\n\t\t\twriteImageError(json, \"encode\", int(index), image.uri, encoded->c_str());\n\t\telse\n\t\t\twriteImageData(json, views, index, image.uri, mime_type, *encoded, output_path, info.kind, settings.texture_embed);\n\t\treturn;\n\t}\n\n\tbool dataUri = image.uri && strncmp(image.uri, \"data:\", 5) == 0;\n\n\tif (image.uri && !dataUri && settings.texture_ref)\n\t{\n\t\t// fast-path: we don't need to read the image to memory\n\t\tappend(json, \"\\\"uri\\\":\\\"\");\n\t\tappend(json, image.uri);\n\t\tappend(json, \"\\\"\");\n\t\treturn;\n\t}\n\n\tstd::string img_data;\n\tstd::string mime_type;\n\tif (!readImage(image, input_path, img_data, mime_type))\n\t{\n\t\twriteImageError(json, \"read\", index, image.uri, NULL);\n\t\treturn;\n\t}\n\n\twriteImageData(json, views, index, image.uri, mime_type.c_str(), img_data, output_path, info.kind, settings.texture_embed);\n}\n\nvoid writeTexture(std::string& json, const cgltf_texture& texture, const ImageInfo* info, cgltf_data* data, const Settings& settings)\n{\n\tif (texture.sampler)\n\t{\n\t\tappend(json, \"\\\"sampler\\\":\");\n\t\tappend(json, size_t(texture.sampler - data->samplers));\n\t}\n\n\tif (texture.image)\n\t{\n\t\tif (info && settings.texture_mode[info->kind] != TextureMode_Raw)\n\t\t{\n\t\t\tconst char* texture_ext = (settings.texture_mode[info->kind] == TextureMode_WebP) ? \"EXT_texture_webp\" : \"KHR_texture_basisu\";\n\n\t\t\tcomma(json);\n\t\t\tappend(json, \"\\\"extensions\\\":{\\\"\");\n\t\t\tappend(json, texture_ext);\n\t\t\tappend(json, \"\\\":{\\\"source\\\":\");\n\t\t\tappend(json, size_t(texture.image - data->images));\n\t\t\tappend(json, \"}}\");\n\n\t\t\treturn; // skip input basisu image if present, as we replace it with the one we encoded\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcomma(json);\n\t\t\tappend(json, \"\\\"source\\\":\");\n\t\t\tappend(json, size_t(texture.image - data->images));\n\t\t}\n\t}\n\n\tif (texture.basisu_image)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"extensions\\\":{\\\"KHR_texture_basisu\\\":{\\\"source\\\":\");\n\t\tappend(json, size_t(texture.basisu_image - data->images));\n\t\tappend(json, \"}}\");\n\t}\n\telse if (texture.webp_image)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"extensions\\\":{\\\"EXT_texture_webp\\\":{\\\"source\\\":\");\n\t\tappend(json, size_t(texture.webp_image - data->images));\n\t\tappend(json, \"}}\");\n\t}\n}\n\nstatic void writePrimitiveAccessor(std::string& json_accessors, const Stream& stream, size_t view, size_t offset, const StreamFormat& format, const QuantizationPosition& qp, const Settings& settings)\n{\n\tcomma(json_accessors);\n\tif (stream.type == cgltf_attribute_type_position)\n\t{\n\t\tfloat min[3] = {};\n\t\tfloat max[3] = {};\n\t\tgetPositionBounds(min, max, stream, qp, settings);\n\n\t\twriteAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, stream.data.size(), min, max, 3);\n\t}\n\telse\n\t{\n\t\twriteAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, stream.data.size());\n\t}\n}\n\nstatic void writePrimitiveAttribute(std::string& json, const Stream& stream, size_t accessor)\n{\n\tcomma(json);\n\tappend(json, \"\\\"\");\n\tif (stream.custom_name)\n\t{\n\t\tappend(json, stream.custom_name);\n\t}\n\telse\n\t{\n\t\tappend(json, attributeType(stream.type));\n\t\tif (stream.type != cgltf_attribute_type_position && stream.type != cgltf_attribute_type_normal && stream.type != cgltf_attribute_type_tangent)\n\t\t{\n\t\t\tappend(json, \"_\");\n\t\t\tappend(json, size_t(stream.index));\n\t\t}\n\t}\n\tappend(json, \"\\\":\");\n\tappend(json, accessor);\n}\n\nstatic void writeMeshAttributesInterleaved(std::string& json, std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, int target, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings)\n{\n\tstruct Attribute\n\t{\n\t\tconst Stream* stream;\n\t\tStreamFormat format;\n\t\tstd::string data;\n\t};\n\n\tstd::vector<Attribute> attributes;\n\tsize_t stride = 0;\n\n\tfor (size_t j = 0; j < mesh.streams.size(); ++j)\n\t{\n\t\tconst Stream& stream = mesh.streams[j];\n\n\t\tif (stream.target != target)\n\t\t\tcontinue;\n\n\t\tAttribute attr = {&stream};\n\t\tattr.format = writeVertexStream(attr.data, stream, qp, qt, settings, /* filters= */ false);\n\t\tassert(attr.format.filter == StreamFormat::Filter_None);\n\n\t\tstride += attr.format.stride;\n\t\tattributes.emplace_back(std::move(attr));\n\t}\n\n\tBufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None;\n\tsize_t view = getBufferView(views, BufferView::Kind_Vertex, StreamFormat::Filter_None, compression, stride, 0);\n\tsize_t offset = views[view].data.size();\n\n\tsize_t vertex_count = mesh.streams[0].data.size();\n\n\tviews[view].data.resize(views[view].data.size() + stride * vertex_count);\n\n\tsize_t write_offset = offset;\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tfor (Attribute& attr : attributes)\n\t\t{\n\t\t\tmemcpy(&views[view].data[write_offset], &attr.data[i * attr.format.stride], attr.format.stride);\n\t\t\twrite_offset += attr.format.stride;\n\t\t}\n\n\tfor (Attribute& attr : attributes)\n\t{\n\t\tconst Stream& stream = *attr.stream;\n\t\tconst StreamFormat& format = attr.format;\n\n\t\twritePrimitiveAccessor(json_accessors, stream, view, offset, format, qp, settings);\n\n\t\tsize_t vertex_accr = accr_offset++;\n\t\twritePrimitiveAttribute(json, stream, vertex_accr);\n\n\t\toffset += attr.format.stride;\n\t}\n}\n\nvoid writeMeshAttributes(std::string& json, std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, int target, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings)\n{\n\tif (settings.mesh_interleaved)\n\t\treturn writeMeshAttributesInterleaved(json, views, json_accessors, accr_offset, mesh, target, qp, qt, settings);\n\n\tstd::string scratch;\n\n\tfor (size_t j = 0; j < mesh.streams.size(); ++j)\n\t{\n\t\tconst Stream& stream = mesh.streams[j];\n\n\t\tif (stream.target != target)\n\t\t\tcontinue;\n\n\t\tscratch.clear();\n\t\tStreamFormat format = writeVertexStream(scratch, stream, qp, qt, settings);\n\t\tBufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None;\n\n\t\tsize_t view = getBufferView(views, BufferView::Kind_Vertex, format.filter, compression, format.stride, stream.type);\n\t\tsize_t offset = views[view].data.size();\n\t\tviews[view].data += scratch;\n\n\t\twritePrimitiveAccessor(json_accessors, stream, view, offset, format, qp, settings);\n\n\t\tsize_t vertex_accr = accr_offset++;\n\t\twritePrimitiveAttribute(json, stream, vertex_accr);\n\t}\n}\n\nsize_t writeMeshIndices(std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const std::vector<unsigned int>& indices, cgltf_primitive_type type, const Settings& settings)\n{\n\tstd::string scratch;\n\tStreamFormat format = writeIndexStream(scratch, indices);\n\tBufferView::Compression compression = settings.compress ? (type == cgltf_primitive_type_triangles ? BufferView::Compression_Index : BufferView::Compression_IndexSequence) : BufferView::Compression_None;\n\n\tsize_t view = getBufferView(views, BufferView::Kind_Index, StreamFormat::Filter_None, compression, format.stride);\n\tsize_t offset = views[view].data.size();\n\tviews[view].data += scratch;\n\n\tcomma(json_accessors);\n\twriteAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, indices.size());\n\n\tsize_t index_accr = accr_offset++;\n\treturn index_accr;\n}\n\nvoid writeMeshGeometry(std::string& json, std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings)\n{\n\tappend(json, \"{\\\"attributes\\\":{\");\n\twriteMeshAttributes(json, views, json_accessors, accr_offset, mesh, 0, qp, qt, settings);\n\tappend(json, \"}\");\n\tif (mesh.type != cgltf_primitive_type_triangles)\n\t{\n\t\tappend(json, \",\\\"mode\\\":\");\n\t\tappend(json, size_t(mesh.type - cgltf_primitive_type_points));\n\t}\n\tif (mesh.targets)\n\t{\n\t\tappend(json, \",\\\"targets\\\":[\");\n\t\tfor (size_t j = 0; j < mesh.targets; ++j)\n\t\t{\n\t\t\tcomma(json);\n\t\t\tappend(json, \"{\");\n\t\t\twriteMeshAttributes(json, views, json_accessors, accr_offset, mesh, int(1 + j), qp, qt, settings);\n\t\t\tappend(json, \"}\");\n\t\t}\n\t\tappend(json, \"]\");\n\t}\n\n\tif (!mesh.indices.empty())\n\t{\n\t\tsize_t index_accr = writeMeshIndices(views, json_accessors, accr_offset, mesh.indices, mesh.type, settings);\n\n\t\tappend(json, \",\\\"indices\\\":\");\n\t\tappend(json, index_accr);\n\t}\n}\n\nstatic size_t writeAnimationTime(std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const std::vector<float>& time, const Settings& settings)\n{\n\tstd::string scratch;\n\tStreamFormat format = writeTimeStream(scratch, time);\n\tBufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None;\n\n\tsize_t view = getBufferView(views, BufferView::Kind_Time, StreamFormat::Filter_None, compression, format.stride);\n\tsize_t offset = views[view].data.size();\n\tviews[view].data += scratch;\n\n\tcomma(json_accessors);\n\twriteAccessor(json_accessors, view, offset, cgltf_type_scalar, format.component_type, format.normalized, time.size(), &time.front(), &time.back(), 1);\n\n\tsize_t time_accr = accr_offset++;\n\treturn time_accr;\n}\n\nstatic size_t writeAnimationTime(std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, float mint, int frames, float period, const Settings& settings)\n{\n\tstd::vector<float> time(frames);\n\n\tfor (int j = 0; j < frames; ++j)\n\t\ttime[j] = mint + float(j) * period;\n\n\treturn writeAnimationTime(views, json_accessors, accr_offset, time, settings);\n}\n\nsize_t writeJointBindMatrices(std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const cgltf_skin& skin, const QuantizationPosition& qp, const Settings& settings)\n{\n\tstd::string scratch;\n\n\tfor (size_t j = 0; j < skin.joints_count; ++j)\n\t{\n\t\tfloat transform[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};\n\n\t\tif (skin.inverse_bind_matrices)\n\t\t{\n\t\t\tcgltf_accessor_read_float(skin.inverse_bind_matrices, j, transform, 16);\n\t\t}\n\n\t\tif (settings.quantize && !settings.pos_float)\n\t\t{\n\t\t\t// pos_offset has to be applied first, thus it results in an offset rotated by the bind matrix\n\t\t\ttransform[12] += qp.offset[0] * transform[0] + qp.offset[1] * transform[4] + qp.offset[2] * transform[8];\n\t\t\ttransform[13] += qp.offset[0] * transform[1] + qp.offset[1] * transform[5] + qp.offset[2] * transform[9];\n\t\t\ttransform[14] += qp.offset[0] * transform[2] + qp.offset[1] * transform[6] + qp.offset[2] * transform[10];\n\n\t\t\t// node_scale will be applied before the rotation/scale from transform\n\t\t\tfor (int k = 0; k < 12; ++k)\n\t\t\t\ttransform[k] *= qp.node_scale;\n\t\t}\n\n\t\tscratch.append(reinterpret_cast<const char*>(transform), sizeof(transform));\n\t}\n\n\tBufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None;\n\n\tsize_t view = getBufferView(views, BufferView::Kind_Skin, StreamFormat::Filter_None, compression, 64);\n\tsize_t offset = views[view].data.size();\n\tviews[view].data += scratch;\n\n\tcomma(json_accessors);\n\twriteAccessor(json_accessors, view, offset, cgltf_type_mat4, cgltf_component_type_r_32f, false, skin.joints_count);\n\n\tsize_t matrix_accr = accr_offset++;\n\treturn matrix_accr;\n}\n\nstatic void writeInstanceData(std::vector<BufferView>& views, std::string& json_accessors, cgltf_animation_path_type type, const std::vector<Attr>& data, const Settings& settings)\n{\n\tBufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None;\n\n\tstd::string scratch;\n\tStreamFormat format = writeKeyframeStream(scratch, type, data, settings);\n\n\tsize_t view = getBufferView(views, BufferView::Kind_Instance, format.filter, compression, format.stride, type);\n\tsize_t offset = views[view].data.size();\n\tviews[view].data += scratch;\n\n\tcomma(json_accessors);\n\twriteAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, data.size());\n}\n\nsize_t writeInstances(std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const std::vector<Instance>& instances, const QuantizationPosition& qp, bool has_color, const Settings& settings)\n{\n\tstd::vector<Attr> position, rotation, scale;\n\tposition.resize(instances.size());\n\trotation.resize(instances.size());\n\tscale.resize(instances.size());\n\n\tStream color = {cgltf_attribute_type_color};\n\tif (has_color)\n\t\tcolor.data.resize(instances.size());\n\n\tfor (size_t i = 0; i < instances.size(); ++i)\n\t{\n\t\tdecomposeTransform(position[i].f, rotation[i].f, scale[i].f, instances[i].transform);\n\n\t\tif (settings.quantize && !settings.pos_float)\n\t\t{\n\t\t\tconst float* transform = instances[i].transform;\n\n\t\t\t// pos_offset has to be applied first, thus it results in an offset rotated by the instance matrix\n\t\t\tposition[i].f[0] += qp.offset[0] * transform[0] + qp.offset[1] * transform[4] + qp.offset[2] * transform[8];\n\t\t\tposition[i].f[1] += qp.offset[0] * transform[1] + qp.offset[1] * transform[5] + qp.offset[2] * transform[9];\n\t\t\tposition[i].f[2] += qp.offset[0] * transform[2] + qp.offset[1] * transform[6] + qp.offset[2] * transform[10];\n\n\t\t\t// node_scale will be applied before the rotation/scale from transform\n\t\t\tscale[i].f[0] *= qp.node_scale;\n\t\t\tscale[i].f[1] *= qp.node_scale;\n\t\t\tscale[i].f[2] *= qp.node_scale;\n\t\t}\n\n\t\tif (has_color)\n\t\t\tmemcpy(color.data[i].f, instances[i].color, sizeof(Attr));\n\t}\n\n\twriteInstanceData(views, json_accessors, cgltf_animation_path_type_translation, position, settings);\n\twriteInstanceData(views, json_accessors, cgltf_animation_path_type_rotation, rotation, settings);\n\twriteInstanceData(views, json_accessors, cgltf_animation_path_type_scale, scale, settings);\n\n\tsize_t result = accr_offset;\n\taccr_offset += 3;\n\n\tif (has_color)\n\t{\n\t\tBufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None;\n\n\t\tstd::string scratch;\n\t\tStreamFormat format = writeVertexStream(scratch, color, QuantizationPosition(), QuantizationTexture(), settings);\n\n\t\tsize_t view = getBufferView(views, BufferView::Kind_Instance, format.filter, compression, format.stride, 0);\n\t\tsize_t offset = views[view].data.size();\n\t\tviews[view].data += scratch;\n\n\t\tcomma(json_accessors);\n\t\twriteAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, instances.size());\n\t\taccr_offset += 1;\n\t}\n\n\treturn result;\n}\n\nvoid writeMeshNode(std::string& json, size_t mesh_offset, cgltf_node* node, cgltf_skin* skin, cgltf_data* data, const QuantizationPosition* qp)\n{\n\tcomma(json);\n\tappend(json, \"{\\\"mesh\\\":\");\n\tappend(json, mesh_offset);\n\tif (skin)\n\t{\n\t\tappend(json, \",\\\"skin\\\":\");\n\t\tappend(json, size_t(skin - data->skins));\n\t}\n\tif (qp)\n\t{\n\t\tappend(json, \",\\\"translation\\\":\");\n\t\tappend(json, qp->offset, 3);\n\t\tappend(json, \",\\\"scale\\\":[\");\n\t\tappend(json, qp->node_scale);\n\t\tappend(json, \",\");\n\t\tappend(json, qp->node_scale);\n\t\tappend(json, \",\");\n\t\tappend(json, qp->node_scale);\n\t\tappend(json, \"]\");\n\t}\n\tif (node && node->weights_count)\n\t{\n\t\tappend(json, \",\\\"weights\\\":\");\n\t\tappend(json, node->weights, node->weights_count);\n\t}\n\tappend(json, \"}\");\n}\n\nvoid writeMeshNodeInstanced(std::string& json, size_t mesh_offset, size_t accr_offset, bool has_color)\n{\n\tcomma(json);\n\tappend(json, \"{\\\"mesh\\\":\");\n\tappend(json, mesh_offset);\n\tappend(json, \",\\\"extensions\\\":{\\\"EXT_mesh_gpu_instancing\\\":{\\\"attributes\\\":{\");\n\n\tcomma(json);\n\tappend(json, \"\\\"TRANSLATION\\\":\");\n\tappend(json, accr_offset + 0);\n\n\tcomma(json);\n\tappend(json, \"\\\"ROTATION\\\":\");\n\tappend(json, accr_offset + 1);\n\n\tcomma(json);\n\tappend(json, \"\\\"SCALE\\\":\");\n\tappend(json, accr_offset + 2);\n\n\tif (has_color)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"_COLOR_0\\\":\");\n\t\tappend(json, accr_offset + 3);\n\t}\n\n\tappend(json, \"}}}\");\n\tappend(json, \"}\");\n}\n\nvoid writeSkin(std::string& json, const cgltf_skin& skin, size_t matrix_accr, const std::vector<NodeInfo>& nodes, cgltf_data* data)\n{\n\tcomma(json);\n\tappend(json, \"{\");\n\tif (skin.name && *skin.name)\n\t{\n\t\tappend(json, \"\\\"name\\\":\\\"\");\n\t\tappend(json, skin.name);\n\t\tappend(json, \"\\\",\");\n\t}\n\tappend(json, \"\\\"joints\\\":[\");\n\tfor (size_t j = 0; j < skin.joints_count; ++j)\n\t{\n\t\tcomma(json);\n\t\tappend(json, size_t(nodes[skin.joints[j] - data->nodes].remap));\n\t}\n\tappend(json, \"]\");\n\tappend(json, \",\\\"inverseBindMatrices\\\":\");\n\tappend(json, matrix_accr);\n\tif (skin.skeleton)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"skeleton\\\":\");\n\t\tappend(json, size_t(nodes[skin.skeleton - data->nodes].remap));\n\t}\n\tappend(json, \"}\");\n}\n\nvoid writeNode(std::string& json, const cgltf_node& node, const std::vector<NodeInfo>& nodes, cgltf_data* data)\n{\n\tconst NodeInfo& ni = nodes[&node - data->nodes];\n\n\tif (node.name && *node.name)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"name\\\":\\\"\");\n\t\tappend(json, node.name);\n\t\tappend(json, \"\\\"\");\n\t}\n\tif (node.has_translation)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"translation\\\":\");\n\t\tappend(json, node.translation, 3);\n\t}\n\tif (node.has_rotation)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"rotation\\\":\");\n\t\tappend(json, node.rotation, 4);\n\t}\n\tif (node.has_scale)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"scale\\\":\");\n\t\tappend(json, node.scale, 3);\n\t}\n\tif (node.has_matrix)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"matrix\\\":\");\n\t\tappend(json, node.matrix, 16);\n\t}\n\n\tbool has_children = !ni.mesh_nodes.empty();\n\tfor (size_t j = 0; j < node.children_count; ++j)\n\t\thas_children |= nodes[node.children[j] - data->nodes].keep;\n\n\tif (has_children)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"children\\\":[\");\n\t\tfor (size_t j = 0; j < node.children_count; ++j)\n\t\t{\n\t\t\tconst NodeInfo& ci = nodes[node.children[j] - data->nodes];\n\n\t\t\tif (ci.keep)\n\t\t\t{\n\t\t\t\tcomma(json);\n\t\t\t\tappend(json, size_t(ci.remap));\n\t\t\t}\n\t\t}\n\t\tfor (size_t j = 0; j < ni.mesh_nodes.size(); ++j)\n\t\t{\n\t\t\tcomma(json);\n\t\t\tappend(json, ni.mesh_nodes[j]);\n\t\t}\n\t\tappend(json, \"]\");\n\t}\n\tif (ni.has_mesh)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"mesh\\\":\");\n\t\tappend(json, ni.mesh_index);\n\t\tif (ni.mesh_skin)\n\t\t{\n\t\t\tappend(json, \",\\\"skin\\\":\");\n\t\t\tappend(json, size_t(ni.mesh_skin - data->skins));\n\t\t}\n\t\tif (node.weights_count)\n\t\t{\n\t\t\tappend(json, \",\\\"weights\\\":\");\n\t\t\tappend(json, node.weights, node.weights_count);\n\t\t}\n\t}\n\tif (node.camera)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"camera\\\":\");\n\t\tappend(json, size_t(node.camera - data->cameras));\n\t}\n\tif (node.light)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"extensions\\\":{\\\"KHR_lights_punctual\\\":{\\\"light\\\":\");\n\t\tappend(json, size_t(node.light - data->lights));\n\t\tappend(json, \"}}\");\n\t}\n}\n\nvoid writeAnimation(std::string& json, std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const Animation& animation, size_t i, cgltf_data* data, const std::vector<NodeInfo>& nodes, const Settings& settings)\n{\n\tstd::vector<const Track*> tracks;\n\n\tfor (size_t j = 0; j < animation.tracks.size(); ++j)\n\t{\n\t\tconst Track& track = animation.tracks[j];\n\n\t\tconst NodeInfo& ni = nodes[track.node - data->nodes];\n\n\t\tif (!ni.keep)\n\t\t\tcontinue;\n\n\t\tif (!settings.anim_const && (ni.animated_path_mask & (1 << track.path)) == 0)\n\t\t\tcontinue;\n\n\t\ttracks.push_back(&track);\n\t}\n\n\tif (tracks.empty())\n\t{\n\t\tfprintf(stderr, \"Warning: ignoring animation %d (%s) because it has no tracks with motion; use -ac to override\\n\", int(i), animation.name ? animation.name : \"\");\n\t\treturn;\n\t}\n\n\tbool needs_time = false;\n\tbool needs_pose = false;\n\n\tfor (size_t j = 0; j < tracks.size(); ++j)\n\t{\n\t\tconst Track& track = *tracks[j];\n\n#ifndef NDEBUG\n\t\tsize_t keyframe_size = (track.interpolation == cgltf_interpolation_type_cubic_spline) ? 3 : 1;\n\t\tsize_t time_size = track.constant ? 1 : (track.time.empty() ? animation.frames : track.time.size());\n\n\t\tassert(track.data.size() == keyframe_size * track.components * time_size);\n#endif\n\n\t\tneeds_time = needs_time || (track.time.empty() && !track.constant);\n\t\tneeds_pose = needs_pose || track.constant;\n\t}\n\n\tbool needs_range = needs_pose && !needs_time && animation.frames > 1;\n\n\tneeds_pose = needs_pose && !(needs_range && tracks.size() == 1);\n\n\tassert(int(needs_time) + int(needs_pose) + int(needs_range) <= 2);\n\n\tfloat animation_period = 1.f / float(settings.anim_freq);\n\tfloat animation_length = float(animation.frames - 1) * animation_period;\n\n\tsize_t time_accr = needs_time ? writeAnimationTime(views, json_accessors, accr_offset, animation.start, animation.frames, animation_period, settings) : 0;\n\tsize_t pose_accr = needs_pose ? writeAnimationTime(views, json_accessors, accr_offset, animation.start, 1, 0.f, settings) : 0;\n\tsize_t range_accr = needs_range ? writeAnimationTime(views, json_accessors, accr_offset, animation.start, 2, animation_length, settings) : 0;\n\n\tstd::string json_samplers;\n\tstd::string json_channels;\n\n\tsize_t track_offset = 0;\n\n\tsize_t last_track_time_accr = 0;\n\tconst Track* last_track_time = NULL;\n\n\tfor (size_t j = 0; j < tracks.size(); ++j)\n\t{\n\t\tconst Track& track = *tracks[j];\n\n\t\tbool range = needs_range && j == 0;\n\t\tint range_size = range ? 2 : 1;\n\n\t\tsize_t track_time_accr = time_accr;\n\t\tif (!track.time.empty())\n\t\t{\n\t\t\t// reuse time accessors between consecutive tracks if possible\n\t\t\tif (last_track_time && track.time == last_track_time->time)\n\t\t\t\ttrack_time_accr = last_track_time_accr;\n\t\t\telse\n\t\t\t{\n\t\t\t\ttrack_time_accr = writeAnimationTime(views, json_accessors, accr_offset, track.time, settings);\n\t\t\t\tlast_track_time_accr = track_time_accr;\n\t\t\t\tlast_track_time = &track;\n\t\t\t}\n\t\t}\n\n\t\tstd::string scratch;\n\t\tStreamFormat format = writeKeyframeStream(scratch, track.path, track.data, settings, track.interpolation == cgltf_interpolation_type_cubic_spline);\n\n\t\tif (range)\n\t\t{\n\t\t\tassert(range_size == 2);\n\t\t\tscratch += scratch;\n\t\t}\n\n\t\tBufferView::Compression compression = settings.compress && track.path != cgltf_animation_path_type_weights ? BufferView::Compression_Attribute : BufferView::Compression_None;\n\n\t\tsize_t view = getBufferView(views, BufferView::Kind_Keyframe, format.filter, compression, format.stride, track.path);\n\t\tsize_t offset = views[view].data.size();\n\t\tviews[view].data += scratch;\n\n\t\tcomma(json_accessors);\n\t\twriteAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, track.data.size() * range_size);\n\n\t\tsize_t data_accr = accr_offset++;\n\n\t\tcomma(json_samplers);\n\t\tappend(json_samplers, \"{\\\"input\\\":\");\n\t\tappend(json_samplers, range ? range_accr : (track.constant ? pose_accr : track_time_accr));\n\t\tappend(json_samplers, \",\\\"output\\\":\");\n\t\tappend(json_samplers, data_accr);\n\t\tif (track.interpolation != cgltf_interpolation_type_linear)\n\t\t{\n\t\t\tappend(json_samplers, \",\\\"interpolation\\\":\\\"\");\n\t\t\tappend(json_samplers, interpolationType(track.interpolation));\n\t\t\tappend(json_samplers, \"\\\"\");\n\t\t}\n\t\tappend(json_samplers, \"}\");\n\n\t\tconst NodeInfo& tni = nodes[track.node - data->nodes];\n\t\tsize_t target_node = size_t(tni.remap);\n\n\t\t// when animating morph weights, quantization may move mesh assignments to a mesh node in which case we need to move the animation output\n\t\tif (track.path == cgltf_animation_path_type_weights && tni.mesh_nodes.size() == 1)\n\t\t\ttarget_node = tni.mesh_nodes[0];\n\n\t\tcomma(json_channels);\n\t\tappend(json_channels, \"{\\\"sampler\\\":\");\n\t\tappend(json_channels, track_offset);\n\t\tappend(json_channels, \",\\\"target\\\":{\\\"node\\\":\");\n\t\tappend(json_channels, target_node);\n\t\tappend(json_channels, \",\\\"path\\\":\\\"\");\n\t\tappend(json_channels, animationPath(track.path));\n\t\tappend(json_channels, \"\\\"}}\");\n\n\t\ttrack_offset++;\n\t}\n\n\tcomma(json);\n\tappend(json, \"{\");\n\tif (animation.name && *animation.name)\n\t{\n\t\tappend(json, \"\\\"name\\\":\\\"\");\n\t\tappend(json, animation.name);\n\t\tappend(json, \"\\\",\");\n\t}\n\tappend(json, \"\\\"samplers\\\":[\");\n\tappend(json, json_samplers);\n\tappend(json, \"],\\\"channels\\\":[\");\n\tappend(json, json_channels);\n\tappend(json, \"]}\");\n}\n\nvoid writeCamera(std::string& json, const cgltf_camera& camera)\n{\n\tcomma(json);\n\tappend(json, \"{\");\n\n\tswitch (camera.type)\n\t{\n\tcase cgltf_camera_type_perspective:\n\t\tappend(json, \"\\\"type\\\":\\\"perspective\\\",\\\"perspective\\\":{\");\n\t\tappend(json, \"\\\"yfov\\\":\");\n\t\tappend(json, camera.data.perspective.yfov);\n\t\tappend(json, \",\\\"znear\\\":\");\n\t\tappend(json, camera.data.perspective.znear);\n\t\tif (camera.data.perspective.aspect_ratio != 0.f)\n\t\t{\n\t\t\tappend(json, \",\\\"aspectRatio\\\":\");\n\t\t\tappend(json, camera.data.perspective.aspect_ratio);\n\t\t}\n\t\tif (camera.data.perspective.zfar != 0.f)\n\t\t{\n\t\t\tappend(json, \",\\\"zfar\\\":\");\n\t\t\tappend(json, camera.data.perspective.zfar);\n\t\t}\n\t\tappend(json, \"}\");\n\t\tbreak;\n\n\tcase cgltf_camera_type_orthographic:\n\t\tappend(json, \"\\\"type\\\":\\\"orthographic\\\",\\\"orthographic\\\":{\");\n\t\tappend(json, \"\\\"xmag\\\":\");\n\t\tappend(json, camera.data.orthographic.xmag);\n\t\tappend(json, \",\\\"ymag\\\":\");\n\t\tappend(json, camera.data.orthographic.ymag);\n\t\tappend(json, \",\\\"znear\\\":\");\n\t\tappend(json, camera.data.orthographic.znear);\n\t\tappend(json, \",\\\"zfar\\\":\");\n\t\tappend(json, camera.data.orthographic.zfar);\n\t\tappend(json, \"}\");\n\t\tbreak;\n\n\tdefault:\n\t\tfprintf(stderr, \"Warning: skipping camera of unknown type\\n\");\n\t}\n\n\tappend(json, \"}\");\n}\n\nvoid writeLight(std::string& json, const cgltf_light& light)\n{\n\tcomma(json);\n\tappend(json, \"{\\\"type\\\":\\\"\");\n\tappend(json, lightType(light.type));\n\tappend(json, \"\\\"\");\n\tif (memcmp(light.color, white, 12) != 0)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"color\\\":\");\n\t\tappend(json, light.color, 3);\n\t}\n\tif (light.intensity != 1.f)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"intensity\\\":\");\n\t\tappend(json, light.intensity);\n\t}\n\tif (light.range != 0.f)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"range\\\":\");\n\t\tappend(json, light.range);\n\t}\n\tif (light.type == cgltf_light_type_spot)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"spot\\\":{\");\n\t\tappend(json, \"\\\"innerConeAngle\\\":\");\n\t\tappend(json, light.spot_inner_cone_angle);\n\t\tappend(json, \",\\\"outerConeAngle\\\":\");\n\t\tappend(json, light.spot_outer_cone_angle == 0.f ? 0.78539816339f : light.spot_outer_cone_angle);\n\t\tappend(json, \"}\");\n\t}\n\tappend(json, \"}\");\n}\n\nvoid writeArray(std::string& json, const char* name, const std::string& contents)\n{\n\tif (contents.empty())\n\t\treturn;\n\n\tcomma(json);\n\tappend(json, \"\\\"\");\n\tappend(json, name);\n\tappend(json, \"\\\":[\");\n\tappend(json, contents);\n\tappend(json, \"]\");\n}\n\nvoid writeExtensions(std::string& json, const ExtensionInfo* extensions, size_t count)\n{\n\tbool used_extensions = false;\n\tbool required_extensions = false;\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tused_extensions |= extensions[i].used;\n\t\trequired_extensions |= extensions[i].used && extensions[i].required;\n\t}\n\n\tif (used_extensions)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"extensionsUsed\\\":[\");\n\t\tfor (size_t i = 0; i < count; ++i)\n\t\t\tif (extensions[i].used)\n\t\t\t{\n\t\t\t\tcomma(json);\n\t\t\t\tappend(json, \"\\\"\");\n\t\t\t\tappend(json, extensions[i].name);\n\t\t\t\tappend(json, \"\\\"\");\n\t\t\t}\n\t\tappend(json, \"]\");\n\t}\n\n\tif (required_extensions)\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"extensionsRequired\\\":[\");\n\t\tfor (size_t i = 0; i < count; ++i)\n\t\t\tif (extensions[i].used && extensions[i].required)\n\t\t\t{\n\t\t\t\tcomma(json);\n\t\t\t\tappend(json, \"\\\"\");\n\t\t\t\tappend(json, extensions[i].name);\n\t\t\t\tappend(json, \"\\\"\");\n\t\t\t}\n\t\tappend(json, \"]\");\n\t}\n}\n\nvoid writeExtras(std::string& json, const cgltf_extras& extras)\n{\n\tif (!extras.data)\n\t\treturn;\n\n\tcomma(json);\n\tappend(json, \"\\\"extras\\\":\");\n\tappendJson(json, extras.data);\n}\n\nvoid writeScene(std::string& json, const cgltf_scene& scene, const std::string& roots, const Settings& settings)\n{\n\tcomma(json);\n\tappend(json, \"{\");\n\tif (scene.name && *scene.name)\n\t{\n\t\tappend(json, \"\\\"name\\\":\\\"\");\n\t\tappend(json, scene.name);\n\t\tappend(json, \"\\\"\");\n\t}\n\tif (!roots.empty())\n\t{\n\t\tcomma(json);\n\t\tappend(json, \"\\\"nodes\\\":[\");\n\t\tappend(json, roots);\n\t\tappend(json, \"]\");\n\t}\n\tif (settings.keep_extras)\n\t\twriteExtras(json, scene.extras);\n\tappend(json, \"}\");\n}\n"
  },
  {
    "path": "js/README.md",
    "content": "# meshoptimizer.js\n\nThis 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.\n\nWhen using the NPM package, package exports can be used to import individual components (e.g. `meshoptimizer/decoder`) as well as the entire package (`meshoptimizer`).\n\n## Structure\n\nEach 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:\n\n- `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.\n- `ready` is a Promise that is resolved when WebAssembly compilation and initialization finishes; any functions are unsafe to call before that happens.\n\nIn addition to that, each component exposes a set of specific functions documented below.\n\n## Decoder\n\n`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.\n\n> 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 `<script>` tag which exposes the module as a global variable `MeshoptDecoder`. The ESM version uses `.mjs` file extension unlike other components, to avoid compatibility issues with prior versions.\n\nTo decode a buffer, one of the decoding functions should be called:\n\n```ts\ndecodeVertexBuffer: (target: Uint8Array, count: number, size: number, source: Uint8Array, filter?: string) => void;\ndecodeIndexBuffer: (target: Uint8Array, count: number, size: number, source: Uint8Array) => void;\ndecodeIndexSequence: (target: Uint8Array, count: number, size: number, source: Uint8Array) => void;\n```\n\nThe `source` should contain the data encoded using meshopt codecs; `count` represents the number of elements (attributes or indices); `size` represents the size of each element and should be divisible by 4 for `decodeVertexBuffer` and equal to 2 or 4 for the index decoders. `target` must be `count * size` bytes.\n\nGiven a valid encoded buffer and the correct input parameters, these functions always succeed; they fail if the input data is malformed.\n\nWhen decoding attribute (vertex) data, additionally one of the decoding filters can be applied to further post-process the decoded data. `filter` must be equal to `\"OCTAHEDRAL\"`, `\"QUATERNION\"` or `\"EXPONENTIAL\"` to activate this extra step. The description of filters can be found in [the specification for EXT_meshopt_compression](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Vendor/EXT_meshopt_compression/README.md).\n\nTo simplify the decoding further, a wrapper function is provided that automatically calls the correct version of the decoding based on `mode` - which should be `\"ATTRIBUTES\"`, `\"TRIANGLES\"` or `\"INDICES\"`. The difference in terminology is due to the fact that the JavaScript API uses the terms established in the glTF extension, whereas the function names match that of the meshoptimizer C++ API.\n\n```ts\ndecodeGltfBuffer: (target: Uint8Array, count: number, size: number, source: Uint8Array, mode: string, filter?: string) => void;\n```\n\nNote that all functions above run synchronously; sometimes decoding large buffers takes time, so this library provides support for asynchronous decoding\nusing WebWorkers via the following API; `useWorkers` must be called once at startup to create the desired number of workers:\n\n```ts\nuseWorkers: (count: number) => void;\ndecodeGltfBufferAsync: (count: number, size: number, source: Uint8Array, mode: string, filter?: string) => Promise<Uint8Array>;\n```\n\n## Encoder\n\n`MeshoptEncoder` (`meshopt_encoder.js`) implements data preprocessing and compression of attribute and index buffers. It can be used to compress data that can be decompressed using the decoder module - note that the encoding process is more complicated and nuanced. It is typically split into three steps:\n\n1. Pre-process the mesh to improve index and vertex locality which increases compression ratio\n2. Quantize the data, either manually using integer or normalized integer format as a target, or using filter encoders\n3. Encode the data\n\nStep 1 is optional but highly recommended for triangle meshes; it can be omitted when compressing data with a predefined order such as animation keyframes.\nStep 2 is the only lossy step in this process; without step 2, encoding will retain all semantics of the input exactly which can result in compressed data that is too large.\n\nTo reverse the process, decoder is used to reverse step 3 and (optionally) 2; the resulting data can typically be fed directly to the GPU. Note that the output of step 3 can also be further compressed in transport using a general-purpose compression algorithm such as Deflate.\n\nTo pre-process the mesh, the following function should be called with the input index buffer:\n\n```ts\nreorderMesh: (indices: Uint32Array, triangles: boolean, optsize: boolean) => [Uint32Array, number];\n```\n\nThe function optimizes the input array for locality of reference (make sure to pass `triangles=true` for triangle lists, and `false` otherwise). `optsize` can choose whether the order should be optimal for transmission size (recommended for Web) or for GPU rendering performance. The function changes the `indices` array in place and returns an additional remap array and the total number of unique vertices.\n\nAfter this function returns, to maintain correct rendering the application should reorder all vertex streams - including morph targets if applicable - according to the remap array. For each original index, remap array contains the new location for that index (or `0xffffffff` if the value is unused), so the remapping pseudocode looks like this:\n\n```ts\nlet newvertices = new VertexArray(unique); // unique is returned by reorderMesh\nfor (let i = 0; i < oldvertices.length; ++i)\n    if (remap[i] != 0xffffffff)\n        newvertices[remap[i]] = oldvertices[i];\n```\n\nWhen the input is a point cloud and not a triangle mesh, it is recommended to reorder the points using a specialized function that performs spatial sorting that can result in significant improvements in compression ratio by the subsequent processing:\n\n```ts\nreorderPoints: (positions: Float32Array, positions_stride: number) => Uint32Array;\n```\n\nThis function returns a remap array just like `reorderMesh`, so the vertices need to be reordered accordingly for every vertex stream - the `positions` input is not modified. Note that it assumes no index buffer is provided, as it is redundant for point clouds.\n\nTo quantize the attribute data (whether it represents a mesh component or something else like a rotation quaternion for a bone), typically some data-specific analysis should be performed to determine the optimal quantization strategy. For linear data such as positions or texture coordinates remapping the input range to 0..1 and quantizing the resulting integer using fixed-point encoding with a given number of bits stored in a 16-bit or 8-bit integer is recommended; however, this is not always best for compression ratio for data with complex cross-component dependencies.\n\nTo that end, three filter encoders are provided: octahedral (optimal for normal or tangent data), quaternion (optimal for unit-length quaternions) and exponential (optimal for compressing floating-point vectors). The last two are recommended for use for animation data, and exponential filter can additionally be used to quantize any floating-point vertex attribute for which integer quantization is not sufficiently precise.\n\n```ts\nencodeFilterOct: (source: Float32Array, count: number, stride: number, bits: number) => Uint8Array;\nencodeFilterQuat: (source: Float32Array, count: number, stride: number, bits: number) => Uint8Array;\nencodeFilterExp: (source: Float32Array, count: number, stride: number, bits: number, mode?: string) => Uint8Array;\n```\n\nAll these functions take a source floating point buffer as an input, and perform a complex transformation that, when reversed by a decoder, results in an optimally quantized decompressed output. Because of this these functions assume specific configuration of input and output data:\n\n- `encodeFilterOct` takes each 4 floats from the source array (for a total of `count` 4-vectors), treats them as a unit vector (XYZ) and fourth component from -1..1 (W), and encodes them into `stride` bytes in a way that, when decoded, the result is stored as a normalized signed 4-vector. `stride` must be 4 (in which case the round-trip result is 4 8-bit normalized values) or 8 (in which case the round-trip result is 4 16-bit normalized values). This encoding is recommended for normals (with stride=4 for medium quality and 8 for high quality output) and tangents (with stride=4 providing enough quality in all cases; note that 4-th component is preserved in case it stores tangent frame winding). `bits` represents the desired precision of each component and must be in `[2..8]` range if `stride=4` and `[2..16]` range if `stride=8`.\n\n- `encodeFilterQuat` takes each 4 floats from the source array (for a total of `count` 4-vectrors), treats them as a unit quaternion, and encodes them into `stride` bytes in a way that, when decoded, the result is stored as a normalized signed 4-vector representing the same rotation as the source quaternion. `stride` must be 8 (the round-trip result is 4 16-bit normalized values). `bits` represents the desired precision of each component and must be in `[4..16]` range, although using less than 9-10 bits is likely going to lead to significant deviation in rotations.\n\n- `encodeFilterExp` takes each K floats from the source array (where `K=stride/4`, for a total of `count` K-vectors), and encodes them into `stride` bytes in a way that, when decoded, the result is stored as K single-precision floating point values. This may seem redundant but it allows to trade some precision for a higher compression ratio due to reduced precision of stored components, controlled by `bits` which must be in `[1..24]` range, and a shared exponent encoding used by the function.\nThe `mode` parameter can be used to influence the exponent sharing and provides a tradeoff between compressed size and quality for various use cases, and can be one of 'Separate', 'SharedVector', 'SharedComponent' and 'Clamped' (defaulting to 'SharedVector').\n\n- `encodeFilterColor` takes each 4 floats from the source array (for a total of `count` 4-vectors), treats them as an RGBA color with each component from 0..1, and encodes them into `stride` bytes in a way that, when decoded, the result is stored as a normalized unsigned 4-vector. `stride` must be 4 (in which case the round-trip result is 4 8-bit normalized values) or 8 (in which case the round-trip result is 4 16-bit normalized values). This encoding is recommended for colors (with stride=4 for medium quality and 8 for high quality output). `bits` represents the desired precision of each component and must be in `[2..8]` range if `stride=4` and `[2..16]` range if `stride=8`.\n\nNote that in all cases using the highest `bits` value allowed by the output `stride` won't change the size of the output array (which is always going to be `count * stride` bytes), but it *will* reduce compression efficiency, as such the lowest acceptable `bits` value is recommended to use. When multiple parts of the data require different levels of precision, encode filters can be called multiple times and the output of the same filter called with the same `stride` can be concatenated even if `bits` are different.\n\nAfter data is quantized using filter encoding or manual quantization, the result should be compressed using one of the following functions that mirror the interface of the decoding functions described above:\n\n```ts\nencodeVertexBuffer: (source: Uint8Array, count: number, size: number) => Uint8Array;\nencodeVertexBufferLevel: (source: Uint8Array, count: number, size: number, level: number, version?: number) => Uint8Array;\nencodeIndexBuffer: (source: Uint8Array, count: number, size: number) => Uint8Array;\nencodeIndexSequence: (source: Uint8Array, count: number, size: number) => Uint8Array;\n\nencodeGltfBuffer: (source: Uint8Array, count: number, size: number, mode: string) => Uint8Array;\n```\n\n`size` is the size of each component in bytes; it must be divisible by 4 for attribute/vertex encoding and must be equal to 2 or 4 for index encoding; additionally, index buffer encoding assumes triangle lists as an input and as such count must be divisible by 3.\n\nNote that the source is specified as byte arrays; for example, to quantize a position stream encoded using 16-bit integers with 5 vertices, `source` must have length of `5 * 8 = 40` bytes (8 bytes for each position - 3\\*2 bytes of data and 2 bytes of padding to conform to alignment requirements), `count` must be 5 and `size` must be 8. When padding data to the alignment boundary make sure to use 0 as padding bytes for optimal compression.\n\nWhen interleaved vertex data is compressed, `encodeVertexBuffer` can be called with the full size of a single interleaved vertex; however, when compressing deinterleaved data, note that `encodeVertexBuffer` should be called on each component individually if the strides of different streams are different.\n\nBy default, `encodeVertexBuffer` uses v1 version of the encoding; this encoding is *not* compatible with `EXT_meshopt_compression` glTF extension but results in higher compression ratios. To encode data compatible with `EXT_meshopt_compression`, use `encodeVertexBufferLevel` with version=0, or - preferably - `encodeGltfBuffer`, which defaults to v0 (but can also be used to encode v1 content by passing version=1).\n\n## Simplifier\n\n`MeshoptSimplifier` (`meshopt_simplifier.js`) implements mesh simplification, producing a mesh with fewer triangles/points that resembles the original mesh in its appearance. The simplification algorithms are lossy and may result in significant change in appearance, but can often be used without visible visual degradation on high poly input meshes or for level of detail variants far away.\n\nTo simplify the mesh, the following function needs to be called first:\n\n```ts\nsimplify(indices: Uint32Array, vertex_positions: Float32Array, vertex_positions_stride: number, target_index_count: number, target_error: number, flags?: [Flags]) => [Uint32Array, number];\n```\n\nGiven an input triangle mesh represented by an index buffer and a position buffer, the algorithm tries to simplify the mesh down to the target index count while maintaining the 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 possible to preprocess the index buffer to discard any vertex attributes that aren't critical and can be rebuilt later.\n\nTarget 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.\n\nThe algorithm uses position data stored in a strided array; `vertex_positions_stride` represents the distance between subsequent positions in `Float32` units and should typically be set to 3. If the input position data is quantized, it's necessary to dequantize it so that the algorithm can estimate the position error correctly. While the algorithm doesn't use other attributes like normals/texture coordinates, it automatically recognizes and preserves attribute discontinuities based on index data. Because of this, for the algorithm to function well, the mesh vertices should be unique (de-duplicated).\n\nUpon completion, the function returns the new index buffer as well as the resulting appearance error. The index buffer can be used to render the simplified mesh with the same vertex buffer(s) as the original one, including non-positional attributes. For example, `simplify` can be called multiple times with different target counts/errors, and the application can select the appropriate index buffer to render for the mesh at runtime to implement level of detail.\n\nTo control behavior of the algorithm more precisely, `flags` may specify an array of strings that enable various additional options:\n\n- `'LockBorder'` locks the vertices that lie on the topological border of the mesh in place such that they don't move during simplification. This can be valuable to simplify independent chunks of a mesh, for example terrain, to ensure that individual levels of detail can be stitched together later without gaps.\n- `'ErrorAbsolute'` 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 `getScale`.\n- `'Sparse'` improves simplification performance assuming input indices are a sparse subset of the mesh. This can be useful when simplifying small mesh subsets independently. For consistency, it is recommended to use absolute errors when sparse simplification is desired.\n- ``Prune`` allows removal of 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.\n\nIn addition to the `Prune` flag, you can explicitly prune isolated components under a target threshold by calling the `simplifyPrune` function:\n\n```ts\nsimplifyPrune: (indices: Uint32Array, vertex_positions: Float32Array, vertex_positions_stride: number, target_error: number) => Uint32Array;\n```\n\nThis can be done before regular simplification or as the only step, which is useful for scenarios like isosurface cleanup.\n\nWhile `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, `simplifyWithAttributes`:\n\n```ts\nsimplifyWithAttributes: (indices: Uint32Array, vertex_positions: Float32Array, vertex_positions_stride: number, vertex_attributes: Float32Array, vertex_attributes_stride: number, attribute_weights: number[], vertex_lock: Uint8Array | null, target_index_count: number, target_error: number, flags?: Flags[]) => [Uint32Array, number];\n```\n\nThis function takes an additional `vertex_attributes` buffer that contains all the attributes to be used. The `attribute_weights` array contains a weight for each attribute, which is used to balance the importance of each attribute during simplification. 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).\n\nThe optional `vertex_lock` parameter can be used to lock some vertices in place, preventing them from being moved during simplification. This is a binary array of the same length as the number of vertices, where `1` means that the vertex is locked and `0` means that it is free to move. This can be used to preserve seams or other important features of the mesh.\n\nWhen the resulting mesh is stored, it might be desireable to remove the redundant vertices from the attribute buffers instead of simply using the original vertex data with the smaller index buffer. For that purpose, the simplifier module provides the `compactMesh` function, which is similar to `reorderMesh` function that the encoder provides, but doesn't perform extra optimizations and merely prepares a new vertex order that can be used to create new, smaller, vertex buffers:\n\n```ts\ncompactMesh: (indices: Uint32Array) => [Uint32Array, number];\n```\n\nThe simplification algorithm uses relative errors for input and output; to convert these errors to absolute units, they need to be multiplied by the scaling factor which depends on the mesh geometry and can be computed by calling the following function with the position data:\n\n```ts\ngetScale: (vertex_positions: Float32Array, vertex_positions_stride: number) => number;\n```\n\nThe algorithms `simplify` and `simplifyWithAttributes` work on triangle meshes. `MeshoptSimplifier` additionally provides an algorithm to simplify point clouds, with optional per-point color support:\n\n```ts\nsimplifyPoints: (vertex_positions: Float32Array, vertex_positions_stride: number, target_vertex_count: number, vertex_colors?: Float32Array, vertex_colors_stride?: number, color_weight?: number) => Uint32Array;\n```\n\n`vertex_colors` is an optional buffer containing RGB colors, with 3 values per point; `color_weight` can be used to balance the importance of color preservation with position preservation, and can be set to `1.0` if the input colors are in `[0..1]` range.\n\nThe resulting indices can be used to render the simplified point cloud; similarly to triangle simplification, to reduce the memory footprint, the point cloud can be reindexed using the remap table returned by `compactMesh`.\n\n## Clusterizer\n\n`MeshoptClusterizer` (`meshopt_clusterizer.js`) implements meshlet generation and optimization.\n\nTo split a triangle mesh into clusters, call `buildMeshlets`, which tries to balance topological efficiency (by maximizing vertex reuse inside meshlets) with culling efficiency.\n\n```ts\nbuildMeshlets(indices: Uint32Array, vertex_positions: Float32Array, vertex_positions_stride: number, max_vertices: number, max_triangles: number, cone_weight?: number) => MeshletBuffers;\n```\n\nThe algorithm uses position data stored in a strided array; `vertex_positions_stride` represents the distance between subsequent positions in `Float32` units.\n\nThe maximum number of triangles and number of vertices per meshlet can be controlled via `max_triangles` and `max_vertices` parameters. However, `max_vertices` must not be greater than 256 and `max_triangles` must not be greater than 512.\n\nAdditionally, if cluster cone culling is to be used, `buildMeshlets` allows specifying a `cone_weight` as a value between 0 and 1 to balance culling efficiency with other forms of culling. By default, `cone_weight` is set to 0.\n\nFor finer control over triangle counts, use `buildMeshletsFlex`, which accepts minimum and maximum triangle limits and an optional `split_factor` to nudge large clusters to split sooner.\n\n```ts\nbuildMeshletsFlex(indices: Uint32Array, vertex_positions: Float32Array, vertex_positions_stride: number, max_vertices: number, min_triangles: number, max_triangles: number, cone_weight?: number, split_factor?: number) => MeshletBuffers;\n```\n\nTo favor spatial splits for ray tracing, `buildMeshletsSpatial` keeps the same controls but replaces cone weighting with `fill_weight` to trade off cluster fullness against SAH cost.\n\n```ts\nbuildMeshletsSpatial(indices: Uint32Array, vertex_positions: Float32Array, vertex_positions_stride: number, max_vertices: number, min_triangles: number, max_triangles: number, fill_weight?: number) => MeshletBuffers;\n```\n\nAll meshlets produced by these builders are implicitly optimized for better triangle and vertex locality.\n\nThe algorithm returns the meshlet data as packed buffers:\n\n```ts\nconst buffers = MeshoptClusterizer.buildMeshlets(indices, positions, stride, /* args */);\n\nconsole.log(buffers.meshlets);      // prints the raw packed Uint32Array containing the meshlet data, i.e., the indices into the vertices and triangles array\nconsole.log(buffers.vertices);      // prints the raw packed Uint32Array containing the indices into the original meshes vertices\nconsole.log(buffers.triangles);     // prints the raw packed Uint8Array containing the indices into the verices array.\nconsole.log(buffers.meshletCount);  // prints the number of meshlets - this is not the same as buffers.meshlets.length because each meshlet consists of 4 unsigned 32-bit integers\n```\n\nIndividual meshlets can be extracted from the packed buffers using `extractMeshlet`. The memory of the returned `Meshlet` object's `vertices` and `triangles` arrays is backed by the `MeshletBuffers` object.\n\n```ts\nconst buffers = MeshoptClusterizer.buildMeshlets(indices, positions, stride, /* args */);\n\nconst meshlet = MeshoptClusterizer.extractMeshlet(buffers, 0);\nconsole.log(meshlet.vertices);  // prints the packed Uint32Array of the first meshlet's vertex indices, i.e., indices into the original meshes vertex buffer\nconsole.log(meshlet.triangles); // prints the packed Uint8Array of the first meshlet's indices into its own vertices array\n\nconsole.log(MeshoptClusterizer.extractMeshlet(buffers, 0).triangles[0] === meshlet.triangles[0]) // prints true\n\nmeshlet.triangles.set([123], 0);\nconsole.log(MeshoptClusterizer.extractMeshlet(buffers, 0).triangles[0] === meshlet.triangles[0]) // still prints true\n```\n\nAfter generating the meshlet data, it's also possible to generate extra culling data for one or more meshlets:\n\n```ts\ncomputeMeshletBounds(buffers: MeshletBuffers, vertex_positions: Float32Array, vertex_positions_stride: number) => Bounds | Bounds[];\n```\n\nIf `buffers` contains more than one meshlet, `computeMeshletBounds` returns an array of `Bounds`. Otherwise, a single `Bounds` object is returned.\n\n```ts\nconst buffers = MeshoptClusterizer.buildMeshlets(indices, positions, stride, /* args */);\nconst bounds = MeshoptClusterizer.computeMeshletBounds(buffers, positions, stride);\nconsole.log(bounds[0].centerX, bounds[0].centerY, bounds[0].centerZ);       // prints the center of the first meshlet's bounding sphere\nconsole.log(bounds[0].radius);                                              // prints the radius of the first meshlet's bounding sphere\nconsole.log(bounds[0].coneApexX, bounds[0].coneApexY, bounds[0].coneApexZ); // prints the apex of the first meshlet's normal cone\nconsole.log(bounds[0].coneAxisX, bounds[0].coneAxisY, bounds[0].coneAxisZ); // prints the axis of the first meshlet's normal cone\nconsole.log(bounds[0].coneCutoff);                                          // prins the cutoff angle of the first meshlet's normal cone\n```\n\nIt is also possible to compute bounds of a vertex cluster that is not generated by `MeshoptClusterizer` using `computeClusterBounds`. Like `buildMeshlets`, this algorithm takes vertex indices and a strided vertex positions array with a vertex stride in `Float32` units as input.\n\n```ts\ncomputeClusterBounds(indices: Uint32Array, vertex_positions: Float32Array, vertex_positions_stride: number) => Bounds;\n```\n\nFinally, it is possible to compute spherical bounds of an arbitrary set of points, which can be useful to compute bounds for arbitrary mesh subsets. Each point can have an optional radius; this can be used to merge the spherical bounds of multiple clusters. The inputs are provided as strided arrays with the stride in `Float32` units.\n\n```ts\ncomputeSphereBounds: (positions: Float32Array, positions_stride: number, radii?: Float32Array, radii_stride?: number) => Bounds;\n```\n\n## License\n\nThis library is available to anybody free of charge, under the terms of MIT License (see LICENSE.md).\n"
  },
  {
    "path": "js/benchmark.js",
    "content": "import { MeshoptEncoder as encoder } from './meshopt_encoder.js';\nimport { MeshoptDecoder as decoder } from './meshopt_decoder.mjs';\nimport { performance } from 'perf_hooks';\n\nprocess.on('unhandledRejection', (error) => {\n\tconsole.log('unhandledRejection', error);\n\tprocess.exit(1);\n});\n\nfunction bytes(view) {\n\treturn new Uint8Array(view.buffer, view.byteOffset, view.byteLength);\n}\n\nvar tests = {\n\troundtripVertexBuffer: function () {\n\t\tvar N = 1024 * 1024;\n\t\tvar data = new Uint8Array(N * 16);\n\n\t\tvar lcg = 1;\n\n\t\tfor (var i = 0; i < N * 16; ++i) {\n\t\t\t// mindstd_rand\n\t\t\tlcg = (lcg * 48271) % 2147483647;\n\n\t\t\tvar k = i % 16;\n\t\t\tif (k <= 8) data[i] = lcg & ((1 << k) - 1);\n\t\t\telse data[i] = i & ((1 << (k - 8)) - 1);\n\t\t}\n\n\t\tvar decoded = new Uint8Array(N * 16);\n\n\t\tvar t0 = performance.now();\n\t\tvar encoded = encoder.encodeVertexBuffer(data, N, 16);\n\t\tvar t1 = performance.now();\n\t\tdecoder.decodeVertexBuffer(decoded, N, 16, encoded);\n\t\tvar t2 = performance.now();\n\n\t\treturn { encodeVertex: t1 - t0, decodeVertex: t2 - t1, bytes: N * 16 };\n\t},\n\n\troundtripIndexBuffer: function () {\n\t\tvar N = 1024 * 1024;\n\t\tvar data = new Uint32Array(N * 3);\n\n\t\tfor (var i = 0; i < N * 3; i += 6) {\n\t\t\tvar v = i / 6;\n\n\t\t\tdata[i + 0] = v;\n\t\t\tdata[i + 1] = v + 1;\n\t\t\tdata[i + 2] = v + 2;\n\n\t\t\tdata[i + 3] = v + 2;\n\t\t\tdata[i + 4] = v + 1;\n\t\t\tdata[i + 5] = v + 3;\n\t\t}\n\n\t\tvar decoded = new Uint32Array(data.length);\n\n\t\tvar t0 = performance.now();\n\t\tvar encoded = encoder.encodeIndexBuffer(bytes(data), data.length, 4);\n\t\tvar t1 = performance.now();\n\t\tdecoder.decodeIndexBuffer(bytes(decoded), data.length, 4, encoded);\n\t\tvar t2 = performance.now();\n\n\t\treturn { encodeIndex: t1 - t0, decodeIndex: t2 - t1, bytes: N * 12 };\n\t},\n\n\tdecodeGltf: function () {\n\t\tvar N = 1024 * 1024;\n\t\tvar data = new Uint8Array(N * 16);\n\n\t\tfor (var i = 0; i < N * 16; i += 4) {\n\t\t\tdata[i + 0] = 0;\n\t\t\tdata[i + 1] = (i % 16) * 1;\n\t\t\tdata[i + 2] = (i % 16) * 2;\n\t\t\tdata[i + 3] = (i % 16) * 8;\n\t\t}\n\n\t\tvar decoded = new Uint8Array(N * 16);\n\n\t\tvar filters = [\n\t\t\t{ name: 'none', filter: 'NONE', stride: 16 },\n\t\t\t{ name: 'oct8', filter: 'OCTAHEDRAL', stride: 4 },\n\t\t\t{ name: 'oct12', filter: 'OCTAHEDRAL', stride: 8 },\n\t\t\t{ name: 'quat12', filter: 'QUATERNION', stride: 8 },\n\t\t\t{ name: 'col8', filter: 'COLOR', stride: 4 },\n\t\t\t{ name: 'col12', filter: 'COLOR', stride: 8 },\n\t\t\t{ name: 'exp', filter: 'EXPONENTIAL', stride: 16 },\n\t\t];\n\n\t\tvar results = { bytes: N * 16 };\n\n\t\tfor (var i = 0; i < filters.length; ++i) {\n\t\t\tvar f = filters[i];\n\t\t\tvar encoded = encoder.encodeVertexBuffer(data, (N * 16) / f.stride, f.stride);\n\n\t\t\tvar t0 = performance.now();\n\t\t\tdecoder.decodeGltfBuffer(decoded, (N * 16) / f.stride, f.stride, encoded, 'ATTRIBUTES', f.filter);\n\t\t\tvar t1 = performance.now();\n\n\t\t\tresults[f.name] = t1 - t0;\n\t\t}\n\n\t\treturn results;\n\t},\n};\n\nPromise.all([encoder.ready, decoder.ready]).then(() => {\n\tvar reps = 10;\n\tvar data = {};\n\n\tfor (var key in tests) {\n\t\tdata[key] = tests[key]();\n\t}\n\n\tfor (var i = 1; i < reps; ++i) {\n\t\tfor (var key in tests) {\n\t\t\tvar nd = tests[key]();\n\t\t\tvar od = data[key];\n\n\t\t\tfor (var idx in nd) {\n\t\t\t\tod[idx] = Math.min(od[idx], nd[idx]);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (var key in tests) {\n\t\tvar rep = key;\n\t\trep += ':\\n';\n\n\t\tfor (var idx in data[key]) {\n\t\t\tif (idx != 'bytes') {\n\t\t\t\trep += idx;\n\t\t\t\trep += ' ';\n\t\t\t\trep += data[key][idx].toFixed(3);\n\t\t\t\trep += ' ms (';\n\t\t\t\trep += ((data[key].bytes / 1e9 / data[key][idx]) * 1000).toFixed(3);\n\t\t\t\trep += ' GB/s)';\n\t\t\t\tif (key == 'decodeGltf' && idx != 'none') {\n\t\t\t\t\trep += '; filter ';\n\t\t\t\t\trep += ((data[key].bytes / 1e9 / (data[key][idx] - data[key]['none'])) * 1000).toFixed(3);\n\t\t\t\t\trep += ' GB/s';\n\t\t\t\t}\n\t\t\t\trep += '\\n';\n\t\t\t}\n\t\t}\n\n\t\tconsole.log(rep);\n\t}\n});\n"
  },
  {
    "path": "js/index.d.ts",
    "content": "export * from './meshopt_encoder.js';\nexport * from './meshopt_decoder.js';\nexport * from './meshopt_simplifier.js';\nexport * from './meshopt_clusterizer.js';\n"
  },
  {
    "path": "js/index.js",
    "content": "export * from './meshopt_encoder.js';\nexport * from './meshopt_decoder.mjs';\nexport * from './meshopt_simplifier.js';\nexport * from './meshopt_clusterizer.js';\n"
  },
  {
    "path": "js/meshopt_clusterizer.d.ts",
    "content": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\n\nexport class Bounds {\n\tcenterX: number;\n\tcenterY: number;\n\tcenterZ: number;\n\tradius: number;\n\tconeApexX: number;\n\tconeApexY: number;\n\tconeApexZ: number;\n\tconeAxisX: number;\n\tconeAxisY: number;\n\tconeAxisZ: number;\n\tconeCutoff: number;\n}\n\nexport class MeshletBuffers {\n\tmeshlets: Uint32Array;\n\tvertices: Uint32Array;\n\ttriangles: Uint8Array;\n\tmeshletCount: number;\n}\n\nexport class Meshlet {\n\tvertices: Uint32Array;\n\ttriangles: Uint8Array;\n}\n\nexport const MeshoptClusterizer: {\n\tsupported: boolean;\n\tready: Promise<void>;\n\n\tbuildMeshlets: (\n\t\tindices: Uint32Array,\n\t\tvertex_positions: Float32Array,\n\t\tvertex_positions_stride: number,\n\t\tmax_vertices: number,\n\t\tmax_triangles: number,\n\t\tcone_weight?: number\n\t) => MeshletBuffers;\n\n\tbuildMeshletsFlex: (\n\t\tindices: Uint32Array,\n\t\tvertex_positions: Float32Array,\n\t\tvertex_positions_stride: number,\n\t\tmax_vertices: number,\n\t\tmin_triangles: number,\n\t\tmax_triangles: number,\n\t\tcone_weight?: number,\n\t\tsplit_factor?: number\n\t) => MeshletBuffers;\n\n\tbuildMeshletsSpatial: (\n\t\tindices: Uint32Array,\n\t\tvertex_positions: Float32Array,\n\t\tvertex_positions_stride: number,\n\t\tmax_vertices: number,\n\t\tmin_triangles: number,\n\t\tmax_triangles: number,\n\t\tfill_weight?: number\n\t) => MeshletBuffers;\n\n\textractMeshlet: (buffers: MeshletBuffers, index: number) => Meshlet;\n\n\tcomputeClusterBounds: (indices: Uint32Array, vertex_positions: Float32Array, vertex_positions_stride: number) => Bounds;\n\tcomputeMeshletBounds: (buffers: MeshletBuffers, vertex_positions: Float32Array, vertex_positions_stride: number) => Bounds[];\n\tcomputeSphereBounds: (positions: Float32Array, positions_stride: number, radii?: Float32Array, radii_stride?: number) => Bounds;\n};\n"
  },
  {
    "path": "js/meshopt_clusterizer.js",
    "content": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\nvar MeshoptClusterizer = (function () {\n\t// Built with clang version 19.1.5-wasi-sdk\n\t// Built from meshoptimizer 1.0\n\tvar wasm =\n\t\t'b9H79Tebbbe96x9Geueu9Geub9Gbb9Giuuueu9Gmuuuuuuuuuuu9999eu9Gouuuuuueu9Gruuuuuuub9Gxuuuuuuuuuuuueu9Gxuuuuuuuuuuu99eu9GPuuuuuuuuuuuuu99b9Gouuuuuub9GluuuubiCAdilvorwDqooqkbiibeilve9Weiiviebeoweuecj:Pdkr;Zeqo9TW9T9VV95dbH9F9F939H79T9F9J9H229F9Jt9VV7bb8A9TW79O9V9Wt9F9I919P29K9nW79O2Wt79c9V919U9KbeY9TW79O9V9Wt9F9I919P29K9nW79O2Wt7S2W94bd39TW79O9V9Wt9F9I919P29K9nW79O2Wt79t9W9Ht9P9H2bo39TW79O9V9Wt9F9J9V9T9W91tWJ2917tWV9c9V919U9K7bw39TW79O9V9Wt9F9J9V9T9W91tW9nW79O2Wt9c9V919U9K7bqE9TW79O9V9Wt9F9J9V9T9W91tW9t9W9OWVW9c9V919U9K7bkL9TW79O9V9Wt9F9V9Wt9P9T9P96W9nW79O2Wtbxl79IV9RbmDwebcekdzHq:Y:beAdbkIbabaec9:fgefcufae9Ugeabci9Uadfcufad9Ugbaeab0Ek;z8JDPue99eux99due99euo99iu8Jjjjjbc:WD9Rgm8KjjjjbdndnalmbcbhPxekamc:Cwfcbc;Kbz:pjjjb8Adndnalcb9imbaoal9nmbamcuaocdtaocFFFFi0Egscbyd;01jjbHjjjjbbgzBd:CwamceBd;8wamascbyd;01jjbHjjjjbbgHBd:GwamcdBd;8wamcualcdtalcFFFFi0Ecbyd;01jjbHjjjjbbgOBd:KwamciBd;8waihsalhAinazasydbcdtfcbBdbasclfhsaAcufgAmbkaihsalhAinazasydbcdtfgCaCydbcefBdbasclfhsaAcufgAmbkaihsalhCcbhXindnazasydbcdtgQfgAydbcb9imbaHaQfaXBdbaAaAydbgQcjjjj94VBdbaQaXfhXkasclfhsaCcufgCmbkalci9UhLdnalci6mbcbhsaihAinaAcwfydbhCaAclfydbhXaHaAydbcdtfgQaQydbgQcefBdbaOaQcdtfasBdbaHaXcdtfgXaXydbgXcefBdbaOaXcdtfasBdbaHaCcdtfgCaCydbgCcefBdbaOaCcdtfasBdbaAcxfhAaLascefgs9hmbkkaihsalhAindnazasydbcdtgCfgXydbgQcu9kmbaXaQcFFFFrGgQBdbaHaCfgCaCydbaQ9RBdbkasclfhsaAcufgAmbxdkkamcuaocdtgsaocFFFFi0EgAcbyd;01jjbHjjjjbbgzBd:CwamceBd;8wamaAcbyd;01jjbHjjjjbbgHBd:GwamcdBd;8wamcualcdtalcFFFFi0Ecbyd;01jjbHjjjjbbgOBd:KwamciBd;8wazcbasz:pjjjbhXalci9UhLaihsalhAinaXasydbcdtfgCaCydbcefBdbasclfhsaAcufgAmbkdnaoTmbcbhsaHhAaXhCaohQinaAasBdbaAclfhAaCydbasfhsaCclfhCaQcufgQmbkkdnalci6mbcbhsaihAinaAcwfydbhCaAclfydbhQaHaAydbcdtfgKaKydbgKcefBdbaOaKcdtfasBdbaHaQcdtfgQaQydbgQcefBdbaOaQcdtfasBdbaHaCcdtfgCaCydbgCcefBdbaOaCcdtfasBdbaAcxfhAaLascefgs9hmbkkaoTmbcbhsaohAinaHasfgCaCydbaXasfydb9RBdbasclfhsaAcufgAmbkkamaLcbyd;01jjbHjjjjbbgsBd:OwamclBd;8wascbaLz:pjjjbhYamcuaLcK2alcjjjjd0Ecbyd;01jjbHjjjjbbg8ABd:SwamcvBd;8wJbbbbhEdnalci6g3mbarcd4hKaihAa8AhsaLhrJbbbbh5inavaAclfydbaK2cdtfgCIdlh8EavaAydbaK2cdtfgXIdlhEavaAcwfydbaK2cdtfgQIdlh8FaCIdwhaaXIdwhhaQIdwhgasaCIdbg8JaXIdbg8KMaQIdbg8LMJbbnn:vUdbasclfaXIdlaCIdlMaQIdlMJbbnn:vUdbaQIdwh8MaCIdwh8NaXIdwhyascxfa8EaE:tg8Eagah:tggNa8FaE:tg8Faaah:tgaN:tgEJbbbbJbbjZa8Ja8K:tg8Ja8FNa8La8K:tg8Ka8EN:tghahNaEaENaaa8KNaga8JN:tgEaENMM:rg8K:va8KJbbbb9BEg8ENUdbasczfaEa8ENUdbascCfaha8ENUdbascwfa8Maya8NMMJbbnn:vUdba5a8KMh5aAcxfhAascKfhsarcufgrmbka5aL:Z:vJbbbZNhEkamcuaLcdtalcFFFF970Ecbyd;01jjbHjjjjbbgCBd:WwamcoBd;8waq:Zhhdna3mbcbhsaChAinaAasBdbaAclfhAaLascefgs9hmbkkaEahNhhamcuaLcltalcFFFFd0Ecbyd;01jjbHjjjjbbg8PBd:0wamcrBd;8wcba8Pa8AaCaLcbz:djjjb8AJFFuuh8MJFFuuh8NJFFuuhydnalci6mbJFFuuhya8AhsaLhAJFFuuh8NJFFuuh8MinascwfIdbgEa8Ma8MaE9EEh8MasclfIdbgEa8Na8NaE9EEh8NasIdbgEayayaE9EEhyascKfhsaAcufgAmbkkah:rhEamaocetgscuaocu9kEcbyd;01jjbHjjjjbbgCBd:4wdndnaoal9nmbaihsalhAinaCasydbcetfcFFi87ebasclfhsaAcufgAmbxdkkaCcFeasz:pjjjb8AkaEJbbbZNh8JcuhIdnalci6mbcbhAJFFuuhEa8AhscuhIinascwfIdba8M:tghahNasIdbay:tghahNasclfIdba8N:tghahNMM:rghaEaIcuSahaE9DVgXEhEaAaIaXEhIascKfhsaLaAcefgA9hmbkkamczfcbcjwz:pjjjb8Aamcwf9cb83ibam9cb83iba8JaxNh8RJbbjZak:th8Lcbh8SJbbbbhRJbbbbh8UJbbbbh8VJbbbbh8WJbbbbh8XJbbbbh8Ycbh8ZcbhPinJbbbbhEdna8STmbJbbjZa8S:Z:vhEkJbbbbhhdna8Ya8YNa8Wa8WNa8Xa8XNMMg8KJbbbb9BmbJbbjZa8K:r:vhhka8VaENh8Ka8UaENh8EaRaENh5aIhLdndndndndna8SaPVTmbamydwg80Tmea8YahNh8Fa8XahNhaa8WahNhgaeamydbcdtfh81cbh3JFFuuhEcvhQcuhLindnaza81a3cdtfydbcdtgsfydbgvTmbaOaHasfydbcdtfhAindndnaCaiaAydbgKcx2fgsclfydbgrcetf8Vebcs4aCasydbgXcetf8Vebcs4faCascwfydbglcetf8Vebcs4fgombcbhsxekcehsazaXcdtfydbgXceSmbcehsazarcdtfydbgrceSmbcehsazalcdtfydbglceSmbdnarcdSaXcdSfalcdSfcd6mbaocefhsxekaocdfhskdnasaQ9kmba8AaKcK2fgXIdwa8K:tghahNaXIdba5:tghahNaXIdla8E:tghahNMM:ra8J:va8LNJbbjZMJ9VO:d86JbbjZaXIdCa8FNaXIdxagNaaaXIdzNMMakN:tghahJ9VO:d869DENghaEasaQ6ahaE9DVgXEhEaKaLaXEhLasaQaXEhQkaAclfhAavcufgvmbkka3cefg3a809hmbkkaLcu9hmekama8KUd:ODama8EUd:KDama5Ud:GDamcuBd:qDamcFFF;7rBdjDa8Pcba8AaYamc:GDfamc:qDfamcjDfz:ejjjbamyd:qDhLdndnaxJbbbb9ETmba8SaD6mbaLcuSmeceh3amIdjDa8R9EmixdkaLcu9hmekdna8STmbabaPcltfgzam8Pib83dbazcwfamcwf8Pib83dbaPcefhPkc3hzinazc98Smvamc:Cwfazfydbcbyd;41jjbH:bjjjbbazc98fhzxbkkcbh3a8Saq9pmbamydwaCaiaLcx2fgsydbcetf8Vebcs4aCascwfydbcetf8Vebcs4faCasclfydbcetf8Vebcs4ffaw9nmekcbhscbhAdna8ZTmbcbhAamczfhXinamczfaAcdtfaXydbgQBdbaXclfhXaAaYaQfRbbTfhAa8Zcufg8ZmbkkamydwhlamydbhXam9cu83i:GDam9cu83i:ODam9cu83i:qDam9cu83i:yDaAc;8eaAclfc:bd6Eh8ZinamcjDfasfcFFF;7rBdbasclfgscz9hmbka8Zcdth80dnalTmbaeaXcdtfhocbhrindnazaoarcdtfydbcdtgsfydbgvTmbaOaHasfydbcdtfhAcuhQcuhsinazaiaAydbgKcx2fgXclfydbcdtfydbazaXydbcdtfydbfazaXcwfydbcdtfydbfgXasaXas6gXEhsaKaQaXEhQaAclfhAavcufgvmbkaQcuSmba8AaQcK2fgAIdwa8M:tgEaENaAIdbay:tgEaENaAIdla8N:tgEaENMM:rhEcbhAindndnasamc:qDfaAfgvydbgX6mbasaX9hmeaEamcjDfaAfIdb9FTmekavasBdbamc:GDfaAfaQBdbamcjDfaAfaEUdbxdkaAclfgAcz9hmbkkarcefgral9hmbkkamczfa80fhQcbhscbhAindnamc:GDfasfydbgXcuSmbaQaAcdtfaXBdbaAcefhAkasclfgscz9hmbkaAa8Zfg8ZTmbJFFuuhhcuhKamczfhsa8ZhvcuhQina8AasydbgXcK2fgAIdwa8M:tgEaENaAIdbay:tgEaENaAIdla8N:tgEaENMM:rhEdndnazaiaXcx2fgAclfydbcdtfydbazaAydbcdtfydbfazaAcwfydbcdtfydbfgAaQ6mbaAaQ9hmeaEah9DTmekaEhhaAhQaXhKkasclfhsavcufgvmbkaKcuSmbaKhLkdnamaiaLcx2fgrydbarclfydbarcwfydbaCabaeadaPawaqa3z:fjjjbTmbaPcefhPJbbbbhRJbbbbh8UJbbbbh8VJbbbbh8WJbbbbh8XJbbbbh8YkcbhXinaOaHaraXcdtfydbcdtgAfydbcdtfgKhsazaAfgvydbgQhAdnaQTmbdninasydbaLSmeasclfhsaAcufgATmdxbkkasaKaQcdtfc98fydbBdbavavydbcufBdbkaXcefgXci9hmbka8AaLcK2fgsIdbhEasIdlhhasIdwh8KasIdxh8EasIdzh5asIdCh8FaYaLfce86bba8Ya8FMh8Ya8Xa5Mh8Xa8Wa8EMh8Wa8Va8KMh8Va8UahMh8UaRaEMhRamydxh8Sxbkkamc:WDf8KjjjjbaPk:joivuv99lu8Jjjjjbca9Rgo8Kjjjjbdndnalcw0mbaiydbhraeabcitfgwalcdtciVBdlawarBdbdnalcd6mbaiclfhralcufhDawcxfhwinarydbhqawcuBdbawc98faqBdbawcwfhwarclfhraDcufgDmbkkalabfhwxekcbhqaoczfcwfcbBdbao9cb83izaocwfcbBdbao9cb83ibJbbjZhkJbbjZhxinadaiaqcdtfydbcK2fhDcbhwinaoczfawfgraDawfIdbgmarIdbgP:tgsaxNaPMgPUdbaoawfgrasamaP:tNarIdbMUdbawclfgwcx9hmbkJbbjZakJbbjZMgk:vhxaqcefgqal9hmbkcbhradcbcecdaoIdlgmaoIdwgP9GEgwaoIdbgsaP9GEawasam9GEgzcdtgwfhHaoczfawfIdbhmaihwalhDinaiarcdtfgqydbhOaqawydbgABdbawaOBdbawclfhwaraHaAcK2fIdbam9DfhraDcufgDmbkdndnarcv6mbavc8X9kmbaralc98f6mekaiydbhraeabcitfgwalcdtciVBdlawarBdbaiclfhralcufhDawcxfhwinarydbhqawcuBdbawc98faqBdbawcwfhwarclfhraDcufgDmbkalabfhwxekaeabcitfgwamUdbawawydlc98GazVBdlabcefaeadaiaravcefgqz:djjjbhDawawydlciGaDabcu7fcdtVBdlaDaeadaiarcdtfalar9Raqz:djjjbhwkaocaf8Kjjjjbawk;yddvue99dninabaecitfgrydlgwcl6mednawciGgDci9hmbabaecitfhbcbhecehqindnaiabydbgDfRbbmbcbhqadaDcK2fgkIdwalIdw:tgxaxNakIdbalIdb:tgxaxNakIdlalIdl:tgxaxNMM:rgxaoIdb9DTmbaoaxUdbavaDBdbarydlhwkabcwfhbaecefgeawcd46mbkaqceGTmdarawciGBdlskdnabcbawcd4gwalaDcdtfIdbarIdb:tgxJbbbb9FEgkaw7aecefgwfgecitfydlabakawfgwcitfydlVci0mbaraDBdlkabawadaialavaoz:ejjjbax:laoIdb9Fmbkkkjlevudnabydwgxaladcetfgm8Vebcs4alaecetfgP8Vebgscs4falaicetfgz8Vebcs4ffaD0abydxaq9pVakVgDce9hmbavawcltfgxab8Pdb83dbaxcwfabcwfgx8Pdb83dbabydbhqdnaxydbgkTmbaoaqcdtfhxakhsinalaxydbcetfcFFi87ebaxclfhxascufgsmbkkabaqakfBdbabydxhxab9cb83dwababydlaxci2fBdlaP8Vebhscbhxkdnascztcz91cu9kmbabaxcefBdwaPax87ebaoabydbcdtfaxcdtfaeBdbkdnam8Uebcu9kmbababydwgxcefBdwamax87ebaoabydbcdtfaxcdtfadBdbkdnaz8Uebcu9kmbababydwgxcefBdwazax87ebaoabydbcdtfaxcdtfaiBdbkarabydlfabydxci2faPRbb86bbarabydlfabydxci2fcefamRbb86bbarabydlfabydxci2fcdfazRbb86bbababydxcefBdxaDk:zPrHue99eue99eue99eu8Jjjjjbc;W;Gb9Rgx8KjjjjbdndnalmbcbhmxekcbhPaxc:m;Gbfcbc;Kbz:pjjjb8Aaxcualci9UgscltascjjjjiGEcbyd;01jjbHjjjjbbgzBd:m9GaxceBd;S9GaxcuascK2gHcKfalcpFFFe0Ecbyd;01jjbHjjjjbbgOBd:q9GaxcdBd;S9Gdnalci6gAmbarcd4hCascdthXaOhQazhLinavaiaPcx2fgrydbaC2cdtfhKavarcwfydbaC2cdtfhYavarclfydbaC2cdtfh8AcbhraLhEinaQarfgmaKarfg3Idbg5a8Aarfg8EIdbg8Fa5a8F9DEg5UdbamaYarfgaIdbg8Fa5a8Fa59DEg8FUdbamcxfgma3Idbg5a8EIdbgha5ah9EEg5UdbamaaIdbgha5aha59EEg5UdbaEa8Fa5MJbbbZNUdbaEaXfhEarclfgrcx9hmbkaQcKfhQaLclfhLaPcefgPas9hmbkkaOaHfgr9cb83dbarczf9cb83dbarcwf9cb83dbaxcuascx2gralc:bjjjl0Ecbyd;01jjbHjjjjbbgCBdN9GaxciBd;S9GascdthgazarfhvaChHazhLcbhPinaxcbcj;Gbz:pjjjbhEaPas2cdthadnaAmbaLhrash3inaEarydbgmc8F91cjjjj94Vam7gmcQ4cx2fg8Ea8EydwcefBdwaEamcd4cFrGcx2fg8Ea8EydbcefBdbaEamcx4cFrGcx2fgmamydlcefBdlarclfhra3cufg3mbkkazaafh8AaCaafhXcbhmcbh3cbh8EcbhainaEamfgrydbhQara3BdbarcwfgKydbhYaKaaBdbarclfgrydbhKara8EBdbaQa3fh3aYaafhaaKa8Efh8Eamcxfgmcj;Gb9hmbkdnaAmbcbhravhminamarBdbamclfhmasarcefgr9hmbkaAmbavhrashminaEa8Aarydbg3cdtfydbg8Ec8F91a8E7cd4cFrGcx2fg8Ea8Eydbg8EcefBdbaXa8Ecdtfa3BdbarclfhramcufgmmbkaHhrashminaEa8Aarydbg3cdtfydbg8Ec8F91a8E7cx4cFrGcx2fg8Ea8Eydlg8EcefBdlava8Ecdtfa3BdbarclfhramcufgmmbkavhrashminaEa8Aarydbg3cdtfydbg8Ec8F91cjjjj94Va8E7cQ4cx2fg8Ea8Eydwg8EcefBdwaXa8Ecdtfa3BdbarclfhramcufgmmbkkaHagfhHaLagfhLaPcefgPci9hmbkaEaocetgrcuaocu9kEcbyd;01jjbHjjjjbbgKBd:y9GaEclBd;S9Gdndnaoal9nmbaihralhminaKarydbcetfcFFi87ebarclfhramcufgmmbxdkkaKcFearz:pjjjb8AkcbhmaEascbyd;01jjbHjjjjbbg8ABd:C9GaOaCaCascdtfaCascitfa8AascbazaKaiawaDaqakz:hjjjbcbh8Ednalci6gambcbh8Ea8Ahrash3ina8EarRbbfh8Earcefhra3cufg3mbkkaEcwf9cb83ibaE9cb83ibalawc9:fgrfcufar9UhrasaDfcufaD9Uh3dnaambara3ara30EhYcbhra8Ehacbhmincbh3dnarTmba8AarfRbbceSh3kamaEaiaCydbcx2fgQydbaQclfydbaQcwfydbaKabaeadamawaqa3a3ce7a8EaY9nVaaamfaY6VGz:fjjjbfhmaCclfhCaaa8AarfRbb9Rhaasarcefgr9hmbkaEydxTmbabamcltfgraE8Pib83dbarcwfaEcwf8Pib83dbamcefhmkczhrinarc98SmeaEc:m;Gbfarfydbcbyd;41jjbH:bjjjbbarc98fhrxbkkaxc;W;Gbf8Kjjjjbamk:YKDQue99lue99iul9:eur99lu8Jjjjjbc;qb9RgP8Kjjjjbaxhsaxhzdndnavax0gHmbdnavTmbcbhOaehzavhAinawaDazydbcx2fgCcwfydbcetfgX8VebhQawaCclfydbcetfgL8VebhKawaCydbcetfgC8VebhYaXce87ebaLce87ebaCce87ebaOaKcs4aYcs4faQcs4ffhOazclfhzaAcufgAmbkaehzavhAinawaDazydbcx2fgCcwfydbcetfcFFi87ebawaCclfydbcetfcFFi87ebawaCydbcetfcFFi87ebazclfhzaAcufgAmbkcehzaqhsaOaq0mekalce86bbalcefcbavcufz:pjjjb8AxekaPaiBdxaPadBdwaPaeBdlavakaqci9Ug8Aaka8Aak6EaHEgK9RhEaxaK9Rh3aKcufh5aKceth8EaKcdtgCc98fh8FavcitgOaC9Rarfc98fhaascufhhavcufhgaraOfh8JJbbjZas:Y:vh8KaravcdtgYfc94fh8LcbazceakaxSEg8Mcdtg8N9RhyJFFuuh8PcuhIcbh8Rcbh8SinaPclfa8ScdtfydbhQaPc8WfcKfcb8Pd:y1jjbgR83ibaPc8Wfczfcb8Pd:q1jjbg8U83ibaPc8Wfcwfcb8Pd11jjbg8V83ibaPcb8Pdj1jjbg8W83i8WaPczfcKfaR83ibaPczfczfa8U83ibaPczfcwfa8V83ibaPa8W83izaQaYfh8XcbhXinabaQaXcdtgLfydbcK2fhAcbhzinaPc8WfazfgCaAazfgOIdbg8YaCIdbg8Za8Ya8Z9DEUdbaCczfgCaOcxfIdbg8YaCIdbg8Za8Ya8Z9EEUdbazclfgzcx9hmbkaPIdnaPId8W:tg80aPId9iaPIdU:tg81NhBaba8XaXcu7cdtfydbcK2fhAcbhzaPId80h83aPId9ehUinaPczfazfgCaAazfgOIdbg8YaCIdbg8Za8Ya8Z9DEUdbaCczfgCaOcxfIdbg8YaCIdbg8Za8Ya8Z9EEUdbazclfgzcx9hmbkaraLfgzaUa83:tg8Ya81Na80a8YNaBMMUdbazaYfaPId8KaPIdC:tg8YaPIdyaPIdK:tg8ZNaPIdaaPIdz:tg80a8YNa80a8ZNMMUdbaXcefgXav9hmbkcbh85dnaHmbcbhAaQhza8JhCavhXinawaDazydbcx2fgOcwfydbcetfgL8Vebh8XawaOclfydbcetfg868Vebh85awaOydbcetfgO8Vebh87aLce87eba86ce87ebaOce87ebaCaAa85cs4a87cs4fa8Xcs4ffgABdbazclfhzaCclfhCaXcufgXmbkavhCinawaDaQydbcx2fgzcwfydbcetfcFFi87ebawazclfydbcetfcFFi87ebawazydbcetfcFFi87ebaQclfhQaCcufgCmbka8Jh85kdndndndndndndndndndna8Eav0mba8Eax0meavaK6mda5aE9pmDcehLaEhXa85Tmlxrka5ag9pmwa8Eax9nmdxlkdnavavaK9UgzaK29Raza320mba5aE9pmwa85Th88ceh86aEhLxvka5ag6mixrka5ag9pmokcbhLaghXa85mikJFFuuh8YcbhQa5hzindnazcefgCaK6mbaLavaC9RgOaK6GmbarazcdtfIdbg8ZaC:YNa8Lavaz9RcdtfIdbg80aO:YNMg81a8Y9Embdndna8KaOahf:YNgB:lJbbb9p9DTmbaB:OhAxekcjjjj94hAka80asaA2aO9R:YNh80dndna8Kazasf:YNgB:lJbbb9p9DTmbaB:OhOxekcjjjj94hOkamasaO2aC9R:Ya8ZNa80MNa81Mg8Za8Ya8Za8Y9DgOEh8YaCaQaOEhQkaza8MfgzaX6mbxlkka85Th88cbh86aghLkJFFuuh8YcbhQaEhCaahAa8FhOaKhzindnazazaK9UgXaK29RaXa320mbdna86TmbaCaCaK9UgXaK29RaXa320mekaraOfIdbg8Zaz:YNaAIdbg80aC:YNMg81a8Y9EmbazhXaCh8Xdna88mba85aOfydbgXh8Xkdndna8Ka8Xahf:YNgB:lJbbb9p9DTmbaB:Oh87xekcjjjj94h87ka80asa872a8X9R:YNh80dndna8KaXahf:YNgB:lJbbb9p9DTmbaB:Oh8Xxekcjjjj94h8Xkamasa8X2aX9R:Ya8ZNa80MNa81Mg8Za8Ya8Za8Y9DgXEh8YazaQaXEhQkaCa8M9RhCaAayfhAaOa8NfhOaza8MfgzcufaL6mbxdkkJFFuuh8YcbhQaEhCaahAa8FhOaKhzindnazaK6mbaLaCaK6GmbaraOfIdbg8Zaz:YNaAIdbg80aC:YNMg81a8Y9Embdndna8Ka85aOfydbg8Xahf:YNgB:lJbbb9p9DTmbaB:Oh86xekcjjjj94h86kamasa862a8X9R:YgBa8ZNa80aBNMNa81Mg8Za8Ya8Za8Y9Dg8XEh8YazaQa8XEhQkaCa8M9RhCaAayfhAaOa8NfhOaza8MfgzcufaX6mbkkaQTmba8Ya8P9DTmba8Yh8PaQh8Ra8ShIka8Scefg8Sci9hmbkdndnaoc8X9kmbaIcb9omeka8Acufh86cbhYindndndnavaY9RaxaYaxfav0Eg8XTmbcbhAaeaYcdtfgzhCa8XhXinawaDaCydbcx2fgOcwfydbcetfgQ8VebhbawaOclfydbcetfgL8VebhrawaOydbcetfgO8VebhKaQce87ebaLce87ebaOce87ebaAarcs4aKcs4fabcs4ffhAaCclfhCaXcufgXmbka8XhOinawaDazydbcx2fgCcwfydbcetfcFFi87ebawaCclfydbcetfcFFi87ebawaCydbcetfcFFi87ebazclfhzaOcufgOmbkaAaq0mekalaYfgzce86bbazcefcba8Xcufz:pjjjb8AxekalaYfgzce86bbazcefcba86z:pjjjb8Aa8Ah8Xka8XaYfgYav9pmdxbkkaravcdtg8XfhLdna8RTmbaPclfaIcdtfydbhza8RhCinaLazydbfcb86bbazclfhzaCcufgCmbkkdnava8R9nmbaPclfaIcdtfydba8Rcdtfhzava8R9RhCinaLazydbfce86bbazclfhzaCcufgCmbkkcbhYindnaYaISmbcbhzaraPclfaYcdtfydbgKa8Xz:ojjjbhCavhXa8RhOinaKaOazaLaCydbgQfRbbgAEcdtfaQBdbaCclfhCaOaAfhOazaA9RcefhzaXcufgXmbkkaYcefgYci9hmbkabaeadaiala8RaocefgCarawaDaqakaxamz:hjjjbabaea8Rcdtgzfadazfaiazfala8Rfava8R9RaCarawaDaqakaxamz:hjjjbkaPc;qbf8Kjjjjbk;Nkovud99euv99eul998Jjjjjbc:W;ae9Rgo8KjjjjbdndnadTmbavcd4hrcbhwcbhDindnaiaeclfydbar2cdtfgvIdbaiaeydbar2cdtfgqIdbgk:tgxaiaecwfydbar2cdtfgmIdlaqIdlgP:tgsNamIdbak:tgzavIdlaP:tgPN:tgkakNaPamIdwaqIdwgH:tgONasavIdwaH:tgHN:tgPaPNaHazNaOaxN:tgxaxNMM:rgsJbbbb9Bmbaoc:W:qefawcx2fgAakas:vUdwaAaxas:vUdlaAaPas:vUdbaoc8Wfawc8K2fgAaq8Pdb83dbaAav8Pdb83dxaAam8Pdb83dKaAcwfaqcwfydbBdbaAcCfavcwfydbBdbaAcafamcwfydbBdbawcefhwkaecxfheaDcifgDad6mbkab9cb83dbabcyf9cb83dbabcaf9cb83dbabcKf9cb83dbabczf9cb83dbabcwf9cb83dbawTmeaocbBd8Sao9cb83iKao9cb83izaoczfaoc8Wfawci2cxaoc8Sfcbcrz:jjjjbaoIdKhCaoIdChXaoIdzhQao9cb83iwao9cb83ibaoaoc:W:qefawcxaoc8Sfcbciz:jjjjbJbbjZhkaoIdwgPJbbbbJbbjZaPaPNaoIdbgPaPNaoIdlgsasNMM:rgx:vaxJbbbb9BEgzNhxasazNhsaPazNhzaoc:W:qefheawhvinaecwfIdbaxNaeIdbazNasaeclfIdbNMMgPakaPak9DEhkaecxfheavcufgvmbkabaCUdwabaXUdlabaQUdbabaoId3UdxdndnakJ;n;m;m899FmbJbbbbhPaoc:W:qefheaoc8WfhvinaCavcwfIdb:taecwfIdbgHNaQavIdb:taeIdbgONaXavclfIdb:taeclfIdbgLNMMaxaHNazaONasaLNMM:vgHaPaHaP9EEhPavc8KfhvaecxfheawcufgwmbkabaxUd8KabasUdaabazUd3abaCaxaPN:tUdKabaXasaPN:tUdCabaQazaPN:tUdzabJbbjZakakN:t:rgkUdydndnaxJbbj:;axJbbj:;9GEgPJbbjZaPJbbjZ9FEJbb;:9cNJbbbZJbbb:;axJbbbb9GEMgP:lJbbb9p9DTmbaP:Ohexekcjjjj94hekabae86b8UdndnasJbbj:;asJbbj:;9GEgPJbbjZaPJbbjZ9FEJbb;:9cNJbbbZJbbb:;asJbbbb9GEMgP:lJbbb9p9DTmbaP:Ohvxekcjjjj94hvkabav86bRdndnazJbbj:;azJbbj:;9GEgPJbbjZaPJbbjZ9FEJbb;:9cNJbbbZJbbb:;azJbbbb9GEMgP:lJbbb9p9DTmbaP:Ohqxekcjjjj94hqkabaq86b8SdndnaecKtcK91:YJbb;:9c:vax:t:lavcKtcK91:YJbb;:9c:vas:t:laqcKtcK91:YJbb;:9c:vaz:t:lakMMMJbb;:9cNJbbjZMgk:lJbbb9p9DTmbak:Ohexekcjjjj94hekaecFbaecFb9iEhexekabcjjj;8iBdycFbhekabae86b8Vxekab9cb83dbabcyf9cb83dbabcaf9cb83dbabcKf9cb83dbabczf9cb83dbabcwf9cb83dbkaoc:W;aef8Kjjjjbk;Iwwvul99iud99eue99eul998Jjjjjbcje9Rgr8Kjjjjbavcd4hwaicd4hDdndnaoTmbarc;abfcbaocdtgvz:pjjjb8Aarc;Gbfcbavz:pjjjb8AarhvarcafhiaohqinavcFFF97BdbaicFFF;7rBdbaiclfhiavclfhvaqcufgqmbkdnadTmbcbhkinaeakaD2cdtfgvIdwhxavIdlhmavIdbhPalakaw2cdtfIdbhsarc;abfhzarhiarc;GbfhHarcafhqc:G1jjbhvaohOinasavcwfIdbaxNavIdbaPNavclfIdbamNMMgAMhCakhXdnaAas:tgAaqIdbgQ9DgLmbaHydbhXkaHaXBdbakhXdnaCaiIdbgK9EmbazydbhXaKhCkazaXBdbaiaCUdbaqaAaQaLEUdbavcxfhvaqclfhqaHclfhHaiclfhiazclfhzaOcufgOmbkakcefgkad9hmbkkadThkJbbbbhCcbhXarc;abfhvarc;Gbfhicbhqinalavydbgzaw2cdtfIdbalaiydbgHaw2cdtfIdbaeazaD2cdtfgzIdwaeaHaD2cdtfgHIdw:tgsasNazIdbaHIdb:tgsasNazIdlaHIdl:tgsasNMM:rMMgsaCasaC9EgzEhCaqaXazEhXaiclfhiavclfhvaoaqcefgq9hmbkaCJbbbZNhKxekadThkcbhXJbbbbhKkJbbbbhCdnaearc;abfaXcdtgifydbgqaD2cdtfgvIdwaearc;GbfaifydbgzaD2cdtfgiIdwgm:tgsasNavIdbaiIdbgY:tgAaANavIdlaiIdlgP:tgQaQNMM:rgxJbbbb9ETmbaxalaqaw2cdtfIdbMalazaw2cdtfIdb:taxaxM:vhCkasaCNamMhmaQaCNaPMhPaAaCNaYMhYdnakmbaDcdthvawcdthiindnalIdbg8AaecwfIdbam:tgCaCNaeIdbaY:tgsasNaeclfIdbaP:tgAaANMM:rgQMgEaK9ETmbJbbbbhxdnaQJbbbb9ETmbaEaK:taQaQM:vhxkaxaCNamMhmaxaANaPMhPaxasNaYMhYa8AaKaQMMJbbbZNhKkaeavfhealaifhladcufgdmbkkabaKUdxabamUdwabaPUdlabaYUdbarcjef8Kjjjjbkjeeiu8Jjjjjbcj8W9Rgr8Kjjjjbaici2hwdnaiTmbawceawce0EhDarhiinaiaeadRbbcdtfydbBdbadcefhdaiclfhiaDcufgDmbkkabarawaladaoz1jjjbarcj8Wf8Kjjjjbk:Reeeu8Jjjjjbca9Rgo8Kjjjjbab9cb83dbabcyf9cb83dbabcaf9cb83dbabcKf9cb83dbabczf9cb83dbabcwf9cb83dbdnadTmbaocbBd3ao9cb83iwao9cb83ibaoaeadaialaoc3falEavcbalEcrz:jjjjbabao8Pib83dbabao8Piw83dwkaocaf8Kjjjjbk:3lequ8JjjjjbcjP9Rgl8Kjjjjbcbhvalcjxfcbaiz:pjjjb8AdndnadTmbcjehoaehrincuhwarhDcuhqavhkdninawakaoalcjxfaDcefRbbfRbb9RcFeGci6aoalcjxfaDRbbfRbb9RcFeGci6faoalcjxfaDcdfRbbfRbb9RcFeGci6fgxaq9mgmEhwdnammbaxce0mdkaxaqaxaq9kEhqaDcifhDadakcefgk9hmbkkaeawci2fgDcdfRbbhqaDcefRbbhxaDRbbhkaeavci2fgDcifaDawav9Rci2zMjjjb8Aakalcjxffaocefgo86bbaxalcjxffao86bbaDcdfaq86bbaDcefax86bbaDak86bbaqalcjxffao86bbarcifhravcefgvad9hmbkalcFeaicetz:pjjjbhoadci2gDceaDce0EhqcbhxindnaoaeRbbgkcetfgw8UebgDcu9kmbawax87ebaocjlfaxcdtfabakcdtfydbBdbaxhDaxcefhxkaeaD86bbaecefheaqcufgqmbkaxcdthDxekcbhDkabalcjlfaDz:ojjjb8AalcjPf8Kjjjjbk9teiucbcbyd;81jjbgeabcifc98GfgbBd;81jjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaik;teeeudndnaeabVciGTmbabhixekdndnadcz9pmbabhixekabhiinaiaeydbBdbaiaeydlBdlaiaeydwBdwaiaeydxBdxaeczfheaiczfhiadc9Wfgdcs0mbkkadcl6mbinaiaeydbBdbaeclfheaiclfhiadc98fgdci0mbkkdnadTmbinaiaeRbb86bbaicefhiaecefheadcufgdmbkkabk:3eedudndnabciGTmbabhixekaecFeGc:b:c:ew2hldndnadcz9pmbabhixekabhiinaialBdxaialBdwaialBdlaialBdbaiczfhiadc9Wfgdcs0mbkkadcl6mbinaialBdbaiclfhiadc98fgdci0mbkkdnadTmbinaiae86bbaicefhiadcufgdmbkkabk9teiucbcbyd;81jjbgeabcrfc94GfgbBd;81jjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaikTeeucbabcbyd;81jjbge9Rcifc98GaefgbBd;81jjbdnabZbcztge9nmbabae9RcFFifcz4nb8Akk:;Deludndndnadch9pmbabaeSmdaeabadfgi9Rcbadcet9R0mekabaead;8qbbxekaeab7ciGhldndndnabae9pmbdnalTmbadhvabhixikdnabciGmbadhvabhixdkadTmiabaeRbb86bbadcufhvdnabcefgiciGmbaecefhexdkavTmiabaeRbe86beadc9:fhvdnabcdfgiciGmbaecdfhexdkavTmiabaeRbd86bdadc99fhvdnabcifgiciGmbaecifhexdkavTmiabaeRbi86biabclfhiaeclfheadc98fhvxekdnalmbdnaiciGTmbadTmlabadcufgifglaeaifRbb86bbdnalciGmbaihdxekaiTmlabadc9:fgifglaeaifRbb86bbdnalciGmbaihdxekaiTmlabadc99fgifglaeaifRbb86bbdnalciGmbaihdxekaiTmlabadc98fgdfaeadfRbb86bbkadcl6mbdnadc98fgocd4cefciGgiTmbaec98fhlabc98fhvinavadfaladfydbBdbadc98fhdaicufgimbkkaocx6mbaec9Wfhvabc9WfhoinaoadfgicxfavadfglcxfydbBdbaicwfalcwfydbBdbaiclfalclfydbBdbaialydbBdbadc9Wfgdci0mbkkadTmdadhidnadciGglTmbaecufhvabcufhoadhiinaoaifavaifRbb86bbaicufhialcufglmbkkadcl6mdaec98fhlabc98fhvinavaifgecifalaifgdcifRbb86bbaecdfadcdfRbb86bbaecefadcefRbb86bbaeadRbb86bbaic98fgimbxikkavcl6mbdnavc98fglcd4cefcrGgdTmbavadcdt9RhvinaiaeydbBdbaeclfheaiclfhiadcufgdmbkkalc36mbinaiaeydbBdbaiaeydlBdlaiaeydwBdwaiaeydxBdxaiaeydzBdzaiaeydCBdCaiaeydKBdKaiaeyd3Bd3aecafheaicafhiavc9Gfgvci0mbkkavTmbdndnavcrGgdmbavhlxekavc94GhlinaiaeRbb86bbaicefhiaecefheadcufgdmbkkavcw6mbinaiaeRbb86bbaiaeRbe86beaiaeRbd86bdaiaeRbi86biaiaeRbl86blaiaeRbv86bvaiaeRbo86boaiaeRbr86braicwfhiaecwfhealc94fglmbkkabkk:nedbcjwktFFuuFFuuFFuubbbbFFuFFFuFFFuFbbbbbbjZbbbbbbbbbbbbbbjZbbbbbbbbbbbbbbjZ86;nAZ86;nAZ86;nAZ86;nA:;86;nAZ86;nAZ86;nAZ86;nA:;86;nAZ86;nAZ86;nAZ86;nA:;bc;0wkxebbbdbbbjNbb'; // embed! wasm\n\n\tvar wasmpack = new Uint8Array([\n\t\t32, 0, 65, 2, 1, 106, 34, 33, 3, 128, 11, 4, 13, 64, 6, 253, 10, 7, 15, 116, 127, 5, 8, 12, 40, 16, 19, 54, 20, 9, 27, 255, 113, 17, 42, 67,\n\t\t24, 23, 146, 148, 18, 14, 22, 45, 70, 69, 56, 114, 101, 21, 25, 63, 75, 136, 108, 28, 118, 29, 73, 115,\n\t]);\n\n\tif (typeof WebAssembly !== 'object') {\n\t\treturn {\n\t\t\tsupported: false,\n\t\t};\n\t}\n\n\tvar instance;\n\n\tvar ready = WebAssembly.instantiate(unpack(wasm), {}).then(function (result) {\n\t\tinstance = result.instance;\n\t\tinstance.exports.__wasm_call_ctors();\n\t});\n\n\tfunction unpack(data) {\n\t\tvar result = new Uint8Array(data.length);\n\t\tfor (var i = 0; i < data.length; ++i) {\n\t\t\tvar ch = data.charCodeAt(i);\n\t\t\tresult[i] = ch > 96 ? ch - 97 : ch > 64 ? ch - 39 : ch + 4;\n\t\t}\n\t\tvar write = 0;\n\t\tfor (var i = 0; i < data.length; ++i) {\n\t\t\tresult[write++] = result[i] < 60 ? wasmpack[result[i]] : (result[i] - 60) * 64 + result[++i];\n\t\t}\n\t\treturn result.buffer.slice(0, write);\n\t}\n\n\tfunction assert(cond) {\n\t\tif (!cond) {\n\t\t\tthrow new Error('Assertion failed');\n\t\t}\n\t}\n\n\tfunction bytes(view) {\n\t\treturn new Uint8Array(view.buffer, view.byteOffset, view.byteLength);\n\t}\n\n\tvar BOUNDS_SIZE = 48;\n\tvar MESHLET_SIZE = 16;\n\n\tfunction extractMeshlet(buffers, index) {\n\t\tvar vertex_offset = buffers.meshlets[index * 4 + 0];\n\t\tvar triangle_offset = buffers.meshlets[index * 4 + 1];\n\t\tvar vertex_count = buffers.meshlets[index * 4 + 2];\n\t\tvar triangle_count = buffers.meshlets[index * 4 + 3];\n\n\t\treturn {\n\t\t\tvertices: buffers.vertices.subarray(vertex_offset, vertex_offset + vertex_count),\n\t\t\ttriangles: buffers.triangles.subarray(triangle_offset, triangle_offset + triangle_count * 3),\n\t\t};\n\t}\n\n\tfunction buildMeshlets(\n\t\tfun,\n\t\tindices,\n\t\tvertex_positions,\n\t\tvertex_count,\n\t\tvertex_positions_stride,\n\t\tmax_vertices,\n\t\tmin_triangles,\n\t\tmax_triangles,\n\t\tparama,\n\t\tparamb\n\t) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar max_meshlets = instance.exports.meshopt_buildMeshletsBound(indices.length, max_vertices, min_triangles);\n\n\t\t// allocate memory\n\t\tvar meshletsp = sbrk(max_meshlets * MESHLET_SIZE);\n\t\tvar meshlet_verticesp = sbrk(indices.length * 4);\n\t\tvar meshlet_trianglesp = sbrk(indices.length);\n\n\t\tvar indicesp = sbrk(indices.byteLength);\n\t\tvar verticesp = sbrk(vertex_positions.byteLength);\n\n\t\t// copy input data to wasm memory\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(bytes(indices), indicesp);\n\t\theap.set(bytes(vertex_positions), verticesp);\n\n\t\tvar count = fun(\n\t\t\tmeshletsp,\n\t\t\tmeshlet_verticesp,\n\t\t\tmeshlet_trianglesp,\n\t\t\tindicesp,\n\t\t\tindices.length,\n\t\t\tverticesp,\n\t\t\tvertex_count,\n\t\t\tvertex_positions_stride,\n\t\t\tmax_vertices,\n\t\t\tmin_triangles,\n\t\t\tmax_triangles,\n\t\t\tparama,\n\t\t\tparamb\n\t\t);\n\n\t\t// heap might (will?) have grown -> re-acquire\n\t\theap = new Uint8Array(instance.exports.memory.buffer);\n\n\t\tvar meshletBytes = heap.subarray(meshletsp, meshletsp + count * MESHLET_SIZE);\n\t\tvar meshlets = new Uint32Array(meshletBytes.buffer, meshletBytes.byteOffset, meshletBytes.byteLength / 4).slice();\n\n\t\tfor (var i = 0; i < count; ++i) {\n\t\t\tvar vertex_offset = meshlets[i * 4 + 0];\n\t\t\tvar triangle_offset = meshlets[i * 4 + 1];\n\t\t\tvar vertex_count = meshlets[i * 4 + 2];\n\t\t\tvar triangle_count = meshlets[i * 4 + 3];\n\n\t\t\tinstance.exports.meshopt_optimizeMeshlet(\n\t\t\t\tmeshlet_verticesp + vertex_offset * 4,\n\t\t\t\tmeshlet_trianglesp + triangle_offset,\n\t\t\t\ttriangle_count,\n\t\t\t\tvertex_count\n\t\t\t);\n\t\t}\n\n\t\tvar last_vertex_offset = meshlets[(count - 1) * 4 + 0];\n\t\tvar last_triangle_offset = meshlets[(count - 1) * 4 + 1];\n\t\tvar last_vertex_count = meshlets[(count - 1) * 4 + 2];\n\t\tvar last_triangle_count = meshlets[(count - 1) * 4 + 3];\n\n\t\tvar used_vertices = last_vertex_offset + last_vertex_count;\n\t\tvar used_triangles = last_triangle_offset + last_triangle_count * 3;\n\n\t\tvar result = {\n\t\t\tmeshlets: meshlets,\n\t\t\tvertices: new Uint32Array(heap.buffer, meshlet_verticesp, used_vertices).slice(),\n\t\t\ttriangles: new Uint8Array(heap.buffer, meshlet_trianglesp, used_triangles * 3).slice(),\n\t\t\tmeshletCount: count,\n\t\t};\n\n\t\t// reset memory\n\t\tsbrk(meshletsp - sbrk(0));\n\n\t\treturn result;\n\t}\n\n\tfunction extractBounds(boundsp) {\n\t\tvar bounds_floats = new Float32Array(instance.exports.memory.buffer, boundsp, BOUNDS_SIZE / 4);\n\n\t\t// see meshopt_Bounds in meshoptimizer.h for layout\n\t\treturn {\n\t\t\tcenterX: bounds_floats[0],\n\t\t\tcenterY: bounds_floats[1],\n\t\t\tcenterZ: bounds_floats[2],\n\t\t\tradius: bounds_floats[3],\n\t\t\tconeApexX: bounds_floats[4],\n\t\t\tconeApexY: bounds_floats[5],\n\t\t\tconeApexZ: bounds_floats[6],\n\t\t\tconeAxisX: bounds_floats[7],\n\t\t\tconeAxisY: bounds_floats[8],\n\t\t\tconeAxisZ: bounds_floats[9],\n\t\t\tconeCutoff: bounds_floats[10],\n\t\t};\n\t}\n\n\tfunction computeMeshletBounds(buffers, vertex_positions, vertex_count, vertex_positions_stride) {\n\t\tvar sbrk = instance.exports.sbrk;\n\n\t\tvar results = [];\n\n\t\t// allocate memory that's constant for all meshlets\n\t\tvar verticesp = sbrk(vertex_positions.byteLength);\n\t\tvar meshlet_verticesp = sbrk(buffers.vertices.byteLength);\n\t\tvar meshlet_trianglesp = sbrk(buffers.triangles.byteLength);\n\t\tvar resultp = sbrk(BOUNDS_SIZE);\n\n\t\t// copy vertices to wasm memory\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(bytes(vertex_positions), verticesp);\n\t\theap.set(bytes(buffers.vertices), meshlet_verticesp);\n\t\theap.set(bytes(buffers.triangles), meshlet_trianglesp);\n\n\t\tfor (var i = 0; i < buffers.meshletCount; ++i) {\n\t\t\tvar vertex_offset = buffers.meshlets[i * 4 + 0];\n\t\t\tvar triangle_offset = buffers.meshlets[i * 4 + 0 + 1];\n\t\t\tvar triangle_count = buffers.meshlets[i * 4 + 0 + 3];\n\n\t\t\tinstance.exports.meshopt_computeMeshletBounds(\n\t\t\t\tresultp,\n\t\t\t\tmeshlet_verticesp + vertex_offset * 4,\n\t\t\t\tmeshlet_trianglesp + triangle_offset,\n\t\t\t\ttriangle_count,\n\t\t\t\tverticesp,\n\t\t\t\tvertex_count,\n\t\t\t\tvertex_positions_stride\n\t\t\t);\n\n\t\t\tresults.push(extractBounds(resultp));\n\t\t}\n\n\t\t// reset memory\n\t\tsbrk(verticesp - sbrk(0));\n\n\t\treturn results;\n\t}\n\n\tfunction computeClusterBounds(indices, vertex_positions, vertex_count, vertex_positions_stride) {\n\t\tvar sbrk = instance.exports.sbrk;\n\n\t\t// allocate memory\n\t\tvar resultp = sbrk(BOUNDS_SIZE);\n\t\tvar indicesp = sbrk(indices.byteLength);\n\t\tvar verticesp = sbrk(vertex_positions.byteLength);\n\n\t\t// copy input data to wasm memory\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(bytes(indices), indicesp);\n\t\theap.set(bytes(vertex_positions), verticesp);\n\n\t\tinstance.exports.meshopt_computeClusterBounds(resultp, indicesp, indices.length, verticesp, vertex_count, vertex_positions_stride);\n\n\t\tvar result = extractBounds(resultp);\n\n\t\t// reset memory\n\t\tsbrk(resultp - sbrk(0));\n\n\t\treturn result;\n\t}\n\n\tfunction computeSphereBounds(positions, count, positions_stride, radii, radii_stride) {\n\t\tvar sbrk = instance.exports.sbrk;\n\n\t\t// allocate memory\n\t\tvar resultp = sbrk(BOUNDS_SIZE);\n\t\tvar positionsp = sbrk(positions.byteLength);\n\t\tvar radiip = radii ? sbrk(radii.byteLength) : 0;\n\n\t\t// copy input data to wasm memory\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(bytes(positions), positionsp);\n\t\tif (radii) {\n\t\t\theap.set(bytes(radii), radiip);\n\t\t}\n\n\t\tinstance.exports.meshopt_computeSphereBounds(resultp, positionsp, count, positions_stride, radiip, radii ? radii_stride : 0);\n\n\t\tvar result = extractBounds(resultp);\n\n\t\t// reset memory\n\t\tsbrk(resultp - sbrk(0));\n\n\t\treturn result;\n\t}\n\n\treturn {\n\t\tready: ready,\n\t\tsupported: true,\n\t\tbuildMeshlets: function (indices, vertex_positions, vertex_positions_stride, max_vertices, max_triangles, cone_weight) {\n\t\t\tassert(indices.length % 3 == 0);\n\t\t\tassert(vertex_positions instanceof Float32Array);\n\t\t\tassert(vertex_positions.length % vertex_positions_stride == 0);\n\t\t\tassert(vertex_positions_stride >= 3);\n\t\t\tassert(max_vertices > 0 && max_vertices <= 256);\n\t\t\tassert(max_triangles >= 1 && max_triangles <= 512);\n\n\t\t\tcone_weight = cone_weight || 0.0;\n\n\t\t\tvar indices32 = indices.BYTES_PER_ELEMENT == 4 ? indices : new Uint32Array(indices);\n\n\t\t\treturn buildMeshlets(\n\t\t\t\tinstance.exports.meshopt_buildMeshletsFlex,\n\t\t\t\tindices32,\n\t\t\t\tvertex_positions,\n\t\t\t\tvertex_positions.length / vertex_positions_stride,\n\t\t\t\tvertex_positions_stride * 4,\n\t\t\t\tmax_vertices,\n\t\t\t\tmax_triangles,\n\t\t\t\tmax_triangles,\n\t\t\t\tcone_weight,\n\t\t\t\t0.0\n\t\t\t);\n\t\t},\n\t\tbuildMeshletsFlex: function (\n\t\t\tindices,\n\t\t\tvertex_positions,\n\t\t\tvertex_positions_stride,\n\t\t\tmax_vertices,\n\t\t\tmin_triangles,\n\t\t\tmax_triangles,\n\t\t\tcone_weight,\n\t\t\tsplit_factor\n\t\t) {\n\t\t\tassert(indices.length % 3 == 0);\n\t\t\tassert(vertex_positions instanceof Float32Array);\n\t\t\tassert(vertex_positions.length % vertex_positions_stride == 0);\n\t\t\tassert(vertex_positions_stride >= 3);\n\t\t\tassert(max_vertices > 0 && max_vertices <= 256);\n\t\t\tassert(min_triangles >= 1 && max_triangles <= 512);\n\t\t\tassert(min_triangles <= max_triangles);\n\n\t\t\tcone_weight = cone_weight || 0.0;\n\t\t\tsplit_factor = split_factor || 0.0;\n\n\t\t\tvar indices32 = indices.BYTES_PER_ELEMENT == 4 ? indices : new Uint32Array(indices);\n\n\t\t\treturn buildMeshlets(\n\t\t\t\tinstance.exports.meshopt_buildMeshletsFlex,\n\t\t\t\tindices32,\n\t\t\t\tvertex_positions,\n\t\t\t\tvertex_positions.length / vertex_positions_stride,\n\t\t\t\tvertex_positions_stride * 4,\n\t\t\t\tmax_vertices,\n\t\t\t\tmin_triangles,\n\t\t\t\tmax_triangles,\n\t\t\t\tcone_weight,\n\t\t\t\tsplit_factor\n\t\t\t);\n\t\t},\n\t\tbuildMeshletsSpatial: function (indices, vertex_positions, vertex_positions_stride, max_vertices, min_triangles, max_triangles, fill_weight) {\n\t\t\tassert(indices.length % 3 == 0);\n\t\t\tassert(vertex_positions instanceof Float32Array);\n\t\t\tassert(vertex_positions.length % vertex_positions_stride == 0);\n\t\t\tassert(vertex_positions_stride >= 3);\n\t\t\tassert(max_vertices > 0 && max_vertices <= 256);\n\t\t\tassert(min_triangles >= 1 && max_triangles <= 512);\n\t\t\tassert(min_triangles <= max_triangles);\n\n\t\t\tfill_weight = fill_weight || 0.0;\n\n\t\t\tvar indices32 = indices.BYTES_PER_ELEMENT == 4 ? indices : new Uint32Array(indices);\n\n\t\t\treturn buildMeshlets(\n\t\t\t\tinstance.exports.meshopt_buildMeshletsSpatial,\n\t\t\t\tindices32,\n\t\t\t\tvertex_positions,\n\t\t\t\tvertex_positions.length / vertex_positions_stride,\n\t\t\t\tvertex_positions_stride * 4,\n\t\t\t\tmax_vertices,\n\t\t\t\tmin_triangles,\n\t\t\t\tmax_triangles,\n\t\t\t\tfill_weight\n\t\t\t);\n\t\t},\n\t\textractMeshlet: function (buffers, index) {\n\t\t\tassert(index >= 0 && index < buffers.meshletCount);\n\n\t\t\treturn extractMeshlet(buffers, index);\n\t\t},\n\t\tcomputeClusterBounds: function (indices, vertex_positions, vertex_positions_stride) {\n\t\t\tassert(indices.length % 3 == 0);\n\t\t\tassert(indices.length / 3 <= 512);\n\t\t\tassert(vertex_positions instanceof Float32Array);\n\t\t\tassert(vertex_positions.length % vertex_positions_stride == 0);\n\t\t\tassert(vertex_positions_stride >= 3);\n\n\t\t\tvar indices32 = indices.BYTES_PER_ELEMENT == 4 ? indices : new Uint32Array(indices);\n\n\t\t\treturn computeClusterBounds(indices32, vertex_positions, vertex_positions.length / vertex_positions_stride, vertex_positions_stride * 4);\n\t\t},\n\t\tcomputeMeshletBounds: function (buffers, vertex_positions, vertex_positions_stride) {\n\t\t\tassert(buffers.meshletCount != 0);\n\t\t\tassert(vertex_positions instanceof Float32Array);\n\t\t\tassert(vertex_positions.length % vertex_positions_stride == 0);\n\t\t\tassert(vertex_positions_stride >= 3);\n\n\t\t\treturn computeMeshletBounds(buffers, vertex_positions, vertex_positions.length / vertex_positions_stride, vertex_positions_stride * 4);\n\t\t},\n\t\tcomputeSphereBounds: function (positions, positions_stride, radii, radii_stride) {\n\t\t\tassert(positions instanceof Float32Array);\n\t\t\tassert(positions.length % positions_stride == 0);\n\t\t\tassert(positions_stride >= 3);\n\t\t\tassert(!radii || radii instanceof Float32Array);\n\t\t\tassert(!radii || radii.length % radii_stride == 0);\n\t\t\tassert(!radii || radii_stride >= 1);\n\t\t\tassert(!radii || positions.length / positions_stride == radii.length / radii_stride);\n\n\t\t\tradii_stride = radii_stride || 0;\n\n\t\t\treturn computeSphereBounds(positions, positions.length / positions_stride, positions_stride * 4, radii, radii_stride * 4);\n\t\t},\n\t};\n})();\n\nexport { MeshoptClusterizer };\n"
  },
  {
    "path": "js/meshopt_clusterizer.test.js",
    "content": "import assert from 'assert/strict';\nimport { MeshoptClusterizer as clusterizer } from './meshopt_clusterizer.js';\n\nprocess.on('unhandledRejection', (error) => {\n\tconsole.log('unhandledRejection', error);\n\tprocess.exit(1);\n});\n\nconst cubeWithNormals = {\n\tvertices: new Float32Array([\n\t\t// n = (0, 0, 1)\n\t\t-1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, -1.0, 1.0, 1.0, 0.0, 0.0, 1.0,\n\t\t// n = (0, 0, -1)\n\t\t-1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 1.0, -1.0, -1.0, 0.0, 0.0, -1.0, -1.0, -1.0, -1.0, 0.0, 0.0, -1.0,\n\t\t// n = (1, 0, 0)\n\t\t1.0, -1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, -1.0, 1.0, 1.0, 0.0, 0.0,\n\t\t// n = (-1, 0, 0)\n\t\t-1.0, -1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 1.0, -1.0, -1.0, 0.0, 0.0, -1.0, -1.0, -1.0, -1.0, 0.0, 0.0,\n\t\t// n = (0, 1, 0)\n\t\t1.0, 1.0, -1.0, 0.0, 1.0, 0.0, -1.0, 1.0, -1.0, 0.0, 1.0, 0.0, -1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0,\n\t\t// n = (0, -1, 0)\n\t\t1.0, -1.0, 1.0, 0.0, -1.0, 0.0, -1.0, -1.0, 1.0, 0.0, -1.0, 0.0, -1.0, -1.0, -1.0, 0.0, -1.0, 0.0, 1.0, -1.0, -1.0, 0.0, -1.0, 0.0,\n\t]),\n\tindices: new Uint32Array([\n\t\t// n = (0, 0, 1)\n\t\t0, 1, 2, 2, 3, 0,\n\t\t// n = (0, 0, -1)\n\t\t4, 5, 6, 6, 7, 4,\n\t\t// n = (1, 0, 0)\n\t\t8, 9, 10, 10, 11, 8,\n\t\t// n = (-1, 0, 0)\n\t\t12, 13, 14, 14, 15, 12,\n\t\t// n = (0, 1, 0)\n\t\t16, 17, 18, 18, 19, 16,\n\t\t// n = (0, -1, 0)\n\t\t20, 21, 22, 22, 23, 20,\n\t]),\n\tvertexStride: 6, // in floats\n};\n\nconst tests = {\n\tbuildMeshlets: function () {\n\t\tconst maxVertices = 4;\n\t\tconst buffers = clusterizer.buildMeshlets(cubeWithNormals.indices, cubeWithNormals.vertices, cubeWithNormals.vertexStride, maxVertices, 512);\n\n\t\tconst expectedVertices = [\n\t\t\tnew Uint32Array([6, 7, 4, 5]),\n\t\t\tnew Uint32Array([14, 15, 12, 13]),\n\t\t\tnew Uint32Array([2, 3, 0, 1]),\n\t\t\tnew Uint32Array([20, 21, 22, 23]),\n\t\t\tnew Uint32Array([10, 11, 8, 9]),\n\t\t\tnew Uint32Array([18, 19, 16, 17]),\n\t\t];\n\t\tconst expectedTriangles = new Uint8Array([0, 1, 2, 2, 3, 0]);\n\n\t\tassert.equal(buffers.meshletCount, 6);\n\n\t\tfor (let i = 0; i < buffers.meshletCount; ++i) {\n\t\t\tconst m = clusterizer.extractMeshlet(buffers, i);\n\t\t\tassert.deepStrictEqual(m.vertices, expectedVertices[i]);\n\t\t\tassert.deepStrictEqual(m.triangles, expectedTriangles);\n\t\t}\n\t},\n\n\tcomputeClusterBounds: function () {\n\t\tfor (let i = 0; i < 6; ++i) {\n\t\t\tconst indexOffset = i * 6;\n\t\t\tconst normalOffset = i * 4 * cubeWithNormals.vertexStride;\n\t\t\tconst bounds = clusterizer.computeClusterBounds(\n\t\t\t\tcubeWithNormals.indices.subarray(indexOffset, 6 + indexOffset),\n\t\t\t\tcubeWithNormals.vertices,\n\t\t\t\tcubeWithNormals.vertexStride\n\t\t\t);\n\t\t\tassert.deepStrictEqual(\n\t\t\t\tnew Int32Array([bounds.coneAxisX, bounds.coneAxisY, bounds.coneAxisZ]),\n\t\t\t\tnew Int32Array(cubeWithNormals.vertices.subarray(3 + normalOffset, 6 + normalOffset))\n\t\t\t);\n\t\t}\n\t},\n\n\tcomputeMeshletBounds: function () {\n\t\tconst maxVertices = 4;\n\t\tconst buffers = clusterizer.buildMeshlets(cubeWithNormals.indices, cubeWithNormals.vertices, cubeWithNormals.vertexStride, maxVertices, 512);\n\n\t\tconst expectedNormals = [\n\t\t\tnew Int32Array([0, 0, -1]),\n\t\t\tnew Int32Array([-1, 0, 0]),\n\t\t\tnew Int32Array([0, 0, 1]),\n\t\t\tnew Int32Array([0, -1, 0]),\n\t\t\tnew Int32Array([1, 0, 0]),\n\t\t\tnew Int32Array([0, 1, 0]),\n\t\t];\n\n\t\tconst bounds = clusterizer.computeMeshletBounds(buffers, cubeWithNormals.vertices, cubeWithNormals.vertexStride);\n\n\t\tassert(bounds.length === 6);\n\t\tassert(bounds.length === buffers.meshletCount);\n\n\t\tbounds.forEach((b, i) => {\n\t\t\tconst normal = new Int32Array([b.coneAxisX, b.coneAxisY, b.coneAxisZ]);\n\t\t\tassert.deepStrictEqual(normal, expectedNormals[i]);\n\t\t});\n\t},\n\n\tcomputeSphereBounds: function () {\n\t\t// positions without per-point radii (tetrahedron)\n\t\tconst positions = new Float32Array([0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1]);\n\t\tconst bp = clusterizer.computeSphereBounds(positions, 3);\n\n\t\tassert(Math.abs(bp.centerX - 0.5) < 1e-3);\n\t\tassert(Math.abs(bp.centerY - 0.5) < 1e-3);\n\t\tassert(Math.abs(bp.centerZ - 0.5) < 1e-3);\n\t\tassert(bp.radius < 0.87);\n\n\t\t// use 4th float of each element as radius (last point has radius=3 enveloping others)\n\t\tconst radii = new Float32Array([0, 1, 2, 3]);\n\t\tconst br = clusterizer.computeSphereBounds(positions, 3, radii, 1);\n\n\t\tassert(Math.abs(br.centerX - 1.0) < 1e-3);\n\t\tassert(Math.abs(br.centerY - 0.0) < 1e-3);\n\t\tassert(Math.abs(br.centerZ - 1.0) < 1e-3);\n\t\tassert(Math.abs(br.radius - 3.0) < 1e-3);\n\t},\n};\n\nclusterizer.ready.then(() => {\n\tvar count = 0;\n\n\tfor (var key in tests) {\n\t\ttests[key]();\n\t\tcount++;\n\t}\n\n\tconsole.log(count, 'tests passed');\n});\n"
  },
  {
    "path": "js/meshopt_decoder.cjs",
    "content": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\nvar MeshoptDecoder = (function () {\n\t// Built with clang version 19.1.5-wasi-sdk\n\t// Built from meshoptimizer 1.0\n\tvar wasm_base =\n\t\t'b9H79Tebbbe8Fv9Gbb9Gvuuuuueu9Giuuub9Geueu9Giuuueuixkbeeeddddillviebeoweuec:W:Odkr;Neqo9TW9T9VV95dbH9F9F939H79T9F9J9H229F9Jt9VV7bb8A9TW79O9V9Wt9F9KW9J9V9KW9wWVtW949c919M9MWVbeY9TW79O9V9Wt9F9KW9J9V9KW69U9KW949c919M9MWVbdE9TW79O9V9Wt9F9KW9J9V9KW69U9KW949tWG91W9U9JWbiL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9p9JtblK9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9r919HtbvL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVT949WboY9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVJ9V29VVbrl79IV9Rbwq;lZkdbk;jYi5ud9:du8Jjjjjbcj;kb9Rgv8Kjjjjbc9:hodnalTmbcuhoaiRbbgrc;WeGc:Ge9hmbarcsGgwce0mbc9:hoalcufadcd4cbawEgDadfgrcKcaawEgqaraq0Egk6mbaicefhxcj;abad9Uc;WFbGcjdadca0EhmaialfgPar9Rgoadfhsavaoadz:jjjjbgzceVhHcbhOdndninaeaO9nmeaPax9RaD6mdamaeaO9RaOamfgoae6EgAcsfglc9WGhCabaOad2fhXaAcethQaxaDfhiaOaeaoaeao6E9RhLalcl4cifcd4hKazcj;cbfaAfhYcbh8AazcjdfhEaHh3incbhodnawTmbaxa8Acd4fRbbhokaocFeGh5cbh8Eazcj;cbfhqinaih8Fdndndndna5a8Ecet4ciGgoc9:fPdebdkaPa8F9RaA6mrazcj;cbfa8EaA2fa8FaAz:jjjjb8Aa8FaAfhixdkazcj;cbfa8EaA2fcbaAz:kjjjb8Aa8FhixekaPa8F9RaK6mva8FaKfhidnaCTmbaPai9RcK6mbaocdtc:q1jjbfcj1jjbawEhaczhrcbhlinargoc9Wfghaqfhrdndndndndndnaaa8Fahco4fRbbalcoG4ciGcdtfydbPDbedvivvvlvkar9cb83bbarcwf9cb83bbxlkarcbaiRbdai8Xbb9c:c:qj:bw9:9c:q;c1:I1e:d9c:b:c:e1z9:gg9cjjjjjz:dg8J9qE86bbaqaofgrcGfag9c8F1:NghcKtc8F91aicdfa8J9c8N1:Nfg8KRbbG86bbarcVfcba8KahcjeGcr4fghRbbag9cjjjjjl:dg8J9qE86bbarc7fcbaha8J9c8L1:NfghRbbag9cjjjjjd:dg8J9qE86bbarctfcbaha8J9c8K1:NfghRbbag9cjjjjje:dg8J9qE86bbarc91fcbaha8J9c8J1:NfghRbbag9cjjjj;ab:dg8J9qE86bbarc4fcbaha8J9cg1:NfghRbbag9cjjjja:dg8J9qE86bbarc93fcbaha8J9ch1:NfghRbbag9cjjjjz:dgg9qE86bbarc94fcbahag9ca1:NfghRbbai8Xbe9c:c:qj:bw9:9c:q;c1:I1e:d9c:b:c:e1z9:gg9cjjjjjz:dg8J9qE86bbarc95fag9c8F1:NgicKtc8F91aha8J9c8N1:NfghRbbG86bbarc96fcbahaicjeGcr4fgiRbbag9cjjjjjl:dg8J9qE86bbarc97fcbaia8J9c8L1:NfgiRbbag9cjjjjjd:dg8J9qE86bbarc98fcbaia8J9c8K1:NfgiRbbag9cjjjjje:dg8J9qE86bbarc99fcbaia8J9c8J1:NfgiRbbag9cjjjj;ab:dg8J9qE86bbarc9:fcbaia8J9cg1:NfgiRbbag9cjjjja:dg8J9qE86bbarcufcbaia8J9ch1:NfgiRbbag9cjjjjz:dgg9qE86bbaiag9ca1:NfhixikaraiRblaiRbbghco4g8Ka8KciSg8KE86bbaqaofgrcGfaiclfa8Kfg8KRbbahcl4ciGg8La8LciSg8LE86bbarcVfa8Ka8Lfg8KRbbahcd4ciGg8La8LciSg8LE86bbarc7fa8Ka8Lfg8KRbbahciGghahciSghE86bbarctfa8Kahfg8KRbbaiRbeghco4g8La8LciSg8LE86bbarc91fa8Ka8Lfg8KRbbahcl4ciGg8La8LciSg8LE86bbarc4fa8Ka8Lfg8KRbbahcd4ciGg8La8LciSg8LE86bbarc93fa8Ka8Lfg8KRbbahciGghahciSghE86bbarc94fa8Kahfg8KRbbaiRbdghco4g8La8LciSg8LE86bbarc95fa8Ka8Lfg8KRbbahcl4ciGg8La8LciSg8LE86bbarc96fa8Ka8Lfg8KRbbahcd4ciGg8La8LciSg8LE86bbarc97fa8Ka8Lfg8KRbbahciGghahciSghE86bbarc98fa8KahfghRbbaiRbigico4g8Ka8KciSg8KE86bbarc99faha8KfghRbbaicl4ciGg8Ka8KciSg8KE86bbarc9:faha8KfghRbbaicd4ciGg8Ka8KciSg8KE86bbarcufaha8KfgrRbbaiciGgiaiciSgiE86bbaraifhixdkaraiRbwaiRbbghcl4g8Ka8KcsSg8KE86bbaqaofgrcGfaicwfa8Kfg8KRbbahcsGghahcsSghE86bbarcVfa8KahfghRbbaiRbeg8Kcl4g8La8LcsSg8LE86bbarc7faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarctfaha8KfghRbbaiRbdg8Kcl4g8La8LcsSg8LE86bbarc91faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarc4faha8KfghRbbaiRbig8Kcl4g8La8LcsSg8LE86bbarc93faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarc94faha8KfghRbbaiRblg8Kcl4g8La8LcsSg8LE86bbarc95faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarc96faha8KfghRbbaiRbvg8Kcl4g8La8LcsSg8LE86bbarc97faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarc98faha8KfghRbbaiRbog8Kcl4g8La8LcsSg8LE86bbarc99faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarc9:faha8KfghRbbaiRbrgicl4g8Ka8KcsSg8KE86bbarcufaha8KfgrRbbaicsGgiaicsSgiE86bbaraifhixekarai8Pbb83bbarcwfaicwf8Pbb83bbaiczfhikdnaoaC9pmbalcdfhlaoczfhraPai9RcL0mekkaoaC6moaimexokaCmva8FTmvkaqaAfhqa8Ecefg8Ecl9hmbkdndndndnawTmbasa8Acd4fRbbgociGPlbedrbkaATmdaza8Afh8Fazcj;cbfhhcbh8EaEhaina8FRbbhraahocbhlinaoahalfRbbgqce4cbaqceG9R7arfgr86bbaoadfhoaAalcefgl9hmbkaacefhaa8Fcefh8FahaAfhha8Ecefg8Ecl9hmbxikkaATmeaza8Afhaazcj;cbfhhcbhoceh8EaYh8FinaEaofhlaa8Vbbhrcbhoinala8FaofRbbcwtahaofRbbgqVc;:FiGce4cbaqceG9R7arfgr87bbaladfhlaLaocefgofmbka8FaQfh8FcdhoaacdfhaahaQfhha8EceGhlcbh8EalmbxdkkaATmbcbaocl49Rh8Eaza8AfRbbhqcwhoa3hlinalRbbaotaqVhqalcefhlaocwfgoca9hmbkcbhhaEh8FaYhainazcj;cbfahfRbbhrcwhoaahlinalRbbaotarVhralaAfhlaocwfgoca9hmbkara8E93aq7hqcbhoa8Fhlinalaqao486bbalcefhlaocwfgoca9hmbka8Fadfh8FaacefhaahcefghaA9hmbkkaEclfhEa3clfh3a8Aclfg8Aad6mbkaXazcjdfaAad2z:jjjjb8AazazcjdfaAcufad2fadz:jjjjb8AaAaOfhOaihxaimbkc9:hoxdkcbc99aPax9RakSEhoxekc9:hokavcj;kbf8Kjjjjbaok:XseHu8Jjjjjbc;ae9Rgv8Kjjjjbc9:hodnaeci9UgrcHfal0mbcuhoaiRbbgwc;WeGc;Ge9hmbawcsGgDce0mbavc;abfcFecjez:kjjjb8AavcUf9cu83ibavc8Wf9cu83ibavcyf9cu83ibavcaf9cu83ibavcKf9cu83ibavczf9cu83ibav9cu83iwav9cu83ibaialfc9WfhqaicefgwarfhldnaeTmbcmcsaDceSEhkcbhxcbhmcbhrcbhicbhoindnalaq9nmbc9:hoxikdndnawRbbgDc;Ve0mbavc;abfaoaDcu7gPcl4fcsGcitfgsydlhzasydbhHdndnaDcsGgsak9pmbavaiaPfcsGcdtfydbaxasEhDaxasTgOfhxxekdndnascsSmbcehOasc987asamffcefhDxekalcefhDal8SbbgscFeGhPdndnascu9mmbaDhlxekalcvfhlaPcFbGhPcrhsdninaD8SbbgOcFbGastaPVhPaOcu9kmeaDcefhDascrfgsc8J9hmbxdkkaDcefhlkcehOaPce4cbaPceG9R7amfhDkaDhmkavc;abfaocitfgsaDBdbasazBdlavaicdtfaDBdbavc;abfaocefcsGcitfgsaHBdbasaDBdlaocdfhoaOaifhidnadcd9hmbabarcetfgsaH87ebasclfaD87ebascdfaz87ebxdkabarcdtfgsaHBdbascwfaDBdbasclfazBdbxekdnaDcpe0mbaxcefgOavaiaqaDcsGfRbbgscl49RcsGcdtfydbascz6gPEhDavaias9RcsGcdtfydbaOaPfgzascsGgOEhsaOThOdndnadcd9hmbabarcetfgHax87ebaHclfas87ebaHcdfaD87ebxekabarcdtfgHaxBdbaHcwfasBdbaHclfaDBdbkavaicdtfaxBdbavc;abfaocitfgHaDBdbaHaxBdlavaicefgicsGcdtfaDBdbavc;abfaocefcsGcitfgHasBdbaHaDBdlavaiaPfgicsGcdtfasBdbavc;abfaocdfcsGcitfgDaxBdbaDasBdlaocifhoaiaOfhiazaOfhxxekaxcbalRbbgHEgAaDc;:eSgDfhzaHcsGhCaHcl4hXdndnaHcs0mbazcefhOxekazhOavaiaX9RcsGcdtfydbhzkdndnaCmbaOcefhxxekaOhxavaiaH9RcsGcdtfydbhOkdndnaDTmbalcefhDxekalcdfhDal8SbegPcFeGhsdnaPcu9kmbalcofhAascFbGhscrhldninaD8SbbgPcFbGaltasVhsaPcu9kmeaDcefhDalcrfglc8J9hmbkaAhDxekaDcefhDkasce4cbasceG9R7amfgmhAkdndnaXcsSmbaDhsxekaDcefhsaD8SbbglcFeGhPdnalcu9kmbaDcvfhzaPcFbGhPcrhldninas8SbbgDcFbGaltaPVhPaDcu9kmeascefhsalcrfglc8J9hmbkazhsxekascefhskaPce4cbaPceG9R7amfgmhzkdndnaCcsSmbashlxekascefhlas8SbbgDcFeGhPdnaDcu9kmbascvfhOaPcFbGhPcrhDdninal8SbbgscFbGaDtaPVhPascu9kmealcefhlaDcrfgDc8J9hmbkaOhlxekalcefhlkaPce4cbaPceG9R7amfgmhOkdndnadcd9hmbabarcetfgDaA87ebaDclfaO87ebaDcdfaz87ebxekabarcdtfgDaABdbaDcwfaOBdbaDclfazBdbkavc;abfaocitfgDazBdbaDaABdlavaicdtfaABdbavc;abfaocefcsGcitfgDaOBdbaDazBdlavaicefgicsGcdtfazBdbavc;abfaocdfcsGcitfgDaABdbaDaOBdlavaiaHcz6aXcsSVfgicsGcdtfaOBdbaiaCTaCcsSVfhiaocifhokawcefhwaocsGhoaicsGhiarcifgrae6mbkkcbc99alaqSEhokavc;aef8Kjjjjbaok:clevu8Jjjjjbcz9Rhvdnaecvfal9nmbc9:skdnaiRbbc;:eGc;qeSmbcuskav9cb83iwaicefhoaialfc98fhrdnaeTmbdnadcdSmbcbhwindnaoar6mbc9:skaocefhlao8SbbgicFeGhddndnaicu9mmbalhoxekaocvfhoadcFbGhdcrhidninal8SbbgDcFbGaitadVhdaDcu9kmealcefhlaicrfgic8J9hmbxdkkalcefhokabawcdtfadc8Etc8F91adcd47avcwfadceGcdtVglydbfgiBdbalaiBdbawcefgwae9hmbxdkkcbhwindnaoar6mbc9:skaocefhlao8SbbgicFeGhddndnaicu9mmbalhoxekaocvfhoadcFbGhdcrhidninal8SbbgDcFbGaitadVhdaDcu9kmealcefhlaicrfgic8J9hmbxdkkalcefhokabawcetfadc8Etc8F91adcd47avcwfadceGcdtVglydbfgi87ebalaiBdbawcefgwae9hmbkkcbc99aoarSEk:Lvoeue99dud99eud99dndnadcl9hmbaeTmeindndnabcdfgd8Sbb:Yab8Sbbgi:Ygl:l:tabcefgv8Sbbgo:Ygr:l:tgwJbb;:9cawawNJbbbbawawJbbbb9GgDEgq:mgkaqaicb9iEalMgwawNakaqaocb9iEarMgqaqNMM:r:vglNJbbbZJbbb:;aDEMgr:lJbbb9p9DTmbar:Ohixekcjjjj94hikadai86bbdndnaqalNJbbbZJbbb:;aqJbbbb9GEMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkavad86bbdndnawalNJbbbZJbbb:;awJbbbb9GEMgw:lJbbb9p9DTmbaw:Ohdxekcjjjj94hdkabad86bbabclfhbaecufgembxdkkaeTmbindndnabclfgd8Ueb:Yab8Uebgi:Ygl:l:tabcdfgv8Uebgo:Ygr:l:tgwJb;:FSawawNJbbbbawawJbbbb9GgDEgq:mgkaqaicb9iEalMgwawNakaqaocb9iEarMgqaqNMM:r:vglNJbbbZJbbb:;aDEMgr:lJbbb9p9DTmbar:Ohixekcjjjj94hikadai87ebdndnaqalNJbbbZJbbb:;aqJbbbb9GEMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkavad87ebdndnawalNJbbbZJbbb:;awJbbbb9GEMgw:lJbbb9p9DTmbaw:Ohdxekcjjjj94hdkabad87ebabcwfhbaecufgembkkk::ioiue99dud99dud99dnaeTmbcbhiabhlindndnal8Uebgv:YgoJ:ji:1Salcof8UebgrciVgw:Y:vgDNJbbbZJbbb:;avcu9kEMgq:lJbbb9p9DTmbaq:Ohkxekcjjjj94hkkalclf8Uebhvalcdf8UebhxabaiarcefciGfcetfak87ebdndnax:YgqaDNJbbbZJbbb:;axcu9kEMgm:lJbbb9p9DTmbam:Ohxxekcjjjj94hxkabaiarciGfgkcd7cetfax87ebdndnav:YgmaDNJbbbZJbbb:;avcu9kEMgP:lJbbb9p9DTmbaP:Ohvxekcjjjj94hvkabaiarcufciGfcetfav87ebdndnawaw2:ZgPaPMaoaoN:taqaqN:tamamN:tgoJbbbbaoJbbbb9GE:raDNJbbbZMgD:lJbbb9p9DTmbaD:Ohrxekcjjjj94hrkabakcetfar87ebalcwfhlaiclfhiaecufgembkkk9mbdnadcd4ae2gdTmbinababydbgecwtcw91:Yaece91cjjj98Gcjjj;8if::NUdbabclfhbadcufgdmbkkk:Tvirud99eudndnadcl9hmbaeTmeindndnabRbbgiabcefgl8Sbbgvabcdfgo8Sbbgrf9R:YJbbuJabcifgwRbbgdce4adVgDcd4aDVgDcl4aDVgD:Z:vgqNJbbbZMgk:lJbbb9p9DTmbak:Ohxxekcjjjj94hxkaoax86bbdndnaraif:YaqNJbbbZMgk:lJbbb9p9DTmbak:Ohoxekcjjjj94hokalao86bbdndnavaifar9R:YaqNJbbbZMgk:lJbbb9p9DTmbak:Ohixekcjjjj94hikabai86bbdndnaDadcetGadceGV:ZaqNJbbbZMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkawad86bbabclfhbaecufgembxdkkaeTmbindndnab8Vebgiabcdfgl8Uebgvabclfgo8Uebgrf9R:YJbFu9habcofgw8Vebgdce4adVgDcd4aDVgDcl4aDVgDcw4aDVgD:Z:vgqNJbbbZMgk:lJbbb9p9DTmbak:Ohxxekcjjjj94hxkaoax87ebdndnaraif:YaqNJbbbZMgk:lJbbb9p9DTmbak:Ohoxekcjjjj94hokalao87ebdndnavaifar9R:YaqNJbbbZMgk:lJbbb9p9DTmbak:Ohixekcjjjj94hikabai87ebdndnaDadcetGadceGV:ZaqNJbbbZMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkawad87ebabcwfhbaecufgembkkk9teiucbcbyd:K1jjbgeabcifc98GfgbBd:K1jjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaik;teeeudndnaeabVciGTmbabhixekdndnadcz9pmbabhixekabhiinaiaeydbBdbaiaeydlBdlaiaeydwBdwaiaeydxBdxaeczfheaiczfhiadc9Wfgdcs0mbkkadcl6mbinaiaeydbBdbaeclfheaiclfhiadc98fgdci0mbkkdnadTmbinaiaeRbb86bbaicefhiaecefheadcufgdmbkkabk:3eedudndnabciGTmbabhixekaecFeGc:b:c:ew2hldndnadcz9pmbabhixekabhiinaialBdxaialBdwaialBdlaialBdbaiczfhiadc9Wfgdcs0mbkkadcl6mbinaialBdbaiclfhiadc98fgdci0mbkkdnadTmbinaiae86bbaicefhiadcufgdmbkkabkk81dbcjwk8Kbbbbdbbblbbbwbbbbbbbebbbdbbblbbbwbbbbc:Kwkl8WNbb'; // embed! base\n\tvar wasm_simd =\n\t\t'b9H79TebbbeKl9Gbb9Gvuuuuueu9Giuuub9Geueuixkbbebeeddddilve9Weeeviebeoweuec:q:6dkr;Neqo9TW9T9VV95dbH9F9F939H79T9F9J9H229F9Jt9VV7bb8A9TW79O9V9Wt9F9KW9J9V9KW9wWVtW949c919M9MWVbdY9TW79O9V9Wt9F9KW9J9V9KW69U9KW949c919M9MWVblE9TW79O9V9Wt9F9KW9J9V9KW69U9KW949tWG91W9U9JWbvL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9p9JtboK9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9r919HtbrL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVT949WbwY9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVJ9V29VVbDl79IV9Rbqq:Ctklbzik9:evu8Jjjjjbcz9Rhbcbheincbhdcbhiinabcwfadfaicjuaead4ceGglE86bbaialfhiadcefgdcw9hmbkaec:q:yjjbfai86bbaecitc:q1jjbfab8Piw83ibaecefgecjd9hmbkk:183lYud97dur978Jjjjjbcj;kb9Rgv8Kjjjjbc9:hodnalTmbcuhoaiRbbgrc;WeGc:Ge9hmbarcsGgwce0mbc9:hoalcufadcd4cbawEgDadfgrcKcaawEgqaraq0Egk6mbaicefhxavaialfgmar9Rgoad;8qbbcj;abad9Uc;WFbGcjdadca0EhPdndndnadTmbaoadfhscbhzinaeaz9nmdamax9RaD6miabazad2fhHaxaDfhOaPaeaz9RazaPfae6EgAcsfgocl4cifcd4hCavcj;cbfaoc9WGgXcetfhQavcj;cbfaXci2fhLavcj;cbfaXfhKcbhYaoc;ab6h8AincbhodnawTmbaxaYcd4fRbbhokaocFeGhEcbh3avcj;cbfh5indndndndnaEa3cet4ciGgoc9:fPdebdkamaO9RaX6mwavcj;cbfa3aX2faOaX;8qbbaOaAfhOxdkavcj;cbfa3aX2fcbaX;8kbxekamaO9RaC6moaoclVcbawEhraOaCfhocbhidna8Ambamao9Rc;Gb6mbcbhlina5alfhidndndndndndnaOalco4fRbbgqciGarfPDbedibledibkaipxbbbbbbbbbbbbbbbbpklbxlkaiaopbblaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLg8Ecdp:mea8EpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9og8Fpxiiiiiiiiiiiiiiiip8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaoclffahc:q:yjjbfRbbfhoxikaiaopbbwaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9og8Fpxssssssssssssssssp8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaocwffahc:q:yjjbfRbbfhoxdkaiaopbbbpklbaoczfhoxekaiaopbbdaoRbbgacitc:q1jjbfpbibaac:q:yjjbfRbbgapsaoRbeghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPpklbaaaocdffahc:q:yjjbfRbbfhokdndndndndndnaqcd4ciGarfPDbedibledibkaiczfpxbbbbbbbbbbbbbbbbpklbxlkaiczfaopbblaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLg8Ecdp:mea8EpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9og8Fpxiiiiiiiiiiiiiiiip8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaoclffahc:q:yjjbfRbbfhoxikaiczfaopbbwaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9og8Fpxssssssssssssssssp8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaocwffahc:q:yjjbfRbbfhoxdkaiczfaopbbbpklbaoczfhoxekaiczfaopbbdaoRbbgacitc:q1jjbfpbibaac:q:yjjbfRbbgapsaoRbeghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPpklbaaaocdffahc:q:yjjbfRbbfhokdndndndndndnaqcl4ciGarfPDbedibledibkaicafpxbbbbbbbbbbbbbbbbpklbxlkaicafaopbblaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLg8Ecdp:mea8EpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9og8Fpxiiiiiiiiiiiiiiiip8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaoclffahc:q:yjjbfRbbfhoxikaicafaopbbwaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9og8Fpxssssssssssssssssp8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaocwffahc:q:yjjbfRbbfhoxdkaicafaopbbbpklbaoczfhoxekaicafaopbbdaoRbbgacitc:q1jjbfpbibaac:q:yjjbfRbbgapsaoRbeghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPpklbaaaocdffahc:q:yjjbfRbbfhokdndndndndndnaqco4arfPDbedibledibkaic8Wfpxbbbbbbbbbbbbbbbbpklbxlkaic8Wfaopbblaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLg8Ecdp:mea8EpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9og8Fpxiiiiiiiiiiiiiiiip8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngicitc:q1jjbfpbibaic:q:yjjbfRbbgipsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Ngqcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaiaoclffaqc:q:yjjbfRbbfhoxikaic8Wfaopbbwaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9og8Fpxssssssssssssssssp8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngicitc:q1jjbfpbibaic:q:yjjbfRbbgipsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Ngqcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaiaocwffaqc:q:yjjbfRbbfhoxdkaic8Wfaopbbbpklbaoczfhoxekaic8WfaopbbdaoRbbgicitc:q1jjbfpbibaic:q:yjjbfRbbgipsaoRbegqcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPpklbaiaocdffaqc:q:yjjbfRbbfhokalc;abfhialcjefaX0meaihlamao9Rc;Fb0mbkkdnaiaX9pmbaici4hlinamao9RcK6mwa5aifhqdndndndndndnaOaico4fRbbalcoG4ciGarfPDbedibledibkaqpxbbbbbbbbbbbbbbbbpkbbxlkaqaopbblaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLg8Ecdp:mea8EpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9og8Fpxiiiiiiiiiiiiiiiip8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spkbbaaaoclffahc:q:yjjbfRbbfhoxikaqaopbbwaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9og8Fpxssssssssssssssssp8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spkbbaaaocwffahc:q:yjjbfRbbfhoxdkaqaopbbbpkbbaoczfhoxekaqaopbbdaoRbbgacitc:q1jjbfpbibaac:q:yjjbfRbbgapsaoRbeghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPpkbbaaaocdffahc:q:yjjbfRbbfhokalcdfhlaiczfgiaX6mbkkaohOaoTmoka5aXfh5a3cefg3cl9hmbkdndndndnawTmbasaYcd4fRbbglciGPlbedwbkaXTmdavcjdfaYfhlavaYfpbdbhgcbhoinalavcj;cbfaofpblbg8JaKaofpblbg8KpmbzeHdOiAlCvXoQrLg8LaQaofpblbg8MaLaofpblbg8NpmbzeHdOiAlCvXoQrLgypmbezHdiOAlvCXorQLg8Ecep9Ta8Epxeeeeeeeeeeeeeeeeg8Fp9op9Hp9rg8Eagp9Uggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp9Uggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp9Uggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp9Uggp9Abbbaladfglaga8LaypmwDKYqk8AExm35Ps8E8Fg8Ecep9Ta8Ea8Fp9op9Hp9rg8Ep9Uggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp9Uggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp9Uggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp9Uggp9Abbbaladfglaga8Ja8KpmwKDYq8AkEx3m5P8Es8Fg8Ja8Ma8NpmwKDYq8AkEx3m5P8Es8Fg8KpmbezHdiOAlvCXorQLg8Ecep9Ta8Ea8Fp9op9Hp9rg8Ep9Uggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp9Uggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp9Uggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp9Uggp9Abbbaladfglaga8Ja8KpmwDKYqk8AExm35Ps8E8Fg8Ecep9Ta8Ea8Fp9op9Hp9rg8Ep9Ug8Fp9Abbbaladfgla8Fa8Ea8Epmlvorlvorlvorlvorp9Ug8Fp9Abbbaladfgla8Fa8Ea8EpmwDqkwDqkwDqkwDqkp9Ug8Fp9Abbbaladfgla8Fa8Ea8EpmxmPsxmPsxmPsxmPsp9Uggp9AbbbaladfhlaoczfgoaX6mbxikkaXTmeavcjdfaYfhlavaYfpbdbhgcbhoinalavcj;cbfaofpblbg8JaKaofpblbg8KpmbzeHdOiAlCvXoQrLg8LaQaofpblbg8MaLaofpblbg8NpmbzeHdOiAlCvXoQrLgypmbezHdiOAlvCXorQLg8Ecep:nea8Epxebebebebebebebebg8Fp9op:bep9rg8Eagp:oeggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp:oeggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp:oeggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp:oeggp9Abbbaladfglaga8LaypmwDKYqk8AExm35Ps8E8Fg8Ecep:nea8Ea8Fp9op:bep9rg8Ep:oeggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp:oeggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp:oeggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp:oeggp9Abbbaladfglaga8Ja8KpmwKDYq8AkEx3m5P8Es8Fg8Ja8Ma8NpmwKDYq8AkEx3m5P8Es8Fg8KpmbezHdiOAlvCXorQLg8Ecep:nea8Ea8Fp9op:bep9rg8Ep:oeggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp:oeggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp:oeggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp:oeggp9Abbbaladfglaga8Ja8KpmwDKYqk8AExm35Ps8E8Fg8Ecep:nea8Ea8Fp9op:bep9rg8Ep:oeg8Fp9Abbbaladfgla8Fa8Ea8Epmlvorlvorlvorlvorp:oeg8Fp9Abbbaladfgla8Fa8Ea8EpmwDqkwDqkwDqkwDqkp:oeg8Fp9Abbbaladfgla8Fa8Ea8EpmxmPsxmPsxmPsxmPsp:oeggp9AbbbaladfhlaoczfgoaX6mbxdkkaXTmbcbhocbalcl4gl9Rc8FGhiavcjdfaYfhravaYfpbdbh8Finaravcj;cbfaofpblbggaKaofpblbg8JpmbzeHdOiAlCvXoQrLg8KaQaofpblbg8LaLaofpblbg8MpmbzeHdOiAlCvXoQrLg8NpmbezHdiOAlvCXorQLg8Eaip:Rea8Ealp:Sep9qg8Ea8Fp9rg8Fp9Abbbaradfgra8Fa8Ea8Epmlvorlvorlvorlvorp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmwDqkwDqkwDqkwDqkp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmxmPsxmPsxmPsxmPsp9rg8Fp9Abbbaradfgra8Fa8Ka8NpmwDKYqk8AExm35Ps8E8Fg8Eaip:Rea8Ealp:Sep9qg8Ep9rg8Fp9Abbbaradfgra8Fa8Ea8Epmlvorlvorlvorlvorp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmwDqkwDqkwDqkwDqkp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmxmPsxmPsxmPsxmPsp9rg8Fp9Abbbaradfgra8Faga8JpmwKDYq8AkEx3m5P8Es8Fgga8La8MpmwKDYq8AkEx3m5P8Es8Fg8JpmbezHdiOAlvCXorQLg8Eaip:Rea8Ealp:Sep9qg8Ep9rg8Fp9Abbbaradfgra8Fa8Ea8Epmlvorlvorlvorlvorp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmwDqkwDqkwDqkwDqkp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmxmPsxmPsxmPsxmPsp9rg8Fp9Abbbaradfgra8Faga8JpmwDKYqk8AExm35Ps8E8Fg8Eaip:Rea8Ealp:Sep9qg8Ep9rg8Fp9Abbbaradfgra8Fa8Ea8Epmlvorlvorlvorlvorp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmwDqkwDqkwDqkwDqkp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmxmPsxmPsxmPsxmPsp9rg8Fp9AbbbaradfhraoczfgoaX6mbkkaYclfgYad6mbkaHavcjdfaAad2;8qbbavavcjdfaAcufad2fad;8qbbaAazfhzc9:hoaOhxaOmbxlkkaeTmbaDalfhrcbhocuhlinaralaD9RglfaD6mdaPaeao9RaoaPfae6Eaofgoae6mbkaial9Rhxkcbc99amax9RakSEhoxekc9:hokavcj;kbf8Kjjjjbaokwbz:bjjjbk:TseHu8Jjjjjbc;ae9Rgv8Kjjjjbc9:hodnaeci9UgrcHfal0mbcuhoaiRbbgwc;WeGc;Ge9hmbawcsGgDce0mbavc;abfcFecje;8kbavcUf9cu83ibavc8Wf9cu83ibavcyf9cu83ibavcaf9cu83ibavcKf9cu83ibavczf9cu83ibav9cu83iwav9cu83ibaialfc9WfhqaicefgwarfhldnaeTmbcmcsaDceSEhkcbhxcbhmcbhrcbhicbhoindnalaq9nmbc9:hoxikdndnawRbbgDc;Ve0mbavc;abfaoaDcu7gPcl4fcsGcitfgsydlhzasydbhHdndnaDcsGgsak9pmbavaiaPfcsGcdtfydbaxasEhDaxasTgOfhxxekdndnascsSmbcehOasc987asamffcefhDxekalcefhDal8SbbgscFeGhPdndnascu9mmbaDhlxekalcvfhlaPcFbGhPcrhsdninaD8SbbgOcFbGastaPVhPaOcu9kmeaDcefhDascrfgsc8J9hmbxdkkaDcefhlkcehOaPce4cbaPceG9R7amfhDkaDhmkavc;abfaocitfgsaDBdbasazBdlavaicdtfaDBdbavc;abfaocefcsGcitfgsaHBdbasaDBdlaocdfhoaOaifhidnadcd9hmbabarcetfgsaH87ebasclfaD87ebascdfaz87ebxdkabarcdtfgsaHBdbascwfaDBdbasclfazBdbxekdnaDcpe0mbaxcefgOavaiaqaDcsGfRbbgscl49RcsGcdtfydbascz6gPEhDavaias9RcsGcdtfydbaOaPfgzascsGgOEhsaOThOdndnadcd9hmbabarcetfgHax87ebaHclfas87ebaHcdfaD87ebxekabarcdtfgHaxBdbaHcwfasBdbaHclfaDBdbkavaicdtfaxBdbavc;abfaocitfgHaDBdbaHaxBdlavaicefgicsGcdtfaDBdbavc;abfaocefcsGcitfgHasBdbaHaDBdlavaiaPfgicsGcdtfasBdbavc;abfaocdfcsGcitfgDaxBdbaDasBdlaocifhoaiaOfhiazaOfhxxekaxcbalRbbgHEgAaDc;:eSgDfhzaHcsGhCaHcl4hXdndnaHcs0mbazcefhOxekazhOavaiaX9RcsGcdtfydbhzkdndnaCmbaOcefhxxekaOhxavaiaH9RcsGcdtfydbhOkdndnaDTmbalcefhDxekalcdfhDal8SbegPcFeGhsdnaPcu9kmbalcofhAascFbGhscrhldninaD8SbbgPcFbGaltasVhsaPcu9kmeaDcefhDalcrfglc8J9hmbkaAhDxekaDcefhDkasce4cbasceG9R7amfgmhAkdndnaXcsSmbaDhsxekaDcefhsaD8SbbglcFeGhPdnalcu9kmbaDcvfhzaPcFbGhPcrhldninas8SbbgDcFbGaltaPVhPaDcu9kmeascefhsalcrfglc8J9hmbkazhsxekascefhskaPce4cbaPceG9R7amfgmhzkdndnaCcsSmbashlxekascefhlas8SbbgDcFeGhPdnaDcu9kmbascvfhOaPcFbGhPcrhDdninal8SbbgscFbGaDtaPVhPascu9kmealcefhlaDcrfgDc8J9hmbkaOhlxekalcefhlkaPce4cbaPceG9R7amfgmhOkdndnadcd9hmbabarcetfgDaA87ebaDclfaO87ebaDcdfaz87ebxekabarcdtfgDaABdbaDcwfaOBdbaDclfazBdbkavc;abfaocitfgDazBdbaDaABdlavaicdtfaABdbavc;abfaocefcsGcitfgDaOBdbaDazBdlavaicefgicsGcdtfazBdbavc;abfaocdfcsGcitfgDaABdbaDaOBdlavaiaHcz6aXcsSVfgicsGcdtfaOBdbaiaCTaCcsSVfhiaocifhokawcefhwaocsGhoaicsGhiarcifgrae6mbkkcbc99alaqSEhokavc;aef8Kjjjjbaok:clevu8Jjjjjbcz9Rhvdnaecvfal9nmbc9:skdnaiRbbc;:eGc;qeSmbcuskav9cb83iwaicefhoaialfc98fhrdnaeTmbdnadcdSmbcbhwindnaoar6mbc9:skaocefhlao8SbbgicFeGhddndnaicu9mmbalhoxekaocvfhoadcFbGhdcrhidninal8SbbgDcFbGaitadVhdaDcu9kmealcefhlaicrfgic8J9hmbxdkkalcefhokabawcdtfadc8Etc8F91adcd47avcwfadceGcdtVglydbfgiBdbalaiBdbawcefgwae9hmbxdkkcbhwindnaoar6mbc9:skaocefhlao8SbbgicFeGhddndnaicu9mmbalhoxekaocvfhoadcFbGhdcrhidninal8SbbgDcFbGaitadVhdaDcu9kmealcefhlaicrfgic8J9hmbxdkkalcefhokabawcetfadc8Etc8F91adcd47avcwfadceGcdtVglydbfgi87ebalaiBdbawcefgwae9hmbkkcbc99aoarSEk:2Pliur97eue978Jjjjjbc8W9Rhiaec98Ghldndnadcl9hmbdnalTmbcbhvabhdinadadpbbbgocKp:RecKp:Sep;6egraocwp:RecKp:Sep;6earp;Geaoczp:RecKp:Sep;6egwp;Gep;Kep;LegDpxbbbbbbbbbbbbbbbbp:2egqarpxbbbjbbbjbbbjbbbjgkp9op9rp;Kegrpxbb;:9cbb;:9cbb;:9cbb;:9cararp;MeaDaDp;Meawaqawakp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFbbbFbbbFbbbFbbbp9oaopxbbbFbbbFbbbFbbbFp9op9qarawp;Meaqp;Kecwp:RepxbFbbbFbbbFbbbFbbp9op9qaDawp;Meaqp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qpkbbadczfhdavclfgval6mbkkalaeSmeaipxbbbbbbbbbbbbbbbbgqpklbaiabalcdtfgdaeciGglcdtgv;8qbbdnalTmbaiaipblbgocKp:RecKp:Sep;6egraocwp:RecKp:Sep;6earp;Geaoczp:RecKp:Sep;6egwp;Gep;Kep;LegDaqp:2egqarpxbbbjbbbjbbbjbbbjgkp9op9rp;Kegrpxbb;:9cbb;:9cbb;:9cbb;:9cararp;MeaDaDp;Meawaqawakp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFbbbFbbbFbbbFbbbp9oaopxbbbFbbbFbbbFbbbFp9op9qarawp;Meaqp;Kecwp:RepxbFbbbFbbbFbbbFbbp9op9qaDawp;Meaqp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qpklbkadaiav;8qbbskaipxFubbFubbFubbFubbgxpklbdnalTmbcbhvabhdinadczfgmampbbbgopxbbbbbbFFbbbbbbFFgkp9oadpbbbgDaopmbediwDqkzHOAKY8AEgwczp:Reczp:Sep;6egraipblbaDaopmlvorxmPsCXQL358E8Fp9op;6eawczp:Sep;6egwp;Gearp;Gep;Kep;Legopxbbbbbbbbbbbbbbbbp:2egqarpxbbbjbbbjbbbjbbbjgPp9op9rp;Kegrpxb;:FSb;:FSb;:FSb;:FSararp;Meaoaop;MeawaqawaPp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFFbbFFbbFFbbFFbbp9oaoawp;Meaqp;Keczp:Rep9qgoarawp;Meaqp;KepxFFbbFFbbFFbbFFbbp9ogrpmwDKYqk8AExm35Ps8E8Fp9qpkbbadaDakp9oaoarpmbezHdiOAlvCXorQLp9qpkbbadcafhdavclfgval6mbkkalaeSmbaiczfpxbbbbbbbbbbbbbbbbgopklbaiaopklbaiabalcitfgdaeciGglcitgv;8qbbaiaxpkladnalTmbaiaipblzgopxbbbbbbFFbbbbbbFFgkp9oaipblbgDaopmbediwDqkzHOAKY8AEgwczp:Reczp:Sep;6egraipblaaDaopmlvorxmPsCXQL358E8Fp9op;6eawczp:Sep;6egwp;Gearp;Gep;Kep;Legopxbbbbbbbbbbbbbbbbp:2egqarpxbbbjbbbjbbbjbbbjgPp9op9rp;Kegrpxb;:FSb;:FSb;:FSb;:FSararp;Meaoaop;MeawaqawaPp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFFbbFFbbFFbbFFbbp9oaoawp;Meaqp;Keczp:Rep9qgoarawp;Meaqp;KepxFFbbFFbbFFbbFFbbp9ogrpmwDKYqk8AExm35Ps8E8Fp9qpklzaiaDakp9oaoarpmbezHdiOAlvCXorQLp9qpklbkadaiav;8qbbkk:Iwllue97euo978Jjjjjbca9Rhidnaec98GglTmbcbhvabhoinaocKfpx:ji:1S:ji:1S:ji:1S:ji:1SaopbbbgraoczfgwpbbbgDpmlvorxmPsCXQL358E8Fgqczp:Segkpxibbbibbbibbbibbbp9qgxp;6ep;Negmaxaxp:1ep;7egxaxp;KearaDpmbediwDqkzHOAKY8AEgxczp:Reczp:Sep;6egrarp;Meaxczp:Sep;6egDaDp;Meaqczp:Reczp:Sep;6egqaqp;Mep;Kep;Kep;Lepxbbbbbbbbbbbbbbbbp:4ep;Jep;Mepxbbn0bbn0bbn0bbn0gxp;KepxFFbbFFbbFFbbFFbbgPp9oamaDp;Meaxp;Keczp:Rep9qgDamarp;Meaxp;KeaPp9oamaqp;Meaxp;Keczp:Rep9qgxpmwDKYqk8AExm35Ps8E8Fgrp5eakclp:RegmpEi:T:j83ibawarp5bampEd:T:j83ibaocwfaDaxpmbezHdiOAlvCXorQLgxp5eampEe:T:j83ibaoaxp5bampEb:T:j83ibaocafhoavclfgval6mbkkdnalaeSmbaiczfpxbbbbbbbbbbbbbbbbgmpklbaiampklbaiabalcitfgoaeciGgvcitgw;8qbbdnavTmbaipx:ji:1S:ji:1S:ji:1S:ji:1SaipblbgraipblzgDpmlvorxmPsCXQL358E8Fgqczp:Segkpxibbbibbbibbbibbbp9qgxp;6ep;Negmaxaxp:1ep;7egxaxp;KearaDpmbediwDqkzHOAKY8AEgxczp:Reczp:Sep;6egrarp;Meaxczp:Sep;6egDaDp;Meaqczp:Reczp:Sep;6egqaqp;Mep;Kep;Kep;Lepxbbbbbbbbbbbbbbbbp:4ep;Jep;Mepxbbn0bbn0bbn0bbn0gxp;KepxFFbbFFbbFFbbFFbbgPp9oamaDp;Meaxp;Keczp:Rep9qgDamarp;Meaxp;KeaPp9oamaqp;Meaxp;Keczp:Rep9qgxpmwDKYqk8AExm35Ps8E8Fgrp5eakclp:RegmpEi:T:j83iKaiarp5bampEd:T:j83izaiaDaxpmbezHdiOAlvCXorQLgxp5eampEe:T:j83iwaiaxp5bampEb:T:j83ibkaoaiaw;8qbbkk;uddiue978Jjjjjbc;ab9Rhidnadcd4ae2glc98GgvTmbcbheabhdinadadpbbbgocwp:Recwp:Sep;6eaocep:SepxbbjFbbjFbbjFbbjFp9opxbbjZbbjZbbjZbbjZp:Uep;Mepkbbadczfhdaeclfgeav6mbkkdnavalSmbaic8WfpxbbbbbbbbbbbbbbbbgopklbaicafaopklbaiczfaopklbaiaopklbaiabavcdtfgdalciGgecdtgv;8qbbdnaeTmbaiaipblbgocwp:Recwp:Sep;6eaocep:SepxbbjFbbjFbbjFbbjFp9opxbbjZbbjZbbjZbbjZp:Uep;Mepklbkadaiav;8qbbkk:CPvdue97euw97eu8Jjjjjbc8W9Rhiaec98Ghldndnadcl9hmbaipxbbbbbbbbbbbbbbbbgvpklbdnalTmbcbhoabhdinadpbbbhradpxbbuJbbuJbbuJbbuJaipblbarcKp:Tep9qgwcep:Seawp9qgDcdp:SeaDp9qgDclp:SeaDp9qgqp;6ep;NegDarcwp:RecKp:SegkarpxFbbbFbbbFbbbFbbbgxp9ogmp:Uep;6ep;Mepxbbn0bbn0bbn0bbn0gPp;Kecwp:RepxbFbbbFbbbFbbbFbbp9oaDamakp:Xearczp:RecKp:Segrp:Uep;6ep;MeaPp;Keaxp9op9qaDamakarp:Uep:Xep;6ep;MeaPp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qaDaqawcep:Rep9oawpxebbbebbbebbbebbbp9op9qp;6ep;MeaPp;KecKp:Rep9qpkbbadczfhdaoclfgoal6mbkkalaeSmeaiavpklaaicafabalcdtfgdaeciGglcdtgo;8qbbaiavpklbdnalTmbaipblahraipxbbuJbbuJbbuJbbuJaipblbarcKp:Tep9qgwcep:Seawp9qgDcdp:SeaDp9qgDclp:SeaDp9qgqp;6ep;NegDarcwp:RecKp:SegkarpxFbbbFbbbFbbbFbbbgxp9ogmp:Uep;6ep;Mepxbbn0bbn0bbn0bbn0gPp;Kecwp:RepxbFbbbFbbbFbbbFbbp9oaDamakp:Xearczp:RecKp:Segrp:Uep;6ep;MeaPp;Keaxp9op9qaDamakarp:Uep:Xep;6ep;MeaPp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qaDaqawcep:Rep9oawpxebbbebbbebbbebbbp9op9qp;6ep;MeaPp;KecKp:Rep9qpklakadaicafao;8qbbskaipxbbbbbbbbbbbbbbbbgvpklbdnalTmbcbhoabhdinadczfgspxbFu9hbFu9hbFu9hbFu9hadpbbbgDaspbbbgPpmlvorxmPsCXQL358E8Fgmczp:Teaipblbp9qgrcep:Searp9qgwcdp:Seawp9qgwclp:Seawp9qgwcwp:Seawp9qgqp;6ep;NegwaDaPpmbediwDqkzHOAKY8AEgDpxFFbbFFbbFFbbFFbbgPp9ogkaDczp:Segxp:Ueamczp:Reczp:Segmp:Xep;6ep;Mepxbbn0bbn0bbn0bbn0gDp;KeaPp9oawakaxamp:Uep:Xep;6ep;MeaDp;Keczp:Rep9qgxawaqarcep:Rep9oarpxebbbebbbebbbebbbp9op9qp;6ep;MeaDp;Keczp:Reawamakp:Uep;6ep;MeaDp;KeaPp9op9qgrpmwDKYqk8AExm35Ps8E8FpkbbadaxarpmbezHdiOAlvCXorQLpkbbadcafhdaoclfgoal6mbkkalaeSmbaiczfpxbbbbbbbbbbbbbbbbgrpklbaiarpklbaiabalcitfgdaeciGglcitgo;8qbbaiavpkladnalTmbaipxbFu9hbFu9hbFu9hbFu9haipblbgDaipblzgPpmlvorxmPsCXQL358E8Fgmczp:Teaipblap9qgrcep:Searp9qgwcdp:Seawp9qgwclp:Seawp9qgwcwp:Seawp9qgqp;6ep;NegwaDaPpmbediwDqkzHOAKY8AEgDpxFFbbFFbbFFbbFFbbgPp9ogkaDczp:Segxp:Ueamczp:Reczp:Segmp:Xep;6ep;Mepxbbn0bbn0bbn0bbn0gDp;KeaPp9oawakaxamp:Uep:Xep;6ep;MeaDp;Keczp:Rep9qgxawaqarcep:Rep9oarpxebbbebbbebbbebbbp9op9qp;6ep;MeaDp;Keczp:Reawamakp:Uep;6ep;MeaDp;KeaPp9op9qgrpmwDKYqk8AExm35Ps8E8FpklzaiaxarpmbezHdiOAlvCXorQLpklbkadaiao;8qbbkk9teiucbcbydj1jjbgeabcifc98GfgbBdj1jjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaikkkebcjwklz:Dbb'; // embed! simd\n\n\tvar detector = new Uint8Array([\n\t\t0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 3, 2, 0, 0, 5, 3, 1, 0, 1, 12, 1, 0, 10, 22, 2, 12, 0, 65, 0, 65, 0, 65, 0, 252, 10, 0, 0,\n\t\t11, 7, 0, 65, 0, 253, 15, 26, 11,\n\t]);\n\tvar wasmpack = new Uint8Array([\n\t\t32, 0, 65, 2, 1, 106, 34, 33, 3, 128, 11, 4, 13, 64, 6, 253, 10, 7, 15, 116, 127, 5, 8, 12, 40, 16, 19, 54, 20, 9, 27, 255, 113, 17, 42, 67,\n\t\t24, 23, 146, 148, 18, 14, 22, 45, 70, 69, 56, 114, 101, 21, 25, 63, 75, 136, 108, 28, 118, 29, 73, 115,\n\t]);\n\n\tif (typeof WebAssembly !== 'object') {\n\t\treturn {\n\t\t\tsupported: false,\n\t\t};\n\t}\n\n\tvar wasm = WebAssembly.validate(detector) ? unpack(wasm_simd) : unpack(wasm_base);\n\n\tvar instance;\n\n\tvar ready = WebAssembly.instantiate(wasm, {}).then(function (result) {\n\t\tinstance = result.instance;\n\t\tinstance.exports.__wasm_call_ctors();\n\t});\n\n\tfunction unpack(data) {\n\t\tvar result = new Uint8Array(data.length);\n\t\tfor (var i = 0; i < data.length; ++i) {\n\t\t\tvar ch = data.charCodeAt(i);\n\t\t\tresult[i] = ch > 96 ? ch - 97 : ch > 64 ? ch - 39 : ch + 4;\n\t\t}\n\t\tvar write = 0;\n\t\tfor (var i = 0; i < data.length; ++i) {\n\t\t\tresult[write++] = result[i] < 60 ? wasmpack[result[i]] : (result[i] - 60) * 64 + result[++i];\n\t\t}\n\t\treturn result.buffer.slice(0, write);\n\t}\n\n\tfunction decode(instance, fun, target, count, size, source, filter) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar count4 = (count + 3) & ~3;\n\t\tvar tp = sbrk(count4 * size);\n\t\tvar sp = sbrk(source.length);\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(source, sp);\n\t\tvar res = fun(tp, count, size, sp, source.length);\n\t\tif (res == 0 && filter) {\n\t\t\tfilter(tp, count4, size);\n\t\t}\n\t\ttarget.set(heap.subarray(tp, tp + count * size));\n\t\tsbrk(tp - sbrk(0));\n\t\tif (res != 0) {\n\t\t\tthrow new Error('Malformed buffer data: ' + res);\n\t\t}\n\t}\n\n\tvar filters = {\n\t\tNONE: '',\n\t\tOCTAHEDRAL: 'meshopt_decodeFilterOct',\n\t\tQUATERNION: 'meshopt_decodeFilterQuat',\n\t\tEXPONENTIAL: 'meshopt_decodeFilterExp',\n\t\tCOLOR: 'meshopt_decodeFilterColor',\n\t};\n\n\tvar decoders = {\n\t\tATTRIBUTES: 'meshopt_decodeVertexBuffer',\n\t\tTRIANGLES: 'meshopt_decodeIndexBuffer',\n\t\tINDICES: 'meshopt_decodeIndexSequence',\n\t};\n\n\tvar workers = [];\n\tvar requestId = 0;\n\n\tfunction createWorker(url) {\n\t\tvar worker = {\n\t\t\tobject: new Worker(url),\n\t\t\tpending: 0,\n\t\t\trequests: {},\n\t\t};\n\n\t\tworker.object.onmessage = function (event) {\n\t\t\tvar data = event.data;\n\n\t\t\tworker.pending -= data.count;\n\t\t\tworker.requests[data.id][data.action](data.value);\n\t\t\tdelete worker.requests[data.id];\n\t\t};\n\n\t\treturn worker;\n\t}\n\n\tfunction initWorkers(count) {\n\t\tvar source =\n\t\t\t'self.ready = WebAssembly.instantiate(new Uint8Array([' +\n\t\t\tnew Uint8Array(wasm) +\n\t\t\t']), {})' +\n\t\t\t'.then(function(result) { result.instance.exports.__wasm_call_ctors(); return result.instance; });' +\n\t\t\t'self.onmessage = ' +\n\t\t\tworkerProcess.name +\n\t\t\t';' +\n\t\t\tdecode.toString() +\n\t\t\tworkerProcess.toString();\n\n\t\tvar blob = new Blob([source], { type: 'text/javascript' });\n\t\tvar url = URL.createObjectURL(blob);\n\n\t\tfor (var i = workers.length; i < count; ++i) {\n\t\t\tworkers[i] = createWorker(url);\n\t\t}\n\n\t\tfor (var i = count; i < workers.length; ++i) {\n\t\t\tworkers[i].object.postMessage({});\n\t\t}\n\n\t\tworkers.length = count;\n\n\t\tURL.revokeObjectURL(url);\n\t}\n\n\tfunction decodeWorker(count, size, source, mode, filter) {\n\t\tvar worker = workers[0];\n\n\t\tfor (var i = 1; i < workers.length; ++i) {\n\t\t\tif (workers[i].pending < worker.pending) {\n\t\t\t\tworker = workers[i];\n\t\t\t}\n\t\t}\n\n\t\treturn new Promise(function (resolve, reject) {\n\t\t\tvar data = new Uint8Array(source);\n\t\t\tvar id = ++requestId;\n\n\t\t\tworker.pending += count;\n\t\t\tworker.requests[id] = { resolve: resolve, reject: reject };\n\t\t\tworker.object.postMessage({ id: id, count: count, size: size, source: data, mode: mode, filter: filter }, [data.buffer]);\n\t\t});\n\t}\n\n\tfunction workerProcess(event) {\n\t\tvar data = event.data;\n\t\tif (!data.id) {\n\t\t\treturn self.close();\n\t\t}\n\t\tself.ready.then(function (instance) {\n\t\t\ttry {\n\t\t\t\tvar target = new Uint8Array(data.count * data.size);\n\t\t\t\tdecode(instance, instance.exports[data.mode], target, data.count, data.size, data.source, instance.exports[data.filter]);\n\t\t\t\tself.postMessage({ id: data.id, count: data.count, action: 'resolve', value: target }, [target.buffer]);\n\t\t\t} catch (error) {\n\t\t\t\tself.postMessage({ id: data.id, count: data.count, action: 'reject', value: error });\n\t\t\t}\n\t\t});\n\t}\n\n\treturn {\n\t\tready: ready,\n\t\tsupported: true,\n\t\tuseWorkers: function (count) {\n\t\t\tinitWorkers(count);\n\t\t},\n\t\tdecodeVertexBuffer: function (target, count, size, source, filter) {\n\t\t\tdecode(instance, instance.exports.meshopt_decodeVertexBuffer, target, count, size, source, instance.exports[filters[filter]]);\n\t\t},\n\t\tdecodeIndexBuffer: function (target, count, size, source) {\n\t\t\tdecode(instance, instance.exports.meshopt_decodeIndexBuffer, target, count, size, source);\n\t\t},\n\t\tdecodeIndexSequence: function (target, count, size, source) {\n\t\t\tdecode(instance, instance.exports.meshopt_decodeIndexSequence, target, count, size, source);\n\t\t},\n\t\tdecodeGltfBuffer: function (target, count, size, source, mode, filter) {\n\t\t\tdecode(instance, instance.exports[decoders[mode]], target, count, size, source, instance.exports[filters[filter]]);\n\t\t},\n\t\tdecodeGltfBufferAsync: function (count, size, source, mode, filter) {\n\t\t\tif (workers.length > 0) {\n\t\t\t\treturn decodeWorker(count, size, source, decoders[mode], filters[filter]);\n\t\t\t}\n\n\t\t\treturn ready.then(function () {\n\t\t\t\tvar target = new Uint8Array(count * size);\n\t\t\t\tdecode(instance, instance.exports[decoders[mode]], target, count, size, source, instance.exports[filters[filter]]);\n\t\t\t\treturn target;\n\t\t\t});\n\t\t},\n\t};\n})();\n\nif (typeof exports === 'object' && typeof module === 'object') module.exports = MeshoptDecoder;\nelse if (typeof define === 'function' && define['amd']) define([], function () { return MeshoptDecoder; });\nelse if (typeof exports === 'object') exports['MeshoptDecoder'] = MeshoptDecoder;\nelse (typeof self !== 'undefined' ? self : this).MeshoptDecoder = MeshoptDecoder;\n"
  },
  {
    "path": "js/meshopt_decoder.d.ts",
    "content": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\nexport const MeshoptDecoder: {\n\tsupported: boolean;\n\tready: Promise<void>;\n\n\tdecodeVertexBuffer: (target: Uint8Array, count: number, size: number, source: Uint8Array, filter?: string) => void;\n\tdecodeIndexBuffer: (target: Uint8Array, count: number, size: number, source: Uint8Array) => void;\n\tdecodeIndexSequence: (target: Uint8Array, count: number, size: number, source: Uint8Array) => void;\n\n\tdecodeGltfBuffer: (target: Uint8Array, count: number, size: number, source: Uint8Array, mode: string, filter?: string) => void;\n\n\tuseWorkers: (count: number) => void;\n\tdecodeGltfBufferAsync: (count: number, size: number, source: Uint8Array, mode: string, filter?: string) => Promise<Uint8Array>;\n};\n"
  },
  {
    "path": "js/meshopt_decoder.mjs",
    "content": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\nvar MeshoptDecoder = (function () {\n\t// Built with clang version 19.1.5-wasi-sdk\n\t// Built from meshoptimizer 1.0\n\tvar wasm_base =\n\t\t'b9H79Tebbbe8Fv9Gbb9Gvuuuuueu9Giuuub9Geueu9Giuuueuixkbeeeddddillviebeoweuec:W:Odkr;Neqo9TW9T9VV95dbH9F9F939H79T9F9J9H229F9Jt9VV7bb8A9TW79O9V9Wt9F9KW9J9V9KW9wWVtW949c919M9MWVbeY9TW79O9V9Wt9F9KW9J9V9KW69U9KW949c919M9MWVbdE9TW79O9V9Wt9F9KW9J9V9KW69U9KW949tWG91W9U9JWbiL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9p9JtblK9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9r919HtbvL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVT949WboY9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVJ9V29VVbrl79IV9Rbwq;lZkdbk;jYi5ud9:du8Jjjjjbcj;kb9Rgv8Kjjjjbc9:hodnalTmbcuhoaiRbbgrc;WeGc:Ge9hmbarcsGgwce0mbc9:hoalcufadcd4cbawEgDadfgrcKcaawEgqaraq0Egk6mbaicefhxcj;abad9Uc;WFbGcjdadca0EhmaialfgPar9Rgoadfhsavaoadz:jjjjbgzceVhHcbhOdndninaeaO9nmeaPax9RaD6mdamaeaO9RaOamfgoae6EgAcsfglc9WGhCabaOad2fhXaAcethQaxaDfhiaOaeaoaeao6E9RhLalcl4cifcd4hKazcj;cbfaAfhYcbh8AazcjdfhEaHh3incbhodnawTmbaxa8Acd4fRbbhokaocFeGh5cbh8Eazcj;cbfhqinaih8Fdndndndna5a8Ecet4ciGgoc9:fPdebdkaPa8F9RaA6mrazcj;cbfa8EaA2fa8FaAz:jjjjb8Aa8FaAfhixdkazcj;cbfa8EaA2fcbaAz:kjjjb8Aa8FhixekaPa8F9RaK6mva8FaKfhidnaCTmbaPai9RcK6mbaocdtc:q1jjbfcj1jjbawEhaczhrcbhlinargoc9Wfghaqfhrdndndndndndnaaa8Fahco4fRbbalcoG4ciGcdtfydbPDbedvivvvlvkar9cb83bbarcwf9cb83bbxlkarcbaiRbdai8Xbb9c:c:qj:bw9:9c:q;c1:I1e:d9c:b:c:e1z9:gg9cjjjjjz:dg8J9qE86bbaqaofgrcGfag9c8F1:NghcKtc8F91aicdfa8J9c8N1:Nfg8KRbbG86bbarcVfcba8KahcjeGcr4fghRbbag9cjjjjjl:dg8J9qE86bbarc7fcbaha8J9c8L1:NfghRbbag9cjjjjjd:dg8J9qE86bbarctfcbaha8J9c8K1:NfghRbbag9cjjjjje:dg8J9qE86bbarc91fcbaha8J9c8J1:NfghRbbag9cjjjj;ab:dg8J9qE86bbarc4fcbaha8J9cg1:NfghRbbag9cjjjja:dg8J9qE86bbarc93fcbaha8J9ch1:NfghRbbag9cjjjjz:dgg9qE86bbarc94fcbahag9ca1:NfghRbbai8Xbe9c:c:qj:bw9:9c:q;c1:I1e:d9c:b:c:e1z9:gg9cjjjjjz:dg8J9qE86bbarc95fag9c8F1:NgicKtc8F91aha8J9c8N1:NfghRbbG86bbarc96fcbahaicjeGcr4fgiRbbag9cjjjjjl:dg8J9qE86bbarc97fcbaia8J9c8L1:NfgiRbbag9cjjjjjd:dg8J9qE86bbarc98fcbaia8J9c8K1:NfgiRbbag9cjjjjje:dg8J9qE86bbarc99fcbaia8J9c8J1:NfgiRbbag9cjjjj;ab:dg8J9qE86bbarc9:fcbaia8J9cg1:NfgiRbbag9cjjjja:dg8J9qE86bbarcufcbaia8J9ch1:NfgiRbbag9cjjjjz:dgg9qE86bbaiag9ca1:NfhixikaraiRblaiRbbghco4g8Ka8KciSg8KE86bbaqaofgrcGfaiclfa8Kfg8KRbbahcl4ciGg8La8LciSg8LE86bbarcVfa8Ka8Lfg8KRbbahcd4ciGg8La8LciSg8LE86bbarc7fa8Ka8Lfg8KRbbahciGghahciSghE86bbarctfa8Kahfg8KRbbaiRbeghco4g8La8LciSg8LE86bbarc91fa8Ka8Lfg8KRbbahcl4ciGg8La8LciSg8LE86bbarc4fa8Ka8Lfg8KRbbahcd4ciGg8La8LciSg8LE86bbarc93fa8Ka8Lfg8KRbbahciGghahciSghE86bbarc94fa8Kahfg8KRbbaiRbdghco4g8La8LciSg8LE86bbarc95fa8Ka8Lfg8KRbbahcl4ciGg8La8LciSg8LE86bbarc96fa8Ka8Lfg8KRbbahcd4ciGg8La8LciSg8LE86bbarc97fa8Ka8Lfg8KRbbahciGghahciSghE86bbarc98fa8KahfghRbbaiRbigico4g8Ka8KciSg8KE86bbarc99faha8KfghRbbaicl4ciGg8Ka8KciSg8KE86bbarc9:faha8KfghRbbaicd4ciGg8Ka8KciSg8KE86bbarcufaha8KfgrRbbaiciGgiaiciSgiE86bbaraifhixdkaraiRbwaiRbbghcl4g8Ka8KcsSg8KE86bbaqaofgrcGfaicwfa8Kfg8KRbbahcsGghahcsSghE86bbarcVfa8KahfghRbbaiRbeg8Kcl4g8La8LcsSg8LE86bbarc7faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarctfaha8KfghRbbaiRbdg8Kcl4g8La8LcsSg8LE86bbarc91faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarc4faha8KfghRbbaiRbig8Kcl4g8La8LcsSg8LE86bbarc93faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarc94faha8KfghRbbaiRblg8Kcl4g8La8LcsSg8LE86bbarc95faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarc96faha8KfghRbbaiRbvg8Kcl4g8La8LcsSg8LE86bbarc97faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarc98faha8KfghRbbaiRbog8Kcl4g8La8LcsSg8LE86bbarc99faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarc9:faha8KfghRbbaiRbrgicl4g8Ka8KcsSg8KE86bbarcufaha8KfgrRbbaicsGgiaicsSgiE86bbaraifhixekarai8Pbb83bbarcwfaicwf8Pbb83bbaiczfhikdnaoaC9pmbalcdfhlaoczfhraPai9RcL0mekkaoaC6moaimexokaCmva8FTmvkaqaAfhqa8Ecefg8Ecl9hmbkdndndndnawTmbasa8Acd4fRbbgociGPlbedrbkaATmdaza8Afh8Fazcj;cbfhhcbh8EaEhaina8FRbbhraahocbhlinaoahalfRbbgqce4cbaqceG9R7arfgr86bbaoadfhoaAalcefgl9hmbkaacefhaa8Fcefh8FahaAfhha8Ecefg8Ecl9hmbxikkaATmeaza8Afhaazcj;cbfhhcbhoceh8EaYh8FinaEaofhlaa8Vbbhrcbhoinala8FaofRbbcwtahaofRbbgqVc;:FiGce4cbaqceG9R7arfgr87bbaladfhlaLaocefgofmbka8FaQfh8FcdhoaacdfhaahaQfhha8EceGhlcbh8EalmbxdkkaATmbcbaocl49Rh8Eaza8AfRbbhqcwhoa3hlinalRbbaotaqVhqalcefhlaocwfgoca9hmbkcbhhaEh8FaYhainazcj;cbfahfRbbhrcwhoaahlinalRbbaotarVhralaAfhlaocwfgoca9hmbkara8E93aq7hqcbhoa8Fhlinalaqao486bbalcefhlaocwfgoca9hmbka8Fadfh8FaacefhaahcefghaA9hmbkkaEclfhEa3clfh3a8Aclfg8Aad6mbkaXazcjdfaAad2z:jjjjb8AazazcjdfaAcufad2fadz:jjjjb8AaAaOfhOaihxaimbkc9:hoxdkcbc99aPax9RakSEhoxekc9:hokavcj;kbf8Kjjjjbaok:XseHu8Jjjjjbc;ae9Rgv8Kjjjjbc9:hodnaeci9UgrcHfal0mbcuhoaiRbbgwc;WeGc;Ge9hmbawcsGgDce0mbavc;abfcFecjez:kjjjb8AavcUf9cu83ibavc8Wf9cu83ibavcyf9cu83ibavcaf9cu83ibavcKf9cu83ibavczf9cu83ibav9cu83iwav9cu83ibaialfc9WfhqaicefgwarfhldnaeTmbcmcsaDceSEhkcbhxcbhmcbhrcbhicbhoindnalaq9nmbc9:hoxikdndnawRbbgDc;Ve0mbavc;abfaoaDcu7gPcl4fcsGcitfgsydlhzasydbhHdndnaDcsGgsak9pmbavaiaPfcsGcdtfydbaxasEhDaxasTgOfhxxekdndnascsSmbcehOasc987asamffcefhDxekalcefhDal8SbbgscFeGhPdndnascu9mmbaDhlxekalcvfhlaPcFbGhPcrhsdninaD8SbbgOcFbGastaPVhPaOcu9kmeaDcefhDascrfgsc8J9hmbxdkkaDcefhlkcehOaPce4cbaPceG9R7amfhDkaDhmkavc;abfaocitfgsaDBdbasazBdlavaicdtfaDBdbavc;abfaocefcsGcitfgsaHBdbasaDBdlaocdfhoaOaifhidnadcd9hmbabarcetfgsaH87ebasclfaD87ebascdfaz87ebxdkabarcdtfgsaHBdbascwfaDBdbasclfazBdbxekdnaDcpe0mbaxcefgOavaiaqaDcsGfRbbgscl49RcsGcdtfydbascz6gPEhDavaias9RcsGcdtfydbaOaPfgzascsGgOEhsaOThOdndnadcd9hmbabarcetfgHax87ebaHclfas87ebaHcdfaD87ebxekabarcdtfgHaxBdbaHcwfasBdbaHclfaDBdbkavaicdtfaxBdbavc;abfaocitfgHaDBdbaHaxBdlavaicefgicsGcdtfaDBdbavc;abfaocefcsGcitfgHasBdbaHaDBdlavaiaPfgicsGcdtfasBdbavc;abfaocdfcsGcitfgDaxBdbaDasBdlaocifhoaiaOfhiazaOfhxxekaxcbalRbbgHEgAaDc;:eSgDfhzaHcsGhCaHcl4hXdndnaHcs0mbazcefhOxekazhOavaiaX9RcsGcdtfydbhzkdndnaCmbaOcefhxxekaOhxavaiaH9RcsGcdtfydbhOkdndnaDTmbalcefhDxekalcdfhDal8SbegPcFeGhsdnaPcu9kmbalcofhAascFbGhscrhldninaD8SbbgPcFbGaltasVhsaPcu9kmeaDcefhDalcrfglc8J9hmbkaAhDxekaDcefhDkasce4cbasceG9R7amfgmhAkdndnaXcsSmbaDhsxekaDcefhsaD8SbbglcFeGhPdnalcu9kmbaDcvfhzaPcFbGhPcrhldninas8SbbgDcFbGaltaPVhPaDcu9kmeascefhsalcrfglc8J9hmbkazhsxekascefhskaPce4cbaPceG9R7amfgmhzkdndnaCcsSmbashlxekascefhlas8SbbgDcFeGhPdnaDcu9kmbascvfhOaPcFbGhPcrhDdninal8SbbgscFbGaDtaPVhPascu9kmealcefhlaDcrfgDc8J9hmbkaOhlxekalcefhlkaPce4cbaPceG9R7amfgmhOkdndnadcd9hmbabarcetfgDaA87ebaDclfaO87ebaDcdfaz87ebxekabarcdtfgDaABdbaDcwfaOBdbaDclfazBdbkavc;abfaocitfgDazBdbaDaABdlavaicdtfaABdbavc;abfaocefcsGcitfgDaOBdbaDazBdlavaicefgicsGcdtfazBdbavc;abfaocdfcsGcitfgDaABdbaDaOBdlavaiaHcz6aXcsSVfgicsGcdtfaOBdbaiaCTaCcsSVfhiaocifhokawcefhwaocsGhoaicsGhiarcifgrae6mbkkcbc99alaqSEhokavc;aef8Kjjjjbaok:clevu8Jjjjjbcz9Rhvdnaecvfal9nmbc9:skdnaiRbbc;:eGc;qeSmbcuskav9cb83iwaicefhoaialfc98fhrdnaeTmbdnadcdSmbcbhwindnaoar6mbc9:skaocefhlao8SbbgicFeGhddndnaicu9mmbalhoxekaocvfhoadcFbGhdcrhidninal8SbbgDcFbGaitadVhdaDcu9kmealcefhlaicrfgic8J9hmbxdkkalcefhokabawcdtfadc8Etc8F91adcd47avcwfadceGcdtVglydbfgiBdbalaiBdbawcefgwae9hmbxdkkcbhwindnaoar6mbc9:skaocefhlao8SbbgicFeGhddndnaicu9mmbalhoxekaocvfhoadcFbGhdcrhidninal8SbbgDcFbGaitadVhdaDcu9kmealcefhlaicrfgic8J9hmbxdkkalcefhokabawcetfadc8Etc8F91adcd47avcwfadceGcdtVglydbfgi87ebalaiBdbawcefgwae9hmbkkcbc99aoarSEk:Lvoeue99dud99eud99dndnadcl9hmbaeTmeindndnabcdfgd8Sbb:Yab8Sbbgi:Ygl:l:tabcefgv8Sbbgo:Ygr:l:tgwJbb;:9cawawNJbbbbawawJbbbb9GgDEgq:mgkaqaicb9iEalMgwawNakaqaocb9iEarMgqaqNMM:r:vglNJbbbZJbbb:;aDEMgr:lJbbb9p9DTmbar:Ohixekcjjjj94hikadai86bbdndnaqalNJbbbZJbbb:;aqJbbbb9GEMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkavad86bbdndnawalNJbbbZJbbb:;awJbbbb9GEMgw:lJbbb9p9DTmbaw:Ohdxekcjjjj94hdkabad86bbabclfhbaecufgembxdkkaeTmbindndnabclfgd8Ueb:Yab8Uebgi:Ygl:l:tabcdfgv8Uebgo:Ygr:l:tgwJb;:FSawawNJbbbbawawJbbbb9GgDEgq:mgkaqaicb9iEalMgwawNakaqaocb9iEarMgqaqNMM:r:vglNJbbbZJbbb:;aDEMgr:lJbbb9p9DTmbar:Ohixekcjjjj94hikadai87ebdndnaqalNJbbbZJbbb:;aqJbbbb9GEMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkavad87ebdndnawalNJbbbZJbbb:;awJbbbb9GEMgw:lJbbb9p9DTmbaw:Ohdxekcjjjj94hdkabad87ebabcwfhbaecufgembkkk::ioiue99dud99dud99dnaeTmbcbhiabhlindndnal8Uebgv:YgoJ:ji:1Salcof8UebgrciVgw:Y:vgDNJbbbZJbbb:;avcu9kEMgq:lJbbb9p9DTmbaq:Ohkxekcjjjj94hkkalclf8Uebhvalcdf8UebhxabaiarcefciGfcetfak87ebdndnax:YgqaDNJbbbZJbbb:;axcu9kEMgm:lJbbb9p9DTmbam:Ohxxekcjjjj94hxkabaiarciGfgkcd7cetfax87ebdndnav:YgmaDNJbbbZJbbb:;avcu9kEMgP:lJbbb9p9DTmbaP:Ohvxekcjjjj94hvkabaiarcufciGfcetfav87ebdndnawaw2:ZgPaPMaoaoN:taqaqN:tamamN:tgoJbbbbaoJbbbb9GE:raDNJbbbZMgD:lJbbb9p9DTmbaD:Ohrxekcjjjj94hrkabakcetfar87ebalcwfhlaiclfhiaecufgembkkk9mbdnadcd4ae2gdTmbinababydbgecwtcw91:Yaece91cjjj98Gcjjj;8if::NUdbabclfhbadcufgdmbkkk:Tvirud99eudndnadcl9hmbaeTmeindndnabRbbgiabcefgl8Sbbgvabcdfgo8Sbbgrf9R:YJbbuJabcifgwRbbgdce4adVgDcd4aDVgDcl4aDVgD:Z:vgqNJbbbZMgk:lJbbb9p9DTmbak:Ohxxekcjjjj94hxkaoax86bbdndnaraif:YaqNJbbbZMgk:lJbbb9p9DTmbak:Ohoxekcjjjj94hokalao86bbdndnavaifar9R:YaqNJbbbZMgk:lJbbb9p9DTmbak:Ohixekcjjjj94hikabai86bbdndnaDadcetGadceGV:ZaqNJbbbZMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkawad86bbabclfhbaecufgembxdkkaeTmbindndnab8Vebgiabcdfgl8Uebgvabclfgo8Uebgrf9R:YJbFu9habcofgw8Vebgdce4adVgDcd4aDVgDcl4aDVgDcw4aDVgD:Z:vgqNJbbbZMgk:lJbbb9p9DTmbak:Ohxxekcjjjj94hxkaoax87ebdndnaraif:YaqNJbbbZMgk:lJbbb9p9DTmbak:Ohoxekcjjjj94hokalao87ebdndnavaifar9R:YaqNJbbbZMgk:lJbbb9p9DTmbak:Ohixekcjjjj94hikabai87ebdndnaDadcetGadceGV:ZaqNJbbbZMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkawad87ebabcwfhbaecufgembkkk9teiucbcbyd:K1jjbgeabcifc98GfgbBd:K1jjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaik;teeeudndnaeabVciGTmbabhixekdndnadcz9pmbabhixekabhiinaiaeydbBdbaiaeydlBdlaiaeydwBdwaiaeydxBdxaeczfheaiczfhiadc9Wfgdcs0mbkkadcl6mbinaiaeydbBdbaeclfheaiclfhiadc98fgdci0mbkkdnadTmbinaiaeRbb86bbaicefhiaecefheadcufgdmbkkabk:3eedudndnabciGTmbabhixekaecFeGc:b:c:ew2hldndnadcz9pmbabhixekabhiinaialBdxaialBdwaialBdlaialBdbaiczfhiadc9Wfgdcs0mbkkadcl6mbinaialBdbaiclfhiadc98fgdci0mbkkdnadTmbinaiae86bbaicefhiadcufgdmbkkabkk81dbcjwk8Kbbbbdbbblbbbwbbbbbbbebbbdbbblbbbwbbbbc:Kwkl8WNbb'; // embed! base\n\tvar wasm_simd =\n\t\t'b9H79TebbbeKl9Gbb9Gvuuuuueu9Giuuub9Geueuixkbbebeeddddilve9Weeeviebeoweuec:q:6dkr;Neqo9TW9T9VV95dbH9F9F939H79T9F9J9H229F9Jt9VV7bb8A9TW79O9V9Wt9F9KW9J9V9KW9wWVtW949c919M9MWVbdY9TW79O9V9Wt9F9KW9J9V9KW69U9KW949c919M9MWVblE9TW79O9V9Wt9F9KW9J9V9KW69U9KW949tWG91W9U9JWbvL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9p9JtboK9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9r919HtbrL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVT949WbwY9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVJ9V29VVbDl79IV9Rbqq:Ctklbzik9:evu8Jjjjjbcz9Rhbcbheincbhdcbhiinabcwfadfaicjuaead4ceGglE86bbaialfhiadcefgdcw9hmbkaec:q:yjjbfai86bbaecitc:q1jjbfab8Piw83ibaecefgecjd9hmbkk:183lYud97dur978Jjjjjbcj;kb9Rgv8Kjjjjbc9:hodnalTmbcuhoaiRbbgrc;WeGc:Ge9hmbarcsGgwce0mbc9:hoalcufadcd4cbawEgDadfgrcKcaawEgqaraq0Egk6mbaicefhxavaialfgmar9Rgoad;8qbbcj;abad9Uc;WFbGcjdadca0EhPdndndnadTmbaoadfhscbhzinaeaz9nmdamax9RaD6miabazad2fhHaxaDfhOaPaeaz9RazaPfae6EgAcsfgocl4cifcd4hCavcj;cbfaoc9WGgXcetfhQavcj;cbfaXci2fhLavcj;cbfaXfhKcbhYaoc;ab6h8AincbhodnawTmbaxaYcd4fRbbhokaocFeGhEcbh3avcj;cbfh5indndndndnaEa3cet4ciGgoc9:fPdebdkamaO9RaX6mwavcj;cbfa3aX2faOaX;8qbbaOaAfhOxdkavcj;cbfa3aX2fcbaX;8kbxekamaO9RaC6moaoclVcbawEhraOaCfhocbhidna8Ambamao9Rc;Gb6mbcbhlina5alfhidndndndndndnaOalco4fRbbgqciGarfPDbedibledibkaipxbbbbbbbbbbbbbbbbpklbxlkaiaopbblaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLg8Ecdp:mea8EpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9og8Fpxiiiiiiiiiiiiiiiip8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaoclffahc:q:yjjbfRbbfhoxikaiaopbbwaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9og8Fpxssssssssssssssssp8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaocwffahc:q:yjjbfRbbfhoxdkaiaopbbbpklbaoczfhoxekaiaopbbdaoRbbgacitc:q1jjbfpbibaac:q:yjjbfRbbgapsaoRbeghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPpklbaaaocdffahc:q:yjjbfRbbfhokdndndndndndnaqcd4ciGarfPDbedibledibkaiczfpxbbbbbbbbbbbbbbbbpklbxlkaiczfaopbblaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLg8Ecdp:mea8EpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9og8Fpxiiiiiiiiiiiiiiiip8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaoclffahc:q:yjjbfRbbfhoxikaiczfaopbbwaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9og8Fpxssssssssssssssssp8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaocwffahc:q:yjjbfRbbfhoxdkaiczfaopbbbpklbaoczfhoxekaiczfaopbbdaoRbbgacitc:q1jjbfpbibaac:q:yjjbfRbbgapsaoRbeghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPpklbaaaocdffahc:q:yjjbfRbbfhokdndndndndndnaqcl4ciGarfPDbedibledibkaicafpxbbbbbbbbbbbbbbbbpklbxlkaicafaopbblaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLg8Ecdp:mea8EpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9og8Fpxiiiiiiiiiiiiiiiip8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaoclffahc:q:yjjbfRbbfhoxikaicafaopbbwaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9og8Fpxssssssssssssssssp8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaocwffahc:q:yjjbfRbbfhoxdkaicafaopbbbpklbaoczfhoxekaicafaopbbdaoRbbgacitc:q1jjbfpbibaac:q:yjjbfRbbgapsaoRbeghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPpklbaaaocdffahc:q:yjjbfRbbfhokdndndndndndnaqco4arfPDbedibledibkaic8Wfpxbbbbbbbbbbbbbbbbpklbxlkaic8Wfaopbblaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLg8Ecdp:mea8EpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9og8Fpxiiiiiiiiiiiiiiiip8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngicitc:q1jjbfpbibaic:q:yjjbfRbbgipsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Ngqcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaiaoclffaqc:q:yjjbfRbbfhoxikaic8Wfaopbbwaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9og8Fpxssssssssssssssssp8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngicitc:q1jjbfpbibaic:q:yjjbfRbbgipsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Ngqcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaiaocwffaqc:q:yjjbfRbbfhoxdkaic8Wfaopbbbpklbaoczfhoxekaic8WfaopbbdaoRbbgicitc:q1jjbfpbibaic:q:yjjbfRbbgipsaoRbegqcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPpklbaiaocdffaqc:q:yjjbfRbbfhokalc;abfhialcjefaX0meaihlamao9Rc;Fb0mbkkdnaiaX9pmbaici4hlinamao9RcK6mwa5aifhqdndndndndndnaOaico4fRbbalcoG4ciGarfPDbedibledibkaqpxbbbbbbbbbbbbbbbbpkbbxlkaqaopbblaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLg8Ecdp:mea8EpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9og8Fpxiiiiiiiiiiiiiiiip8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spkbbaaaoclffahc:q:yjjbfRbbfhoxikaqaopbbwaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9og8Fpxssssssssssssssssp8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spkbbaaaocwffahc:q:yjjbfRbbfhoxdkaqaopbbbpkbbaoczfhoxekaqaopbbdaoRbbgacitc:q1jjbfpbibaac:q:yjjbfRbbgapsaoRbeghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPpkbbaaaocdffahc:q:yjjbfRbbfhokalcdfhlaiczfgiaX6mbkkaohOaoTmoka5aXfh5a3cefg3cl9hmbkdndndndnawTmbasaYcd4fRbbglciGPlbedwbkaXTmdavcjdfaYfhlavaYfpbdbhgcbhoinalavcj;cbfaofpblbg8JaKaofpblbg8KpmbzeHdOiAlCvXoQrLg8LaQaofpblbg8MaLaofpblbg8NpmbzeHdOiAlCvXoQrLgypmbezHdiOAlvCXorQLg8Ecep9Ta8Epxeeeeeeeeeeeeeeeeg8Fp9op9Hp9rg8Eagp9Uggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp9Uggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp9Uggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp9Uggp9Abbbaladfglaga8LaypmwDKYqk8AExm35Ps8E8Fg8Ecep9Ta8Ea8Fp9op9Hp9rg8Ep9Uggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp9Uggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp9Uggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp9Uggp9Abbbaladfglaga8Ja8KpmwKDYq8AkEx3m5P8Es8Fg8Ja8Ma8NpmwKDYq8AkEx3m5P8Es8Fg8KpmbezHdiOAlvCXorQLg8Ecep9Ta8Ea8Fp9op9Hp9rg8Ep9Uggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp9Uggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp9Uggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp9Uggp9Abbbaladfglaga8Ja8KpmwDKYqk8AExm35Ps8E8Fg8Ecep9Ta8Ea8Fp9op9Hp9rg8Ep9Ug8Fp9Abbbaladfgla8Fa8Ea8Epmlvorlvorlvorlvorp9Ug8Fp9Abbbaladfgla8Fa8Ea8EpmwDqkwDqkwDqkwDqkp9Ug8Fp9Abbbaladfgla8Fa8Ea8EpmxmPsxmPsxmPsxmPsp9Uggp9AbbbaladfhlaoczfgoaX6mbxikkaXTmeavcjdfaYfhlavaYfpbdbhgcbhoinalavcj;cbfaofpblbg8JaKaofpblbg8KpmbzeHdOiAlCvXoQrLg8LaQaofpblbg8MaLaofpblbg8NpmbzeHdOiAlCvXoQrLgypmbezHdiOAlvCXorQLg8Ecep:nea8Epxebebebebebebebebg8Fp9op:bep9rg8Eagp:oeggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp:oeggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp:oeggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp:oeggp9Abbbaladfglaga8LaypmwDKYqk8AExm35Ps8E8Fg8Ecep:nea8Ea8Fp9op:bep9rg8Ep:oeggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp:oeggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp:oeggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp:oeggp9Abbbaladfglaga8Ja8KpmwKDYq8AkEx3m5P8Es8Fg8Ja8Ma8NpmwKDYq8AkEx3m5P8Es8Fg8KpmbezHdiOAlvCXorQLg8Ecep:nea8Ea8Fp9op:bep9rg8Ep:oeggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp:oeggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp:oeggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp:oeggp9Abbbaladfglaga8Ja8KpmwDKYqk8AExm35Ps8E8Fg8Ecep:nea8Ea8Fp9op:bep9rg8Ep:oeg8Fp9Abbbaladfgla8Fa8Ea8Epmlvorlvorlvorlvorp:oeg8Fp9Abbbaladfgla8Fa8Ea8EpmwDqkwDqkwDqkwDqkp:oeg8Fp9Abbbaladfgla8Fa8Ea8EpmxmPsxmPsxmPsxmPsp:oeggp9AbbbaladfhlaoczfgoaX6mbxdkkaXTmbcbhocbalcl4gl9Rc8FGhiavcjdfaYfhravaYfpbdbh8Finaravcj;cbfaofpblbggaKaofpblbg8JpmbzeHdOiAlCvXoQrLg8KaQaofpblbg8LaLaofpblbg8MpmbzeHdOiAlCvXoQrLg8NpmbezHdiOAlvCXorQLg8Eaip:Rea8Ealp:Sep9qg8Ea8Fp9rg8Fp9Abbbaradfgra8Fa8Ea8Epmlvorlvorlvorlvorp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmwDqkwDqkwDqkwDqkp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmxmPsxmPsxmPsxmPsp9rg8Fp9Abbbaradfgra8Fa8Ka8NpmwDKYqk8AExm35Ps8E8Fg8Eaip:Rea8Ealp:Sep9qg8Ep9rg8Fp9Abbbaradfgra8Fa8Ea8Epmlvorlvorlvorlvorp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmwDqkwDqkwDqkwDqkp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmxmPsxmPsxmPsxmPsp9rg8Fp9Abbbaradfgra8Faga8JpmwKDYq8AkEx3m5P8Es8Fgga8La8MpmwKDYq8AkEx3m5P8Es8Fg8JpmbezHdiOAlvCXorQLg8Eaip:Rea8Ealp:Sep9qg8Ep9rg8Fp9Abbbaradfgra8Fa8Ea8Epmlvorlvorlvorlvorp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmwDqkwDqkwDqkwDqkp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmxmPsxmPsxmPsxmPsp9rg8Fp9Abbbaradfgra8Faga8JpmwDKYqk8AExm35Ps8E8Fg8Eaip:Rea8Ealp:Sep9qg8Ep9rg8Fp9Abbbaradfgra8Fa8Ea8Epmlvorlvorlvorlvorp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmwDqkwDqkwDqkwDqkp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmxmPsxmPsxmPsxmPsp9rg8Fp9AbbbaradfhraoczfgoaX6mbkkaYclfgYad6mbkaHavcjdfaAad2;8qbbavavcjdfaAcufad2fad;8qbbaAazfhzc9:hoaOhxaOmbxlkkaeTmbaDalfhrcbhocuhlinaralaD9RglfaD6mdaPaeao9RaoaPfae6Eaofgoae6mbkaial9Rhxkcbc99amax9RakSEhoxekc9:hokavcj;kbf8Kjjjjbaokwbz:bjjjbk:TseHu8Jjjjjbc;ae9Rgv8Kjjjjbc9:hodnaeci9UgrcHfal0mbcuhoaiRbbgwc;WeGc;Ge9hmbawcsGgDce0mbavc;abfcFecje;8kbavcUf9cu83ibavc8Wf9cu83ibavcyf9cu83ibavcaf9cu83ibavcKf9cu83ibavczf9cu83ibav9cu83iwav9cu83ibaialfc9WfhqaicefgwarfhldnaeTmbcmcsaDceSEhkcbhxcbhmcbhrcbhicbhoindnalaq9nmbc9:hoxikdndnawRbbgDc;Ve0mbavc;abfaoaDcu7gPcl4fcsGcitfgsydlhzasydbhHdndnaDcsGgsak9pmbavaiaPfcsGcdtfydbaxasEhDaxasTgOfhxxekdndnascsSmbcehOasc987asamffcefhDxekalcefhDal8SbbgscFeGhPdndnascu9mmbaDhlxekalcvfhlaPcFbGhPcrhsdninaD8SbbgOcFbGastaPVhPaOcu9kmeaDcefhDascrfgsc8J9hmbxdkkaDcefhlkcehOaPce4cbaPceG9R7amfhDkaDhmkavc;abfaocitfgsaDBdbasazBdlavaicdtfaDBdbavc;abfaocefcsGcitfgsaHBdbasaDBdlaocdfhoaOaifhidnadcd9hmbabarcetfgsaH87ebasclfaD87ebascdfaz87ebxdkabarcdtfgsaHBdbascwfaDBdbasclfazBdbxekdnaDcpe0mbaxcefgOavaiaqaDcsGfRbbgscl49RcsGcdtfydbascz6gPEhDavaias9RcsGcdtfydbaOaPfgzascsGgOEhsaOThOdndnadcd9hmbabarcetfgHax87ebaHclfas87ebaHcdfaD87ebxekabarcdtfgHaxBdbaHcwfasBdbaHclfaDBdbkavaicdtfaxBdbavc;abfaocitfgHaDBdbaHaxBdlavaicefgicsGcdtfaDBdbavc;abfaocefcsGcitfgHasBdbaHaDBdlavaiaPfgicsGcdtfasBdbavc;abfaocdfcsGcitfgDaxBdbaDasBdlaocifhoaiaOfhiazaOfhxxekaxcbalRbbgHEgAaDc;:eSgDfhzaHcsGhCaHcl4hXdndnaHcs0mbazcefhOxekazhOavaiaX9RcsGcdtfydbhzkdndnaCmbaOcefhxxekaOhxavaiaH9RcsGcdtfydbhOkdndnaDTmbalcefhDxekalcdfhDal8SbegPcFeGhsdnaPcu9kmbalcofhAascFbGhscrhldninaD8SbbgPcFbGaltasVhsaPcu9kmeaDcefhDalcrfglc8J9hmbkaAhDxekaDcefhDkasce4cbasceG9R7amfgmhAkdndnaXcsSmbaDhsxekaDcefhsaD8SbbglcFeGhPdnalcu9kmbaDcvfhzaPcFbGhPcrhldninas8SbbgDcFbGaltaPVhPaDcu9kmeascefhsalcrfglc8J9hmbkazhsxekascefhskaPce4cbaPceG9R7amfgmhzkdndnaCcsSmbashlxekascefhlas8SbbgDcFeGhPdnaDcu9kmbascvfhOaPcFbGhPcrhDdninal8SbbgscFbGaDtaPVhPascu9kmealcefhlaDcrfgDc8J9hmbkaOhlxekalcefhlkaPce4cbaPceG9R7amfgmhOkdndnadcd9hmbabarcetfgDaA87ebaDclfaO87ebaDcdfaz87ebxekabarcdtfgDaABdbaDcwfaOBdbaDclfazBdbkavc;abfaocitfgDazBdbaDaABdlavaicdtfaABdbavc;abfaocefcsGcitfgDaOBdbaDazBdlavaicefgicsGcdtfazBdbavc;abfaocdfcsGcitfgDaABdbaDaOBdlavaiaHcz6aXcsSVfgicsGcdtfaOBdbaiaCTaCcsSVfhiaocifhokawcefhwaocsGhoaicsGhiarcifgrae6mbkkcbc99alaqSEhokavc;aef8Kjjjjbaok:clevu8Jjjjjbcz9Rhvdnaecvfal9nmbc9:skdnaiRbbc;:eGc;qeSmbcuskav9cb83iwaicefhoaialfc98fhrdnaeTmbdnadcdSmbcbhwindnaoar6mbc9:skaocefhlao8SbbgicFeGhddndnaicu9mmbalhoxekaocvfhoadcFbGhdcrhidninal8SbbgDcFbGaitadVhdaDcu9kmealcefhlaicrfgic8J9hmbxdkkalcefhokabawcdtfadc8Etc8F91adcd47avcwfadceGcdtVglydbfgiBdbalaiBdbawcefgwae9hmbxdkkcbhwindnaoar6mbc9:skaocefhlao8SbbgicFeGhddndnaicu9mmbalhoxekaocvfhoadcFbGhdcrhidninal8SbbgDcFbGaitadVhdaDcu9kmealcefhlaicrfgic8J9hmbxdkkalcefhokabawcetfadc8Etc8F91adcd47avcwfadceGcdtVglydbfgi87ebalaiBdbawcefgwae9hmbkkcbc99aoarSEk:2Pliur97eue978Jjjjjbc8W9Rhiaec98Ghldndnadcl9hmbdnalTmbcbhvabhdinadadpbbbgocKp:RecKp:Sep;6egraocwp:RecKp:Sep;6earp;Geaoczp:RecKp:Sep;6egwp;Gep;Kep;LegDpxbbbbbbbbbbbbbbbbp:2egqarpxbbbjbbbjbbbjbbbjgkp9op9rp;Kegrpxbb;:9cbb;:9cbb;:9cbb;:9cararp;MeaDaDp;Meawaqawakp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFbbbFbbbFbbbFbbbp9oaopxbbbFbbbFbbbFbbbFp9op9qarawp;Meaqp;Kecwp:RepxbFbbbFbbbFbbbFbbp9op9qaDawp;Meaqp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qpkbbadczfhdavclfgval6mbkkalaeSmeaipxbbbbbbbbbbbbbbbbgqpklbaiabalcdtfgdaeciGglcdtgv;8qbbdnalTmbaiaipblbgocKp:RecKp:Sep;6egraocwp:RecKp:Sep;6earp;Geaoczp:RecKp:Sep;6egwp;Gep;Kep;LegDaqp:2egqarpxbbbjbbbjbbbjbbbjgkp9op9rp;Kegrpxbb;:9cbb;:9cbb;:9cbb;:9cararp;MeaDaDp;Meawaqawakp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFbbbFbbbFbbbFbbbp9oaopxbbbFbbbFbbbFbbbFp9op9qarawp;Meaqp;Kecwp:RepxbFbbbFbbbFbbbFbbp9op9qaDawp;Meaqp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qpklbkadaiav;8qbbskaipxFubbFubbFubbFubbgxpklbdnalTmbcbhvabhdinadczfgmampbbbgopxbbbbbbFFbbbbbbFFgkp9oadpbbbgDaopmbediwDqkzHOAKY8AEgwczp:Reczp:Sep;6egraipblbaDaopmlvorxmPsCXQL358E8Fp9op;6eawczp:Sep;6egwp;Gearp;Gep;Kep;Legopxbbbbbbbbbbbbbbbbp:2egqarpxbbbjbbbjbbbjbbbjgPp9op9rp;Kegrpxb;:FSb;:FSb;:FSb;:FSararp;Meaoaop;MeawaqawaPp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFFbbFFbbFFbbFFbbp9oaoawp;Meaqp;Keczp:Rep9qgoarawp;Meaqp;KepxFFbbFFbbFFbbFFbbp9ogrpmwDKYqk8AExm35Ps8E8Fp9qpkbbadaDakp9oaoarpmbezHdiOAlvCXorQLp9qpkbbadcafhdavclfgval6mbkkalaeSmbaiczfpxbbbbbbbbbbbbbbbbgopklbaiaopklbaiabalcitfgdaeciGglcitgv;8qbbaiaxpkladnalTmbaiaipblzgopxbbbbbbFFbbbbbbFFgkp9oaipblbgDaopmbediwDqkzHOAKY8AEgwczp:Reczp:Sep;6egraipblaaDaopmlvorxmPsCXQL358E8Fp9op;6eawczp:Sep;6egwp;Gearp;Gep;Kep;Legopxbbbbbbbbbbbbbbbbp:2egqarpxbbbjbbbjbbbjbbbjgPp9op9rp;Kegrpxb;:FSb;:FSb;:FSb;:FSararp;Meaoaop;MeawaqawaPp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFFbbFFbbFFbbFFbbp9oaoawp;Meaqp;Keczp:Rep9qgoarawp;Meaqp;KepxFFbbFFbbFFbbFFbbp9ogrpmwDKYqk8AExm35Ps8E8Fp9qpklzaiaDakp9oaoarpmbezHdiOAlvCXorQLp9qpklbkadaiav;8qbbkk:Iwllue97euo978Jjjjjbca9Rhidnaec98GglTmbcbhvabhoinaocKfpx:ji:1S:ji:1S:ji:1S:ji:1SaopbbbgraoczfgwpbbbgDpmlvorxmPsCXQL358E8Fgqczp:Segkpxibbbibbbibbbibbbp9qgxp;6ep;Negmaxaxp:1ep;7egxaxp;KearaDpmbediwDqkzHOAKY8AEgxczp:Reczp:Sep;6egrarp;Meaxczp:Sep;6egDaDp;Meaqczp:Reczp:Sep;6egqaqp;Mep;Kep;Kep;Lepxbbbbbbbbbbbbbbbbp:4ep;Jep;Mepxbbn0bbn0bbn0bbn0gxp;KepxFFbbFFbbFFbbFFbbgPp9oamaDp;Meaxp;Keczp:Rep9qgDamarp;Meaxp;KeaPp9oamaqp;Meaxp;Keczp:Rep9qgxpmwDKYqk8AExm35Ps8E8Fgrp5eakclp:RegmpEi:T:j83ibawarp5bampEd:T:j83ibaocwfaDaxpmbezHdiOAlvCXorQLgxp5eampEe:T:j83ibaoaxp5bampEb:T:j83ibaocafhoavclfgval6mbkkdnalaeSmbaiczfpxbbbbbbbbbbbbbbbbgmpklbaiampklbaiabalcitfgoaeciGgvcitgw;8qbbdnavTmbaipx:ji:1S:ji:1S:ji:1S:ji:1SaipblbgraipblzgDpmlvorxmPsCXQL358E8Fgqczp:Segkpxibbbibbbibbbibbbp9qgxp;6ep;Negmaxaxp:1ep;7egxaxp;KearaDpmbediwDqkzHOAKY8AEgxczp:Reczp:Sep;6egrarp;Meaxczp:Sep;6egDaDp;Meaqczp:Reczp:Sep;6egqaqp;Mep;Kep;Kep;Lepxbbbbbbbbbbbbbbbbp:4ep;Jep;Mepxbbn0bbn0bbn0bbn0gxp;KepxFFbbFFbbFFbbFFbbgPp9oamaDp;Meaxp;Keczp:Rep9qgDamarp;Meaxp;KeaPp9oamaqp;Meaxp;Keczp:Rep9qgxpmwDKYqk8AExm35Ps8E8Fgrp5eakclp:RegmpEi:T:j83iKaiarp5bampEd:T:j83izaiaDaxpmbezHdiOAlvCXorQLgxp5eampEe:T:j83iwaiaxp5bampEb:T:j83ibkaoaiaw;8qbbkk;uddiue978Jjjjjbc;ab9Rhidnadcd4ae2glc98GgvTmbcbheabhdinadadpbbbgocwp:Recwp:Sep;6eaocep:SepxbbjFbbjFbbjFbbjFp9opxbbjZbbjZbbjZbbjZp:Uep;Mepkbbadczfhdaeclfgeav6mbkkdnavalSmbaic8WfpxbbbbbbbbbbbbbbbbgopklbaicafaopklbaiczfaopklbaiaopklbaiabavcdtfgdalciGgecdtgv;8qbbdnaeTmbaiaipblbgocwp:Recwp:Sep;6eaocep:SepxbbjFbbjFbbjFbbjFp9opxbbjZbbjZbbjZbbjZp:Uep;Mepklbkadaiav;8qbbkk:CPvdue97euw97eu8Jjjjjbc8W9Rhiaec98Ghldndnadcl9hmbaipxbbbbbbbbbbbbbbbbgvpklbdnalTmbcbhoabhdinadpbbbhradpxbbuJbbuJbbuJbbuJaipblbarcKp:Tep9qgwcep:Seawp9qgDcdp:SeaDp9qgDclp:SeaDp9qgqp;6ep;NegDarcwp:RecKp:SegkarpxFbbbFbbbFbbbFbbbgxp9ogmp:Uep;6ep;Mepxbbn0bbn0bbn0bbn0gPp;Kecwp:RepxbFbbbFbbbFbbbFbbp9oaDamakp:Xearczp:RecKp:Segrp:Uep;6ep;MeaPp;Keaxp9op9qaDamakarp:Uep:Xep;6ep;MeaPp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qaDaqawcep:Rep9oawpxebbbebbbebbbebbbp9op9qp;6ep;MeaPp;KecKp:Rep9qpkbbadczfhdaoclfgoal6mbkkalaeSmeaiavpklaaicafabalcdtfgdaeciGglcdtgo;8qbbaiavpklbdnalTmbaipblahraipxbbuJbbuJbbuJbbuJaipblbarcKp:Tep9qgwcep:Seawp9qgDcdp:SeaDp9qgDclp:SeaDp9qgqp;6ep;NegDarcwp:RecKp:SegkarpxFbbbFbbbFbbbFbbbgxp9ogmp:Uep;6ep;Mepxbbn0bbn0bbn0bbn0gPp;Kecwp:RepxbFbbbFbbbFbbbFbbp9oaDamakp:Xearczp:RecKp:Segrp:Uep;6ep;MeaPp;Keaxp9op9qaDamakarp:Uep:Xep;6ep;MeaPp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qaDaqawcep:Rep9oawpxebbbebbbebbbebbbp9op9qp;6ep;MeaPp;KecKp:Rep9qpklakadaicafao;8qbbskaipxbbbbbbbbbbbbbbbbgvpklbdnalTmbcbhoabhdinadczfgspxbFu9hbFu9hbFu9hbFu9hadpbbbgDaspbbbgPpmlvorxmPsCXQL358E8Fgmczp:Teaipblbp9qgrcep:Searp9qgwcdp:Seawp9qgwclp:Seawp9qgwcwp:Seawp9qgqp;6ep;NegwaDaPpmbediwDqkzHOAKY8AEgDpxFFbbFFbbFFbbFFbbgPp9ogkaDczp:Segxp:Ueamczp:Reczp:Segmp:Xep;6ep;Mepxbbn0bbn0bbn0bbn0gDp;KeaPp9oawakaxamp:Uep:Xep;6ep;MeaDp;Keczp:Rep9qgxawaqarcep:Rep9oarpxebbbebbbebbbebbbp9op9qp;6ep;MeaDp;Keczp:Reawamakp:Uep;6ep;MeaDp;KeaPp9op9qgrpmwDKYqk8AExm35Ps8E8FpkbbadaxarpmbezHdiOAlvCXorQLpkbbadcafhdaoclfgoal6mbkkalaeSmbaiczfpxbbbbbbbbbbbbbbbbgrpklbaiarpklbaiabalcitfgdaeciGglcitgo;8qbbaiavpkladnalTmbaipxbFu9hbFu9hbFu9hbFu9haipblbgDaipblzgPpmlvorxmPsCXQL358E8Fgmczp:Teaipblap9qgrcep:Searp9qgwcdp:Seawp9qgwclp:Seawp9qgwcwp:Seawp9qgqp;6ep;NegwaDaPpmbediwDqkzHOAKY8AEgDpxFFbbFFbbFFbbFFbbgPp9ogkaDczp:Segxp:Ueamczp:Reczp:Segmp:Xep;6ep;Mepxbbn0bbn0bbn0bbn0gDp;KeaPp9oawakaxamp:Uep:Xep;6ep;MeaDp;Keczp:Rep9qgxawaqarcep:Rep9oarpxebbbebbbebbbebbbp9op9qp;6ep;MeaDp;Keczp:Reawamakp:Uep;6ep;MeaDp;KeaPp9op9qgrpmwDKYqk8AExm35Ps8E8FpklzaiaxarpmbezHdiOAlvCXorQLpklbkadaiao;8qbbkk9teiucbcbydj1jjbgeabcifc98GfgbBdj1jjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaikkkebcjwklz:Dbb'; // embed! simd\n\n\tvar detector = new Uint8Array([\n\t\t0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 3, 2, 0, 0, 5, 3, 1, 0, 1, 12, 1, 0, 10, 22, 2, 12, 0, 65, 0, 65, 0, 65, 0, 252, 10, 0, 0,\n\t\t11, 7, 0, 65, 0, 253, 15, 26, 11,\n\t]);\n\tvar wasmpack = new Uint8Array([\n\t\t32, 0, 65, 2, 1, 106, 34, 33, 3, 128, 11, 4, 13, 64, 6, 253, 10, 7, 15, 116, 127, 5, 8, 12, 40, 16, 19, 54, 20, 9, 27, 255, 113, 17, 42, 67,\n\t\t24, 23, 146, 148, 18, 14, 22, 45, 70, 69, 56, 114, 101, 21, 25, 63, 75, 136, 108, 28, 118, 29, 73, 115,\n\t]);\n\n\tif (typeof WebAssembly !== 'object') {\n\t\treturn {\n\t\t\tsupported: false,\n\t\t};\n\t}\n\n\tvar wasm = WebAssembly.validate(detector) ? unpack(wasm_simd) : unpack(wasm_base);\n\n\tvar instance;\n\n\tvar ready = WebAssembly.instantiate(wasm, {}).then(function (result) {\n\t\tinstance = result.instance;\n\t\tinstance.exports.__wasm_call_ctors();\n\t});\n\n\tfunction unpack(data) {\n\t\tvar result = new Uint8Array(data.length);\n\t\tfor (var i = 0; i < data.length; ++i) {\n\t\t\tvar ch = data.charCodeAt(i);\n\t\t\tresult[i] = ch > 96 ? ch - 97 : ch > 64 ? ch - 39 : ch + 4;\n\t\t}\n\t\tvar write = 0;\n\t\tfor (var i = 0; i < data.length; ++i) {\n\t\t\tresult[write++] = result[i] < 60 ? wasmpack[result[i]] : (result[i] - 60) * 64 + result[++i];\n\t\t}\n\t\treturn result.buffer.slice(0, write);\n\t}\n\n\tfunction decode(instance, fun, target, count, size, source, filter) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar count4 = (count + 3) & ~3;\n\t\tvar tp = sbrk(count4 * size);\n\t\tvar sp = sbrk(source.length);\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(source, sp);\n\t\tvar res = fun(tp, count, size, sp, source.length);\n\t\tif (res == 0 && filter) {\n\t\t\tfilter(tp, count4, size);\n\t\t}\n\t\ttarget.set(heap.subarray(tp, tp + count * size));\n\t\tsbrk(tp - sbrk(0));\n\t\tif (res != 0) {\n\t\t\tthrow new Error('Malformed buffer data: ' + res);\n\t\t}\n\t}\n\n\tvar filters = {\n\t\tNONE: '',\n\t\tOCTAHEDRAL: 'meshopt_decodeFilterOct',\n\t\tQUATERNION: 'meshopt_decodeFilterQuat',\n\t\tEXPONENTIAL: 'meshopt_decodeFilterExp',\n\t\tCOLOR: 'meshopt_decodeFilterColor',\n\t};\n\n\tvar decoders = {\n\t\tATTRIBUTES: 'meshopt_decodeVertexBuffer',\n\t\tTRIANGLES: 'meshopt_decodeIndexBuffer',\n\t\tINDICES: 'meshopt_decodeIndexSequence',\n\t};\n\n\tvar workers = [];\n\tvar requestId = 0;\n\n\tfunction createWorker(url) {\n\t\tvar worker = {\n\t\t\tobject: new Worker(url),\n\t\t\tpending: 0,\n\t\t\trequests: {},\n\t\t};\n\n\t\tworker.object.onmessage = function (event) {\n\t\t\tvar data = event.data;\n\n\t\t\tworker.pending -= data.count;\n\t\t\tworker.requests[data.id][data.action](data.value);\n\t\t\tdelete worker.requests[data.id];\n\t\t};\n\n\t\treturn worker;\n\t}\n\n\tfunction initWorkers(count) {\n\t\tvar source =\n\t\t\t'self.ready = WebAssembly.instantiate(new Uint8Array([' +\n\t\t\tnew Uint8Array(wasm) +\n\t\t\t']), {})' +\n\t\t\t'.then(function(result) { result.instance.exports.__wasm_call_ctors(); return result.instance; });' +\n\t\t\t'self.onmessage = ' +\n\t\t\tworkerProcess.name +\n\t\t\t';' +\n\t\t\tdecode.toString() +\n\t\t\tworkerProcess.toString();\n\n\t\tvar blob = new Blob([source], { type: 'text/javascript' });\n\t\tvar url = URL.createObjectURL(blob);\n\n\t\tfor (var i = workers.length; i < count; ++i) {\n\t\t\tworkers[i] = createWorker(url);\n\t\t}\n\n\t\tfor (var i = count; i < workers.length; ++i) {\n\t\t\tworkers[i].object.postMessage({});\n\t\t}\n\n\t\tworkers.length = count;\n\n\t\tURL.revokeObjectURL(url);\n\t}\n\n\tfunction decodeWorker(count, size, source, mode, filter) {\n\t\tvar worker = workers[0];\n\n\t\tfor (var i = 1; i < workers.length; ++i) {\n\t\t\tif (workers[i].pending < worker.pending) {\n\t\t\t\tworker = workers[i];\n\t\t\t}\n\t\t}\n\n\t\treturn new Promise(function (resolve, reject) {\n\t\t\tvar data = new Uint8Array(source);\n\t\t\tvar id = ++requestId;\n\n\t\t\tworker.pending += count;\n\t\t\tworker.requests[id] = { resolve: resolve, reject: reject };\n\t\t\tworker.object.postMessage({ id: id, count: count, size: size, source: data, mode: mode, filter: filter }, [data.buffer]);\n\t\t});\n\t}\n\n\tfunction workerProcess(event) {\n\t\tvar data = event.data;\n\t\tif (!data.id) {\n\t\t\treturn self.close();\n\t\t}\n\t\tself.ready.then(function (instance) {\n\t\t\ttry {\n\t\t\t\tvar target = new Uint8Array(data.count * data.size);\n\t\t\t\tdecode(instance, instance.exports[data.mode], target, data.count, data.size, data.source, instance.exports[data.filter]);\n\t\t\t\tself.postMessage({ id: data.id, count: data.count, action: 'resolve', value: target }, [target.buffer]);\n\t\t\t} catch (error) {\n\t\t\t\tself.postMessage({ id: data.id, count: data.count, action: 'reject', value: error });\n\t\t\t}\n\t\t});\n\t}\n\n\treturn {\n\t\tready: ready,\n\t\tsupported: true,\n\t\tuseWorkers: function (count) {\n\t\t\tinitWorkers(count);\n\t\t},\n\t\tdecodeVertexBuffer: function (target, count, size, source, filter) {\n\t\t\tdecode(instance, instance.exports.meshopt_decodeVertexBuffer, target, count, size, source, instance.exports[filters[filter]]);\n\t\t},\n\t\tdecodeIndexBuffer: function (target, count, size, source) {\n\t\t\tdecode(instance, instance.exports.meshopt_decodeIndexBuffer, target, count, size, source);\n\t\t},\n\t\tdecodeIndexSequence: function (target, count, size, source) {\n\t\t\tdecode(instance, instance.exports.meshopt_decodeIndexSequence, target, count, size, source);\n\t\t},\n\t\tdecodeGltfBuffer: function (target, count, size, source, mode, filter) {\n\t\t\tdecode(instance, instance.exports[decoders[mode]], target, count, size, source, instance.exports[filters[filter]]);\n\t\t},\n\t\tdecodeGltfBufferAsync: function (count, size, source, mode, filter) {\n\t\t\tif (workers.length > 0) {\n\t\t\t\treturn decodeWorker(count, size, source, decoders[mode], filters[filter]);\n\t\t\t}\n\n\t\t\treturn ready.then(function () {\n\t\t\t\tvar target = new Uint8Array(count * size);\n\t\t\t\tdecode(instance, instance.exports[decoders[mode]], target, count, size, source, instance.exports[filters[filter]]);\n\t\t\t\treturn target;\n\t\t\t});\n\t\t},\n\t};\n})();\n\nexport { MeshoptDecoder };\n"
  },
  {
    "path": "js/meshopt_decoder.test.js",
    "content": "import assert from 'assert/strict';\nimport { MeshoptDecoder as decoder } from './meshopt_decoder.mjs';\n\nprocess.on('unhandledRejection', (error) => {\n\tconsole.log('unhandledRejection', error);\n\tprocess.exit(1);\n});\n\nvar tests = {\n\tdecodeVertexBuffer: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xa0, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x58, 0x57, 0x58, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x00, 0x00, 0x00, 0x58, 0x01, 0x08, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x17, 0x18, 0x17, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x00, 0x00,\n\t\t\t0x00, 0x17, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t]);\n\n\t\tvar expected = new Uint8Array([\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 0, 0, 244, 1, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 0, 0, 244, 1, 44, 1, 44, 1, 0, 0, 0,\n\t\t\t0, 244, 1, 244, 1,\n\t\t]);\n\n\t\tvar result = new Uint8Array(expected.length);\n\t\tdecoder.decodeVertexBuffer(result, 4, 12, encoded);\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeVertexBuffer_More: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xa0, 0x00, 0x01, 0x2a, 0xaa, 0xaa, 0xaa, 0x02, 0x04, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x03, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10,\n\t\t\t0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t]);\n\n\t\tvar expected = new Uint8Array([\n\t\t\t0, 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,\n\t\t\t10, 20, 80, 0, 11, 22, 88, 0, 12, 24, 96, 0, 13, 26, 104, 0, 14, 28, 112, 0, 15, 30, 120,\n\t\t]);\n\n\t\tvar result = new Uint8Array(expected.length);\n\t\tdecoder.decodeVertexBuffer(result, 16, 4, encoded);\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeVertexBuffer_Mode2: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xa0, 0x02, 0x08, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x02, 0x0a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x02, 0x0c, 0xcc, 0xcc,\n\t\t\t0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x02, 0x0e, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t]);\n\n\t\tvar expected = new Uint8Array([\n\t\t\t0, 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,\n\t\t\t45, 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,\n\t\t]);\n\n\t\tvar result = new Uint8Array(expected.length);\n\t\tdecoder.decodeVertexBuffer(result, 16, 4, encoded);\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeVertexBufferV1: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xa1, 0xee, 0xaa, 0xee, 0x00, 0x4b, 0x4b, 0x4b, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x7d, 0x7d, 0x7d, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x62,\n\t\t]);\n\n\t\tvar expected = new Uint8Array([\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 0, 0, 244, 1, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 0, 0, 244, 1, 44, 1, 44, 1, 0, 0, 0,\n\t\t\t0, 244, 1, 244, 1,\n\t\t]);\n\n\t\tvar result = new Uint8Array(expected.length);\n\t\tdecoder.decodeVertexBuffer(result, 4, 12, encoded);\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeVertexBufferV1_Custom: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xa1, 0xd4, 0x94, 0xd4, 0x01, 0x0e, 0x00, 0x58, 0x57, 0x58, 0x02, 0x02, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x58,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x7d, 0x7d,\n\t\t\t0x7d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x62,\n\t\t]);\n\n\t\tvar expected = new Uint8Array([\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 0, 0, 244, 1, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 0, 0, 244, 1, 44, 1, 44, 1, 0, 0, 0,\n\t\t\t0, 244, 1, 244, 1,\n\t\t]);\n\n\t\tvar result = new Uint8Array(expected.length);\n\t\tdecoder.decodeVertexBuffer(result, 4, 12, encoded);\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeVertexBufferV1_Deltas: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xa1, 0x99, 0x99, 0x01, 0x2a, 0xaa, 0xaa, 0xaa, 0x02, 0x04, 0x44, 0x44, 0x44, 0x43, 0x33, 0x33, 0x33, 0x02, 0x06, 0x66, 0x66, 0x66, 0x66,\n\t\t\t0x66, 0x66, 0x66, 0x02, 0x08, 0x88, 0x88, 0x88, 0x87, 0x77, 0x77, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x01, 0x01,\n\t\t]);\n\n\t\tvar expected = new Uint16Array([\n\t\t\t248, 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,\n\t\t\t264, 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,\n\t\t\t279, 252, 262, 252, 282, 248, 263, 250, 285, 244,\n\t\t]);\n\n\t\tvar result = new Uint16Array(expected.length);\n\t\tdecoder.decodeVertexBuffer(new Uint8Array(result.buffer), 16, 8, encoded);\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeIndexBuffer16: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xe0, 0xf0, 0x10, 0xfe, 0xff, 0xf0, 0x0c, 0xff, 0x02, 0x02, 0x02, 0x00, 0x76, 0x87, 0x56, 0x67, 0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98,\n\t\t\t0x01, 0x69, 0x00, 0x00,\n\t\t]);\n\n\t\tvar expected = new Uint16Array([0, 1, 2, 2, 1, 3, 4, 6, 5, 7, 8, 9]);\n\n\t\tvar result = new Uint16Array(expected.length);\n\t\tdecoder.decodeIndexBuffer(new Uint8Array(result.buffer), 12, 2, encoded);\n\n\t\tassert.deepEqual(result, expected);\n\t},\n\n\tdecodeIndexBuffer32: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xe0, 0xf0, 0x10, 0xfe, 0xff, 0xf0, 0x0c, 0xff, 0x02, 0x02, 0x02, 0x00, 0x76, 0x87, 0x56, 0x67, 0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98,\n\t\t\t0x01, 0x69, 0x00, 0x00,\n\t\t]);\n\n\t\tvar expected = new Uint32Array([0, 1, 2, 2, 1, 3, 4, 6, 5, 7, 8, 9]);\n\n\t\tvar result = new Uint32Array(expected.length);\n\t\tdecoder.decodeIndexBuffer(new Uint8Array(result.buffer), 12, 4, encoded);\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeIndexBufferV1: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xe1, 0xf0, 0x10, 0xfe, 0x1f, 0x3d, 0x00, 0x0a, 0x00, 0x76, 0x87, 0x56, 0x67, 0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98, 0x01, 0x69, 0x00,\n\t\t\t0x00,\n\t\t]);\n\n\t\tvar expected = new Uint32Array([0, 1, 2, 2, 1, 3, 0, 1, 2, 2, 1, 5, 2, 1, 4]);\n\n\t\tvar result = new Uint32Array(expected.length);\n\t\tdecoder.decodeIndexBuffer(new Uint8Array(result.buffer), 15, 4, encoded);\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeIndexBufferV1_More: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xe1, 0xf0, 0x10, 0xfe, 0xff, 0xf0, 0x0c, 0xff, 0x02, 0x02, 0x02, 0x00, 0x76, 0x87, 0x56, 0x67, 0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98,\n\t\t\t0x01, 0x69, 0x00, 0x00,\n\t\t]);\n\n\t\tvar expected = new Uint32Array([0, 1, 2, 2, 1, 3, 4, 6, 5, 7, 8, 9]);\n\n\t\tvar result = new Uint32Array(expected.length);\n\t\tdecoder.decodeIndexBuffer(new Uint8Array(result.buffer), 12, 4, encoded);\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeIndexBufferV1_3Edges: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xe1, 0xf0, 0x20, 0x30, 0x40, 0x00, 0x76, 0x87, 0x56, 0x67, 0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98, 0x01, 0x69, 0x00, 0x00,\n\t\t]);\n\t\tvar expected = new Uint32Array([0, 1, 2, 1, 0, 3, 2, 1, 4, 0, 2, 5]);\n\n\t\tvar result = new Uint32Array(expected.length);\n\t\tdecoder.decodeIndexBuffer(new Uint8Array(result.buffer), 12, 4, encoded);\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeIndexSequence: function () {\n\t\tvar encoded = new Uint8Array([0xd1, 0x00, 0x04, 0xcd, 0x01, 0x04, 0x07, 0x98, 0x1f, 0x00, 0x00, 0x00, 0x00]);\n\n\t\tvar expected = new Uint32Array([0, 1, 51, 2, 49, 1000]);\n\n\t\tvar result = new Uint32Array(expected.length);\n\t\tdecoder.decodeIndexSequence(new Uint8Array(result.buffer), 6, 4, encoded);\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeFilterOct8: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xa0, 0x01, 0x07, 0x00, 0x00, 0x00, 0x1e, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x8b, 0x8c, 0xfd, 0x00, 0x01, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, 0x00,\n\t\t]);\n\n\t\tvar expected = new Uint8Array([0, 1, 127, 0, 0, 159, 82, 1, 255, 1, 127, 0, 1, 130, 241, 1]);\n\n\t\tvar result = new Uint8Array(expected.length);\n\t\tdecoder.decodeVertexBuffer(new Uint8Array(result.buffer), 4, 4, encoded, /* filter= */ 'OCTAHEDRAL');\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeFilterOct12: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xa0, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x3d, 0x5a, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x0e, 0x0d, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x9a, 0x99, 0x26,\n\t\t\t0x01, 0x3f, 0x00, 0x00, 0x00, 0x0e, 0x0d, 0x0a, 0x00, 0x00, 0x01, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0x07,\n\t\t\t0x00, 0x00,\n\t\t]);\n\n\t\tvar expected = new Uint16Array([0, 16, 32767, 0, 0, 32621, 3088, 1, 32764, 16, 471, 0, 307, 28541, 16093, 1]);\n\n\t\tvar result = new Uint16Array(expected.length);\n\t\tdecoder.decodeVertexBuffer(new Uint8Array(result.buffer), 4, 8, encoded, /* filter= */ 'OCTAHEDRAL');\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeFilterQuat12: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xa0, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x3d, 0x5a, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x0e, 0x0d, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x9a, 0x99, 0x26,\n\t\t\t0x01, 0x3f, 0x00, 0x00, 0x00, 0x0e, 0x0d, 0x0a, 0x00, 0x00, 0x01, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n\t\t\t0xfc, 0x07,\n\t\t]);\n\n\t\tvar expected = new Uint16Array([32767, 0, 11, 0, 0, 25013, 0, 21166, 11, 0, 23504, 22830, 158, 14715, 0, 29277]);\n\n\t\tvar result = new Uint16Array(expected.length);\n\t\tdecoder.decodeVertexBuffer(new Uint8Array(result.buffer), 4, 8, encoded, /* filter= */ 'QUATERNION');\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeFilterExp: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0xff, 0xf7, 0xff, 0xff, 0x02, 0xff,\n\t\t\t0xff, 0x7f, 0xfe,\n\t\t]);\n\n\t\tvar expected = new Uint32Array([0, 0x3fc00000, 0xc2100000, 0x49fffffe]);\n\n\t\tvar result = new Uint32Array(expected.length);\n\t\tdecoder.decodeVertexBuffer(new Uint8Array(result.buffer), 1, 16, encoded, /* filter= */ 'EXPONENTIAL');\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeFilterColor8: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xa0, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x7e, 0x7d, 0x4c, 0x01, 0x3f, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfe, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x83,\n\t\t\t0x82, 0x80, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x7d, 0x3f, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x7f, 0xc1, 0xff,\n\t\t]);\n\n\t\tvar expected = new Uint8Array([254, 1, 0, 255, 0, 254, 0, 128, 1, 0, 255, 64, 102, 102, 102, 191]);\n\n\t\tvar result = new Uint8Array(expected.length);\n\t\tdecoder.decodeVertexBuffer(new Uint8Array(result.buffer), 4, 4, encoded, /* filter= */ 'COLOR');\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeFilterColor12: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xa0, 0x01, 0x1b, 0x00, 0x00, 0x00, 0xcc, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x06, 0x05, 0x04, 0x01, 0x29, 0x00, 0x00, 0x00, 0x01, 0x3f, 0x00,\n\t\t\t0x00, 0x00, 0x0d, 0x0f, 0x10, 0x01, 0x38, 0x00, 0x00, 0x00, 0x03, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x16, 0x15, 0x08, 0x01, 0x21, 0x00, 0x00,\n\t\t\t0x00, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x05, 0x03, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x07, 0x01, 0xfc, 0xff, 0x0f,\n\t\t]);\n\n\t\tvar expected = new Uint16Array([65519, 16, 0, 65535, 0, 65519, 0, 32776, 16, 0, 65535, 16388, 26214, 26214, 26214, 49147]);\n\n\t\tvar result = new Uint16Array(expected.length);\n\t\tdecoder.decodeVertexBuffer(new Uint8Array(result.buffer), 4, 8, encoded, /* filter= */ 'COLOR');\n\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeGltfBuffer: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xa0, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x58, 0x57, 0x58, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x00, 0x00, 0x00, 0x58, 0x01, 0x08, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x17, 0x18, 0x17, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x00, 0x00,\n\t\t\t0x00, 0x17, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t]);\n\n\t\tvar expected = new Uint8Array([\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 0, 0, 244, 1, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 0, 0, 244, 1, 44, 1, 44, 1, 0, 0, 0,\n\t\t\t0, 244, 1, 244, 1,\n\t\t]);\n\n\t\tvar result = new Uint8Array(expected.length);\n\t\tdecoder.decodeGltfBuffer(result, 4, 12, encoded, /* mode= */ 'ATTRIBUTES');\n\t\tassert.deepStrictEqual(result, expected);\n\t},\n\n\tdecodeGltfBufferAsync: function () {\n\t\tvar encoded = new Uint8Array([\n\t\t\t0xa0, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x58, 0x57, 0x58, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x00, 0x00, 0x00, 0x58, 0x01, 0x08, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3f, 0x00, 0x00, 0x00, 0x17, 0x18, 0x17, 0x01, 0x26, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x00, 0x00,\n\t\t\t0x00, 0x17, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t]);\n\n\t\tvar expected = new Uint8Array([\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 0, 0, 244, 1, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 0, 0, 244, 1, 44, 1, 44, 1, 0, 0, 0,\n\t\t\t0, 244, 1, 244, 1,\n\t\t]);\n\n\t\tdecoder.decodeGltfBufferAsync(4, 12, encoded, /* mode= */ 'ATTRIBUTES').then(function (result) {\n\t\t\tassert.deepStrictEqual(result, expected);\n\t\t});\n\t},\n};\n\ndecoder.ready.then(() => {\n\tvar count = 0;\n\n\tfor (var key in tests) {\n\t\ttests[key]();\n\t\tcount++;\n\t}\n\n\tconsole.log(count, 'tests passed');\n});\n"
  },
  {
    "path": "js/meshopt_decoder_reference.js",
    "content": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\n\n// This is the reference decoder implementation by Jasper St. Pierre.\n// It follows the decoder interface and should be a drop-in replacement for the actual decoder from meshopt_decoder module\n// It is provided for educational value and is not recommended for use in production because it's not performance-optimized.\n\nconst MeshoptDecoder = {};\nMeshoptDecoder.supported = true;\nMeshoptDecoder.ready = Promise.resolve();\n\nfunction assert(cond) {\n\tif (!cond) {\n\t\tthrow new Error('Assertion failed');\n\t}\n}\n\nfunction dezig(v) {\n\treturn (v & 1) !== 0 ? ~(v >>> 1) : v >>> 1;\n}\n\nMeshoptDecoder.decodeVertexBuffer = (target, elementCount, byteStride, source, filter) => {\n\tassert(source[0] === 0xa0 || source[0] === 0xa1);\n\tconst version = source[0] & 0x0f;\n\n\tconst maxBlockElements = Math.min((0x2000 / byteStride) & ~0x000f, 0x100);\n\n\tconst deltas = new Uint8Array(maxBlockElements * byteStride);\n\n\tconst tailSize = version === 0 ? byteStride : byteStride + byteStride / 4;\n\tconst tailDataOffs = source.length - tailSize;\n\n\t// What deltas are stored relative to\n\tconst tempData = source.slice(tailDataOffs, tailDataOffs + byteStride);\n\n\t// Channel modes for v1\n\tconst channels = version === 0 ? null : source.slice(tailDataOffs + byteStride, tailDataOffs + tailSize);\n\n\tlet srcOffs = 1; // Skip header byte\n\n\tconst headerModes = [\n\t\t[0, 2, 4, 8], // v0\n\t\t[0, 1, 2, 4], // v1, when control is 0\n\t\t[1, 2, 4, 8], // v1, when control is 1\n\t];\n\n\t// Attribute blocks\n\tfor (let dstElemBase = 0; dstElemBase < elementCount; dstElemBase += maxBlockElements) {\n\t\tconst attrBlockElementCount = Math.min(elementCount - dstElemBase, maxBlockElements);\n\t\tconst groupCount = ((attrBlockElementCount + 0x0f) & ~0x0f) >>> 4;\n\t\tconst headerByteCount = ((groupCount + 0x03) & ~0x03) >>> 2;\n\n\t\t// Control modes for v1\n\t\tconst controlBitsOffs = srcOffs;\n\t\tsrcOffs += version === 0 ? 0 : byteStride / 4;\n\n\t\t// Zero out deltas to simplify logic\n\t\tdeltas.fill(0x00);\n\n\t\t// Data blocks\n\t\tfor (let byte = 0; byte < byteStride; byte++) {\n\t\t\tconst deltaBase = byte * attrBlockElementCount;\n\n\t\t\t// Control mode for current byte for v1\n\t\t\tconst controlMode = version === 0 ? 0 : (source[controlBitsOffs + (byte >>> 2)] >>> ((byte & 0x03) << 1)) & 0x03;\n\n\t\t\tif (controlMode === 2) {\n\t\t\t\t// All byte deltas are 0; no data is stored for this byte\n\t\t\t\tcontinue;\n\t\t\t} else if (controlMode === 3) {\n\t\t\t\t// Byte deltas are stored uncompressed with no header bits\n\t\t\t\tdeltas.set(source.subarray(srcOffs, srcOffs + attrBlockElementCount), deltaBase);\n\t\t\t\tsrcOffs += attrBlockElementCount;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Header bits are omitted for v1 when using control modes 2/3\n\t\t\tconst headerBitsOffs = srcOffs;\n\t\t\tsrcOffs += headerByteCount;\n\n\t\t\tfor (let group = 0; group < groupCount; group++) {\n\t\t\t\tconst mode = (source[headerBitsOffs + (group >>> 2)] >>> ((group & 0x03) << 1)) & 0x03;\n\t\t\t\tconst modeBits = headerModes[version === 0 ? 0 : controlMode + 1][mode];\n\n\t\t\t\tconst deltaOffs = deltaBase + (group << 4);\n\n\t\t\t\tif (modeBits === 0) {\n\t\t\t\t\t// All 16 byte deltas are 0; the size of the encoded block is 0 bytes\n\t\t\t\t} else if (modeBits === 1) {\n\t\t\t\t\t// Deltas are using 1-bit sentinel encoding; the size of the encoded block is [2..18] bytes\n\t\t\t\t\tconst srcBase = srcOffs;\n\t\t\t\t\tsrcOffs += 0x02;\n\t\t\t\t\tfor (let m = 0; m < 0x10; m++) {\n\t\t\t\t\t\t// Bits are stored from least significant to most significant for 1-bit encoding\n\t\t\t\t\t\tconst shift = m & 0x07;\n\t\t\t\t\t\tlet delta = (source[srcBase + (m >>> 3)] >>> shift) & 0x01;\n\t\t\t\t\t\tif (delta === 1) delta = source[srcOffs++];\n\t\t\t\t\t\tdeltas[deltaOffs + m] = delta;\n\t\t\t\t\t}\n\t\t\t\t} else if (modeBits === 2) {\n\t\t\t\t\t// Deltas are using 2-bit sentinel encoding; the size of the encoded block is [4..20] bytes\n\t\t\t\t\tconst srcBase = srcOffs;\n\t\t\t\t\tsrcOffs += 0x04;\n\t\t\t\t\tfor (let m = 0; m < 0x10; m++) {\n\t\t\t\t\t\t// 0 = >>> 6, 1 = >>> 4, 2 = >>> 2, 3 = >>> 0\n\t\t\t\t\t\tconst shift = 6 - ((m & 0x03) << 1);\n\t\t\t\t\t\tlet delta = (source[srcBase + (m >>> 2)] >>> shift) & 0x03;\n\t\t\t\t\t\tif (delta === 3) delta = source[srcOffs++];\n\t\t\t\t\t\tdeltas[deltaOffs + m] = delta;\n\t\t\t\t\t}\n\t\t\t\t} else if (modeBits === 4) {\n\t\t\t\t\t// Deltas are using 4-bit sentinel encoding; the size of the encoded block is [8..24] bytes\n\t\t\t\t\tconst srcBase = srcOffs;\n\t\t\t\t\tsrcOffs += 0x08;\n\t\t\t\t\tfor (let m = 0; m < 0x10; m++) {\n\t\t\t\t\t\t// 0 = >>> 6, 1 = >>> 4, 2 = >>> 2, 3 = >>> 0\n\t\t\t\t\t\tconst shift = 4 - ((m & 0x01) << 2);\n\t\t\t\t\t\tlet delta = (source[srcBase + (m >>> 1)] >>> shift) & 0x0f;\n\t\t\t\t\t\tif (delta === 0xf) delta = source[srcOffs++];\n\t\t\t\t\t\tdeltas[deltaOffs + m] = delta;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// All 16 byte deltas are stored verbatim; the size of the encoded block is 16 bytes\n\t\t\t\t\tdeltas.set(source.subarray(srcOffs, srcOffs + 0x10), deltaOffs);\n\t\t\t\t\tsrcOffs += 0x10;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Go through and apply deltas to data\n\t\tfor (let elem = 0; elem < attrBlockElementCount; elem++) {\n\t\t\tconst dstElem = dstElemBase + elem;\n\n\t\t\tfor (let byteGroup = 0; byteGroup < byteStride; byteGroup += 4) {\n\t\t\t\tlet channelMode = version === 0 ? 0 : channels[byteGroup >>> 2] & 0x03;\n\t\t\t\tassert(channelMode !== 0x03);\n\n\t\t\t\tif (channelMode === 0) {\n\t\t\t\t\t// Channel 0 (byte deltas): Byte deltas are stored as zigzag-encoded differences between the byte values of the element and the byte values of the previous element in the same position.\n\t\t\t\t\tfor (let byte = byteGroup; byte < byteGroup + 4; byte++) {\n\t\t\t\t\t\tconst delta = dezig(deltas[byte * attrBlockElementCount + elem]);\n\t\t\t\t\t\tconst temp = (tempData[byte] + delta) & 0xff; // wrap around\n\n\t\t\t\t\t\tconst dstOffs = dstElem * byteStride + byte;\n\t\t\t\t\t\ttarget[dstOffs] = tempData[byte] = temp;\n\t\t\t\t\t}\n\t\t\t\t} else if (channelMode === 1) {\n\t\t\t\t\t// Channel 1 (2-byte deltas): 2-byte deltas are computed as zigzag-encoded differences between 16-bit values of the element and the previous element in the same position.\n\t\t\t\t\tfor (let byte = byteGroup; byte < byteGroup + 4; byte += 2) {\n\t\t\t\t\t\tconst delta = dezig(deltas[byte * attrBlockElementCount + elem] + (deltas[(byte + 1) * attrBlockElementCount + elem] << 8));\n\t\t\t\t\t\tlet temp = tempData[byte] + (tempData[byte + 1] << 8);\n\n\t\t\t\t\t\ttemp = (temp + delta) & 0xffff; // wrap around\n\n\t\t\t\t\t\tconst dstOffs = dstElem * byteStride + byte;\n\t\t\t\t\t\ttarget[dstOffs] = tempData[byte] = temp & 0xff;\n\t\t\t\t\t\ttarget[dstOffs + 1] = tempData[byte + 1] = temp >>> 8;\n\t\t\t\t\t}\n\t\t\t\t} else if (channelMode === 2) {\n\t\t\t\t\t// Channel 2 (4-byte XOR deltas): 4-byte deltas are computed as XOR between 32-bit values of the element and the previous element in the same position, with an additional rotation applied based on the high 4 bits of the channel mode byte.\n\t\t\t\t\tconst byte = byteGroup;\n\t\t\t\t\tconst delta =\n\t\t\t\t\t\tdeltas[byte * attrBlockElementCount + elem] +\n\t\t\t\t\t\t(deltas[(byte + 1) * attrBlockElementCount + elem] << 8) +\n\t\t\t\t\t\t(deltas[(byte + 2) * attrBlockElementCount + elem] << 16) +\n\t\t\t\t\t\t(deltas[(byte + 3) * attrBlockElementCount + elem] << 24);\n\t\t\t\t\tlet temp = tempData[byte] + (tempData[byte + 1] << 8) + (tempData[byte + 2] << 16) + (tempData[byte + 3] << 24);\n\n\t\t\t\t\tconst rot = channels[byteGroup >>> 2] >>> 4;\n\t\t\t\t\ttemp = temp ^ ((delta >>> rot) | (delta << (32 - rot))); // rotate and XOR\n\n\t\t\t\t\tconst dstOffs = dstElem * byteStride + byte;\n\t\t\t\t\ttarget[dstOffs] = tempData[byte] = temp & 0xff;\n\t\t\t\t\ttarget[dstOffs + 1] = tempData[byte + 1] = (temp >>> 8) & 0xff;\n\t\t\t\t\ttarget[dstOffs + 2] = tempData[byte + 2] = (temp >>> 16) & 0xff;\n\t\t\t\t\ttarget[dstOffs + 3] = tempData[byte + 3] = temp >>> 24;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconst tailSizePadded = Math.max(tailSize, version === 0 ? 32 : 24);\n\tassert(srcOffs == source.length - tailSizePadded);\n\n\t// Filters - only applied if filter isn't undefined or NONE\n\tif (filter === 'OCTAHEDRAL') {\n\t\tassert(byteStride === 4 || byteStride === 8);\n\n\t\tconst dst = byteStride === 4 ? new Int8Array(target.buffer) : new Int16Array(target.buffer);\n\t\tconst maxInt = byteStride === 4 ? 127 : 32767;\n\n\t\tfor (let i = 0; i < 4 * elementCount; i += 4) {\n\t\t\tlet x = dst[i + 0],\n\t\t\t\ty = dst[i + 1],\n\t\t\t\tone = dst[i + 2];\n\t\t\tx /= one;\n\t\t\ty /= one;\n\t\t\tconst z = 1.0 - Math.abs(x) - Math.abs(y);\n\t\t\tconst t = Math.max(-z, 0.0);\n\t\t\tx -= x >= 0 ? t : -t;\n\t\t\ty -= y >= 0 ? t : -t;\n\t\t\tconst h = maxInt / Math.hypot(x, y, z);\n\t\t\tdst[i + 0] = Math.round(x * h);\n\t\t\tdst[i + 1] = Math.round(y * h);\n\t\t\tdst[i + 2] = Math.round(z * h);\n\t\t\t// keep dst[i + 3] as is\n\t\t}\n\t} else if (filter === 'QUATERNION') {\n\t\tassert(byteStride === 8);\n\n\t\tconst dst = new Int16Array(target.buffer);\n\n\t\tfor (let i = 0; i < 4 * elementCount; i += 4) {\n\t\t\tconst inputW = dst[i + 3];\n\t\t\tconst maxComponent = inputW & 0x03;\n\t\t\tconst s = Math.SQRT1_2 / (inputW | 0x03);\n\t\t\tlet x = dst[i + 0] * s;\n\t\t\tlet y = dst[i + 1] * s;\n\t\t\tlet z = dst[i + 2] * s;\n\t\t\tlet w = Math.sqrt(Math.max(0.0, 1.0 - x ** 2 - y ** 2 - z ** 2));\n\t\t\tdst[i + ((maxComponent + 1) % 4)] = Math.round(x * 32767);\n\t\t\tdst[i + ((maxComponent + 2) % 4)] = Math.round(y * 32767);\n\t\t\tdst[i + ((maxComponent + 3) % 4)] = Math.round(z * 32767);\n\t\t\tdst[i + ((maxComponent + 0) % 4)] = Math.round(w * 32767);\n\t\t}\n\t} else if (filter === 'EXPONENTIAL') {\n\t\tassert((byteStride & 0x03) === 0x00);\n\n\t\tconst src = new Int32Array(target.buffer);\n\t\tconst dst = new Float32Array(target.buffer);\n\t\tfor (let i = 0; i < (byteStride * elementCount) / 4; i++) {\n\t\t\tconst v = src[i],\n\t\t\t\texp = v >> 24,\n\t\t\t\tmantissa = (v << 8) >> 8;\n\t\t\tdst[i] = 2.0 ** exp * mantissa;\n\t\t}\n\t} else if (filter === 'COLOR') {\n\t\tassert(byteStride === 4 || byteStride === 8);\n\n\t\tconst maxInt = (1 << (byteStride * 2)) - 1;\n\n\t\tconst data = byteStride === 4 ? new Uint8Array(target.buffer) : new Uint16Array(target.buffer, 0, elementCount * 4);\n\t\tconst dataSigned = byteStride === 4 ? new Int8Array(target.buffer) : new Int16Array(target.buffer, 0, elementCount * 4);\n\n\t\tfor (let i = 0; i < elementCount * 4; i += 4) {\n\t\t\tconst y = data[i + 0];\n\t\t\tconst co = dataSigned[i + 1];\n\t\t\tconst cg = dataSigned[i + 2];\n\t\t\tconst alphaInput = data[i + 3];\n\n\t\t\t// Recover scale from alpha high bit - find highest bit set\n\t\t\tconst alphaBit = 31 - Math.clz32(alphaInput);\n\t\t\tconst as = (1 << (alphaBit + 1)) - 1;\n\n\t\t\t// YCoCg to RGB conversion\n\t\t\tconst r = y + co - cg;\n\t\t\tconst g = y + cg;\n\t\t\tconst b = y - co - cg;\n\n\t\t\t// Expand alpha by one bit, replicating last bit\n\t\t\tlet a = alphaInput & (as >> 1);\n\t\t\ta = (a << 1) | (a & 1);\n\n\t\t\t// Scale to full range\n\t\t\tconst ss = maxInt / as;\n\n\t\t\t// Store result\n\t\t\tdata[i + 0] = Math.round(r * ss);\n\t\t\tdata[i + 1] = Math.round(g * ss);\n\t\t\tdata[i + 2] = Math.round(b * ss);\n\t\t\tdata[i + 3] = Math.round(a * ss);\n\t\t}\n\t}\n};\n\nfunction pushfifo(fifo, n) {\n\tfor (let i = fifo.length - 1; i > 0; i--) fifo[i] = fifo[i - 1];\n\tfifo[0] = n;\n}\n\nMeshoptDecoder.decodeIndexBuffer = (target, count, byteStride, source) => {\n\tassert(source[0] === 0xe1);\n\tassert(count % 3 === 0);\n\tassert(byteStride === 2 || byteStride === 4);\n\n\tlet dst;\n\tif (byteStride === 2) dst = new Uint16Array(target.buffer);\n\telse dst = new Uint32Array(target.buffer);\n\n\tconst triCount = count / 3;\n\n\tlet codeOffs = 0x01;\n\tlet dataOffs = codeOffs + triCount;\n\tlet codeauxOffs = source.length - 0x10;\n\n\tfunction readLEB128() {\n\t\tlet n = 0;\n\t\tfor (let i = 0; ; i += 7) {\n\t\t\tconst b = source[dataOffs++];\n\t\t\tn |= (b & 0x7f) << i;\n\n\t\t\tif (b < 0x80) return n;\n\t\t}\n\t}\n\n\tlet next = 0,\n\t\tlast = 0;\n\tconst edgefifo = new Uint32Array(32);\n\tconst vertexfifo = new Uint32Array(16);\n\n\tfunction decodeIndex(v) {\n\t\treturn (last += dezig(v));\n\t}\n\n\tlet dstOffs = 0;\n\tfor (let i = 0; i < triCount; i++) {\n\t\tconst code = source[codeOffs++];\n\t\tconst b0 = code >>> 4,\n\t\t\tb1 = code & 0x0f;\n\n\t\tif (b0 < 0x0f) {\n\t\t\tconst a = edgefifo[(b0 << 1) + 0],\n\t\t\t\tb = edgefifo[(b0 << 1) + 1];\n\t\t\tlet c = -1;\n\n\t\t\tif (b1 === 0x00) {\n\t\t\t\tc = next++;\n\t\t\t\tpushfifo(vertexfifo, c);\n\t\t\t} else if (b1 < 0x0d) {\n\t\t\t\tc = vertexfifo[b1];\n\t\t\t} else if (b1 === 0x0d) {\n\t\t\t\tc = --last;\n\t\t\t\tpushfifo(vertexfifo, c);\n\t\t\t} else if (b1 === 0x0e) {\n\t\t\t\tc = ++last;\n\t\t\t\tpushfifo(vertexfifo, c);\n\t\t\t} else if (b1 === 0x0f) {\n\t\t\t\tconst v = readLEB128();\n\t\t\t\tc = decodeIndex(v);\n\t\t\t\tpushfifo(vertexfifo, c);\n\t\t\t}\n\n\t\t\t// fifo pushes happen backwards\n\t\t\tpushfifo(edgefifo, b);\n\t\t\tpushfifo(edgefifo, c);\n\t\t\tpushfifo(edgefifo, c);\n\t\t\tpushfifo(edgefifo, a);\n\n\t\t\tdst[dstOffs++] = a;\n\t\t\tdst[dstOffs++] = b;\n\t\t\tdst[dstOffs++] = c;\n\t\t} else {\n\t\t\t// b0 === 0x0F\n\t\t\tlet a = -1,\n\t\t\t\tb = -1,\n\t\t\t\tc = -1;\n\n\t\t\tif (b1 < 0x0e) {\n\t\t\t\tconst e = source[codeauxOffs + b1];\n\t\t\t\tconst z = e >>> 4,\n\t\t\t\t\tw = e & 0x0f;\n\n\t\t\t\ta = next++;\n\n\t\t\t\tif (z === 0x00) b = next++;\n\t\t\t\telse b = vertexfifo[z - 1];\n\n\t\t\t\tif (w === 0x00) c = next++;\n\t\t\t\telse c = vertexfifo[w - 1];\n\n\t\t\t\tpushfifo(vertexfifo, a);\n\t\t\t\tif (z === 0x00) pushfifo(vertexfifo, b);\n\t\t\t\tif (w === 0x00) pushfifo(vertexfifo, c);\n\t\t\t} else {\n\t\t\t\tconst e = source[dataOffs++];\n\t\t\t\tif (e === 0x00) next = 0;\n\n\t\t\t\tconst z = e >>> 4,\n\t\t\t\t\tw = e & 0x0f;\n\n\t\t\t\tif (b1 === 0x0e) a = next++;\n\t\t\t\telse a = decodeIndex(readLEB128());\n\n\t\t\t\tif (z === 0x00) b = next++;\n\t\t\t\telse if (z === 0x0f) b = decodeIndex(readLEB128());\n\t\t\t\telse b = vertexfifo[z - 1];\n\n\t\t\t\tif (w === 0x00) c = next++;\n\t\t\t\telse if (w === 0x0f) c = decodeIndex(readLEB128());\n\t\t\t\telse c = vertexfifo[w - 1];\n\n\t\t\t\tpushfifo(vertexfifo, a);\n\t\t\t\tif (z === 0x00 || z === 0x0f) pushfifo(vertexfifo, b);\n\t\t\t\tif (w === 0x00 || w === 0x0f) pushfifo(vertexfifo, c);\n\t\t\t}\n\n\t\t\tpushfifo(edgefifo, a);\n\t\t\tpushfifo(edgefifo, b);\n\t\t\tpushfifo(edgefifo, b);\n\t\t\tpushfifo(edgefifo, c);\n\t\t\tpushfifo(edgefifo, c);\n\t\t\tpushfifo(edgefifo, a);\n\n\t\t\tdst[dstOffs++] = a;\n\t\t\tdst[dstOffs++] = b;\n\t\t\tdst[dstOffs++] = c;\n\t\t}\n\t}\n};\n\nMeshoptDecoder.decodeIndexSequence = (target, count, byteStride, source) => {\n\tassert(source[0] === 0xd1);\n\tassert(byteStride === 2 || byteStride === 4);\n\n\tlet dst;\n\tif (byteStride === 2) dst = new Uint16Array(target.buffer);\n\telse dst = new Uint32Array(target.buffer);\n\n\tlet dataOffs = 0x01;\n\n\tfunction readLEB128() {\n\t\tlet n = 0;\n\t\tfor (let i = 0; ; i += 7) {\n\t\t\tconst b = source[dataOffs++];\n\t\t\tn |= (b & 0x7f) << i;\n\n\t\t\tif (b < 0x80) return n;\n\t\t}\n\t}\n\n\tconst last = new Uint32Array(2);\n\n\tfor (let i = 0; i < count; i++) {\n\t\tconst v = readLEB128();\n\t\tconst b = v & 0x01;\n\t\tconst delta = dezig(v >>> 1);\n\t\tdst[i] = last[b] += delta;\n\t}\n};\n\nMeshoptDecoder.decodeGltfBuffer = (target, count, size, source, mode, filter) => {\n\tconst table = {\n\t\tATTRIBUTES: MeshoptDecoder.decodeVertexBuffer,\n\t\tTRIANGLES: MeshoptDecoder.decodeIndexBuffer,\n\t\tINDICES: MeshoptDecoder.decodeIndexSequence,\n\t};\n\tassert(table[mode] !== undefined);\n\ttable[mode](target, count, size, source, filter);\n};\n\nMeshoptDecoder.decodeGltfBufferAsync = (count, size, source, mode, filter) => {\n\tconst target = new Uint8Array(count * size);\n\tMeshoptDecoder.decodeGltfBuffer(target, count, size, source, mode, filter);\n\treturn Promise.resolve(target);\n};\n\n// node.js interface:\n// for (let k in MeshoptDecoder) exports[k] = MeshoptDecoder[k];\n\nexport { MeshoptDecoder };\n"
  },
  {
    "path": "js/meshopt_encoder.d.ts",
    "content": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\nexport type ExpMode = 'Separate' | 'SharedVector' | 'SharedComponent' | 'Clamped';\n\nexport const MeshoptEncoder: {\n\tsupported: boolean;\n\tready: Promise<void>;\n\n\treorderMesh: (indices: Uint32Array, triangles: boolean, optsize: boolean) => [Uint32Array, number];\n\treorderPoints: (positions: Float32Array, positions_stride: number) => Uint32Array;\n\n\tencodeVertexBuffer: (source: Uint8Array, count: number, size: number) => Uint8Array;\n\tencodeVertexBufferLevel: (source: Uint8Array, count: number, size: number, level: number, version?: number) => Uint8Array;\n\tencodeIndexBuffer: (source: Uint8Array, count: number, size: number) => Uint8Array;\n\tencodeIndexSequence: (source: Uint8Array, count: number, size: number) => Uint8Array;\n\n\tencodeGltfBuffer: (source: Uint8Array, count: number, size: number, mode: string, version?: number) => Uint8Array;\n\n\tencodeFilterOct: (source: Float32Array, count: number, stride: number, bits: number) => Uint8Array;\n\tencodeFilterQuat: (source: Float32Array, count: number, stride: number, bits: number) => Uint8Array;\n\tencodeFilterExp: (source: Float32Array, count: number, stride: number, bits: number, mode?: ExpMode) => Uint8Array;\n\tencodeFilterColor: (source: Float32Array, count: number, stride: number, bits: number) => Uint8Array;\n};\n"
  },
  {
    "path": "js/meshopt_encoder.js",
    "content": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\nvar MeshoptEncoder = (function () {\n\t// Built with clang version 19.1.5-wasi-sdk\n\t// Built from meshoptimizer 1.0\n\tvar wasm =\n\t\t'b9H79Tebbbe9ok9Geueu9Geub9Gbb9Gruuuuuuueu9Gvuuuuueu9Gduueu9Gluuuueu9Gvuuuuub9Gouuuuuub9Gluuuub9GiuuueuiE8AdilveoveovrrwrrrDDoDrbqqbelve9Weiiviebeoweuec;G:Qdkr:PlCo9TW9T9VV95dbH9F9F939H79T9F9J9H229F9Jt9VV7bb8F9TW79O9V9Wt9FW9U9J9V9KW9wWVtW949c919M9MWV9mW4W2be8A9TW79O9V9Wt9FW9U9J9V9KW9wWVtW949c919M9MWVbd8F9TW79O9V9Wt9FW9U9J9V9KW9wWVtW949c919M9MWV9c9V919U9KbiE9TW79O9V9Wt9FW9U9J9V9KW9wWVtW949wWV79P9V9UblY9TW79O9V9Wt9FW9U9J9V9KW69U9KW949c919M9MWVbv8E9TW79O9V9Wt9FW9U9J9V9KW69U9KW949c919M9MWV9c9V919U9Kbo8A9TW79O9V9Wt9FW9U9J9V9KW69U9KW949wWV79P9V9UbrE9TW79O9V9Wt9FW9U9J9V9KW69U9KW949tWG91W9U9JWbwa9TW79O9V9Wt9FW9U9J9V9KW69U9KW949tWG91W9U9JW9c9V919U9KbDL9TW79O9V9Wt9FW9U9J9V9KWS9P2tWV9p9JtbqK9TW79O9V9Wt9FW9U9J9V9KWS9P2tWV9r919HtbkL9TW79O9V9Wt9FW9U9J9V9KWS9P2tWVT949WbxY9TW79O9V9Wt9FW9U9J9V9KWS9P2tWVJ9V29VVbmE9TW79O9V9Wt9F9V9Wt9P9T9P96W9wWVtW94J9H9J9OWbza9TW79O9V9Wt9F9V9Wt9P9T9P96W9wWVtW94J9H9J9OW9ttV9P9WbHa9TW79O9V9Wt9F9V9Wt9P9T9P96W9wWVtW94SWt9J9O9sW9T9H9WbOK9TW79O9V9Wt9F79W9Ht9P9H29t9VVt9sW9T9H9WbAl79IV9RbXDwebcekdKYq:p28Adbk:Bhdhud9:8Jjjjjbc;qw9Rgr8KjjjjbcbhwdnaeTmbabcbyd;m:kjjbaoaocb9iEgDc:GeV86bbarc;adfcbcjdz:xjjjb8AdnaiTmbarc;adfadalz:wjjjb8Akarc;abfalfcbcbcjdal9RalcFe0Ez:xjjjb8Aarc;abfarc;adfalz:wjjjb8AarcUf9cb83ibarc8Wf9cb83ibarcyf9cb83ibarcaf9cb83ibarcKf9cb83ibarczf9cb83ibar9cb83iwar9cb83ibcj;abal9Uc;WFbGcjdalca0Ehqdnaicd6mbavcd9imbaDTmbadcefhkaqci2gxal2hmarc;alfclfhParc;qlfceVhsarc;qofclVhzarc;qofcKfhHarc;qofczfhOcbhAincdhCcbhodnavci6mbaH9cb83ibaO9cb83ibar9cb83i;yoar9cb83i;qoadaAfgoybbhXcbhQincbhwcbhLdninaoalfhKaoybbgYaX7aLVhLawcP0meaKhoaYhXawcefgwaQfai6mbkkcbhXarc;qofhwincwh8AcwhEdnaLaX93gocFeGg3cs0mbclhEa3ci0mba3cb9hcethEkdnaocw4cFeGg3cs0mbclh8Aa3ci0mba3cb9hceth8Aka8AaEfh3awydbh5cwh8AcwhEdnaocz4cFeGg8Ecs0mbclhEa8Eci0mba8Ecb9hcethEka3a5fh3dnaocFFFFb0mbclh8AaocFFF8F0mbaocFFFr0ceth8Akawa3aEfa8AfBdbawclfhwaXcefgXcw9hmbkaKhoaYhXaQczfgQai6mbkcbhocehwazhLinawaoaLydbarc;qofaocdtfydb6EhoaLclfhLawcefgwcw9hmbkcihCkcbh3arc;qlfcbcjdz:xjjjb8Aarc;alfcwfcbBdbar9cb83i;alaoclth8Fadhaaqhhakh5inarc;qlfadcba3cufgoaoa30Eal2falz:wjjjb8Aaiahaiah6Ehgdnaqaia39Ra3aqfai6EgYcsfc9WGgoaY9nmbarc;qofaYfcbaoaY9Rz:xjjjb8Akada3al2fh8Jcbh8Kina8Ka8FVcl4hQarc;alfa8Kcdtfh8LaAh8Mcbh8Nina8NaAfhwdndndndndndna8KPldebidkasa8Mc98GgLfhoa5aLfh8Aarc;qlfawc98GgLfRbbhXcwhwinaoRbbawtaXVhXaocefhoawcwfgwca9hmbkaYTmla8Ncith8Ea8JaLfhEcbhKinaERbbhLcwhoa8AhwinawRbbaotaLVhLawcefhwaocwfgoca9hmbkarc;qofaKfaLaX7aQ93a8E486bba8Aalfh8AaEalfhEaLhXaKcefgKaY9hmbxlkkaYTmia8Mc9:Ghoa8NcitcwGhEarc;qlfawceVfRbbcwtarc;qlfawc9:GfRbbVhLarc;qofhwaghXinawa5aofRbbcwtaaaofRbbVg8AaL9RgLcetaLcztcz91cs47cFFiGaE486bbaoalfhoawcefhwa8AhLa3aXcufgX9hmbxikkaYTmda8Jawfhoarc;qlfawfRbbhLarc;qofhwaghXinawaoRbbg8AaL9RgLcetaLcKtcK91cr4786bbawcefhwaoalfhoa8AhLa3aXcufgX9hmbxdkkaYTmeka8LydbhEcbhKarc;qofhoincdhLcbhwinaLaoawfRbbcb9hfhLawcefgwcz9hmbkclhXcbhwinaXaoawfRbbcd0fhXawcefgwcz9hmbkcwh8Acbhwina8AaoawfRbbcP0fh8Aawcefgwcz9hmbkaLaXaLaX6Egwa8Aawa8A6Egwczawcz6EaEfhEaoczfhoaKczfgKaY6mbka8LaEBdbka8Mcefh8Ma8Ncefg8Ncl9hmbka8Kcefg8KaC9hmbkaaamfhaahaxfhha5amfh5a3axfg3ai6mbkcbhocehwaPhLinawaoaLydbarc;alfaocdtfydb6EhoaLclfhLawcefgXhwaCaX9hmbkaraAcd4fa8FcdVaoaocdSE86bbaAclfgAal6mbkkabaefh8Kabcefhoalcd4gecbaDEhkadcefhOarc;abfceVhHcbhmdndninaiam9nmearc;qofcbcjdz:xjjjb8Aa8Kao9Rak6mdadamal2gwfhxcbh8JaOawfhzaocbakz:xjjjbghakfh5aqaiam9Ramaqfai6Egscsfgocl4cifcd4hCaoc9WGg8LThPindndndndndndndndndndnaDTmbara8Jcd4fRbbgLciGPlbedlbkasTmdaxa8Jfhoarc;abfa8JfRbbhLarc;qofhwashXinawaoRbbg8AaL9RgLcetaLcKtcK91cr4786bbawcefhwaoalfhoa8AhLaXcufgXmbxikkasTmia8JcitcwGhEarc;abfa8JceVfRbbcwtarc;abfa8Jc9:GgofRbbVhLaxaofhoarc;qofhwashXinawao8Vbbg8AaL9RgLcetaLcztcz91cs47cFFiGaE486bbawcefhwaoalfhoa8AhLaXcufgXmbxdkkaHa8Jc98GgEfhoazaEfh8Aarc;abfaEfRbbhXcwhwinaoRbbawtaXVhXaocefhoawcwfgwca9hmbkasTmbaLcl4hYa8JcitcKGh3axaEfhEcbhKinaERbbhLcwhoa8AhwinawRbbaotaLVhLawcefhwaocwfgoca9hmbkarc;qofaKfaLaX7aY93a3486bba8Aalfh8AaEalfhEaLhXaKcefgKas9hmbkkaDmbcbhoxlka8LTmbcbhodninarc;qofaofgwcwf8Pibaw8Pib:e9qTmeaoczfgoa8L9pmdxbkkdnavmbcehoxikcbhEaChKaChYinarc;qofaEfgocwf8Pibhyao8Pibh8PcdhLcbhwinaLaoawfRbbcb9hfhLawcefgwcz9hmbkclhXcbhwinaXaoawfRbbcd0fhXawcefgwcz9hmbkcwh8Acbhwina8AaoawfRbbcP0fh8Aawcefgwcz9hmbkaLaXaLaX6Egoa8Aaoa8A6Egoczaocz6EaYfhYaocucbaya8P:e9cb9sEgwaoaw6EaKfhKaEczfgEa8L9pmdxbkkaha8Jcd4fgoaoRbbcda8JcetcoGtV86bbxikdnaKas6mbaYas6mbaha8Jcd4fgoaoRbbcia8JcetcoGtV86bba8Ka59Ras6mra5arc;qofasz:wjjjbasfh5xikaKaY9phokaha8Jcd4fgwawRbbaoa8JcetcoGtV86bbka8Ka59RaC6mla5cbaCz:xjjjbgAaCfhYdndna8LmbaPhoxekdna8KaY9RcK9pmbaPhoxekaocdtc:q1jjbfcj1jjbaDEg5ydxggcetc;:FFFeGh8Fcuh3cuagtcu7cFeGhacbh8Marc;qofhLinarc;qofa8MfhQczhEdndndnagPDbeeeeeeedekcucbaQcwf8PibaQ8Pib:e9cb9sEhExekcbhoa8FhEinaEaaaLaofRbb9nfhEaocefgocz9hmbkkcih8Ecbh8Ainczhwdndndna5a8AcdtfydbgKPDbeeeeeeedekcucbaQcwf8PibaQ8Pib:e9cb9sEhwxekaKcetc;:FFFeGhwcuaKtcu7cFeGhXcbhoinawaXaLaofRbb9nfhwaocefgocz9hmbkkdndnawaE6mbaKa39hmeawaE9hmea5a8EcdtfydbcwSmeka8Ah8EawhEka8Acefg8Aci9hmbkaAa8Mco4fgoaoRbba8Ea8Mci4coGtV86bbdndndna5a8Ecdtfydbg3PDdbbbbbbbebkdncwa39Tg8ETmbcua3tcu7hwdndna3ceSmbcbh8NaLhQinaQhoa8Eh8AcbhXinaoRbbgEawcFeGgKaEaK6EaXa3tVhXaocefhoa8Acufg8AmbkaYaX86bbaQa8EfhQaYcefhYa8Na8Efg8Ncz6mbxdkkcbh8NaLhQinaQhoa8Eh8AcbhXinaoRbbgEawcFeGgKaEaK6EaXcetVhXaocefhoa8Acufg8AmbkaYaX:T9cFe:d9c:c:qj:bw9:9c:q;c1:I1e:d9c:b:c:e1z9:9ca188bbaQa8EfhQaYcefhYa8Na8Efg8Ncz6mbkkcbhoinaYaLaofRbbgX86bbaYaXawcFeG9pfhYaocefgocz9hmbxikkdna3ceSmbinaYcb86bbaYcefhYxbkkinaYcb86bbaYcefhYxbkkaYaQ8Pbb83bbaYcwfaQcwf8Pbb83bbaYczfhYka8Mczfg8Ma8L9pgomeaLczfhLa8KaY9RcK9pmbkkaoTmlaYh5aYTmlka8Jcefg8Jal9hmbkarc;abfaxascufal2falz:wjjjb8Aasamfhma5hoa5mbkcbhwxdkdna8Kao9RakalfgwcKcaaDEgLawaL0EgX9pmbcbhwxdkdnawaL9pmbaocbaXaw9Rgwz:xjjjbawfhokaoarc;adfalz:wjjjbalfhodnaDTmbaoaraez:wjjjbaefhokaoab9Rhwxekcbhwkarc;qwf8Kjjjjbawk5babaeadaialcdcbyd;m:kjjbz:bjjjbk9reduaecd4gdaefgicaaica0Eabcj;abae9Uc;WFbGcjdaeca0Egifcufai9Uae2aiadfaicl4cifcd4f2fcefkmbcbabBd;m:kjjbk:Ese5u8Jjjjjbc;ae9Rgl8Kjjjjbcbhvdnaici9UgocHfae0mbabcbyd;q:kjjbgrc;GeV86bbalc;abfcFecjez:xjjjb8AalcUfgw9cu83ibalc8WfgD9cu83ibalcyfgq9cu83ibalcafgk9cu83ibalcKfgx9cu83ibalczfgm9cu83ibal9cu83iwal9cu83ibabaefc9WfhPabcefgsaofhednaiTmbcmcsarcb9kgzEhHcbhOcbhAcbhCcbhXcbhQindnaeaP9nmbcbhvxikaQcufhvadaCcdtfgLydbhKaLcwfydbhYaLclfydbh8AcbhEdndndninalc;abfavcsGcitfgoydlh3dndndnaoydbgoaK9hmba3a8ASmekdnaoa8A9hmba3aY9hmbaEcefhExekaoaY9hmea3aK9hmeaEcdfhEkaEc870mdaXcufhvaLaEciGcx2goc;i1jjbfydbcdtfydbh3aLaoc;e1jjbfydbcdtfydbh8AaLaoc;a1jjbfydbcdtfydbhKcbhodnindnalavcsGcdtfydba39hmbaohYxdkcuhYavcufhvaocefgocz9hmbkkaOa3aOSgvaYce9iaYaH9oVgoGfhOdndndncbcsavEaYaoEgvcs9hmbarce9imba3a3aAa3cefaASgvEgAcefSmecmcsavEhvkasavaEcdtc;WeGV86bbavcs9hmea3aA9Rgvcetavc8F917hvinaeavcFb0crtavcFbGV86bbaecefheavcje6hoavcr4hvaoTmbka3hAxvkcPhvasaEcdtcPV86bba3hAkavTmiavaH9omicdhocehEaQhYxlkavcufhvaEclfgEc;ab9hmbkkdnaLceaYaOSceta8AaOSEcx2gvc;a1jjbfydbcdtfydbgKTaLavc;e1jjbfydbcdtfydbg8AceSGaLavc;i1jjbfydbcdtfydbg3cdSGaOcb9hGazGg5ce9hmbaw9cu83ibaD9cu83ibaq9cu83ibak9cu83ibax9cu83ibam9cu83ibal9cu83iwal9cu83ibcbhOkcbhEaXcufgvhodnindnalaocsGcdtfydba8A9hmbaEhYxdkcuhYaocufhoaEcefgEcz9hmbkkcbhodnindnalavcsGcdtfydba39hmbaohExdkcuhEavcufhvaocefgocz9hmbkkaOaKaOSg8EfhLdndnaYcm0mbaYcefhYxekcbcsa8AaLSgvEhYaLavfhLkdndnaEcm0mbaEcefhExekcbcsa3aLSgvEhEaLavfhLkc9:cua8EEh8FcbhvaEaYcltVgacFeGhodndndninavc:W1jjbfRbbaoSmeavcefgvcz9hmbxdkka5aKaO9havcm0VVmbasavc;WeV86bbxekasa8F86bbaeaa86bbaecefhekdna8EmbaKaA9Rgvcetavc8F917hvinaeavcFb0gocrtavcFbGV86bbavcr4hvaecefheaombkaKhAkdnaYcs9hmba8AaA9Rgvcetavc8F917hvinaeavcFb0gocrtavcFbGV86bbavcr4hvaecefheaombka8AhAkdnaEcs9hmba3aA9Rgvcetavc8F917hvinaeavcFb0gocrtavcFbGV86bbavcr4hvaecefheaombka3hAkalaXcdtfaKBdbaXcefcsGhvdndnaYPzbeeeeeeeeeeeeeebekalavcdtfa8ABdbaXcdfcsGhvkdndnaEPzbeeeeeeeeeeeeeebekalavcdtfa3BdbavcefcsGhvkcihoalc;abfaQcitfgEaKBdlaEa8ABdbaQcefcsGhYcdhEavhXaLhOxekcdhoalaXcdtfa3BdbcehEaXcefcsGhXaQhYkalc;abfaYcitfgva8ABdlava3Bdbalc;abfaQaEfcsGcitfgva3BdlavaKBdbascefhsaQaofcsGhQaCcifgCai6mbkkdnaeaP9nmbcbhvxekcbhvinaeavfavc:W1jjbfRbb86bbavcefgvcz9hmbkaeab9Ravfhvkalc;aef8KjjjjbavkZeeucbhddninadcefgdc8F0meceadtae6mbkkadcrfcFeGcr9Uci2cdfabci9U2cHfkmbcbabBd;q:kjjbk:Adewu8Jjjjjbcz9Rhlcbhvdnaicvfae0mbcbhvabcbRb;q:kjjbc;qeV86bbal9cb83iwabcefhoabaefc98fhrdnaiTmbcbhwcbhDindnaoar6mbcbskadaDcdtfydbgqalcwfawaqav9Rgvavc8F91gv7av9Rc507gwcdtfgkydb9Rgvc8E91c9:Gavcdt7awVhvinaoavcFb0gecrtavcFbGV86bbavcr4hvaocefhoaembkakaqBdbaqhvaDcefgDai9hmbkkdnaoar9nmbcbskaocbBbbaoab9RclfhvkavkBeeucbhddninadcefgdc8F0meceadtae6mbkkadcwfcFeGcr9Uab2cvfk:bvli99dui99ludnaeTmbcuadcetcuftcu7:Zhvdndncuaicuftcu7:ZgoJbbbZMgr:lJbbb9p9DTmbar:Ohwxekcjjjj94hwkcbhicbhDinalclfIdbgrJbbbbJbbjZalIdbgq:lar:lMalcwfIdbgk:lMgr:varJbbbb9BEgrNhxaqarNhrdndnakJbbbb9GTmbaxhqxekJbbjZar:l:tgqaq:maxJbbbb9GEhqJbbjZax:l:tgxax:marJbbbb9GEhrkdndnalcxfIdbgxJbbj:;axJbbj:;9GEgkJbbjZakJbbjZ9FEavNJbbbZJbbb:;axJbbbb9GEMgx:lJbbb9p9DTmbax:Ohmxekcjjjj94hmkdndnaqJbbj:;aqJbbj:;9GEgxJbbjZaxJbbjZ9FEaoNJbbbZJbbb:;aqJbbbb9GEMgq:lJbbb9p9DTmbaq:OhPxekcjjjj94hPkdndnarJbbj:;arJbbj:;9GEgqJbbjZaqJbbjZ9FEaoNJbbbZJbbb:;arJbbbb9GEMgr:lJbbb9p9DTmbar:Ohsxekcjjjj94hskdndnadcl9hmbabaifgzas86bbazcifam86bbazcdfaw86bbazcefaP86bbxekabaDfgzas87ebazcofam87ebazclfaw87ebazcdfaP87ebkalczfhlaiclfhiaDcwfhDaecufgembkkk;hlld99eud99eudnaeTmbdndncuaicuftcu7:ZgvJbbbZMgo:lJbbb9p9DTmbao:Ohixekcjjjj94hikaic;8FiGhrinabcofcicdalclfIdb:lalIdb:l9EgialcwfIdb:lalaicdtfIdb:l9EEgialcxfIdb:lalaicdtfIdb:l9EEgiarV87ebdndnJbbj:;JbbjZalaicdtfIdbJbbbb9DEgoalaicd7cdtfIdbJ;Zl:1ZNNgwJbbj:;awJbbj:;9GEgDJbbjZaDJbbjZ9FEavNJbbbZJbbb:;awJbbbb9GEMgw:lJbbb9p9DTmbaw:Ohqxekcjjjj94hqkabcdfaq87ebdndnalaicefciGcdtfIdbJ;Zl:1ZNaoNgwJbbj:;awJbbj:;9GEgDJbbjZaDJbbjZ9FEavNJbbbZJbbb:;awJbbbb9GEMgw:lJbbb9p9DTmbaw:Ohqxekcjjjj94hqkabaq87ebdndnaoalaicufciGcdtfIdbJ;Zl:1ZNNgoJbbj:;aoJbbj:;9GEgwJbbjZawJbbjZ9FEavNJbbbZJbbb:;aoJbbbb9GEMgo:lJbbb9p9DTmbao:Ohixekcjjjj94hikabclfai87ebabcwfhbalczfhlaecufgembkkk;3viDue99eu8Jjjjjbcjd9Rgo8Kjjjjbadcd4hrdndndndnavcd9hmbadcl6meaohwarhDinawc:CuBdbawclfhwaDcufgDmbkaeTmiadcl6mdarcdthqalhkcbhxinaohwakhDarhminawawydbgPcbaDIdbgs:8cL4cFeGc:cufasJbbbb9BEgzaPaz9kEBdbaDclfhDawclfhwamcufgmmbkakaqfhkaxcefgxaeSmixbkkaeTmdxekaeTmekarcdthkavce9hhqadcl6hdcbhxindndndnaqmbadmdc:CuhDalhwarhminaDcbawIdbgs:8cL4cFeGc:cufasJbbbb9BEgPaDaP9kEhDawclfhwamcufgmmbxdkkc:CuhDdndnavPleddbdkadmdaohwalhmarhPinawcbamIdbgs:8cL4cFeGgzc;:bazc;:b0Ec:cufasJbbbb9BEBdbamclfhmawclfhwaPcufgPmbxdkkadmecbhwarhminaoawfcbalawfIdbgs:8cL4cFeGgPc8AaPc8A0Ec:cufasJbbbb9BEBdbawclfhwamcufgmmbkkadmbcbhwarhPinaDhmdnavceSmbaoawfydbhmkdndnalawfIdbgscjjj;8iamai9RcefgmcLt9R::NJbbbZJbbb:;asJbbbb9GEMgs:lJbbb9p9DTmbas:Ohzxekcjjjj94hzkabawfazcFFFrGamcKtVBdbawclfhwaPcufgPmbkkabakfhbalakfhlaxcefgxae9hmbkkaocjdf8Kjjjjbk:Ylvdud99due99iudnaeTmbceaicufgvthocuaitcu7:Zhrcuavtcu7:Zhwcbhvadcl9hhDcbhqindndnalcwfIdbgkJbbbbakJbbbb9GEgkJbbjZakJbbjZ9FEarNJbbbZMgk:lJbbb9p9DTmbak:Ohixekcjjjj94hikdndnalIdbgkJbbbbakJbbbb9GEgkJbbjZakJbbjZ9FEarNJbbbZMgk:lJbbb9p9DTmbak:Ohdxekcjjjj94hdkadai9Rcd9TgxaifhidndnalclfIdbgkJbbbbakJbbbb9GEgkJbbjZakJbbjZ9FEarNJbbbZMgk:lJbbb9p9DTmbak:Ohdxekcjjjj94hdkadai9Rcd9ThddndnalcxfIdbgkJbbbbakJbbbb9GEgkJbbjZakJbbjZ9FEawNJbbbZMgk:lJbbb9p9DTmbak:Ohmxekcjjjj94hmkadaifhiaoamVhmdndnaDmbabavfgPai86bbaPcifam86bbaPcdfad86bbaPcefax86bbxekabaqfgPai87ebaPcofam87ebaPclfad87ebaPcdfax87ebkalczfhlavclfhvaqcwfhqaecufgembkkk;YqdXui998Jjjjjbc:qd9Rgv8Kjjjjbavc:Sefcbc;Kbz:xjjjb8AcbhodnadTmbcbhoaiTmbdndnabaeSmbaehrxekavcuadcdtgwadcFFFFi0Ecbyd;u:kjjbHjjjjbbgrBd:SeavceBd:mdaraeawz:wjjjb8Akavc:GefcwfcbBdbav9cb83i:Geavc:Gefaradaiavc:Sefz:pjjjbavyd:GehDadci9Ugqcbyd;u:kjjbHjjjjbbheavc:Sefavyd:mdgkcdtfaeBdbavakcefgwBd:mdaecbaqz:xjjjbhxavc:SefawcdtfcuaicdtaicFFFFi0Ecbyd;u:kjjbHjjjjbbgmBdbavakcdfgPBd:mdalc;ebfhsaDheamhwinawalIdbasaeydbgzcwazcw6EcdtfIdbMUdbaeclfheawclfhwaicufgimbkavc:SefaPcdtfcuaqcdtadcFFFF970Ecbyd;u:kjjbHjjjjbbgPBdbdnadci6mbarheaPhwaqhiinawamaeydbcdtfIdbamaeclfydbcdtfIdbMamaecwfydbcdtfIdbMUdbaecxfheawclfhwaicufgimbkkakcifhoalc;ebfhHavc;qbfhOavheavyd:KehAavyd:OehCcbhzcbhwcbhXcehQinaehLcihkarawci2gKcdtfgeydbhsaeclfydbhdabaXcx2fgicwfaecwfydbgYBdbaiclfadBdbaiasBdbaxawfce86bbaOaYBdwaOadBdlaOasBdbaPawcdtfcbBdbdnazTmbcihkaLhiinaOakcdtfaiydbgeBdbakaeaY9haeas9haead9hGGfhkaiclfhiazcufgzmbkkaXcefhXcbhzinaCaAarazaKfcdtfydbcdtgifydbcdtfgYheaDaifgdydbgshidnasTmbdninaeydbawSmeaeclfheaicufgiTmdxbkkaeaYascdtfc98fydbBdbadadydbcufBdbkazcefgzci9hmbkdndnakTmbcuhwJbbbbh8Acbhdavyd:KehYavyd:OehKindndnaDaOadcdtfydbcdtgzfydbgembadcefhdxekadcs0hiamazfgsIdbhEasalcbadcefgdaiEcdtfIdbaHaecwaecw6EcdtfIdbMg3Udba3aE:th3aecdthiaKaYazfydbcdtfheinaPaeydbgzcdtfgsa3asIdbMgEUdbaEa8Aa8AaE9DgsEh8AazawasEhwaeclfheaic98fgimbkkadak9hmbkawcu9hmekaQaq9pmdindnaxaQfRbbmbaQhwxdkaqaQcefgQ9hmbxikkakczakcz6EhzaOheaLhOawcu9hmbkkaocdtavc:Seffc98fhedninaoTmeaeydbcbyd;y:kjjbH:bjjjbbaec98fheaocufhoxbkkavc:qdf8Kjjjjbk;IlevucuaicdtgvaicFFFFi0Egocbyd;u:kjjbHjjjjbbhralalyd9GgwcdtfarBdbalawcefBd9GabarBdbaocbyd;u:kjjbHjjjjbbhralalyd9GgocdtfarBdbalaocefBd9GabarBdlcuadcdtadcFFFFi0Ecbyd;u:kjjbHjjjjbbhralalyd9GgocdtfarBdbalaocefBd9GabarBdwabydbcbavz:xjjjb8Aadci9UhDdnadTmbabydbhoaehladhrinaoalydbcdtfgvavydbcefBdbalclfhlarcufgrmbkkdnaiTmbabydbhlabydlhrcbhvaihoinaravBdbarclfhralydbavfhvalclfhlaocufgombkkdnadci6mbabydlhrabydwhvcbhlinaecwfydbhoaeclfydbhdaraeydbcdtfgwawydbgwcefBdbavawcdtfalBdbaradcdtfgdadydbgdcefBdbavadcdtfalBdbaraocdtfgoaoydbgocefBdbavaocdtfalBdbaecxfheaDalcefgl9hmbkkdnaiTmbabydlheabydbhlinaeaeydbalydb9RBdbalclfhlaeclfheaicufgimbkkkQbabaeadaic;K1jjbz:ojjjbkQbabaeadaic;m:jjjbz:ojjjbk9DeeuabcFeaicdtz:xjjjbhlcbhbdnadTmbindnalaeydbcdtfgiydbcu9hmbaiabBdbabcefhbkaeclfheadcufgdmbkkabk:Vvioud9:du8Jjjjjbc;Wa9Rgl8Kjjjjbcbhvalcxfcbc;Kbz:xjjjb8AalcuadcitgoadcFFFFe0Ecbyd;u:kjjbHjjjjbbgrBdxalceBd2araeadaicezNjjjbalcuaoadcjjjjoGEcbyd;u:kjjbHjjjjbbgwBdzadcdthednadTmbabhiinaiavBdbaiclfhiadavcefgv9hmbkkawaefhDalabBdwalawBdl9cbhqindnadTmbaq9cq9:hkarhvaDhiadheinaiav8Pibak1:NcFrG87ebavcwfhvaicdfhiaecufgembkkalclfaq:NceGcdtfydbhxalclfaq9ce98gq:NceGcdtfydbhmalc;Wbfcbcjaz:xjjjb8AaDhvadhidnadTmbinalc;Wbfav8VebcdtfgeaeydbcefBdbavcdfhvaicufgimbkkcbhvcbhiinalc;WbfavfgeydbhoaeaiBdbaoaifhiavclfgvcja9hmbkadhvdndnadTmbinalc;WbfaDamydbgicetf8VebcdtfgeaeydbgecefBdbaxaecdtfaiBdbamclfhmavcufgvmbkaq9cv9smdcbhvinabawydbcdtfavBdbawclfhwadavcefgv9hmbxdkkaq9cv9smekkclhvdninavc98Smealcxfavfydbcbyd;y:kjjbH:bjjjbbavc98fhvxbkkalc;Waf8Kjjjjbk:Jwliuo99iud9:cbhv8Jjjjjbca9Rgoczfcwfcbyd:8:kjjbBdbaocb8Pd:0:kjjb83izaocwfcbyd;i:kjjbBdbaocb8Pd;a:kjjb83ibaicd4hrdndnadmbJFFuFhwJFFuuhDJFFuuhqJFFuFhkJFFuuhxJFFuFhmxekarcdthPaehsincbhiinaoczfaifgzasaifIdbgwazIdbgDaDaw9EEUdbaoaifgzawazIdbgDaDaw9DEUdbaiclfgicx9hmbkasaPfhsavcefgvad9hmbkaoIdKhDaoIdwhwaoIdChqaoIdlhkaoIdzhxaoIdbhmkdnadTmbJbbbbJbFu9hJbbbbamax:tgmamJbbbb9DEgmakaq:tgkakam9DEgkawaD:tgwawak9DEgw:vawJbbbb9BEhwdnalmbarcdthoindndnaeclfIdbaq:tawNJbbbZMgk:lJbbb9p9DTmbak:Ohixekcjjjj94hikai:S9cC:ghHdndnaeIdbax:tawNJbbbZMgk:lJbbb9p9DTmbak:Ohixekcjjjj94hikaHai:S:ehHdndnaecwfIdbaD:tawNJbbbZMgk:lJbbb9p9DTmbak:Ohixekcjjjj94hikabaHai:T9cy:g:e83ibaeaofheabcwfhbadcufgdmbxdkkarcdthoindndnaeIdbax:tawNJbbbZMgk:lJbbb9p9DTmbak:Ohixekcjjjj94hikai:SgH9ca:gaH9cz:g9cjjj;4s:d:eaH9cFe:d:e9cF:bj;4:pj;ar:d9c:bd9:9c:p;G:d;4j:E;ar:d9cH9:9c;d;H:W:y:m:g;d;Hb:d9cv9:9c;j:KM;j:KM;j:Kd:dhOdndnaeclfIdbaq:tawNJbbbZMgk:lJbbb9p9DTmbak:Ohixekcjjjj94hikai:SgH9ca:gaH9cz:g9cjjj;4s:d:eaH9cFe:d:e9cF:bj;4:pj;ar:d9c:bd9:9c:p;G:d;4j:E;ar:d9cH9:9c;d;H:W:y:m:g;d;Hb:d9cq9:9cM;j:KM;j:KM;jl:daO:ehOdndnaecwfIdbaD:tawNJbbbZMgk:lJbbb9p9DTmbak:Ohixekcjjjj94hikabaOai:SgH9ca:gaH9cz:g9cjjj;4s:d:eaH9cFe:d:e9cF:bj;4:pj;ar:d9c:bd9:9c:p;G:d;4j:E;ar:d9cH9:9c;d;H:W:y:m:g;d;Hb:d9cC9:9c:KM;j:KM;j:KMD:d:e83ibaeaofheabcwfhbadcufgdmbkkk9teiucbcbyd;C:kjjbgeabcifc98GfgbBd;C:kjjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaik;teeeudndnaeabVciGTmbabhixekdndnadcz9pmbabhixekabhiinaiaeydbBdbaiaeydlBdlaiaeydwBdwaiaeydxBdxaeczfheaiczfhiadc9Wfgdcs0mbkkadcl6mbinaiaeydbBdbaeclfheaiclfhiadc98fgdci0mbkkdnadTmbinaiaeRbb86bbaicefhiaecefheadcufgdmbkkabk:3eedudndnabciGTmbabhixekaecFeGc:b:c:ew2hldndnadcz9pmbabhixekabhiinaialBdxaialBdwaialBdlaialBdbaiczfhiadc9Wfgdcs0mbkkadcl6mbinaialBdbaiclfhiadc98fgdci0mbkkdnadTmbinaiae86bbaicefhiadcufgdmbkkabk9teiucbcbyd;C:kjjbgeabcrfc94GfgbBd;C:kjjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaikTeeucbabcbyd;C:kjjbge9Rcifc98GaefgbBd;C:kjjbdnabZbcztge9nmbabae9RcFFifcz4nb8Akkk;Uddbcjwk;mdbbbbdbbblbbbwbbbbbbbebbbdbbblbbbwbbbbbbbbbbbbbbbb4:h9w9N94:P:gW:j9O:ye9Pbbbbbbebbbdbbbebbbdbbbbbbbdbbbbbbbebbbbbbb:l29hZ;69:9kZ;N;76Z;rg97Z;z;o9xZ8J;B85Z;:;u9yZ;b;k9HZ:2;Z9DZ9e:l9mZ59A8KZ:r;T3Z:A:zYZ79OHZ;j4::8::Y:D9V8:bbbb9s:49:Z8R:hBZ9M9M;M8:L;z;o8:;8:PG89q;x:J878R:hQ8::M:B;e87bbbbbbjZbbjZbbjZ:E;V;N8::Y:DsZ9i;H;68:xd;R8:;h0838:;W:NoZbbbb:WV9O8:uf888:9i;H;68:9c9G;L89;n;m9m89;D8Ko8:bbbbf:8tZ9m836ZS:2AZL;zPZZ818EZ9e:lxZ;U98F8:819E;68:FFuuFFuuFFuuFFuFFFuFFFuFbc;mqkCebbbebbbebbbdbbb9G:vbb'; // embed! wasm\n\n\tvar wasmpack = new Uint8Array([\n\t\t32, 0, 65, 2, 1, 106, 34, 33, 3, 128, 11, 4, 13, 64, 6, 253, 10, 7, 15, 116, 127, 5, 8, 12, 40, 16, 19, 54, 20, 9, 27, 255, 113, 17, 42, 67,\n\t\t24, 23, 146, 148, 18, 14, 22, 45, 70, 69, 56, 114, 101, 21, 25, 63, 75, 136, 108, 28, 118, 29, 73, 115,\n\t]);\n\n\tif (typeof WebAssembly !== 'object') {\n\t\treturn {\n\t\t\tsupported: false,\n\t\t};\n\t}\n\n\tvar instance;\n\n\tvar ready = WebAssembly.instantiate(unpack(wasm), {}).then(function (result) {\n\t\tinstance = result.instance;\n\t\tinstance.exports.__wasm_call_ctors();\n\t\tinstance.exports.meshopt_encodeVertexVersion(1);\n\t\tinstance.exports.meshopt_encodeIndexVersion(1);\n\t});\n\n\tfunction unpack(data) {\n\t\tvar result = new Uint8Array(data.length);\n\t\tfor (var i = 0; i < data.length; ++i) {\n\t\t\tvar ch = data.charCodeAt(i);\n\t\t\tresult[i] = ch > 96 ? ch - 97 : ch > 64 ? ch - 39 : ch + 4;\n\t\t}\n\t\tvar write = 0;\n\t\tfor (var i = 0; i < data.length; ++i) {\n\t\t\tresult[write++] = result[i] < 60 ? wasmpack[result[i]] : (result[i] - 60) * 64 + result[++i];\n\t\t}\n\t\treturn result.buffer.slice(0, write);\n\t}\n\n\tfunction assert(cond) {\n\t\tif (!cond) {\n\t\t\tthrow new Error('Assertion failed');\n\t\t}\n\t}\n\n\tfunction bytes(view) {\n\t\treturn new Uint8Array(view.buffer, view.byteOffset, view.byteLength);\n\t}\n\n\tfunction reorder(fun, indices, vertices, optf) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar ip = sbrk(indices.length * 4);\n\t\tvar rp = sbrk(vertices * 4);\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\tvar indices8 = bytes(indices);\n\t\theap.set(indices8, ip);\n\t\tif (optf) {\n\t\t\toptf(ip, ip, indices.length, vertices);\n\t\t}\n\t\tvar unique = fun(rp, ip, indices.length, vertices);\n\t\t// heap may have grown\n\t\theap = new Uint8Array(instance.exports.memory.buffer);\n\t\tvar remap = new Uint32Array(vertices);\n\t\tnew Uint8Array(remap.buffer).set(heap.subarray(rp, rp + vertices * 4));\n\t\tindices8.set(heap.subarray(ip, ip + indices.length * 4));\n\t\tsbrk(ip - sbrk(0));\n\n\t\tfor (var i = 0; i < indices.length; ++i) indices[i] = remap[indices[i]];\n\n\t\treturn [remap, unique];\n\t}\n\n\tfunction spatialsort(fun, positions, count, stride) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar ip = sbrk(count * 4);\n\t\tvar sp = sbrk(count * stride);\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(bytes(positions), sp);\n\t\tfun(ip, sp, count, stride);\n\t\t// heap may have grown\n\t\theap = new Uint8Array(instance.exports.memory.buffer);\n\t\tvar remap = new Uint32Array(count);\n\t\tnew Uint8Array(remap.buffer).set(heap.subarray(ip, ip + count * 4));\n\t\tsbrk(ip - sbrk(0));\n\t\treturn remap;\n\t}\n\n\tfunction encode(fun, bound, source, count, size, level, version) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar tp = sbrk(bound);\n\t\tvar sp = sbrk(count * size);\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(bytes(source), sp);\n\t\tvar res = fun(tp, bound, sp, count, size, level, version);\n\t\tvar target = new Uint8Array(res);\n\t\ttarget.set(heap.subarray(tp, tp + res));\n\t\tsbrk(tp - sbrk(0));\n\t\treturn target;\n\t}\n\n\tfunction maxindex(source) {\n\t\tvar result = 0;\n\t\tfor (var i = 0; i < source.length; ++i) {\n\t\t\tvar index = source[i];\n\t\t\tresult = result < index ? index : result;\n\t\t}\n\t\treturn result;\n\t}\n\n\tfunction index32(source, size) {\n\t\tassert(size == 2 || size == 4);\n\t\tif (size == 4) {\n\t\t\treturn new Uint32Array(source.buffer, source.byteOffset, source.byteLength / 4);\n\t\t} else {\n\t\t\tvar view = new Uint16Array(source.buffer, source.byteOffset, source.byteLength / 2);\n\t\t\treturn new Uint32Array(view); // copies each element\n\t\t}\n\t}\n\n\tfunction filter(fun, source, count, stride, bits, insize, mode) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar tp = sbrk(count * stride);\n\t\tvar sp = sbrk(count * insize);\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(bytes(source), sp);\n\t\tfun(tp, count, stride, bits, sp, mode);\n\t\tvar target = new Uint8Array(count * stride);\n\t\ttarget.set(heap.subarray(tp, tp + count * stride));\n\t\tsbrk(tp - sbrk(0));\n\t\treturn target;\n\t}\n\n\treturn {\n\t\tready: ready,\n\t\tsupported: true,\n\t\treorderMesh: function (indices, triangles, optsize) {\n\t\t\tvar optf = triangles\n\t\t\t\t? optsize\n\t\t\t\t\t? instance.exports.meshopt_optimizeVertexCacheStrip\n\t\t\t\t\t: instance.exports.meshopt_optimizeVertexCache\n\t\t\t\t: undefined;\n\t\t\treturn reorder(instance.exports.meshopt_optimizeVertexFetchRemap, indices, maxindex(indices) + 1, optf);\n\t\t},\n\t\treorderPoints: function (positions, positions_stride) {\n\t\t\tassert(positions instanceof Float32Array);\n\t\t\tassert(positions.length % positions_stride == 0);\n\t\t\tassert(positions_stride >= 3);\n\t\t\treturn spatialsort(instance.exports.meshopt_spatialSortRemap, positions, positions.length / positions_stride, positions_stride * 4);\n\t\t},\n\t\tencodeVertexBuffer: function (source, count, size) {\n\t\t\tassert(size > 0 && size <= 256);\n\t\t\tassert(size % 4 == 0);\n\t\t\tvar bound = instance.exports.meshopt_encodeVertexBufferBound(count, size);\n\t\t\treturn encode(instance.exports.meshopt_encodeVertexBuffer, bound, source, count, size);\n\t\t},\n\t\tencodeVertexBufferLevel: function (source, count, size, level, version) {\n\t\t\tassert(size > 0 && size <= 256);\n\t\t\tassert(size % 4 == 0);\n\t\t\tassert(level >= 0 && level <= 3);\n\t\t\tassert(version === undefined || version == 0 || version == 1);\n\t\t\tvar bound = instance.exports.meshopt_encodeVertexBufferBound(count, size);\n\t\t\treturn encode(instance.exports.meshopt_encodeVertexBufferLevel, bound, source, count, size, level, version === undefined ? -1 : version);\n\t\t},\n\t\tencodeIndexBuffer: function (source, count, size) {\n\t\t\tassert(size == 2 || size == 4);\n\t\t\tassert(count % 3 == 0);\n\t\t\tvar indices = index32(source, size);\n\t\t\tvar bound = instance.exports.meshopt_encodeIndexBufferBound(count, maxindex(indices) + 1);\n\t\t\treturn encode(instance.exports.meshopt_encodeIndexBuffer, bound, indices, count, 4);\n\t\t},\n\t\tencodeIndexSequence: function (source, count, size) {\n\t\t\tassert(size == 2 || size == 4);\n\t\t\tvar indices = index32(source, size);\n\t\t\tvar bound = instance.exports.meshopt_encodeIndexSequenceBound(count, maxindex(indices) + 1);\n\t\t\treturn encode(instance.exports.meshopt_encodeIndexSequence, bound, indices, count, 4);\n\t\t},\n\t\tencodeGltfBuffer: function (source, count, size, mode, version) {\n\t\t\tvar table = {\n\t\t\t\tATTRIBUTES: this.encodeVertexBufferLevel,\n\t\t\t\tTRIANGLES: this.encodeIndexBuffer,\n\t\t\t\tINDICES: this.encodeIndexSequence,\n\t\t\t};\n\t\t\tassert(table[mode]);\n\t\t\treturn table[mode](source, count, size, /* level= */ 2, version === undefined ? 0 : version);\n\t\t},\n\t\tencodeFilterOct: function (source, count, stride, bits) {\n\t\t\tassert(stride == 4 || stride == 8);\n\t\t\tassert(bits >= 2 && bits <= 16);\n\t\t\treturn filter(instance.exports.meshopt_encodeFilterOct, source, count, stride, bits, 16);\n\t\t},\n\t\tencodeFilterQuat: function (source, count, stride, bits) {\n\t\t\tassert(stride == 8);\n\t\t\tassert(bits >= 4 && bits <= 16);\n\t\t\treturn filter(instance.exports.meshopt_encodeFilterQuat, source, count, stride, bits, 16);\n\t\t},\n\t\tencodeFilterExp: function (source, count, stride, bits, mode) {\n\t\t\tassert(stride > 0 && stride % 4 == 0);\n\t\t\tassert(bits >= 1 && bits <= 24);\n\t\t\tvar table = {\n\t\t\t\tSeparate: 0,\n\t\t\t\tSharedVector: 1,\n\t\t\t\tSharedComponent: 2,\n\t\t\t\tClamped: 3,\n\t\t\t};\n\t\t\treturn filter(instance.exports.meshopt_encodeFilterExp, source, count, stride, bits, stride, mode ? table[mode] : 1);\n\t\t},\n\t\tencodeFilterColor: function (source, count, stride, bits) {\n\t\t\tassert(stride == 4 || stride == 8);\n\t\t\tassert(bits >= 2 && bits <= 16);\n\t\t\treturn filter(instance.exports.meshopt_encodeFilterColor, source, count, stride, bits, 16);\n\t\t},\n\t};\n})();\n\nexport { MeshoptEncoder };\n"
  },
  {
    "path": "js/meshopt_encoder.test.js",
    "content": "import assert from 'assert/strict';\nimport { MeshoptEncoder as encoder } from './meshopt_encoder.js';\nimport { MeshoptDecoder as decoder } from './meshopt_decoder.mjs';\n\nprocess.on('unhandledRejection', (error) => {\n\tconsole.log('unhandledRejection', error);\n\tprocess.exit(1);\n});\n\nfunction bytes(view) {\n\treturn new Uint8Array(view.buffer, view.byteOffset, view.byteLength);\n}\n\nvar tests = {\n\treorderMesh: function () {\n\t\tvar indices = new Uint32Array([4, 2, 5, 3, 1, 4, 0, 1, 3, 1, 2, 4]);\n\n\t\tvar expected = new Uint32Array([0, 1, 2, 3, 1, 0, 4, 3, 0, 5, 3, 4]);\n\n\t\tvar remap = new Uint32Array([5, 3, 1, 4, 0, 2]);\n\n\t\tvar res = encoder.reorderMesh(indices, /* triangles= */ true, /* optsize= */ true);\n\t\tassert.deepEqual(indices, expected);\n\t\tassert.deepEqual(res[0], remap);\n\t\tassert.equal(res[1], 6); // unique\n\t},\n\n\treorderPoints: function () {\n\t\tvar points = new Float32Array([1, 1, 1, 11, 11, 11, 2, 2, 2, 12, 12, 12]);\n\n\t\tvar expected = new Uint32Array([0, 2, 1, 3]);\n\n\t\tvar remap = encoder.reorderPoints(points, 3);\n\t\tassert.deepEqual(remap, expected);\n\t},\n\n\troundtripVertexBuffer: function () {\n\t\tvar data = new Uint8Array(16 * 4);\n\n\t\t// this tests 0/2/4/8 bit groups in one stream\n\t\tfor (var i = 0; i < 16; ++i) {\n\t\t\tdata[i * 4 + 0] = 0;\n\t\t\tdata[i * 4 + 1] = i * 1;\n\t\t\tdata[i * 4 + 2] = i * 2;\n\t\t\tdata[i * 4 + 3] = i * 8;\n\t\t}\n\n\t\tvar encoded = encoder.encodeVertexBuffer(data, 16, 4);\n\t\tassert.equal(encoded[0], 0xa1);\n\n\t\tvar decoded = new Uint8Array(16 * 4);\n\t\tdecoder.decodeVertexBuffer(decoded, 16, 4, encoded);\n\n\t\tassert.deepEqual(decoded, data);\n\t},\n\n\troundtripVertexBufferV1: function () {\n\t\tvar data = new Uint8Array(16 * 4);\n\n\t\t// this tests 0/2/4/8 bit groups in one stream\n\t\tfor (var i = 0; i < 16; ++i) {\n\t\t\tdata[i * 4 + 0] = 0;\n\t\t\tdata[i * 4 + 1] = i * 1;\n\t\t\tdata[i * 4 + 2] = i * 2;\n\t\t\tdata[i * 4 + 3] = i * 8;\n\t\t}\n\n\t\tvar encoded = encoder.encodeVertexBufferLevel(data, 16, 4, 3, /* version= */ 1);\n\t\tassert.equal(encoded[0], 0xa1);\n\n\t\tvar decoded = new Uint8Array(16 * 4);\n\t\tdecoder.decodeVertexBuffer(decoded, 16, 4, encoded);\n\n\t\tassert.deepEqual(decoded, data);\n\t},\n\n\troundtripIndexBuffer: function () {\n\t\tvar data = new Uint32Array([0, 1, 2, 2, 1, 3, 4, 6, 5, 7, 8, 9]);\n\n\t\tvar encoded = encoder.encodeIndexBuffer(bytes(data), data.length, 4);\n\t\tvar decoded = new Uint32Array(data.length);\n\t\tdecoder.decodeIndexBuffer(bytes(decoded), data.length, 4, encoded);\n\n\t\tassert.deepEqual(decoded, data);\n\t},\n\n\troundtripIndexBuffer16: function () {\n\t\tvar data = new Uint16Array([0, 1, 2, 2, 1, 3, 4, 6, 5, 7, 8, 9]);\n\n\t\tvar encoded = encoder.encodeIndexBuffer(bytes(data), data.length, 2);\n\t\tvar decoded = new Uint16Array(data.length);\n\t\tdecoder.decodeIndexBuffer(bytes(decoded), data.length, 2, encoded);\n\n\t\tassert.deepEqual(decoded, data);\n\t},\n\n\troundtripIndexSequence: function () {\n\t\tvar data = new Uint32Array([0, 1, 51, 2, 49, 1000]);\n\n\t\tvar encoded = encoder.encodeIndexSequence(bytes(data), data.length, 4);\n\t\tvar decoded = new Uint32Array(data.length);\n\t\tdecoder.decodeIndexSequence(bytes(decoded), data.length, 4, encoded);\n\n\t\tassert.deepEqual(decoded, data);\n\t},\n\n\troundtripIndexSequence16: function () {\n\t\tvar data = new Uint16Array([0, 1, 51, 2, 49, 1000]);\n\n\t\tvar encoded = encoder.encodeIndexSequence(bytes(data), data.length, 2);\n\t\tvar decoded = new Uint16Array(data.length);\n\t\tdecoder.decodeIndexSequence(bytes(decoded), data.length, 2, encoded);\n\n\t\tassert.deepEqual(decoded, data);\n\t},\n\n\tencodeFilterOct8: function () {\n\t\tvar data = new Float32Array([1, 0, 0, 0, 0, -1, 0, 0, 0.7071068, 0, 0.707168, 1, -0.7071068, 0, -0.707168, 1]);\n\n\t\tvar expected = new Uint8Array([0x7f, 0, 0x7f, 0, 0, 0x81, 0x7f, 0, 0x3f, 0, 0x7f, 0x7f, 0x81, 0x40, 0x7f, 0x7f]);\n\n\t\t// 4 vectors, encode each vector into 4 bytes with 8 bits of precision/component\n\t\tvar encoded = encoder.encodeFilterOct(data, 4, 4, 8);\n\t\tassert.deepEqual(encoded, expected);\n\t},\n\n\tencodeFilterOct12: function () {\n\t\tvar data = new Float32Array([1, 0, 0, 0, 0, -1, 0, 0, 0.7071068, 0, 0.707168, 1, -0.7071068, 0, -0.707168, 1]);\n\n\t\tvar expected = new Uint16Array([0x7ff, 0, 0x7ff, 0, 0x0, 0xf801, 0x7ff, 0, 0x3ff, 0, 0x7ff, 0x7fff, 0xf801, 0x400, 0x7ff, 0x7fff]);\n\n\t\t// 4 vectors, encode each vector into 8 bytes with 12 bits of precision/component\n\t\tvar encoded = encoder.encodeFilterOct(data, 4, 8, 12);\n\t\tassert.deepEqual(encoded, bytes(expected));\n\t},\n\n\tencodeFilterQuat12: function () {\n\t\tvar data = new Float32Array([1, 0, 0, 0, 0, -1, 0, 0, 0.7071068, 0, 0, 0.707168, -0.7071068, 0, 0, -0.707168]);\n\n\t\tvar expected = new Uint16Array([0, 0, 0, 0x7fc, 0, 0, 0, 0x7fd, 0x7ff, 0, 0, 0x7ff, 0x7ff, 0, 0, 0x7ff]);\n\n\t\t// 4 quaternions, encode each quaternion into 8 bytes with 12 bits of precision/component\n\t\tvar encoded = encoder.encodeFilterQuat(data, 4, 8, 12);\n\t\tassert.deepEqual(encoded, bytes(expected));\n\t},\n\n\tencodeFilterExp: function () {\n\t\tvar data = new Float32Array([1, -23.4, -0.1]);\n\n\t\tvar expected = new Uint32Array([0xf7000200, 0xf7ffd133, 0xf7ffffcd]);\n\n\t\t// 1 vector with 3 components (12 bytes), encode each vector into 12 bytes with 15 bits of precision/component\n\t\tvar encoded = encoder.encodeFilterExp(data, 1, 12, 15);\n\t\tassert.deepEqual(encoded, bytes(expected));\n\t},\n\n\tencodeFilterExpMode: function () {\n\t\tvar data = new Float32Array([1, -23.4, -0.1, 11.0]);\n\n\t\tvar expected = new Uint32Array([0xf3002000, 0xf7ffd133, 0xf3fffccd, 0xf7001600]);\n\n\t\t// 2 vectors with 2 components (8 bytes), encode each vector into 8 bytes with 15 bits of precision/component\n\t\tvar encoded = encoder.encodeFilterExp(data, 2, 8, 15, 'SharedComponent');\n\t\tassert.deepEqual(encoded, bytes(expected));\n\t},\n\n\tencodeFilterExpClamp: function () {\n\t\tvar data = new Float32Array([1, -23.4, -0.1]);\n\n\t\tvar expected = new Uint32Array([0xf3002000, 0xf7ffd133, 0xf2fff99a]);\n\n\t\t// 1 vector with 3 components (12 bytes), encode each vector into 12 bytes with 15 bits of precision/component\n\t\t// exponents are separate but clamped to 0\n\t\tvar encoded = encoder.encodeFilterExp(data, 1, 12, 15, 'Clamped');\n\t\tassert.deepEqual(encoded, bytes(expected));\n\t},\n\n\tencodeFilterColor8: function () {\n\t\tvar data = new Float32Array([1, 0, 0, 1, 0, 1, 0, 0.5, 0, 0, 1, 0.25, 0.4, 0.4, 0.4, 0.75]);\n\n\t\tvar expected = new Uint8Array([0x40, 0x7f, 0xc1, 0xff, 0x7f, 0x00, 0x7f, 0xc0, 0x40, 0x81, 0xc0, 0xa0, 0x66, 0x00, 0x00, 0xdf]);\n\n\t\t// 4 vectors, encode each vector into 4 bytes with 8 bits of precision/component\n\t\tvar encoded = encoder.encodeFilterColor(data, 4, 4, 8);\n\t\tassert.deepEqual(encoded, expected);\n\t},\n\n\tencodeFilterColor12: function () {\n\t\tvar data = new Float32Array([1, 0, 0, 1, 0, 1, 0, 0.5, 0, 0, 1, 0.25, 0.4, 0.4, 0.4, 0.75]);\n\n\t\tvar expected = new Uint16Array([\n\t\t\t0x0400, 0x07ff, 0xfc01, 0x0fff, 0x07ff, 0x0000, 0x07ff, 0x0c00, 0x0400, 0xf801, 0xfc00, 0x0a00, 0x0666, 0x0000, 0x0000, 0x0dff,\n\t\t]);\n\n\t\t// 4 vectors, encode each vector into 8 bytes with 12 bits of precision/component\n\t\tvar encoded = encoder.encodeFilterColor(data, 4, 8, 12);\n\t\tassert.deepEqual(encoded, bytes(expected));\n\t},\n\n\tencodeGltfBuffer: function () {\n\t\tvar data = new Uint32Array([0, 1, 2, 2, 1, 3, 4, 6, 5, 7, 8, 9]);\n\n\t\tvar encoded = encoder.encodeGltfBuffer(bytes(data), data.length, 4, 'TRIANGLES');\n\t\tvar decoded = new Uint32Array(data.length);\n\t\tdecoder.decodeGltfBuffer(bytes(decoded), data.length, 4, encoded, 'TRIANGLES');\n\n\t\tassert.equal(encoded[0], 0xe1);\n\t\tassert.deepEqual(decoded, data);\n\t},\n\n\tencodeGltfBufferAttribute: function () {\n\t\tvar data = new Uint32Array([0, 1, 2, 2, 1, 3, 4, 6, 5, 7, 8, 9]);\n\n\t\tvar encoded = encoder.encodeGltfBuffer(bytes(data), data.length, 4, 'ATTRIBUTES');\n\t\tvar decoded = new Uint32Array(data.length);\n\t\tdecoder.decodeGltfBuffer(bytes(decoded), data.length, 4, encoded, 'ATTRIBUTES');\n\n\t\tassert.equal(encoded[0], 0xa0);\n\t\tassert.deepEqual(decoded, data);\n\t},\n\n\tencodeGltfBufferAttributeV1: function () {\n\t\tvar data = new Uint32Array([0, 1, 2, 2, 1, 3, 4, 6, 5, 7, 8, 9]);\n\n\t\tvar encoded = encoder.encodeGltfBuffer(bytes(data), data.length, 4, 'ATTRIBUTES', 1);\n\t\tvar decoded = new Uint32Array(data.length);\n\t\tdecoder.decodeGltfBuffer(bytes(decoded), data.length, 4, encoded, 'ATTRIBUTES');\n\n\t\tassert.equal(encoded[0], 0xa1);\n\t\tassert.deepEqual(decoded, data);\n\t},\n};\n\nPromise.all([encoder.ready, decoder.ready]).then(() => {\n\tvar count = 0;\n\n\tfor (var key in tests) {\n\t\ttests[key]();\n\t\tcount++;\n\t}\n\n\tconsole.log(count, 'tests passed');\n});\n"
  },
  {
    "path": "js/meshopt_simplifier.d.ts",
    "content": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\nexport type Flags = 'LockBorder' | 'Sparse' | 'ErrorAbsolute' | 'Prune' | 'Regularize' | 'Permissive';\n\nexport const MeshoptSimplifier: {\n\tsupported: boolean;\n\tready: Promise<void>;\n\n\tcompactMesh: (indices: Uint32Array) => [Uint32Array, number];\n\n\tsimplify: (\n\t\tindices: Uint32Array,\n\t\tvertex_positions: Float32Array,\n\t\tvertex_positions_stride: number,\n\t\ttarget_index_count: number,\n\t\ttarget_error: number,\n\t\tflags?: Flags[]\n\t) => [Uint32Array, number];\n\n\tsimplifyWithAttributes: (\n\t\tindices: Uint32Array,\n\t\tvertex_positions: Float32Array,\n\t\tvertex_positions_stride: number,\n\t\tvertex_attributes: Float32Array,\n\t\tvertex_attributes_stride: number,\n\t\tattribute_weights: number[],\n\t\tvertex_lock: Uint8Array | null,\n\t\ttarget_index_count: number,\n\t\ttarget_error: number,\n\t\tflags?: Flags[]\n\t) => [Uint32Array, number];\n\n\tsimplifyWithUpdate: (\n\t\tindices: Uint32Array,\n\t\tvertex_positions: Float32Array,\n\t\tvertex_positions_stride: number,\n\t\tvertex_attributes: Float32Array,\n\t\tvertex_attributes_stride: number,\n\t\tattribute_weights: number[],\n\t\tvertex_lock: Uint8Array | null,\n\t\ttarget_index_count: number,\n\t\ttarget_error: number,\n\t\tflags?: Flags[]\n\t) => [number, number];\n\n\tsimplifySloppy: (\n\t\tindices: Uint32Array,\n\t\tvertex_positions: Float32Array,\n\t\tvertex_positions_stride: number,\n\t\tvertex_lock: Uint8Array | null,\n\t\ttarget_index_count: number,\n\t\ttarget_error: number\n\t) => [Uint32Array, number];\n\n\tgetScale: (vertex_positions: Float32Array, vertex_positions_stride: number) => number;\n\n\tsimplifyPoints: (\n\t\tvertex_positions: Float32Array,\n\t\tvertex_positions_stride: number,\n\t\ttarget_vertex_count: number,\n\t\tvertex_colors?: Float32Array,\n\t\tvertex_colors_stride?: number,\n\t\tcolor_weight?: number\n\t) => Uint32Array;\n\n\tsimplifyPrune: (indices: Uint32Array, vertex_positions: Float32Array, vertex_positions_stride: number, target_error: number) => Uint32Array;\n};\n"
  },
  {
    "path": "js/meshopt_simplifier.js",
    "content": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\nvar MeshoptSimplifier = (function () {\n\t// Built with clang version 19.1.5-wasi-sdk\n\t// Built from meshoptimizer 1.0\n\tvar wasm =\n\t\t'b9H79Tebbbe:6eO9Geueu9Geub9Gbb9Gsuuuuuuuuuuuu99uueu9Gvuuuuub9Gruuuuuuub9Gouuuuuue999Gvuuuuueu9Gzuuuuuuuuuuu99uuuub9Gquuuuuuu99uueu9GPuuuuuuuuuuu99uueu9Gquuuuuuuu99ueu9Gruuuuuu99eu9Gwuuuuuu99ueu9Giuuue999Gluuuueu9Gluuuub9GiuuueuiLQdilvorlwDiqkxmPszbHHbelve9Weiiviebeoweuec:G:Pdkr:Bdxo9TW9T9VV95dbH9F9F939H79T9F9J9H229F9Jt9VV7bbz9TW79O9V9Wt9F79P9T9W29P9M95bw8E9TW79O9V9Wt9F79P9T9W29P9M959x9Pt9OcttV9P9I91tW7bD8A9TW79O9V9Wt9F79P9T9W29P9M959x9Pt9O9v9W9K9HtWbqQ9TW79O9V9Wt9F79P9T9W29P9M959t29V9W9W95bkX9TW79O9V9Wt9F79P9T9W29P9M959qV919UWbxQ9TW79O9V9Wt9F79P9T9W29P9M959q9V9P9Ut7bmX9TW79O9V9Wt9F79P9T9W29P9M959t9J9H2WbPa9TW79O9V9Wt9F9V9Wt9P9T9P96W9wWVtW94SWt9J9O9sW9T9H9Wbs59TW79O9V9Wt9F9NW9UWV9HtW9q9V79Pt9P9V9U9sW9T9H9Wbzl79IV9RbHDwebcekdCXqM;YeQdbk;A1er3ue99euE99Que9:r998Jjjjjbcj;sb9Rgs8Kjjjjbcbhzasc:Cefcbc;Kbz:tjjjb8AdnabaeSmbabaeadcdtzMjjjb8AkdnamcdGTmbalcrfci4cbyd1:jjjbHjjjjbbhHasc:Cefasyd;8egecdtfaHBdbasaecefBd;8ecbhlcbhednadTmbabheadhOinaHaeydbci4fcb86bbaeclfheaOcufgOmbkcbhlabheadhOinaHaeydbgAci4fgCaCRbbgCceaAcrGgAtV86bbaCcu7aA4ceGalfhlaeclfheaOcufgOmbkcualcdtalcFFFFi0Ehekaecbyd1:jjjbHjjjjbbhzasc:Cefasyd;8egecdtfazBdbasaecefBd;8ealcd4alfhOcehHinaHgecethHaeaO6mbkcbhXcuaecdtgOaecFFFFi0Ecbyd1:jjjbHjjjjbbhHasc:Cefasyd;8egAcdtfaHBdbasaAcefBd;8eaHcFeaOz:tjjjbhQdnadTmbaecufhLcbhKindndnaQabaXcdtfgYydbgAc:v;t;h;Ev2aLGgOcdtfgCydbgHcuSmbceheinazaHcdtfydbaASmdaOaefhHaecefheaQaHaLGgOcdtfgCydbgHcu9hmbkkazaKcdtfaABdbaCaKBdbaKhHaKcefhKkaYaHBdbaXcefgXad9hmbkkaQcbyd:m:jjjbH:bjjjbbasasyd;8ecufBd;8ekcbh8AcualcefgecdtaecFFFFi0Ecbyd1:jjjbHjjjjbbhXasc:Cefasyd;8egecdtfaXBdbasaXBdNeasaecefBd;8ecuadcitadcFFFFe0Ecbyd1:jjjbHjjjjbbhEasc:Cefasyd;8egecdtfaEBdbasaEBd:yeasaecefBd;8eascNefabadalcbz:cjjjbcualcdtgealcFFFFi0Eg3cbyd1:jjjbHjjjjbbhAasc:Cefasyd;8egHcdtfaABdbasaHcefBd;8ea3cbyd1:jjjbHjjjjbbhKasc:Cefasyd;8egHcdtfaKBdbasaHcefBd;8eaAaKaialavazasc:Cefz:djjjbalcbyd1:jjjbHjjjjbbh5asc:Cefasyd;8egHcdtfa5BdbasaHcefBd;8ea3cbyd1:jjjbHjjjjbbhHasc:Cefasyd;8egOcdtfaHBdbasaOcefBd;8ea3cbyd1:jjjbHjjjjbbhOasc:Cefasyd;8egCcdtfaOBdbasaCcefBd;8eaHcFeaez:tjjjbh8EaOcFeaez:tjjjbh8FdnalTmbaEcwfhaindnaXa8AgOcefg8AcdtfydbgCaXaOcdtgefydbgHSmbaCaH9RhhaEaHcitfhga8Faefh8Ja8Eaefh8KcbhQindndnagaQcitfydbgLaO9hmba8KaOBdba8JaOBdbxekdnaXaLcdtg8LfgeclfydbgHaeydbgeSmbaEaecitgCfydbaOSmeaHae9Rh8Maecu7aHfhYaaaCfhHcbheinaYaeSmeaecefheaHydbhCaHcwfhHaCaO9hmbkaea8M6meka8Fa8LfgeaOaLaeydbcuSEBdba8KaLaOa8KydbcuSEBdbkaQcefgQah9hmbkka8Aal9hmbkaAhHaKhOa8FhCa8EhQcbheindndnaeaHydbgL9hmbdnaeaOydbgL9hmbaQydbhLdnaCydbgYcu9hmbaLcu9hmba5aefcb86bbxikdnaYcuSmbaLcuSmbaeaYSmbaAaYcdtfydbaAaLcdtfydb9hmba5aefcd86bbxika5aefh8KdnaeaYSmbaeaLSmba8Kce86bbxika8Kcl86bbxdkdnaeaKaLcdtgYfydb9hmbdnaCydbg8KcuSmbaea8KSmbaQydbghcuSmbaeahSmba8FaYfydbggcuSmbagaLSmba8EaYfydbgYcuSmbaYaLSmbdnaAa8KcdtfydbgLaAaYcdtfydb9hmbaLaAahcdtfydbgYSmbaYaAagcdtfydb9hmba5aefcd86bbxlka5aefcl86bbxika5aefcl86bbxdka5aefcl86bbxeka5aefa5aLfRbb86bbkaHclfhHaOclfhOaCclfhCaQclfhQalaecefge9hmbkdnamcaGTmbaEcwfh8Jcbh8Nindndna5a8NfgyRbbg8Pc9:fPibebekdndndnaAa8Ncdtfydbgea8N9hmbdnaqmbcbhgxdkdnazTmbcbhga8NheinagaqazaecdtgefydbfRbbcdGce4VhgaKaefydbgea8N9hmbxikkcbhga8NheinagaqaefRbbcdGce4VhgaKaecdtfydbgea8N9hmbxdkka5aefRbbhexeka8NheindnaXaecdtgafgeclfydbgHaeydbgeSmbaHae9Rh8AaEaecitfh8MaAaafh8Lcbh8Kina8Ma8KcitfydbgYhednindnaXaecdtgLfgeclfydbgHaeydbgeSmbdnaAaEaecitgOfydbcdtfydba8LydbgQ9hmbcehexikaHae9Rhhaecu7aHfhCa8JaOfhHcbheinaCaeSmeaecefheaHydbhOaHcwfhHaAaOcdtfydbaQ9hmbkaeah6hexdkaKaLfydbgeaY9hmbkcbhekagaece7Vhga8Kcefg8Ka8A9hmbkkaKaafydbgea8N9hmbka8PciagceGEhekayae86bbka8Ncefg8Nal9hmbkkdnaqTmbdndnazTmbazheaAhHalhOindnaqaeydbfRbbceGTmba5aHydbfcl86bbkaeclfheaHclfhHaOcufgOmbxdkkaqheaAhHalhOindnaeRbbceGTmba5aHydbfcl86bbkaecefheaHclfhHaOcufgOmbkkaAhealhOa5hHindna5aeydbfRbbcl9hmbaHcl86bbkaeclfheaHcefhHaOcufgOmbkkamceGTmba5healhHindnaeRbbce9hmbaecl86bbkaecefheaHcufgHmbkkcbhIcualcx2alc;v:Q;v:Qe0Ecbyd1:jjjbHjjjjbbhaasc:Cefasyd;8egecdtfaaBdbasaecefBd;8easc:qefcbBdbas9cb83i1eaaaialavazasc1efz:ejjjbh8RdndnaDmbcbhycbhCxekcbhCawhecbhHindnaeIdbJbbbb9ETmbasaCcdtfaHBdbaCcefhCkaeclfheaDaHcefgH9hmbkcuaCal2gecdtaecFFFFi0Ecbyd1:jjjbHjjjjbbhyasc:Cefasyd;8egecdtfayBdbasaecefBd;8ealTmbdnaCmbcbhCxekarcd4h8KdnazTmbaCcdthhcbhXayhYinaoazaXcdtfydba8K2cdtfhLasheaYhHaChOinaHaLaeydbcdtgQfIdbawaQfIdbNUdbaeclfheaHclfhHaOcufgOmbkaYahfhYaXcefgXal9hmbxdkkaCcdthhcbhXayhYinaoaXa8K2cdtfhLasheaYhHaChOinaHaLaeydbcdtgQfIdbawaQfIdbNUdbaeclfheaHclfhHaOcufgOmbkaYahfhYaXcefgXal9hmbkkcualc8S2gHalc;D;O;f8U0EgQcbyd1:jjjbHjjjjbbheasc:Cefasyd;8egOcdtfaeBdbasaOcefBd;8eaecbaHz:tjjjbh8ScbhDcbh8KdnaCTmbcbhIaQcbyd1:jjjbHjjjjbbh8Kasc:Cefasyd;8egecdtfa8KBdbasaecefBd;8ea8KcbaHz:tjjjb8AcuaCal2gecltgHaecFFFFb0Ecbyd1:jjjbHjjjjbbhDasc:Cefasyd;8egecdtfaDBdbasaecefBd;8eaDcbaHz:tjjjb8AamcjjjjdGTmbcualcltgealcFFFFb0Ecbyd1:jjjbHjjjjbbhIasc:Cefasyd;8egHcdtfaIBdbasaHcefBd;8eaIcbaez:tjjjb8AkdnadTmbcbhLabhHinaaaHclfydbgXcx2fgeIdbaaaHydbgYcx2fgOIdbgR:tg8UaaaHcwfydbghcx2fgQIdlaOIdlg8V:tg8WNaQIdbaR:tg8XaeIdla8V:tg8YN:tg8Zh80aeIdwaOIdwg81:tgBa8XNaQIdwa81:tg83a8UN:tgUh8Xa8Ya83Na8WaBN:tg8Yh8Udna8Za8ZNa8Ya8YNaUaUNMM:rgBJbbbb9EgOTmba8ZaB:vh80aUaB:vh8Xa8YaB:vh8Uka8SaAaYcdtfydbgQc8S2fgea8UaB:rg8Wa8UNNg85aeIdbMUdbaea8Xa8Wa8XNg86Ng87aeIdlMUdlaea80a8Wa80Ng83Ng88aeIdwMUdwaea86a8UNg86aeIdxMUdxaea83a8UNg89aeIdzMUdzaea83a8XNg8:aeIdCMUdCaea8Ua8Wa80a81Na8UaRNa8Va8XNMM:mgZNg83Ng8UaeIdKMUdKaea8Xa83Ng8XaeId3MUd3aea80a83Ng80aeIdaMUdaaea83aZNg83aeId8KMUd8Kaea8WaeIdyMUdya8SaAaXcdtfydbgXc8S2fgea85aeIdbMUdbaea87aeIdlMUdlaea88aeIdwMUdwaea86aeIdxMUdxaea89aeIdzMUdzaea8:aeIdCMUdCaea8UaeIdKMUdKaea8XaeId3MUd3aea80aeIdaMUdaaea83aeId8KMUd8Kaea8WaeIdyMUdya8SaAahcdtfydbgYc8S2fgea85aeIdbMUdbaea87aeIdlMUdlaea88aeIdwMUdwaea86aeIdxMUdxaea89aeIdzMUdzaea8:aeIdCMUdCaea8UaeIdKMUdKaea8XaeId3MUd3aea80aeIdaMUdaaea83aeId8KMUd8Kaea8WaeIdyMUdydnaITmbdnaOTmba8ZaB:vh8ZaUaB:vhUa8YaB:vh8YkaIaQcltfgeaBJbbbZNg8UaUNg8WaeIdlMUdlaea8Ua8ZNg8XaeIdwMUdwaea8Ua8YNg80aeIdbMUdbaea8UaR:ma8YNaUa8VN:ta81a8ZN:tNg8UaeIdxMUdxaIaXcltfgea8WaeIdlMUdlaea8XaeIdwMUdwaea80aeIdbMUdbaea8UaeIdxMUdxaIaYcltfgea8WaeIdlMUdlaea8XaeIdwMUdwaea80aeIdbMUdbaea8UaeIdxMUdxkaHcxfhHaLcifgLad6mbkkdnalTmbJ;n;m;m89J:v:;;w8ZamczGEh8YcbhOaAhQaahHa8SheindnaOaQydb9hmbaecxfgLaLIdbJbbbbMUdbaeczfgLaLIdbJbbbbMUdbaecCfgLaLIdbJbbbbMUdbaea8YaecyfgLIdbg8ZNg8UaeIdbMUdbaeclfgXa8UaXIdbMUdbaecwfgXa8UaXIdbMUdbaecKfgXaXIdbaHIdbg8Xa8UN:tUdbaHcwfIdbh8Waec3fgXaXIdba8UaHclfIdbg80N:tUdbaecafgXaXIdba8Ua8WN:tUdbaec8KfgXIdbhUaLa8Za8UMUdbaXaUa8Ua8Wa8WNa8Xa8XNa80a80NMMNMUdbkaQclfhQaHcxfhHaec8SfhealaOcefgO9hmbkkdnadTmbcbhhabhYinabahcdtfhXcbhHina5aXaHc:G1jjbfydbcdtfydbgOfRbbhedndna5aYaHfydbgQfRbbgLc99fcFeGcpe0mbaec99fcFeGc;:e6mekdnaLcufcFeGce0mba8EaQcdtfydbaO9hmekdnaecufcFeGce0mba8FaOcdtfydbaQ9hmekJbbacJbbacJbbbZaecFeGceSEaLcFeGceSEh88aaaOcx2fgeIdwaaaQcx2fgLIdwgB:tg80:mh86aeIdlaLIdlg83:tg8Z:mh89aeIdbaLIdbgR:tgU:mh8:dnaaaXaHc:K1jjbfydbcdtfydbcx2fgeIdwaB:tg8Va80a80NaUaUNa8Za8ZNMMg8YNa8Va80NaeIdbaR:tg81aUNa8ZaeIdla83:tg85NMMg8Wa80N:tg8Xa8XNa81a8YNa8WaUN:tg8Ua8UNa85a8YNa8Wa8ZN:tg8Wa8WNMM:rg87Jbbbb9ETmba8Xa87:vh8Xa8Wa87:vh8Wa8Ua87:vh8Uka88a8Y:rNg8Ya8XaBNa8UaRNa83a8WNMM:mgZNg87aZNhZa8Xa87Nhna8Wa87Nhca8Ua87Nh9ca8Ya8XNg87a8WNhJa87a8UNh9ea8Ya8WNgTa8UNhSa8Xa87Nh87a8WaTNhTa8Ua8Ya8UNNh9hdnaUa85Na81a89NMg8Xa8XNa8Za8VNa85a86NMg8Ua8UNa80a81Na8Va8:NMg8Wa8WNMM:rg80Jbbbb9ETmba8Xa80:vh8Xa8Wa80:vh8Wa8Ua80:vh8Uka8SaAaQcdtfydbc8S2fgeaeIdba9ha8Ua88a80:rNg80a8UNNMgUMUdbaeaTa8Wa80a8WNg8VNMg81aeIdlMUdlaea87a8Xa80a8XNg8ZNMg85aeIdwMUdwaeaSa8Va8UNMg8VaeIdxMUdxaea9ea8Za8UNMg87aeIdzMUdzaeaJa8Za8WNMg8ZaeIdCMUdCaea9ca8Ua80a8XaBNa8UaRNa83a8WNMMgB:mNg80NMg8UaeIdKMUdKaeaca8Wa80NMg8WaeId3MUd3aeana8Xa80NMg8XaeIdaMUdaaeaZaBa80N:tg80aeId8KMUd8Kaea8YJbbbbMg8YaeIdyMUdya8SaAaOcdtfydbc8S2fgeaUaeIdbMUdbaea81aeIdlMUdlaea85aeIdwMUdwaea8VaeIdxMUdxaea87aeIdzMUdzaea8ZaeIdCMUdCaea8UaeIdKMUdKaea8WaeId3MUd3aea8XaeIdaMUdaaea80aeId8KMUd8Kaea8YaeIdyMUdykaHclfgHcx9hmbkaYcxfhYahcifghad6mbkaCTmbcbhYinJbbbbh8YaaabaYcdtfgeclfydbghcx2fgHIdwaaaeydbggcx2fgOIdwg81:tg8Wa8WNaHIdbaOIdbg85:tg8Xa8XNaHIdlaOIdlg87:tg80a80NMMgRaaaecwfydbgEcx2fgeIdwa81:tg8ZNa8Wa8Wa8ZNa8XaeIdba85:tgUNa80aeIdla87:tgBNMMg8UN:tJbbbbJbbjZaRa8Za8ZNaUaUNaBaBNMMg8VNa8Ua8UN:tg83:va83Jbbbb9BEg83Nh89a8Va8WNa8Za8UN:ta83Nh8:aRaBNa80a8UN:ta83NhZa8Va80NaBa8UN:ta83NhnaRaUNa8Xa8UN:ta83Nhca8Va8XNaUa8UN:ta83Nh9ca8XaBNaUa80N:tg8Ua8UNa80a8ZNaBa8WN:tg8Ua8UNa8WaUNa8Za8XN:tg8Ua8UNMM:rJbbbZNh8UayagaC2g8LcdtfhHayaEaC2g8JcdtfhOayahaC2g8AcdtfhQa81:mhJa87:mh9ea85:mhTcbhLaChXJbbbbhBJbbbbh83JbbbbhRJbbbbh8VJbbbbh81Jbbbbh85Jbbbbh87Jbbbbh88Jbbbbh86inascjdfaLfgecwfa8Ua8:aQIdbaHIdbg8Z:tg80Na89aOIdba8Z:tgUNMg8WNUdbaeclfa8Uana80NaZaUNMg8XNUdbaea8Ua9ca80NacaUNMg80NUdbaecxfa8UaJa8WNa9ea8XNa8ZaTa80NMMMg8ZNUdba8Ua8Wa8XNNa8VMh8Va8Ua8Wa80NNa81Mh81a8Ua8Xa80NNa85Mh85a8Ua8Za8ZNNa8YMh8Ya8Ua8Wa8ZNNaBMhBa8Ua8Xa8ZNNa83Mh83a8Ua80a8ZNNaRMhRa8Ua8Wa8WNNa87Mh87a8Ua8Xa8XNNa88Mh88a8Ua80a80NNa86Mh86aHclfhHaQclfhQaOclfhOaLczfhLaXcufgXmbka8Kagc8S2fgea86aeIdbMUdbaea88aeIdlMUdlaea87aeIdwMUdwaea85aeIdxMUdxaea81aeIdzMUdzaea8VaeIdCMUdCaeaRaeIdKMUdKaea83aeId3MUd3aeaBaeIdaMUdaaea8YaeId8KMUd8Kaea8UaeIdyMUdya8Kahc8S2fgea86aeIdbMUdbaea88aeIdlMUdlaea87aeIdwMUdwaea85aeIdxMUdxaea81aeIdzMUdzaea8VaeIdCMUdCaeaRaeIdKMUdKaea83aeId3MUd3aeaBaeIdaMUdaaea8YaeId8KMUd8Kaea8UaeIdyMUdya8KaEc8S2fgea86aeIdbMUdbaea88aeIdlMUdlaea87aeIdwMUdwaea85aeIdxMUdxaea81aeIdzMUdzaea8VaeIdCMUdCaeaRaeIdKMUdKaea83aeId3MUd3aeaBaeIdaMUdaaea8YaeId8KMUd8Kaea8UaeIdyMUdyaDa8LcltfhXcbhHaChQinaXaHfgeascjdfaHfgOIdbaeIdbMUdbaeclfgLaOclfIdbaLIdbMUdbaecwfgLaOcwfIdbaLIdbMUdbaecxfgeaOcxfIdbaeIdbMUdbaHczfhHaQcufgQmbkaDa8AcltfhXcbhHaChQinaXaHfgeascjdfaHfgOIdbaeIdbMUdbaeclfgLaOclfIdbaLIdbMUdbaecwfgLaOcwfIdbaLIdbMUdbaecxfgeaOcxfIdbaeIdbMUdbaHczfhHaQcufgQmbkaDa8JcltfhXcbhHaChQinaXaHfgeascjdfaHfgOIdbaeIdbMUdbaeclfgLaOclfIdbaLIdbMUdbaecwfgLaOcwfIdbaLIdbMUdbaecxfgeaOcxfIdbaeIdbMUdbaHczfhHaQcufgQmbkaYcifgYad6mbkkcbhOdndnamcwGg9imbJbbbbhRcbh6cbh9kcbh0xekcbh6a3cbyd1:jjjbHjjjjbbh0asc:Cefasyd;8egecdtfa0BdbasaecefBd;8ecua0alabadaAz:fjjjbgQcltaQcjjjjiGEcbyd1:jjjbHjjjjbbh9kasc:Cefasyd;8egecdtfa9kBdbasaecefBd;8ea9kaQa0aaalz:gjjjbJFFuuhRaQTmba9kheaQhHinaeIdbg8UaRaRa8U9EEhRaeclfheaHcufgHmbkaQh6kasydNeh9mdnalTmba9mclfhea9mydbhQa5hHalhLcbhOincbaeydbgXaQ9RaHRbbcpeGEaOfhOaHcefhHaeclfheaXhQaLcufgLmbkaOce4hOkcuadaO9Rcifg9ncx2a9nc;v:Q;v:Qe0Ecbyd1:jjjbHjjjjbbh9oasc:Cefasyd;8egecdtfa9oBdbasaecefBd;8ecua9ncdta9ncFFFFi0Ecbyd1:jjjbHjjjjbbh9pasc:Cefasyd;8egecdtfa9pBdbasaecefBd;8ea3cbyd1:jjjbHjjjjbbh8Pasc:Cefasyd;8egecdtfa8PBdbasaecefBd;8ealcbyd1:jjjbHjjjjbbh9qasc:Cefasyd;8egecdtfa9qBdbasaecefBd;8eaxaxNa8RJbbjZamclGEgnanN:vh88Jbbbbh86dnadak9nmbdna9nci6mbasyd:yeh9raCclth9sa9ocwfh9tJbbbbh87Jbbbbh86inascNefabadalaAz:cjjjbabhgcbh8Ncbh3inaba3cdtfh8LcbheindnaAagaefydbgOcdtghfydbgQaAa8Laec:W1jjbfydbcdtfydbgHcdtgEfydbgLSmba5aHfRbbgYcv2a5aOfRbbgXfc;a1jjbfRbbg8AaXcv2aYfg8Jc;a1jjbfRbbg8MVcFeGTmbdnaLaQ9nmba8Jc;G1jjbfRbbcFeGmekdnaXcufcFeGce0mbaYTmba8EahfydbaH9hmekdnaXTmbaYcufcFeGce0mba8FaEfydbaO9hmeka9oa8Ncx2fgQaHaOa8McFeGgLEBdlaQaOaHaLEBdbaQaLa8AGcb9hBdwa8Ncefh8Nkaeclfgecx9hmbkdna3cifg3ad9pmbagcxfhga8Ncifa9n9nmekka8NTmdcbh8Jina8SaAa9oa8Jcx2fghydbgLcdtgQfydbggc8S2fgeIdwaaahydlgXcx2fgHIdwg8XNaeIdzaHIdbg80NaeIdaMg8Ua8UMMa8XNaeIdlaHIdlg8ZNaeIdCa8XNaeId3Mg8Ua8UMMa8ZNaeIdba80NaeIdxa8ZNaeIdKMg8Ua8UMMa80NaeId8KMMM:lh8UJbbbbJbbjZaeIdyg8W:va8WJbbbb9BEh8Wdndnahydwg8LmbJFFuuh83xekJbbbbJbbjZa8SaAaXcdtfydbc8S2fgeIdygU:vaUJbbbb9BEaeIdwaaaLcx2fgHIdwgUNaeIdzaHIdbg8YNaeIdaMgBaBMMaUNaeIdlaHIdlgBNaeIdCaUNaeId3MgUaUMMaBNaeIdba8YNaeIdxaBNaeIdKMgUaUMMa8YNaeId8KMMM:lNh83ka8Wa8UNhBdnaCTmba8KaLc8S2fgOIdwa8XNaOIdza80NaOIdaMg8Ua8UMMa8XNaOIdla8ZNaOIdCa8XNaOId3Mg8Ua8UMMa8ZNaOIdba80NaOIdxa8ZNaOIdKMg8Ua8UMMa80NaOId8KMMMh8UayaXaC2gYcdtfhHaDaLaC2gEcltfheaOIdyhUaChOinaHIdbg8Wa8WaUNaecxfIdba8XaecwfIdbNa80aeIdbNa8ZaeclfIdbNMMMg8Wa8WM:tNa8UMh8UaHclfhHaeczfheaOcufgOmbkdndna8LmbJbbbbh8Wxeka8KaXc8S2fgOIdwaaaLcx2fgeIdwg80NaOIdzaeIdbg8ZNaOIdaMg8Wa8WMMa80NaOIdlaeIdlgUNaOIdCa80NaOId3Mg8Wa8WMMaUNaOIdba8ZNaOIdxaUNaOIdKMg8Wa8WMMa8ZNaOId8KMMMh8WayaEcdtfhHaDaYcltfheaOIdyh8YaChOinaHIdbg8Xa8Xa8YNaecxfIdba80aecwfIdbNa8ZaeIdbNaUaeclfIdbNMMMg8Xa8XM:tNa8WMh8WaHclfhHaeczfheaOcufgOmbka8W:lh8WkaBa8U:lMhBa83a8WMh83dndndna5aLfRbbc9:fPddbekaKaQfydbgQaLSmbaAaXcdtfydbhEindndna8EaQcdtgYfydbgecuSmbaAaecdtfydbaESmekdna8FaYfydbgecuSmbaAaecdtfydbaESmekaXheka8KaQc8S2fgOIdwaaaecx2fgHIdwg8XNaOIdzaHIdbg80NaOIdaMg8Ua8UMMa8XNaOIdlaHIdlg8ZNaOIdCa8XNaOId3Mg8Ua8UMMa8ZNaOIdba80NaOIdxa8ZNaOIdKMg8Ua8UMMa80NaOId8KMMMh8UayaeaC2cdtfhHaDaQaC2cltfheaOIdyhUaChOinaHIdbg8Wa8WaUNaecxfIdba8XaecwfIdbNa80aeIdbNa8ZaeclfIdbNMMMg8Wa8WM:tNa8UMh8UaHclfhHaeczfheaOcufgOmbkaBa8U:lMhBaKaYfydbgQaL9hmbkka5aXfRbbci9hmea8LTmeaKaXcdtfydbgQaXSmeindndna8EaQcdtgYfydbgecuSmbaAaecdtfydbagSmekdna8FaYfydbgecuSmbaAaecdtfydbagSmekaLheka8KaQc8S2fgOIdwaaaecx2fgHIdwg8XNaOIdzaHIdbg80NaOIdaMg8Ua8UMMa8XNaOIdlaHIdlg8ZNaOIdCa8XNaOId3Mg8Ua8UMMa8ZNaOIdba80NaOIdxa8ZNaOIdKMg8Ua8UMMa80NaOId8KMMMh8UayaeaC2cdtfhHaDaQaC2cltfheaOIdyhUaChOinaHIdbg8Wa8WaUNaecxfIdba8XaecwfIdbNa80aeIdbNa8ZaeclfIdbNMMMg8Wa8WM:tNa8UMh8UaHclfhHaeczfheaOcufgOmbka83a8U:lMh83aKaYfydbgQaX9hmbxdkkdna8Fa8Ea8EaQfydbaXSEaKaQfydbgYcdtfydbgQcu9hmbaKaXcdtfydbhQka8KaYc8S2fgOIdwaaaQcx2fgeIdwg8XNaOIdzaeIdbg80NaOIdaMg8Ua8UMMa8XNaOIdlaeIdlg8ZNaOIdCa8XNaOId3Mg8Ua8UMMa8ZNaOIdba80NaOIdxa8ZNaOIdKMg8Ua8UMMa80NaOId8KMMMh8UayaQaC2ggcdtfhHaDaYaC2gEcltfheaOIdyhUaChOinaHIdbg8Wa8WaUNaecxfIdba8XaecwfIdbNa80aeIdbNa8ZaeclfIdbNMMMg8Wa8WM:tNa8UMh8UaHclfhHaeczfheaOcufgOmbkdndna8LmbJbbbbh8Wxeka8KaQc8S2fgOIdwaaaYcx2fgeIdwg80NaOIdzaeIdbg8ZNaOIdaMg8Wa8WMMa80NaOIdlaeIdlgUNaOIdCa80NaOId3Mg8Wa8WMMaUNaOIdba8ZNaOIdxaUNaOIdKMg8Wa8WMMa8ZNaOId8KMMMh8WayaEcdtfhHaDagcltfheaOIdyh8YaChOinaHIdbg8Xa8Xa8YNaecxfIdba80aecwfIdbNa8ZaeIdbNaUaeclfIdbNMMMg8Xa8XM:tNa8WMh8WaHclfhHaeczfheaOcufgOmbka8W:lh8WkaBa8U:lMhBa83a8WMh83kaha83aBa83aB9DgeEUdwahaLaXaea8Lcb9hGgeEBdlahaXaLaeEBdba8Jcefg8Ja8N9hmbkascjdfcbcj;qbz:tjjjb8Aa9thea8NhHinascjdfaeydbcA4cF8FGgOcFAaOcFA6EcdtfgOaOydbcefBdbaecxfheaHcufgHmbkcbhecbhHinascjdfaefgOydbhQaOaHBdbaQaHfhHaeclfgecj;qb9hmbkcbhea9thHinascjdfaHydbcA4cF8FGgOcFAaOcFA6EcdtfgOaOydbgOcefBdba9paOcdtfaeBdbaHcxfhHa8Naecefge9hmbkadak9RgOci9Uh9udnalTmbcbhea8PhHinaHaeBdbaHclfhHalaecefge9hmbkkcbh9va9qcbalz:tjjjbh9waOcO9Uh9xa9uce4h9ycbh3cbh8Adnina9oa9pa8Acdtfydbcx2fg8JIdwg8Ua889Emea3a9u9pmeJFFuuh8Wdna9ya8N9pmba9oa9pa9ycdtfydbcx2fIdwJbb;aZNh8Wkdna8Ua8W9ETmba8Ua869ETmba3a9x0mdkdna9waAa8Jydlg8Mcdtg9zfgEydbgQfg9ARbba9waAa8Jydbggcdtg9Bfydbgefg9CRbbVmba5agfRbbh9Ddna9maecdtfgHclfydbgOaHydbgHSmbaOaH9RhLaaaQcx2fhYaaaecx2fhha9raHcitfhecbhHceh8Ldnindna8PaeydbcdtfydbgOaQSmba8PaeclfydbcdtfydbgXaQSmbaOaXSmbaaaXcx2fgXIdbaaaOcx2fgOIdbg8X:tg8UahIdlaOIdlg80:tg8YNahIdba8X:tgBaXIdla80:tg8WN:tg8Za8UaYIdla80:tg83NaYIdba8X:tg8Va8WN:tg80Na8WahIdwaOIdwgU:tg81Na8YaXIdwaU:tg8XN:tg8Ya8WaYIdwaU:tg85Na83a8XN:tg8WNa8XaBNa81a8UN:tgUa8Xa8VNa85a8UN:tg8UNMMa8Za8ZNa8Ya8YNaUaUNMMa80a80Na8Wa8WNa8Ua8UNMMN:rJbbj8:N9FmdkaecwfheaHcefgHaL6h8LaLaH9hmbkka8LceGTmba9ycefh9yxekdndndndna9Dc9:fPdebdkagheinaEydbhOdndna8EaecdtgHfydbgecuSmbaAaecdtfydbaOSmekdna8FaHfydbgecuSmbaAaecdtfydbaOSmeka8Mheka8PaHfaeBdbaKaHfydbgeag9hmbxikkdna8Fa8Ea8Ea9Bfydba8MSEaKa9Bfydbggcdtfydbgecu9hmbaKa9zfydbheka8Pa9Bfa8MBdbaeh8Mka8Pagcdtfa8MBdbka9Cce86bba9Ace86bba8JIdwg8Ua86a86a8U9DEh86a9vcefh9vcecda9DceSEa3fh3ka8Acefg8Aa8N9hmbkka9vTmddnalTmbcbhXcbhhindna8PahcdtgefydbgOahSmbaAaOcdtfydbhgdnahaAaefydb9hgEmba8Sagc8S2fgea8Sahc8S2fgHIdbaeIdbMUdbaeaHIdlaeIdlMUdlaeaHIdwaeIdwMUdwaeaHIdxaeIdxMUdxaeaHIdzaeIdzMUdzaeaHIdCaeIdCMUdCaeaHIdKaeIdKMUdKaeaHId3aeId3MUd3aeaHIdaaeIdaMUdaaeaHId8KaeId8KMUd8KaeaHIdyaeIdyMUdyaITmbaIagcltfgeaIahcltfgHIdbaeIdbMUdbaeaHIdlaeIdlMUdlaeaHIdwaeIdwMUdwaeaHIdxaeIdxMUdxkaCTmba8KaOc8S2fgea8Kahc8S2g8LfgHIdbaeIdbMUdbaeaHIdlaeIdlMUdlaeaHIdwaeIdwMUdwaeaHIdxaeIdxMUdxaeaHIdzaeIdzMUdzaeaHIdCaeIdCMUdCaeaHIdKaeIdKMUdKaeaHId3aeId3MUd3aeaHIdaaeIdaMUdaaeaHId8KaeId8KMUd8KaeaHIdyaeIdyMUdya9saO2hYaDhHaChQinaHaYfgeaHaXfgOIdbaeIdbMUdbaeclfgLaOclfIdbaLIdbMUdbaecwfgLaOcwfIdbaLIdbMUdbaecxfgeaOcxfIdbaeIdbMUdbaHczfhHaQcufgQmbkaEmbJbbbbJbbjZa8Sa8LfgeIdyg8U:va8UJbbbb9BEaeIdwaaagcx2fgHIdwg8UNaeIdzaHIdbg8WNaeIdaMg8Xa8XMMa8UNaeIdlaHIdlg8XNaeIdCa8UNaeId3Mg8Ua8UMMa8XNaeIdba8WNaeIdxa8XNaeIdKMg8Ua8UMMa8WNaeId8KMMM:lNg8Ua87a87a8U9DEh87kaXa9sfhXahcefghal9hmbkcbhHa8EheindnaeydbgOcuSmbdnaHa8PaOcdtgQfydbgO9hmbcuhOa8EaQfydbgQcuSmba8PaQcdtfydbhOkaeaOBdbkaeclfhealaHcefgH9hmbkcbhHa8FheindnaeydbgOcuSmbdnaHa8PaOcdtgQfydbgO9hmbcuhOa8FaQfydbgQcuSmba8PaQcdtfydbhOkaeaOBdbkaeclfhealaHcefgH9hmbkka87a86aCEh87cbhHabhecbhOindnaAa8PaeydbcdtfydbgXcdtfydbgQaAa8PaeclfydbcdtfydbgYcdtfydbgLSmbaQaAa8PaecwfydbcdtfydbggcdtfydbghSmbaLahSmbabaHcdtfgQaXBdbaQcwfagBdbaQclfaYBdbaHcifhHkaecxfheaOcifgOad6mbkdndna9imbaHhdxekdnaHak0mbaHhdxekdnaRa879FmbaHhdxekJFFuuhRcbhdabhecbhOindna9ka0aeydbgQcdtfydbcdtfIdbg8Ua879ETmbaeclf8Pdbh9EabadcdtfgLaQBdbaLclfa9E83dba8UaRaRa8U9EEhRadcifhdkaecxfheaOcifgOaH6mbkkadak0mbxdkkascNefabadalaAz:cjjjbkdndnadak0mbadhhxekdna9imbadhhxekdnaRa889FmbadhhxekcehLinaRJbb;aZNg8Ua88a8Ua889DEh8XJbbbbh8Udna6Tmba9khea6hHinaeIdbg8Wa8Ua8Wa8X9FEa8Ua8Wa8U9EEh8UaeclfheaHcufgHmbkkJFFuuhRcbhhabhecbhHindna9ka0aeydbgOcdtfydbcdtfIdbg8Wa8X9ETmbaeclf8Pdbh9EabahcdtfgQaOBdbaQclfa9E83dba8WaRaRa8W9EEhRahcifhhkaecxfheaHcifgHad6mbkdnaLahad9hVceGmbadhhxdka8Ua86a86a8U9DEh86ahak9nmecbhLahhdaRa889FmbkkdnamcjjjjdGTmba9qcbalz:tjjjbh8LdnahTmbabheahhHina8LaeydbgOfce86bba8LaAaOcdtfydbfce86bbaeclfheaHcufgHmbkkascNefabahalaAz:cjjjbdndndnalTmbcbhQasyd:yehEindna8LaQfRbbTmbdna5aQfRbbgecl0mbceaetcQGmekdnaAaQcdtgXfydbgeaQSmbaaaQcx2fgHaaaecx2fge8Pdb83dbaHcwfaecwfydbBdbxeka8SaQc8S2fgLIdyg9ca9cJL:3;rUNg8UMh88aLIdwg9ha8UMhRaLIdlgxa8UMh8VaLIdbg9Fa8UMhUaLIdag9Ga8UaaaQcx2fggIdwg89N:th81aLId3g9Ha8UagIdlg8:N:th85aLIdKg9IagIdbgZa8UN:th8YJbbbbhcaLIdCg9JJbbbbMh87aLIdzg9KJbbbbMhBaLIdxgWJbbbbMh83dndnaCTmbaQhOinJbbbba88a8KaOc8S2fgHIdyg8U:va8UJbbbb9BEh8UaDaOaC2cltfheaHIdaa88Na81Mh81aHId3a88Na85Mh85aHIdKa88Na8YMh8YaHIdCa88Na87Mh87aHIdza88NaBMhBaHIdxa88Na83Mh83aHIdwa88NaRMhRaHIdla88Na8VMh8VaHIdba88NaUMhUaChHina81aecxfIdbg8ZaecwfIdbg8WNa8UN:th81a85a8ZaeclfIdbg8XNa8UN:th85a87a8Wa8XNa8UN:th87aUaeIdbg80a80Na8UN:thUa8Ya8Za80Na8UN:th8YaBa8Wa80Na8UN:thBa83a8Xa80Na8UN:th83aRa8Wa8WNa8UN:thRa8Va8Xa8XNa8UN:th8VaeczfheaHcufgHmbkaKaOcdtfydbgOaQ9hmbkaITmbaIaQcltfgeIdxhSaeIdwhJaeIdlh9eaeIdbh8UxekJbbbbhSJbbbbhJJbbbbh9eJbbbbh8UkaBaU:vg8Xa8YNa81:ta87aBa83aU:vg8WN:tg81a8Va83a8WN:tg8Z:vg80a8Wa8YNa85:tg8VN:th85aJa8Ua8XN:ta9ea8Ua8WN:tg83a80N:tg87aRaBa8XN:ta81a80N:tgB:vgR:mh81a83a8Z:vgJ:mh9ednJbbbba8Ua8UaU:vgTN:ta83aJN:ta87aRN:tg83:la88J:983:g81Ng8U9ETmba81a85Na9ea8VNaTa8YNaS:tMMa83:vhckaU:la8U9ETmba8Z:la8U9ETmbaB:la8U9ETmbaT:macNa8X:ma81acNa85aB:vMgBNa8W:ma9eacNa80:maBNa8Va8Z:vMMg87Na8Y:maU:vMMMh88a9maXfgeclfydbgHaeydbge9RhYaEaecitfhXJbbbbh8UdnaHaeSg8JmbJbbbbh8UaXheaYhOinaaaeclfydbcx2fgHIdwa89:tg8Wa8WNaHIdbaZ:tg8Wa8WNaHIdla8::tg8Wa8WNMMg8Waaaeydbcx2fgHIdwa89:tg8Xa8XNaHIdbaZ:tg8Xa8XNaHIdla8::tg8Xa8XNMMg8Xa8Ua8Ua8X9DEg8Ua8Ua8W9DEh8UaecwfheaOcufgOmbkkaBa89:tg8Wa8WNa88aZ:tg8Wa8WNa87a8::tg8Wa8WNMMa8U:rg8Ua8UN9EmbaLId8Khcdna8JmbcbhOcehLdninaaaXclfydbcx2fgeIdbaaaXydbcx2fgHIdbg8X:tg8Ua8:aHIdlg80:tg8YNaZa8X:tg83aeIdla80:tg8WN:tg8Za8Ua87a80:tgRNa88a8X:tg8Va8WN:tg80Na8Wa89aHIdwgU:tg81Na8YaeIdwaU:tg8XN:tg8Ya8WaBaU:tg85NaRa8XN:tg8WNa8Xa83Na81a8UN:tgUa8Xa8VNa85a8UN:tg8UNMMa8Za8ZNa8Ya8YNaUaUNMMa80a80Na8Wa8WNa8Ua8UNMMN:rJbbj8:N9FmeaXcwfhXaOcefgOaY6hLaYaO9hmbkkaLceGmekJbbbbJbbjZa9c:va9cJbbbb9BEg8Ua9haBNa9Ka88Na9GMg8Wa8WMMaBNaxa87Na9JaBNa9HMg8Wa8WMMa87Na9Fa88NaWa87Na9IMg8Wa8WMMa88NacMMM:lNa8Ua9ha89Na9KaZNa9GMg8Wa8WMMa89Naxa8:Na9Ja89Na9HMg8Wa8WMMa8:Na9FaZNaWa8:Na9IMg8Wa8WMMaZNacMMM:lNJbb;aZNJ:983:g81M9EmbagaBUdwaga87Udlaga88UdbkaQcefgQal9hmbkaCTmecbhLindna8LaLfRbbTmbaAaLcdtgefydbaL9hmba5aLfhEaaaLcx2fhOaKaefh8JayaLaC2cdtfh8AcbhgincuhQdnaERbbci9hmbaLhQa8JydbgeaLSmbayagcdtgHfhXa8AaHfIdbh8UaLhQinaQhHcuhQdnaXaeaC2cdtfIdba8U9CmbaHcuSmbaHhQa8Kaec8S2fIdya8KaHc8S2fIdy9ETmbaehQkaKaecdtfydbgeaL9hmbkkayagcdtfhXaDagcltfhYaLheinaXaeaC2cdtfJbbbbJbbjZa8KaeaQaQcuSEgHc8S2fIdyg8U:va8UJbbbb9BEaYaHaC2cltfgHIdwaOIdwNaHIdbaOIdbNaHIdlaOIdlNMMaHIdxMNUdbaKaecdtfydbgeaL9hmbkagcefggaC9hmbkkaLcefgLalSmixbkkaCmekcbhCkaiavaoarawaCalaaayazasa8Rasc1efa5a8Laqz:hjjjbkdnamcjjjjlGTmbazmbahTmbcbhQabheina5aeydbgAfRbbc3thLaecwfgKydbhHdndna8EaAcdtgYfydbaeclfgXydbgOSmbcbhCa8FaOcdtfydbaA9hmekcjjjj94hCkaeaLaCVaAVBdba5aOfRbbc3thLdndna8EaOcdtfydbaHSmbcbhCa8FaHcdtfydbaO9hmekcjjjj94hCkaXaLaCVaOVBdba5aHfRbbc3thCdndna8EaHcdtfydbaASmbcbhOa8FaYfydbaH9hmekcjjjj94hOkaKaCaOVaHVBdbaecxfheaQcifgQah6mbkkdnazTmbahTmbahheinabazabydbcdtfydbBdbabclfhbaecufgembkkdnaPTmbaPana86:rNUdbkasyd;8egecdtasc:Ceffc98fhHdninaeTmeaHydbcbyd:m:jjjbH:bjjjbbaHc98fhHaecufhexbkkascj;sbf8Kjjjjbahk;Yieouabydlhvabydbclfcbaicdtz:tjjjbhoadci9UhrdnadTmbdnalTmbaehwadhDinaoalawydbcdtfydbcdtfgqaqydbcefBdbawclfhwaDcufgDmbxdkkaehwadhDinaoawydbcdtfgqaqydbcefBdbawclfhwaDcufgDmbkkdnaiTmbcbhDaohwinawydbhqawaDBdbawclfhwaqaDfhDaicufgimbkkdnadci6mbinaecwfydbhwaeclfydbhDaeydbhidnalTmbalawcdtfydbhwalaDcdtfydbhDalaicdtfydbhikavaoaicdtfgqydbcitfaDBdbavaqydbcitfawBdlaqaqydbcefBdbavaoaDcdtfgqydbcitfawBdbavaqydbcitfaiBdlaqaqydbcefBdbavaoawcdtfgwydbcitfaiBdbavawydbcitfaDBdlawawydbcefBdbaecxfhearcufgrmbkkabydbcbBdbk:todDue99aicd4aifhrcehwinawgDcethwaDar6mbkcuaDcdtgraDcFFFFi0Ecbyd1:jjjbHjjjjbbhwaoaoyd9GgqcefBd9GaoaqcdtfawBdbawcFearz:tjjjbhkdnaiTmbalcd4hlaDcufhxcbhminamhDdnavTmbavamcdtfydbhDkcbadaDal2cdtfgDydlgwawcjjjj94SEgwcH4aw7c:F:b:DD2cbaDydbgwawcjjjj94SEgwcH4aw7c;D;O:B8J27cbaDydwgDaDcjjjj94SEgDcH4aD7c:3F;N8N27axGhwamcdthPdndndnavTmbakawcdtfgrydbgDcuSmeadavaPfydbal2cdtfgsIdbhzcehqinaqhrdnadavaDcdtfydbal2cdtfgqIdbaz9CmbaqIdlasIdl9CmbaqIdwasIdw9BmlkarcefhqakawarfaxGgwcdtfgrydbgDcu9hmbxdkkakawcdtfgrydbgDcuSmbadamal2cdtfgsIdbhzcehqinaqhrdnadaDal2cdtfgqIdbaz9CmbaqIdlasIdl9CmbaqIdwasIdw9BmikarcefhqakawarfaxGgwcdtfgrydbgDcu9hmbkkaramBdbamhDkabaPfaDBdbamcefgmai9hmbkkakcbyd:m:jjjbH:bjjjbbaoaoyd9GcufBd9GdnaeTmbaiTmbcbhDaehwinawaDBdbawclfhwaiaDcefgD9hmbkcbhDaehwindnaDabydbgrSmbawaearcdtfgrydbBdbaraDBdbkawclfhwabclfhbaiaDcefgD9hmbkkk:hrdvuv998Jjjjjbca9Rgoczfcwfcbyd11jjbBdbaocb8Pdj1jjb83izaocwfcbydN1jjbBdbaocb8Pd:m1jjb83ibdnadTmbaicd4hrdnabmbdnalTmbcbhwinaealawcdtfydbar2cdtfhDcbhiinaoczfaifgqaDaifIdbgkaqIdbgxaxak9EEUdbaoaifgqakaqIdbgxaxak9DEUdbaiclfgicx9hmbkawcefgwad9hmbxikkarcdthwcbhDincbhiinaoczfaifgqaeaifIdbgkaqIdbgxaxak9EEUdbaoaifgqakaqIdbgxaxak9DEUdbaiclfgicx9hmbkaeawfheaDcefgDad9hmbxdkkdnalTmbcbhwinabawcx2fgiaealawcdtfydbar2cdtfgDIdbUdbaiaDIdlUdlaiaDIdwUdwcbhiinaoczfaifgqaDaifIdbgkaqIdbgxaxak9EEUdbaoaifgqakaqIdbgxaxak9DEUdbaiclfgicx9hmbkawcefgwad9hmbxdkkarcdthlcbhwaehDinabawcx2fgiaeawar2cdtfgqIdbUdbaiaqIdlUdlaiaqIdwUdwcbhiinaoczfaifgqaDaifIdbgkaqIdbgxaxak9EEUdbaoaifgqakaqIdbgxaxak9DEUdbaiclfgicx9hmbkaDalfhDawcefgwad9hmbkkJbbbbaoIdbaoIdzgx:tgkakJbbbb9DEgkaoIdlaoIdCgm:tgPaPak9DEgkaoIdwaoIdKgP:tgsasak9DEhsdnabTmbadTmbJbbbbJbbjZas:vasJbbbb9BEhkinabakabIdbax:tNUdbabclfgoakaoIdbam:tNUdbabcwfgoakaoIdbaP:tNUdbabcxfhbadcufgdmbkkdnavTmbavaPUdwavamUdlavaxUdbkask:ZlewudnaeTmbcbhvabhoinaoavBdbaoclfhoaeavcefgv9hmbkkdnaiTmbcbhrinadarcdtfhwcbhDinalawaDcdtgvc:G1jjbfydbcdtfydbcdtfydbhodnabalawavfydbcdtfydbgqcdtfgkydbgvaqSmbinakabavgqcdtfgxydbgvBdbaxhkaqav9hmbkkdnabaocdtfgkydbgvaoSmbinakabavgocdtfgxydbgvBdbaxhkaoav9hmbkkdnaqaoSmbabaqaoaqao0Ecdtfaqaoaqao6EBdbkaDcefgDci9hmbkarcifgrai6mbkkdnaembcbskcbhxindnalaxcdtgvfydbax9hmbaxhodnabavfgDydbgvaxSmbaDhqinaqabavgocdtfgkydbgvBdbakhqaoav9hmbkkaDaoBdbkaxcefgxae9hmbkcbhvabhocbhkindndnavalydbgq9hmbdnavaoydbgq9hmbaoakBdbakcefhkxdkaoabaqcdtfydbBdbxekaoabaqcdtfydbBdbkaoclfhoalclfhlaeavcefgv9hmbkakk;Jiilud99duabcbaecltz:tjjjbhvdnalTmbadhoaihralhwinarcwfIdbhDarclfIdbhqavaoydbcltfgkarIdbakIdbMUdbakclfgxaqaxIdbMUdbakcwfgxaDaxIdbMUdbakcxfgkakIdbJbbjZMUdbaoclfhoarcxfhrawcufgwmbkkdnaeTmbavhraehkinarcxfgoIdbhDaocbBdbararIdbJbbbbJbbjZaD:vaDJbbbb9BEgDNUdbarclfgoaDaoIdbNUdbarcwfgoaDaoIdbNUdbarczfhrakcufgkmbkkdnalTmbinavadydbcltfgrcxfgkaicwfIdbarcwfIdb:tgDaDNaiIdbarIdb:tgDaDNaiclfIdbarclfIdb:tgDaDNMMgDakIdbgqaqaD9DEUdbadclfhdaicxfhialcufglmbkkdnaeTmbavcxfhrinabarIdbUdbarczfhrabclfhbaecufgembkkk:moerudnaoTmbaecd4hzdnavTmbaicd4hHavcdthOcbhAindnaPaAfRbbTmbaAhednaDTmbaDaAcdtfydbhekdnasTmbasaefRbbceGmekdnamaAfRbbclSmbabaeaz2cdtfgiaraAcx2fgCIdbakNaxIdbMUdbaiaCIdlakNaxIdlMUdlaiaCIdwakNaxIdwMUdwkadaeaH2cdtfhXaqheawhiavhCinaXaeydbcdtgQfaiIdbalaQfIdb:vUdbaeclfheaiclfhiaCcufgCmbkkawaOfhwaAcefgAao9hmbxdkkdnasmbcbheaDhiindnaPaefRbbTmbaehCdnaDTmbaiydbhCkamaefRbbclSmbabaCaz2cdtfgCarIdbakNaxIdbMUdbaCarclfIdbakNaxIdlMUdlaCarcwfIdbakNaxIdwMUdwkaiclfhiarcxfhraoaecefge9hmbxdkkdnaDTmbindnaPRbbTmbasaDydbgefRbbceGmbamRbbclSmbabaeaz2cdtfgearIdbakNaxIdbMUdbaearclfIdbakNaxIdlMUdlaearcwfIdbakNaxIdwMUdwkaPcefhPaDclfhDamcefhmarcxfhraocufgombxdkkazcdthicbheindnaPaefRbbTmbasaefRbbceGmbamaefRbbclSmbabarIdbakNaxIdbMUdbabclfarclfIdbakNaxIdlMUdbabcwfarcwfIdbakNaxIdwMUdbkarcxfhrabaifhbaoaecefge9hmbkkk8MbabaeadaialavcbcbcbcbcbaoarawaDz:bjjjbk8MbabaeadaialavaoarawaDaqakaxamaPz:bjjjbkRbababaeadaialavaoarawaDaqakaxcjjjjdVamz:bjjjbk:d8Koque99due99duq998Jjjjjbc;Wb9Rgq8Kjjjjbcbhkaqcxfcbc;Kbz:tjjjb8Aaqcualcx2alc;v:Q;v:Qe0Ecbyd1:jjjbHjjjjbbgxBdxaqceBd2axaialavcbcbz:ejjjb8AaqcualcdtalcFFFFi0Egmcbyd1:jjjbHjjjjbbgiBdzaqcdBd2dndnJFF959eJbbjZawJbbjZawJbbjZ9DE:vawJ9VO:d869DEgw:lJbbb9p9DTmbaw:OhPxekcjjjj94hPkadci9Uhsarco9UhzdndnaombaPcd9imekdnalTmbaPcuf:YhwdnaoTmbcbhvaihHaxhOindndnaoavfRbbceGTmbavcjjjjlVhAxekdndnaOclfIdbawNJbbbZMgC:lJbbb9p9DTmbaC:OhAxekcjjjj94hAkaAcqthAdndnaOcwfIdbawNJbbbZMgC:lJbbb9p9DTmbaC:OhXxekcjjjj94hXkaAaXVhAdndnaOIdbawNJbbbZMgC:lJbbb9p9DTmbaC:OhXxekcjjjj94hXkaAaXcCtVhAkaHaABdbaHclfhHaOcxfhOalavcefgv9hmbxdkkaxhvaihOalhHindndnavIdbawNJbbbZMgC:lJbbb9p9DTmbaC:OhAxekcjjjj94hAkaAcCthAdndnavclfIdbawNJbbbZMgC:lJbbb9p9DTmbaC:OhXxekcjjjj94hXkaXcqtaAVhAdndnavcwfIdbawNJbbbZMgC:lJbbb9p9DTmbaC:OhXxekcjjjj94hXkaOaAaXVBdbavcxfhvaOclfhOaHcufgHmbkkadTmbcbhkaehvcbhOinakaiavclfydbcdtfydbgHaiavcwfydbcdtfydbgA9haiavydbcdtfydbgXaH9haXaA9hGGfhkavcxfhvaOcifgOad6mbkkarci9UhQdndnaz:Z:rJbbbZMgw:lJbbb9p9DTmbaw:Ohvxekcjjjj94hvkaQ:ZhLcbhKc:bwhzdninakaQ9pmeazaP9Rcd9imeavazcufavaz9iEaPcefavaP9kEhYdnalTmbaYcuf:YhwdnaoTmbcbhOaihHaxhvindndnaoaOfRbbceGTmbaOcjjjjlVhAxekdndnavclfIdbawNJbbbZMgC:lJbbb9p9DTmbaC:OhAxekcjjjj94hAkaAcqthAdndnavcwfIdbawNJbbbZMgC:lJbbb9p9DTmbaC:OhXxekcjjjj94hXkaAaXVhAdndnavIdbawNJbbbZMgC:lJbbb9p9DTmbaC:OhXxekcjjjj94hXkaAaXcCtVhAkaHaABdbaHclfhHavcxfhvalaOcefgO9hmbxdkkaxhvaihOalhHindndnavIdbawNJbbbZMgC:lJbbb9p9DTmbaC:OhAxekcjjjj94hAkaAcCthAdndnavclfIdbawNJbbbZMgC:lJbbb9p9DTmbaC:OhXxekcjjjj94hXkaXcqtaAVhAdndnavcwfIdbawNJbbbZMgC:lJbbb9p9DTmbaC:OhXxekcjjjj94hXkaOaAaXVBdbavcxfhvaOclfhOaHcufgHmbkkcbhOdnadTmbaehvcbhHinaOaiavclfydbcdtfydbgAaiavcwfydbcdtfydbgX9haiavydbcdtfydbgraA9haraX9hGGfhOavcxfhvaHcifgHad6mbkkJbbbbh8Adnas:ZgCaL:taY:Ygwaz:Y:tgENak:Zg3aO:Zg5:tNa3aL:tawaP:Y:tg8ENa5aC:tNMg8FJbbbb9BmbaCa3:ta8EaEa5aL:tNNNa8F:vh8AkdndnaOaQ0mbaOhkaYhPxekaOhsaYhzkdndnaKcl0mbdna8AawMJbbbZMgw:lJbbb9p9DTmbaw:Ohvxdkcjjjj94hvxekaPazfcd9ThvkaKcefgKcs9hmbkkdndndnakmbJbbjZhwcbhicdhvaDmexdkalcd4alfhHcehOinaOgvcethOavaH6mbkcbhOaqcuavcdtgYavcFFFFi0Ecbyd1:jjjbHjjjjbbgKBdCaqciBd2aqamcbyd1:jjjbHjjjjbbgzBdKaqclBd2dndndndnalTmbaPcuf:YhwaoTmecbhOaihAaxhHindndnaoaOfRbbceGTmbaOcjjjjlVhXxekdndnaHclfIdbawNJbbbZMgC:lJbbb9p9DTmbaC:OhXxekcjjjj94hXkaXcqthXdndnaHcwfIdbawNJbbbZMgC:lJbbb9p9DTmbaC:Ohrxekcjjjj94hrkaXarVhXdndnaHIdbawNJbbbZMgC:lJbbb9p9DTmbaC:Ohrxekcjjjj94hrkaXarcCtVhXkaAaXBdbaAclfhAaHcxfhHalaOcefgO9hmbxikkaKcFeaYz:tjjjb8AcbhPcbhvxdkaxhOaihHalhAindndnaOIdbawNJbbbZMgC:lJbbb9p9DTmbaC:OhXxekcjjjj94hXkaXcCthXdndnaOclfIdbawNJbbbZMgC:lJbbb9p9DTmbaC:Ohrxekcjjjj94hrkarcqtaXVhXdndnaOcwfIdbawNJbbbZMgC:lJbbb9p9DTmbaC:Ohrxekcjjjj94hrkaHaXarVBdbaOcxfhOaHclfhHaAcufgAmbkkaKcFeaYz:tjjjbhravcufhocbhPcbhYindndndnaraiaYcdtgKfydbgAcm4aA7c:v;t;h;Ev2gvcs4av7aoGgHcdtfgXydbgOcuSmbcehvinaiaOcdtgOfydbaASmdaHavfhOavcefhvaraOaoGgHcdtfgXydbgOcu9hmbkkaXaYBdbaPhvaPcefhPxekazaOfydbhvkazaKfavBdbaYcefgYal9hmbkcuaPc8S2gOaPc;D;O;f8U0Ehvkcbhraqavcbyd1:jjjbHjjjjbbgvBd3aqcvBd2avcbaOz:tjjjbhOdnadTmbaehiinJbbnnJbbjZazaiydbgAcdtfydbgvazaiclfydbgHcdtfydbgYSavazaicwfydbgXcdtfydbgKSGgoEh8EdnaxaHcx2fgHIdbaxaAcx2fgAIdbg5:tgCaxaXcx2fgXIdlaAIdlg8A:tgwNaXIdba5:tg3aHIdla8A:tg8FN:tgLaLNa8FaXIdwaAIdwgE:tgaNawaHIdwaE:tg8FN:tgwawNa8Fa3NaaaCN:tgCaCNMM:rg3Jbbbb9ETmbaLa3:vhLaCa3:vhCawa3:vhwkaOavc8S2fgvavIdbawa8Ea3:rNg3awNNg8FMUdbavaCa3aCNgaNghavIdlMUdlavaLa3aLNg8ENggavIdwMUdwavaaawNgaavIdxMUdxava8EawNg8JavIdzMUdzava8EaCNg8EavIdCMUdCavawa3aLaENawa5Na8AaCNMM:mg8ANg5NgwavIdKMUdKavaCa5NgCavId3MUd3avaLa5NgLavIdaMUdaava5a8ANg5avId8KMUd8Kava3avIdyMUdydnaombaOaYc8S2fgva8FavIdbMUdbavahavIdlMUdlavagavIdwMUdwavaaavIdxMUdxava8JavIdzMUdzava8EavIdCMUdCavawavIdKMUdKavaCavId3MUd3avaLavIdaMUdaava5avId8KMUd8Kava3avIdyMUdyaOaKc8S2fgva8FavIdbMUdbavahavIdlMUdlavagavIdwMUdwavaaavIdxMUdxava8JavIdzMUdzava8EavIdCMUdCavawavIdKMUdKavaCavId3MUd3avaLavIdaMUdaava5avId8KMUd8Kava3avIdyMUdykaicxfhiarcifgrad6mbkkcbhAaqcuaPcdtgvaPcFFFFi0Egicbyd1:jjjbHjjjjbbgHBdaaqcoBd2aqaicbyd1:jjjbHjjjjbbgiBd8KaqcrBd2aHcFeavz:tjjjbhYdnalTmbazhHinJbbbbJbbjZaOaHydbgXc8S2fgvIdygw:vawJbbbb9BEavIdwaxcwfIdbgwNavIdzaxIdbgCNavIdaMgLaLMMawNavIdlaxclfIdbgLNavIdCawNavId3MgwawMMaLNavIdbaCNavIdxaLNavIdKMgwawMMaCNavId8KMMM:lNhwdndnaYaXcdtgvfgXydbcuSmbaiavfIdbaw9ETmekaXaABdbaiavfawUdbkaHclfhHaxcxfhxalaAcefgA9hmbkkJbbbbhwdnaPTmbinaiIdbgCawawaC9DEhwaiclfhiaPcufgPmbkkakcd4akfhOcehiinaigvcethiavaO6mbkcbhiaqcuavcdtgOavcFFFFi0Ecbyd1:jjjbHjjjjbbgHBdyaHcFeaOz:tjjjbhXdnadTmbavcufhrcbhPcbhxindnazaeaxcdtfgvydbcdtfydbgiazavclfydbcdtfydbgOSmbaiazavcwfydbcdtfydbgvSmbaOavSmbaYavcdtfydbhAdndnaYaOcdtfydbgvaYaicdtfydbgi9pmbavaA9pmbaAhlaihoavhAxekdnaAai9pmbaAav9pmbaihlavhoxekavhlaAhoaihAkabaPcx2fgvaABdbavcwfaoBdbavclfalBdbdnaXaoc:3F;N8N2alc:F:b:DD27aAc;D;O:B8J27arGgOcdtfgvydbgicuSmbcehHinaHhvdnabaicx2fgiydbaA9hmbaiydlal9hmbaiydwaoSmikavcefhHaXaOavfarGgOcdtfgvydbgicu9hmbkkavaPBdbaPcefhPkaxcifgxad6mbkaPci2hikdnaDmbcwhvxdkaw:rhwcwhvkaDawUdbkavcdthvdninavTmeavc98fgvaqcxffydbcbyd:m:jjjbH:bjjjbbxbkkaqc;Wbf8Kjjjjbaik:2ldwue9:8Jjjjjbc;Wb9Rgr8Kjjjjbcbhwarcxfcbc;Kbz:tjjjb8AdnabaeSmbabaeadcdtzMjjjb8AkarcualcdtalcFFFFi0EgDcbyd1:jjjbHjjjjbbgqBdxarceBd2aqcbaialavcbarcxfz:djjjbcualcx2alc;v:Q;v:Qe0Ecbyd1:jjjbHjjjjbbhkarcxfaryd2gxcdtgmfakBdbaraxcefgPBd2akaialavcbcbz:ejjjb8AarcxfaPcdtfaDcbyd1:jjjbHjjjjbbgvBdbaraxcdfgiBd2arcxfaicdtfcuavalaeadaqz:fjjjbgecltaecjjjjiGEcbyd1:jjjbHjjjjbbgiBdbaiaeavakalz:gjjjbdnadTmbaoaoNhocbhwabhlcbhkindnaiavalydbgecdtfydbcdtfIdbao9ETmbalclf8PdbhsabawcdtfgqaeBdbaqclfas83dbawcifhwkalcxfhlakcifgkad6mbkkaxcifhlamarcxffcwfhkdninalTmeakydbcbyd:m:jjjbH:bjjjbbakc98fhkalcufhlxbkkarc;Wbf8Kjjjjbawk:XCoDud99vue99vuo998Jjjjjbc;Wb9Rgw8KjjjjbdndnarmbcbhDxekawcxfcbc;Kbz:tjjjb8Aawcuadcx2adc;v:Q;v:Qe0Ecbyd1:jjjbHjjjjbbgqBdxawceBd2aqaeadaicbcbz:ejjjb8AawcuadcdtadcFFFFi0Egkcbyd1:jjjbHjjjjbbgxBdzawcdBd2adcd4adfhmceheinaegicetheaiam6mbkcbhPawcuaicdtgsaicFFFFi0Ecbyd1:jjjbHjjjjbbgzBdCawciBd2dndnar:ZgH:rJbbbZMgO:lJbbb9p9DTmbaO:Ohexekcjjjj94hekaicufhAc:bwhDcbhCadhXcbhQinaeaDcufaeaD9iEaPcefaeaP9kEhLdndnadTmbaLcuf:YhOaqhiaxheadhmindndnaiIdbaONJbbbZMgK:lJbbb9p9DTmbaK:OhYxekcjjjj94hYkaYcCthYdndnaiclfIdbaONJbbbZMgK:lJbbb9p9DTmbaK:Oh8Axekcjjjj94h8Aka8AcqtaYVhYdndnaicwfIdbaONJbbbZMgK:lJbbb9p9DTmbaK:Oh8Axekcjjjj94h8AkaeaYa8AVBdbaicxfhiaeclfheamcufgmmbkazcFeasz:tjjjbhEcbh3cbh5indnaEaxa5cdtfydbgYcm4aY7c:v;t;h;Ev2gics4ai7aAGgmcdtfg8AydbgecuSmbaeaYSmbcehiinaEamaifaAGgmcdtfg8AydbgecuSmeaicefhiaeaY9hmbkka8AaYBdba3aecuSfh3a5cefg5ad9hmbxdkkazcFeasz:tjjjb8Acbh3kJbbbbh8EdnaX:ZgKaH:taL:YgOaD:Y:tg8FNaC:Zgaa3:Zgh:tNaaaH:taOaP:Y:tggNahaK:tNMg8JJbbbb9BmbaKaa:taga8FahaH:tNNNa8J:vh8EkaPaLa3ar0giEhPaCa3aiEhCdna3arSmbaLaDaiEgDaP9Rcd9imbdndnaQcl0mbdna8EaOMJbbbZMgO:lJbbb9p9DTmbaO:Ohexdkcjjjj94hexekaPaDfcd9Theka3aXaiEhXaQcefgQcs9hmekkdndnaCmbcihicbhDxekcbhiawakcbyd1:jjjbHjjjjbbg5BdKawclBd2aPcuf:YhKdndnadTmbaqhiaxheadhmindndnaiIdbaKNJbbbZMgO:lJbbb9p9DTmbaO:OhYxekcjjjj94hYkaYcCthYdndnaiclfIdbaKNJbbbZMgO:lJbbb9p9DTmbaO:Oh8Axekcjjjj94h8Aka8AcqtaYVhYdndnaicwfIdbaKNJbbbZMgO:lJbbb9p9DTmbaO:Oh8Axekcjjjj94h8AkaeaYa8AVBdbaicxfhiaeclfheamcufgmmbkazcFeasz:tjjjbhEcbhDcbh3indndndnaEaxa3cdtgLfydbgYcm4aY7c:v;t;h;Ev2gics4ai7aAGgmcdtfg8AydbgecuSmbcehiinaxaecdtgefydbaYSmdamaifheaicefhiaEaeaAGgmcdtfg8Aydbgecu9hmbkka8Aa3BdbaDhiaDcefhDxeka5aefydbhika5aLfaiBdba3cefg3ad9hmbkcuaDc32giaDc;j:KM;jb0EhexekazcFeasz:tjjjb8AcbhDcbhekawaecbyd1:jjjbHjjjjbbgeBd3awcvBd2aecbaiz:tjjjbh8Aavcd4hxdnadTmbdnalTmbaxcdthEa5hYaqhealhmadhAina8AaYydbc32fgiaeIdbaiIdbMUdbaiaeclfIdbaiIdlMUdlaiaecwfIdbaiIdwMUdwaiamIdbaiIdxMUdxaiamclfIdbaiIdzMUdzaiamcwfIdbaiIdCMUdCaiaiIdKJbbjZMUdKaYclfhYaecxfheamaEfhmaAcufgAmbxdkka5hmaqheadhYina8Aamydbc32fgiaeIdbaiIdbMUdbaiaeclfIdbaiIdlMUdlaiaecwfIdbaiIdwMUdwaiaiIdxJbbbbMUdxaiaiIdzJbbbbMUdzaiaiIdCJbbbbMUdCaiaiIdKJbbjZMUdKamclfhmaecxfheaYcufgYmbkkdnaDTmba8AhiaDheinaiaiIdbJbbbbJbbjZaicKfIdbgO:vaOJbbbb9BEgONUdbaiclfgmaOamIdbNUdbaicwfgmaOamIdbNUdbaicxfgmaOamIdbNUdbaiczfgmaOamIdbNUdbaicCfgmaOamIdbNUdbaic3fhiaecufgembkkcbhYawcuaDcdtgLaDcFFFFi0Egicbyd1:jjjbHjjjjbbgeBdaawcoBd2awaicbyd1:jjjbHjjjjbbgEBd8KaecFeaLz:tjjjbh3dnadTmbJbbjZJbbjZaK:vaPceSEaoNgOaONhKaxcdthxalheinaKaec;81jjbalEgmIdwa8Aa5ydbgAc32fgiIdC:tgOaONamIdbaiIdx:tgOaONamIdlaiIdz:tgOaONMMNaqcwfIdbaiIdw:tgOaONaqIdbaiIdb:tgOaONaqclfIdbaiIdl:tgOaONMMMhOdndna3aAcdtgifgmydbcuSmbaEaifIdbaO9ETmekamaYBdbaEaifaOUdbka5clfh5aqcxfhqaeaxfheadaYcefgY9hmbkkaba3aLzMjjjb8AcrhikaicdthiinaiTmeaic98fgiawcxffydbcbyd:m:jjjbH:bjjjbbxbkkawc;Wbf8KjjjjbaDk:Ydidui99ducbhi8Jjjjjbca9Rglczfcwfcbyd11jjbBdbalcb8Pdj1jjb83izalcwfcbydN1jjbBdbalcb8Pd:m1jjb83ibdndnaembJbbjFhvJbbjFhoJbbjFhrxekadcd4cdthwincbhdinalczfadfgDabadfIdbgvaDIdbgoaoav9EEUdbaladfgDavaDIdbgoaoav9DEUdbadclfgdcx9hmbkabawfhbaicefgiae9hmbkalIdwalIdK:thralIdlalIdC:thoalIdbalIdz:thvkJbbbbavavJbbbb9DEgvaoaoav9DEgvararav9DEk9DeeuabcFeaicdtz:tjjjbhlcbhbdnadTmbindnalaeydbcdtfgiydbcu9hmbaiabBdbabcefhbkaeclfheadcufgdmbkkabk;7idqui998Jjjjjbc;Wb9Rgl8Kjjjjbalcxfcbc;Kbz:tjjjb8Aadcd4adfhvcehoinaogrcethoarav6mbkalcuarcdtgoarcFFFFi0Ecbyd1:jjjbHjjjjbbgvBdxavcFeaoz:tjjjbhwdnadTmbaicd4hDarcufhqcbhkindndnawcbaeakaD2cdtfgrydlgiaicjjjj94SEgocH4ao7c:F:b:DD2cbarydbgxaxcjjjj94SEgocH4ao7c;D;O:B8J27cbarydwgmamcjjjj94SEgrcH4ar7c:3F;N8N27aqGgvcdtfgrydbgocuSmbam::hPai::hsax::hzcehiinaihrdnaeaoaD2cdtfgiIdbaz9CmbaiIdlas9CmbaiIdwaP9BmikarcefhiawavarfaqGgvcdtfgrydbgocu9hmbkkarakBdbakhokabakcdtfaoBdbakcefgkad9hmbkkcbhrdninarc98Smealcxfarfydbcbyd:m:jjjbH:bjjjbbarc98fhrxbkkalc;Wbf8Kjjjjbk9teiucbcbyd:q:jjjbgeabcifc98GfgbBd:q:jjjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaik;teeeudndnaeabVciGTmbabhixekdndnadcz9pmbabhixekabhiinaiaeydbBdbaiaeydlBdlaiaeydwBdwaiaeydxBdxaeczfheaiczfhiadc9Wfgdcs0mbkkadcl6mbinaiaeydbBdbaeclfheaiclfhiadc98fgdci0mbkkdnadTmbinaiaeRbb86bbaicefhiaecefheadcufgdmbkkabk:3eedudndnabciGTmbabhixekaecFeGc:b:c:ew2hldndnadcz9pmbabhixekabhiinaialBdxaialBdwaialBdlaialBdbaiczfhiadc9Wfgdcs0mbkkadcl6mbinaialBdbaiclfhiadc98fgdci0mbkkdnadTmbinaiae86bbaicefhiadcufgdmbkkabk9teiucbcbyd:q:jjjbgeabcrfc94GfgbBd:q:jjjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaikTeeucbabcbyd:q:jjjbge9Rcifc98GaefgbBd:q:jjjbdnabZbcztge9nmbabae9RcFFifcz4nb8Akkk:Iedbcjwk1eFFuuFFuuFFuuFFuFFFuFFFuFbbbbbbbbebbbdbbbbbbbebbbebbbdbbbbbbbbbbbeeeeebebbebbebebbbeebbbbbbbbbbbbeeeeeebebbeeebeebbbbebebbbbbbbbbbbbbbbbbbc1Dkxebbbdbbb:GNbb'; // embed! wasm\n\n\tvar wasmpack = new Uint8Array([\n\t\t32, 0, 65, 2, 1, 106, 34, 33, 3, 128, 11, 4, 13, 64, 6, 253, 10, 7, 15, 116, 127, 5, 8, 12, 40, 16, 19, 54, 20, 9, 27, 255, 113, 17, 42, 67,\n\t\t24, 23, 146, 148, 18, 14, 22, 45, 70, 69, 56, 114, 101, 21, 25, 63, 75, 136, 108, 28, 118, 29, 73, 115,\n\t]);\n\n\tif (typeof WebAssembly !== 'object') {\n\t\treturn {\n\t\t\tsupported: false,\n\t\t};\n\t}\n\n\tvar instance;\n\n\tvar ready = WebAssembly.instantiate(unpack(wasm), {}).then(function (result) {\n\t\tinstance = result.instance;\n\t\tinstance.exports.__wasm_call_ctors();\n\t});\n\n\tfunction unpack(data) {\n\t\tvar result = new Uint8Array(data.length);\n\t\tfor (var i = 0; i < data.length; ++i) {\n\t\t\tvar ch = data.charCodeAt(i);\n\t\t\tresult[i] = ch > 96 ? ch - 97 : ch > 64 ? ch - 39 : ch + 4;\n\t\t}\n\t\tvar write = 0;\n\t\tfor (var i = 0; i < data.length; ++i) {\n\t\t\tresult[write++] = result[i] < 60 ? wasmpack[result[i]] : (result[i] - 60) * 64 + result[++i];\n\t\t}\n\t\treturn result.buffer.slice(0, write);\n\t}\n\n\tfunction assert(cond) {\n\t\tif (!cond) {\n\t\t\tthrow new Error('Assertion failed');\n\t\t}\n\t}\n\n\tfunction bytes(view) {\n\t\treturn new Uint8Array(view.buffer, view.byteOffset, view.byteLength);\n\t}\n\n\tfunction genremap(fun, positions, vertices, stride) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar rp = sbrk(vertices * 4);\n\t\tvar sp = sbrk(vertices * stride * 4);\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(bytes(positions), sp);\n\t\tfun(rp, sp, vertices, stride * 4);\n\t\t// heap may have grown\n\t\theap = new Uint8Array(instance.exports.memory.buffer);\n\t\tvar remap = new Uint32Array(vertices);\n\t\tnew Uint8Array(remap.buffer).set(heap.subarray(rp, rp + vertices * 4));\n\t\tsbrk(rp - sbrk(0));\n\t\treturn remap;\n\t}\n\n\tfunction reorder(fun, indices, vertices) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar ip = sbrk(indices.length * 4);\n\t\tvar rp = sbrk(vertices * 4);\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\tvar indices8 = bytes(indices);\n\t\theap.set(indices8, ip);\n\t\tvar unique = fun(rp, ip, indices.length, vertices);\n\t\t// heap may have grown\n\t\theap = new Uint8Array(instance.exports.memory.buffer);\n\t\tvar remap = new Uint32Array(vertices);\n\t\tnew Uint8Array(remap.buffer).set(heap.subarray(rp, rp + vertices * 4));\n\t\tindices8.set(heap.subarray(ip, ip + indices.length * 4));\n\t\tsbrk(ip - sbrk(0));\n\n\t\tfor (var i = 0; i < indices.length; ++i) indices[i] = remap[indices[i]];\n\n\t\treturn [remap, unique];\n\t}\n\n\tfunction maxindex(source) {\n\t\tvar result = 0;\n\t\tfor (var i = 0; i < source.length; ++i) {\n\t\t\tvar index = source[i];\n\t\t\tresult = result < index ? index : result;\n\t\t}\n\t\treturn result;\n\t}\n\n\tfunction simplify(fun, indices, index_count, vertex_positions, vertex_count, vertex_positions_stride, target_index_count, target_error, options) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar te = sbrk(4);\n\t\tvar ti = sbrk(index_count * 4);\n\t\tvar sp = sbrk(vertex_count * vertex_positions_stride);\n\t\tvar si = sbrk(index_count * 4);\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(bytes(vertex_positions), sp);\n\t\theap.set(bytes(indices), si);\n\t\tvar result = fun(ti, si, index_count, sp, vertex_count, vertex_positions_stride, target_index_count, target_error, options, te);\n\t\t// heap may have grown\n\t\theap = new Uint8Array(instance.exports.memory.buffer);\n\t\tvar target = new Uint32Array(result);\n\t\tbytes(target).set(heap.subarray(ti, ti + result * 4));\n\t\tvar error = new Float32Array(1);\n\t\tbytes(error).set(heap.subarray(te, te + 4));\n\t\tsbrk(te - sbrk(0));\n\t\treturn [target, error[0]];\n\t}\n\n\tfunction simplifyAttr(\n\t\tfun,\n\t\tindices,\n\t\tindex_count,\n\t\tvertex_positions,\n\t\tvertex_count,\n\t\tvertex_positions_stride,\n\t\tvertex_attributes,\n\t\tvertex_attributes_stride,\n\t\tattribute_weights,\n\t\tvertex_lock,\n\t\ttarget_index_count,\n\t\ttarget_error,\n\t\toptions\n\t) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar te = sbrk(4);\n\t\tvar ti = sbrk(index_count * 4);\n\t\tvar sp = sbrk(vertex_count * vertex_positions_stride);\n\t\tvar sa = sbrk(vertex_count * vertex_attributes_stride);\n\t\tvar sw = sbrk(attribute_weights.length * 4);\n\t\tvar si = sbrk(index_count * 4);\n\t\tvar vl = vertex_lock ? sbrk(vertex_count) : 0;\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(bytes(vertex_positions), sp);\n\t\theap.set(bytes(vertex_attributes), sa);\n\t\theap.set(bytes(attribute_weights), sw);\n\t\theap.set(bytes(indices), si);\n\t\tif (vertex_lock) {\n\t\t\theap.set(bytes(vertex_lock), vl);\n\t\t}\n\t\tvar result = fun(\n\t\t\tti,\n\t\t\tsi,\n\t\t\tindex_count,\n\t\t\tsp,\n\t\t\tvertex_count,\n\t\t\tvertex_positions_stride,\n\t\t\tsa,\n\t\t\tvertex_attributes_stride,\n\t\t\tsw,\n\t\t\tattribute_weights.length,\n\t\t\tvl,\n\t\t\ttarget_index_count,\n\t\t\ttarget_error,\n\t\t\toptions,\n\t\t\tte\n\t\t);\n\t\t// heap may have grown\n\t\theap = new Uint8Array(instance.exports.memory.buffer);\n\t\tvar target = new Uint32Array(result);\n\t\tbytes(target).set(heap.subarray(ti, ti + result * 4));\n\t\tvar error = new Float32Array(1);\n\t\tbytes(error).set(heap.subarray(te, te + 4));\n\t\tsbrk(te - sbrk(0));\n\t\treturn [target, error[0]];\n\t}\n\n\tfunction simplifyUpdate(\n\t\tfun,\n\t\tindices,\n\t\tindex_count,\n\t\tvertex_positions,\n\t\tvertex_count,\n\t\tvertex_positions_stride,\n\t\tvertex_attributes,\n\t\tvertex_attributes_stride,\n\t\tattribute_weights,\n\t\tvertex_lock,\n\t\ttarget_index_count,\n\t\ttarget_error,\n\t\toptions\n\t) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar te = sbrk(4);\n\t\tvar sp = sbrk(vertex_count * vertex_positions_stride);\n\t\tvar sa = sbrk(vertex_count * vertex_attributes_stride);\n\t\tvar sw = sbrk(attribute_weights.length * 4);\n\t\tvar si = sbrk(index_count * 4);\n\t\tvar vl = vertex_lock ? sbrk(vertex_count) : 0;\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(bytes(vertex_positions), sp);\n\t\theap.set(bytes(vertex_attributes), sa);\n\t\theap.set(bytes(attribute_weights), sw);\n\t\theap.set(bytes(indices), si);\n\t\tif (vertex_lock) {\n\t\t\theap.set(bytes(vertex_lock), vl);\n\t\t}\n\t\tvar result = fun(\n\t\t\tsi,\n\t\t\tindex_count,\n\t\t\tsp,\n\t\t\tvertex_count,\n\t\t\tvertex_positions_stride,\n\t\t\tsa,\n\t\t\tvertex_attributes_stride,\n\t\t\tsw,\n\t\t\tattribute_weights.length,\n\t\t\tvl,\n\t\t\ttarget_index_count,\n\t\t\ttarget_error,\n\t\t\toptions,\n\t\t\tte\n\t\t);\n\t\t// heap may have grown\n\t\theap = new Uint8Array(instance.exports.memory.buffer);\n\t\tbytes(indices).set(heap.subarray(si, si + result * 4));\n\t\tbytes(vertex_positions).set(heap.subarray(sp, sp + vertex_count * vertex_positions_stride));\n\t\tbytes(vertex_attributes).set(heap.subarray(sa, sa + vertex_count * vertex_attributes_stride));\n\t\tvar error = new Float32Array(1);\n\t\tbytes(error).set(heap.subarray(te, te + 4));\n\t\tsbrk(te - sbrk(0));\n\t\treturn [result, error[0]];\n\t}\n\n\tfunction simplifyScale(fun, vertex_positions, vertex_count, vertex_positions_stride) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar sp = sbrk(vertex_count * vertex_positions_stride);\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(bytes(vertex_positions), sp);\n\t\tvar result = fun(sp, vertex_count, vertex_positions_stride);\n\t\tsbrk(sp - sbrk(0));\n\t\treturn result;\n\t}\n\n\tfunction simplifyPoints(\n\t\tfun,\n\t\tvertex_positions,\n\t\tvertex_count,\n\t\tvertex_positions_stride,\n\t\tvertex_colors,\n\t\tvertex_colors_stride,\n\t\tcolor_weight,\n\t\ttarget_vertex_count\n\t) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar ti = sbrk(target_vertex_count * 4);\n\t\tvar sp = sbrk(vertex_count * vertex_positions_stride);\n\t\tvar sc = sbrk(vertex_count * vertex_colors_stride);\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(bytes(vertex_positions), sp);\n\t\tif (vertex_colors) {\n\t\t\theap.set(bytes(vertex_colors), sc);\n\t\t}\n\t\tvar result = fun(ti, sp, vertex_count, vertex_positions_stride, sc, vertex_colors_stride, color_weight, target_vertex_count);\n\t\t// heap may have grown\n\t\theap = new Uint8Array(instance.exports.memory.buffer);\n\t\tvar target = new Uint32Array(result);\n\t\tbytes(target).set(heap.subarray(ti, ti + result * 4));\n\t\tsbrk(ti - sbrk(0));\n\t\treturn target;\n\t}\n\n\tfunction simplifySloppy(\n\t\tfun,\n\t\tindices,\n\t\tindex_count,\n\t\tvertex_positions,\n\t\tvertex_count,\n\t\tvertex_positions_stride,\n\t\tvertex_lock,\n\t\ttarget_index_count,\n\t\ttarget_error\n\t) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar te = sbrk(4);\n\t\tvar ti = sbrk(index_count * 4);\n\t\tvar sp = sbrk(vertex_count * vertex_positions_stride);\n\t\tvar si = sbrk(index_count * 4);\n\t\tvar vl = vertex_lock ? sbrk(vertex_count) : 0;\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(bytes(vertex_positions), sp);\n\t\theap.set(bytes(indices), si);\n\t\tif (vertex_lock) {\n\t\t\theap.set(bytes(vertex_lock), vl);\n\t\t}\n\t\tvar result = fun(ti, si, index_count, sp, vertex_count, vertex_positions_stride, vl, target_index_count, target_error, te);\n\t\t// heap may have grown\n\t\theap = new Uint8Array(instance.exports.memory.buffer);\n\t\tvar target = new Uint32Array(result);\n\t\tbytes(target).set(heap.subarray(ti, ti + result * 4));\n\t\tvar error = new Float32Array(1);\n\t\tbytes(error).set(heap.subarray(te, te + 4));\n\t\tsbrk(te - sbrk(0));\n\t\treturn [target, error[0]];\n\t}\n\n\tfunction simplifyPrune(fun, indices, index_count, vertex_positions, vertex_count, vertex_positions_stride, target_error) {\n\t\tvar sbrk = instance.exports.sbrk;\n\t\tvar ti = sbrk(index_count * 4);\n\t\tvar sp = sbrk(vertex_count * vertex_positions_stride);\n\t\tvar si = sbrk(index_count * 4);\n\t\tvar heap = new Uint8Array(instance.exports.memory.buffer);\n\t\theap.set(bytes(vertex_positions), sp);\n\t\theap.set(bytes(indices), si);\n\t\tvar result = fun(ti, si, index_count, sp, vertex_count, vertex_positions_stride, target_error);\n\t\t// heap may have grown\n\t\theap = new Uint8Array(instance.exports.memory.buffer);\n\t\tvar target = new Uint32Array(result);\n\t\tbytes(target).set(heap.subarray(ti, ti + result * 4));\n\t\tsbrk(ti - sbrk(0));\n\t\treturn target;\n\t}\n\n\tvar simplifyOptions = {\n\t\tLockBorder: 1,\n\t\tSparse: 2,\n\t\tErrorAbsolute: 4,\n\t\tPrune: 8,\n\t\tRegularize: 16,\n\t\tPermissive: 32,\n\t\t_InternalDebug: 1 << 30, // internal, don't use!\n\t};\n\n\treturn {\n\t\tready: ready,\n\t\tsupported: true,\n\n\t\tcompactMesh: function (indices) {\n\t\t\tassert(\n\t\t\t\tindices instanceof Uint32Array || indices instanceof Int32Array || indices instanceof Uint16Array || indices instanceof Int16Array\n\t\t\t);\n\t\t\tassert(indices.length % 3 == 0);\n\n\t\t\tvar indices32 = indices.BYTES_PER_ELEMENT == 4 ? indices : new Uint32Array(indices);\n\t\t\treturn reorder(instance.exports.meshopt_optimizeVertexFetchRemap, indices32, maxindex(indices) + 1);\n\t\t},\n\n\t\tgeneratePositionRemap: function (vertex_positions, vertex_positions_stride) {\n\t\t\tassert(vertex_positions instanceof Float32Array);\n\t\t\tassert(vertex_positions.length % vertex_positions_stride == 0);\n\t\t\tassert(vertex_positions_stride >= 3);\n\n\t\t\treturn genremap(\n\t\t\t\tinstance.exports.meshopt_generatePositionRemap,\n\t\t\t\tvertex_positions,\n\t\t\t\tvertex_positions.length / vertex_positions_stride,\n\t\t\t\tvertex_positions_stride\n\t\t\t);\n\t\t},\n\n\t\tsimplify: function (indices, vertex_positions, vertex_positions_stride, target_index_count, target_error, flags) {\n\t\t\tassert(\n\t\t\t\tindices instanceof Uint32Array || indices instanceof Int32Array || indices instanceof Uint16Array || indices instanceof Int16Array\n\t\t\t);\n\t\t\tassert(indices.length % 3 == 0);\n\t\t\tassert(vertex_positions instanceof Float32Array);\n\t\t\tassert(vertex_positions.length % vertex_positions_stride == 0);\n\t\t\tassert(vertex_positions_stride >= 3);\n\t\t\tassert(target_index_count >= 0 && target_index_count <= indices.length);\n\t\t\tassert(target_index_count % 3 == 0);\n\t\t\tassert(target_error >= 0);\n\n\t\t\tvar options = 0;\n\t\t\tfor (var i = 0; i < (flags ? flags.length : 0); ++i) {\n\t\t\t\tassert(flags[i] in simplifyOptions);\n\t\t\t\toptions |= simplifyOptions[flags[i]];\n\t\t\t}\n\n\t\t\tvar indices32 = indices.BYTES_PER_ELEMENT == 4 ? indices : new Uint32Array(indices);\n\t\t\tvar result = simplify(\n\t\t\t\tinstance.exports.meshopt_simplify,\n\t\t\t\tindices32,\n\t\t\t\tindices.length,\n\t\t\t\tvertex_positions,\n\t\t\t\tvertex_positions.length / vertex_positions_stride,\n\t\t\t\tvertex_positions_stride * 4,\n\t\t\t\ttarget_index_count,\n\t\t\t\ttarget_error,\n\t\t\t\toptions\n\t\t\t);\n\t\t\tresult[0] = indices instanceof Uint32Array ? result[0] : new indices.constructor(result[0]);\n\n\t\t\treturn result;\n\t\t},\n\n\t\tsimplifyWithAttributes: function (\n\t\t\tindices,\n\t\t\tvertex_positions,\n\t\t\tvertex_positions_stride,\n\t\t\tvertex_attributes,\n\t\t\tvertex_attributes_stride,\n\t\t\tattribute_weights,\n\t\t\tvertex_lock,\n\t\t\ttarget_index_count,\n\t\t\ttarget_error,\n\t\t\tflags\n\t\t) {\n\t\t\tassert(\n\t\t\t\tindices instanceof Uint32Array || indices instanceof Int32Array || indices instanceof Uint16Array || indices instanceof Int16Array\n\t\t\t);\n\t\t\tassert(indices.length % 3 == 0);\n\t\t\tassert(vertex_positions instanceof Float32Array);\n\t\t\tassert(vertex_positions.length % vertex_positions_stride == 0);\n\t\t\tassert(vertex_positions_stride >= 3);\n\t\t\tassert(vertex_attributes instanceof Float32Array);\n\t\t\tassert(vertex_attributes.length == vertex_attributes_stride * (vertex_positions.length / vertex_positions_stride));\n\t\t\tassert(vertex_attributes_stride >= 0);\n\t\t\tassert(vertex_lock == null || vertex_lock instanceof Uint8Array);\n\t\t\tassert(vertex_lock == null || vertex_lock.length == vertex_positions.length / vertex_positions_stride);\n\t\t\tassert(target_index_count >= 0 && target_index_count <= indices.length);\n\t\t\tassert(target_index_count % 3 == 0);\n\t\t\tassert(target_error >= 0);\n\t\t\tassert(Array.isArray(attribute_weights));\n\t\t\tassert(vertex_attributes_stride >= attribute_weights.length);\n\t\t\tassert(attribute_weights.length <= 32);\n\t\t\tfor (var i = 0; i < attribute_weights.length; ++i) {\n\t\t\t\tassert(attribute_weights[i] >= 0);\n\t\t\t}\n\n\t\t\tvar options = 0;\n\t\t\tfor (var i = 0; i < (flags ? flags.length : 0); ++i) {\n\t\t\t\tassert(flags[i] in simplifyOptions);\n\t\t\t\toptions |= simplifyOptions[flags[i]];\n\t\t\t}\n\n\t\t\tvar indices32 = indices.BYTES_PER_ELEMENT == 4 ? indices : new Uint32Array(indices);\n\t\t\tvar result = simplifyAttr(\n\t\t\t\tinstance.exports.meshopt_simplifyWithAttributes,\n\t\t\t\tindices32,\n\t\t\t\tindices.length,\n\t\t\t\tvertex_positions,\n\t\t\t\tvertex_positions.length / vertex_positions_stride,\n\t\t\t\tvertex_positions_stride * 4,\n\t\t\t\tvertex_attributes,\n\t\t\t\tvertex_attributes_stride * 4,\n\t\t\t\tnew Float32Array(attribute_weights),\n\t\t\t\tvertex_lock,\n\t\t\t\ttarget_index_count,\n\t\t\t\ttarget_error,\n\t\t\t\toptions\n\t\t\t);\n\t\t\tresult[0] = indices instanceof Uint32Array ? result[0] : new indices.constructor(result[0]);\n\n\t\t\treturn result;\n\t\t},\n\n\t\tsimplifyWithUpdate: function (\n\t\t\tindices,\n\t\t\tvertex_positions,\n\t\t\tvertex_positions_stride,\n\t\t\tvertex_attributes,\n\t\t\tvertex_attributes_stride,\n\t\t\tattribute_weights,\n\t\t\tvertex_lock,\n\t\t\ttarget_index_count,\n\t\t\ttarget_error,\n\t\t\tflags\n\t\t) {\n\t\t\tassert(\n\t\t\t\tindices instanceof Uint32Array || indices instanceof Int32Array || indices instanceof Uint16Array || indices instanceof Int16Array\n\t\t\t);\n\t\t\tassert(indices.length % 3 == 0);\n\t\t\tassert(vertex_positions instanceof Float32Array);\n\t\t\tassert(vertex_positions.length % vertex_positions_stride == 0);\n\t\t\tassert(vertex_positions_stride >= 3);\n\t\t\tassert(vertex_attributes instanceof Float32Array);\n\t\t\tassert(vertex_attributes.length == vertex_attributes_stride * (vertex_positions.length / vertex_positions_stride));\n\t\t\tassert(vertex_attributes_stride >= 0);\n\t\t\tassert(vertex_lock == null || vertex_lock instanceof Uint8Array);\n\t\t\tassert(vertex_lock == null || vertex_lock.length == vertex_positions.length / vertex_positions_stride);\n\t\t\tassert(target_index_count >= 0 && target_index_count <= indices.length);\n\t\t\tassert(target_index_count % 3 == 0);\n\t\t\tassert(target_error >= 0);\n\t\t\tassert(Array.isArray(attribute_weights));\n\t\t\tassert(vertex_attributes_stride >= attribute_weights.length);\n\t\t\tassert(attribute_weights.length <= 32);\n\t\t\tfor (var i = 0; i < attribute_weights.length; ++i) {\n\t\t\t\tassert(attribute_weights[i] >= 0);\n\t\t\t}\n\n\t\t\tvar options = 0;\n\t\t\tfor (var i = 0; i < (flags ? flags.length : 0); ++i) {\n\t\t\t\tassert(flags[i] in simplifyOptions);\n\t\t\t\toptions |= simplifyOptions[flags[i]];\n\t\t\t}\n\n\t\t\tvar indices32 = indices.BYTES_PER_ELEMENT == 4 ? indices : new Uint32Array(indices);\n\t\t\tvar result = simplifyUpdate(\n\t\t\t\tinstance.exports.meshopt_simplifyWithUpdate,\n\t\t\t\tindices32,\n\t\t\t\tindices.length,\n\t\t\t\tvertex_positions,\n\t\t\t\tvertex_positions.length / vertex_positions_stride,\n\t\t\t\tvertex_positions_stride * 4,\n\t\t\t\tvertex_attributes,\n\t\t\t\tvertex_attributes_stride * 4,\n\t\t\t\tnew Float32Array(attribute_weights),\n\t\t\t\tvertex_lock,\n\t\t\t\ttarget_index_count,\n\t\t\t\ttarget_error,\n\t\t\t\toptions\n\t\t\t);\n\t\t\tif (indices !== indices32) {\n\t\t\t\t// copy back indices if they were converted to Uint32Array\n\t\t\t\tfor (var i = 0; i < result[0]; ++i) {\n\t\t\t\t\tindices[i] = indices32[i];\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn result;\n\t\t},\n\n\t\tgetScale: function (vertex_positions, vertex_positions_stride) {\n\t\t\tassert(vertex_positions instanceof Float32Array);\n\t\t\tassert(vertex_positions.length % vertex_positions_stride == 0);\n\t\t\tassert(vertex_positions_stride >= 3);\n\t\t\treturn simplifyScale(\n\t\t\t\tinstance.exports.meshopt_simplifyScale,\n\t\t\t\tvertex_positions,\n\t\t\t\tvertex_positions.length / vertex_positions_stride,\n\t\t\t\tvertex_positions_stride * 4\n\t\t\t);\n\t\t},\n\n\t\tsimplifyPoints: function (vertex_positions, vertex_positions_stride, target_vertex_count, vertex_colors, vertex_colors_stride, color_weight) {\n\t\t\tassert(vertex_positions instanceof Float32Array);\n\t\t\tassert(vertex_positions.length % vertex_positions_stride == 0);\n\t\t\tassert(vertex_positions_stride >= 3);\n\t\t\tassert(target_vertex_count >= 0 && target_vertex_count <= vertex_positions.length / vertex_positions_stride);\n\t\t\tif (vertex_colors) {\n\t\t\t\tassert(vertex_colors instanceof Float32Array);\n\t\t\t\tassert(vertex_colors.length % vertex_colors_stride == 0);\n\t\t\t\tassert(vertex_colors_stride >= 3);\n\t\t\t\tassert(vertex_positions.length / vertex_positions_stride == vertex_colors.length / vertex_colors_stride);\n\t\t\t\treturn simplifyPoints(\n\t\t\t\t\tinstance.exports.meshopt_simplifyPoints,\n\t\t\t\t\tvertex_positions,\n\t\t\t\t\tvertex_positions.length / vertex_positions_stride,\n\t\t\t\t\tvertex_positions_stride * 4,\n\t\t\t\t\tvertex_colors,\n\t\t\t\t\tvertex_colors_stride * 4,\n\t\t\t\t\tcolor_weight,\n\t\t\t\t\ttarget_vertex_count\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\treturn simplifyPoints(\n\t\t\t\t\tinstance.exports.meshopt_simplifyPoints,\n\t\t\t\t\tvertex_positions,\n\t\t\t\t\tvertex_positions.length / vertex_positions_stride,\n\t\t\t\t\tvertex_positions_stride * 4,\n\t\t\t\t\tundefined,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t\ttarget_vertex_count\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\n\t\tsimplifySloppy: function (indices, vertex_positions, vertex_positions_stride, vertex_lock, target_index_count, target_error) {\n\t\t\tassert(\n\t\t\t\tindices instanceof Uint32Array || indices instanceof Int32Array || indices instanceof Uint16Array || indices instanceof Int16Array\n\t\t\t);\n\t\t\tassert(indices.length % 3 == 0);\n\t\t\tassert(vertex_positions instanceof Float32Array);\n\t\t\tassert(vertex_positions.length % vertex_positions_stride == 0);\n\t\t\tassert(vertex_positions_stride >= 3);\n\t\t\tassert(vertex_lock == null || vertex_lock instanceof Uint8Array);\n\t\t\tassert(vertex_lock == null || vertex_lock.length == vertex_positions.length / vertex_positions_stride);\n\t\t\tassert(target_index_count >= 0 && target_index_count <= indices.length);\n\t\t\tassert(target_index_count % 3 == 0);\n\t\t\tassert(target_error >= 0);\n\n\t\t\tvar indices32 = indices.BYTES_PER_ELEMENT == 4 ? indices : new Uint32Array(indices);\n\t\t\tvar result = simplifySloppy(\n\t\t\t\tinstance.exports.meshopt_simplifySloppy,\n\t\t\t\tindices32,\n\t\t\t\tindices.length,\n\t\t\t\tvertex_positions,\n\t\t\t\tvertex_positions.length / vertex_positions_stride,\n\t\t\t\tvertex_positions_stride * 4,\n\t\t\t\tvertex_lock,\n\t\t\t\ttarget_index_count,\n\t\t\t\ttarget_error\n\t\t\t);\n\t\t\tresult[0] = indices instanceof Uint32Array ? result[0] : new indices.constructor(result[0]);\n\n\t\t\treturn result;\n\t\t},\n\n\t\tsimplifyPrune: function (indices, vertex_positions, vertex_positions_stride, target_error) {\n\t\t\tassert(\n\t\t\t\tindices instanceof Uint32Array || indices instanceof Int32Array || indices instanceof Uint16Array || indices instanceof Int16Array\n\t\t\t);\n\t\t\tassert(indices.length % 3 == 0);\n\t\t\tassert(vertex_positions instanceof Float32Array);\n\t\t\tassert(vertex_positions.length % vertex_positions_stride == 0);\n\t\t\tassert(vertex_positions_stride >= 3);\n\t\t\tassert(target_error >= 0);\n\n\t\t\tvar indices32 = indices.BYTES_PER_ELEMENT == 4 ? indices : new Uint32Array(indices);\n\t\t\tvar result = simplifyPrune(\n\t\t\t\tinstance.exports.meshopt_simplifyPrune,\n\t\t\t\tindices32,\n\t\t\t\tindices.length,\n\t\t\t\tvertex_positions,\n\t\t\t\tvertex_positions.length / vertex_positions_stride,\n\t\t\t\tvertex_positions_stride * 4,\n\t\t\t\ttarget_error\n\t\t\t);\n\t\t\tresult = indices instanceof Uint32Array ? result : new indices.constructor(result);\n\n\t\t\treturn result;\n\t\t},\n\t};\n})();\n\nexport { MeshoptSimplifier };\n"
  },
  {
    "path": "js/meshopt_simplifier.test.js",
    "content": "import assert from 'assert/strict';\nimport { MeshoptSimplifier as simplifier } from './meshopt_simplifier.js';\n\nprocess.on('unhandledRejection', (error) => {\n\tconsole.log('unhandledRejection', error);\n\tprocess.exit(1);\n});\n\nvar tests = {\n\tcompactMesh: function () {\n\t\tvar indices = new Uint32Array([0, 1, 3, 3, 1, 5]);\n\n\t\tvar expected = new Uint32Array([0, 1, 2, 2, 1, 3]);\n\n\t\tvar missing = 2 ** 32 - 1;\n\n\t\tvar remap = new Uint32Array([0, 1, missing, 2, missing, 3]);\n\n\t\tvar res = simplifier.compactMesh(indices);\n\t\tassert.deepEqual(indices, expected);\n\t\tassert.deepEqual(res[0], remap);\n\t\tassert.equal(res[1], 4); // unique\n\t},\n\n\tsimplify: function () {\n\t\t// 0\n\t\t// 1 2\n\t\t// 3 4 5\n\t\tvar indices = new Uint32Array([0, 2, 1, 1, 2, 3, 3, 2, 4, 2, 5, 4]);\n\n\t\tvar positions = new Float32Array([0, 4, 0, 0, 1, 0, 2, 2, 0, 0, 0, 0, 1, 0, 0, 4, 0, 0]);\n\n\t\tvar res = simplifier.simplify(indices, positions, 3, /* target indices */ 3, /* target error */ 0.01);\n\n\t\tvar expected = new Uint32Array([0, 5, 3]);\n\n\t\tassert.deepEqual(res[0], expected);\n\t\tassert(res[1] < 1e-4); // error\n\t},\n\n\tsimplify16: function () {\n\t\t// 0\n\t\t// 1 2\n\t\t// 3 4 5\n\t\tvar indices = new Uint16Array([0, 2, 1, 1, 2, 3, 3, 2, 4, 2, 5, 4]);\n\n\t\tvar positions = new Float32Array([0, 4, 0, 0, 1, 0, 2, 2, 0, 0, 0, 0, 1, 0, 0, 4, 0, 0]);\n\n\t\tvar res = simplifier.simplify(indices, positions, 3, /* target indices */ 3, /* target error */ 0.01);\n\n\t\tvar expected = new Uint16Array([0, 5, 3]);\n\n\t\tassert.deepEqual(res[0], expected);\n\t\tassert(res[1] < 1e-4); // error\n\t},\n\n\tsimplifyLockBorder: function () {\n\t\t// 0\n\t\t// 1 2\n\t\t// 3 4 5\n\t\tvar indices = new Uint32Array([0, 2, 1, 1, 2, 3, 3, 2, 4, 2, 5, 4]);\n\n\t\tvar positions = new Float32Array([0, 2, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0]);\n\n\t\tvar res = simplifier.simplify(indices, positions, 3, /* target indices */ 3, /* target error */ 0.01, ['LockBorder']);\n\n\t\tvar expected = new Uint32Array([0, 2, 1, 1, 2, 3, 3, 2, 4, 2, 5, 4]);\n\n\t\tassert.deepEqual(res[0], expected);\n\t\tassert(res[1] < 1e-4); // error\n\t},\n\n\tsimplifyAttr: function () {\n\t\tvar vb_pos = new Float32Array(8 * 3 * 3);\n\t\tvar vb_att = new Float32Array(8 * 3 * 3);\n\n\t\tfor (var y = 0; y < 8; ++y) {\n\t\t\t// first four rows are a blue gradient, next four rows are a yellow gradient\n\t\t\tvar r = y < 4 ? 0.8 + y * 0.05 : 0;\n\t\t\tvar g = y < 4 ? 0.8 + y * 0.05 : 0;\n\t\t\tvar b = y < 4 ? 0 : 0.8 + (7 - y) * 0.05;\n\n\t\t\tfor (var x = 0; x < 3; ++x) {\n\t\t\t\tvb_pos[(y * 3 + x) * 3 + 0] = x;\n\t\t\t\tvb_pos[(y * 3 + x) * 3 + 1] = y;\n\t\t\t\tvb_pos[(y * 3 + x) * 3 + 2] = 0.03 * x + 0.028 * (y % 2) + (x == 2 && y == 7 ? 1 : 0) * 0.03;\n\t\t\t\tvb_att[(y * 3 + x) * 3 + 0] = r;\n\t\t\t\tvb_att[(y * 3 + x) * 3 + 1] = g;\n\t\t\t\tvb_att[(y * 3 + x) * 3 + 2] = b;\n\t\t\t}\n\t\t}\n\n\t\tvar ib = new Uint32Array(7 * 2 * 6);\n\n\t\tfor (var y = 0; y < 7; ++y) {\n\t\t\tfor (var x = 0; x < 2; ++x) {\n\t\t\t\tib[(y * 2 + x) * 6 + 0] = (y + 0) * 3 + (x + 0);\n\t\t\t\tib[(y * 2 + x) * 6 + 1] = (y + 0) * 3 + (x + 1);\n\t\t\t\tib[(y * 2 + x) * 6 + 2] = (y + 1) * 3 + (x + 0);\n\t\t\t\tib[(y * 2 + x) * 6 + 3] = (y + 1) * 3 + (x + 0);\n\t\t\t\tib[(y * 2 + x) * 6 + 4] = (y + 0) * 3 + (x + 1);\n\t\t\t\tib[(y * 2 + x) * 6 + 5] = (y + 1) * 3 + (x + 1);\n\t\t\t}\n\t\t}\n\n\t\tvar attr_weights = [0.5, 0.5, 0.5];\n\n\t\tvar res = simplifier.simplifyWithAttributes(ib, vb_pos, 3, vb_att, 3, attr_weights, null, 6 * 3, 1e-2);\n\n\t\tvar expected = new Uint32Array([0, 2, 11, 0, 11, 9, 9, 11, 12, 12, 11, 14, 12, 14, 23, 12, 23, 21]);\n\n\t\tassert.deepEqual(res[0], expected);\n\t},\n\n\tsimplifyUpdate: function () {\n\t\tvar indices = new Uint32Array([0, 1, 3, 3, 1, 4, 4, 1, 2, 0, 3, 2, 3, 4, 2]);\n\n\t\tvar positions = new Float32Array([0, 0, 0, 1, 1, 0, 2, 0, 0, 0.9, 0.2, 0.1, 1.1, 0.2, 0.1]);\n\t\tvar attributes = new Float32Array([0, 0, 0, 0.2, 0.1]);\n\n\t\tvar res = simplifier.simplifyWithUpdate(indices, positions, 3, attributes, 1, [1], null, 9, 1);\n\n\t\tvar expected = new Uint32Array([0, 1, 3, 3, 1, 2, 0, 3, 2]);\n\n\t\tassert.equal(res[0], expected.length);\n\t\tassert.deepEqual(indices.subarray(0, expected.length), expected);\n\n\t\t// border vertices haven't moved but may have small floating point drift\n\t\tfor (var i = 0; i < 3; ++i) {\n\t\t\tassert(Math.abs(attributes[i]) < 1e-6);\n\t\t}\n\n\t\t// center vertex got updated\n\t\tassert(Math.abs(positions[3 * 3 + 0] - 0.88) < 1e-2);\n\t\tassert(Math.abs(positions[3 * 3 + 1] - 0.19) < 1e-2);\n\t\tassert(Math.abs(positions[3 * 3 + 2] - 0.11) < 1e-2);\n\t\tassert(Math.abs(attributes[3] - 0.18) < 1e-2);\n\t},\n\n\tsimplifyLockFlags: function () {\n\t\t// 0\n\t\t// 1 2\n\t\t// 3 4 5\n\t\tvar indices = new Uint32Array([0, 2, 1, 1, 2, 3, 3, 2, 4, 2, 5, 4]);\n\n\t\tvar positions = new Float32Array([0, 2, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0]);\n\t\tvar locks = new Uint8Array([1, 1, 1, 1, 0, 1]); // only vertex 4 can move\n\n\t\tvar res = simplifier.simplifyWithAttributes(indices, positions, 3, new Float32Array(), 0, [], locks, 3, 0.01);\n\n\t\tvar expected = new Uint32Array([0, 2, 1, 1, 2, 3, 2, 5, 3]);\n\n\t\tassert.deepEqual(res[0], expected);\n\t\tassert(res[1] < 1e-4); // error\n\t},\n\n\tgetScale: function () {\n\t\tvar positions = new Float32Array([0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3]);\n\n\t\tassert(simplifier.getScale(positions, 3) == 3.0);\n\t},\n\n\tsimplifyPoints: function () {\n\t\tvar positions = new Float32Array([0, 0, 0, 100, 0, 0, 100, 1, 1, 110, 0, 0]);\n\t\tvar colors = new Float32Array([1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]);\n\n\t\tvar expected = new Uint32Array([0, 1]);\n\n\t\tvar expectedC = new Uint32Array([0, 2]);\n\n\t\tvar res = simplifier.simplifyPoints(positions, 3, 2);\n\t\tassert.deepEqual(res, expected);\n\n\t\t// note: recommended value for color_weight is 1e-2 but here we push color weight to be very high to bias candidate selection for testing\n\t\tvar resC1 = simplifier.simplifyPoints(positions, 3, 2, colors, 3, 1e-1);\n\t\tassert.deepEqual(resC1, expectedC);\n\n\t\tvar resC2 = simplifier.simplifyPoints(positions, 3, 2, colors, 3, 1e-2);\n\t\tassert.deepEqual(resC2, expected);\n\t},\n\n\tsimplifyPrune: function () {\n\t\tvar indices = new Uint32Array([0, 1, 2, 3, 4, 5, 6, 7, 8]);\n\n\t\tvar positions = new Float32Array([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]);\n\n\t\tvar expected = new Uint32Array([6, 7, 8]);\n\n\t\tvar res = simplifier.simplifyPrune(indices, positions, 3, 0.5);\n\t\tassert.deepEqual(res, expected);\n\t},\n};\n\nPromise.all([simplifier.ready]).then(() => {\n\tvar count = 0;\n\n\tfor (var key in tests) {\n\t\ttests[key]();\n\t\tcount++;\n\t}\n\n\tconsole.log(count, 'tests passed');\n});\n"
  },
  {
    "path": "js/package.json",
    "content": "{\n\t\"name\": \"meshoptimizer\",\n\t\"version\": \"1.0.1\",\n\t\"description\": \"Mesh optimization library that makes meshes smaller and faster to render\",\n\t\"author\": \"Arseny Kapoulkine\",\n\t\"license\": \"MIT\",\n\t\"bugs\": \"https://github.com/zeux/meshoptimizer/issues\",\n\t\"homepage\": \"https://github.com/zeux/meshoptimizer\",\n\t\"keywords\": [\n\t\t\"compression\",\n\t\t\"mesh\"\n\t],\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"https://github.com/zeux/meshoptimizer\"\n\t},\n\t\"files\": [\n\t\t\"*.cjs\",\n\t\t\"*.mjs\",\n\t\t\"*.js\",\n\t\t\"*.ts\"\n\t],\n\t\"type\": \"module\",\n\t\"main\": \"index.js\",\n\t\"types\": \"index.d.ts\",\n\t\"exports\": {\n\t\t\".\": {\n\t\t\t\"types\": \"./index.d.ts\",\n\t\t\t\"default\": \"./index.js\"\n\t\t},\n\t\t\"./encoder\": {\n\t\t\t\"types\": \"./meshopt_encoder.d.ts\",\n\t\t\t\"default\": \"./meshopt_encoder.js\"\n\t\t},\n\t\t\"./decoder\": {\n\t\t\t\"types\": \"./meshopt_decoder.d.ts\",\n\t\t\t\"default\": \"./meshopt_decoder.mjs\"\n\t\t},\n\t\t\"./simplifier\": {\n\t\t\t\"types\": \"./meshopt_simplifier.d.ts\",\n\t\t\t\"default\": \"./meshopt_simplifier.js\"\n\t\t},\n\t\t\"./clusterizer\": {\n\t\t\t\"types\": \"./meshopt_clusterizer.d.ts\",\n\t\t\t\"default\": \"./meshopt_clusterizer.js\"\n\t\t},\n\t\t\"./decoder.cjs\": {\n\t\t\t\"require\": \"./meshopt_decoder.cjs\"\n\t\t}\n\t},\n\t\"scripts\": {\n\t\t\"test\": \"node meshopt_encoder.test.js && node meshopt_decoder.test.js && node meshopt_simplifier.test.js && node meshopt_clusterizer.test.js\",\n\t\t\"prepublishOnly\": \"npm test\"\n\t}\n}\n"
  },
  {
    "path": "js/wasi_trace.js",
    "content": "// Usage:\n// 1. import { wasi_trace } from './wasi_trace.js';\n// 2. Pass wasi_trace as an import object to WebAssembly.instantiate\n// 3. Call wasi_trace.init(instance) after instantiation\n\nvar instance;\n\nvar wasi_snapshot_preview1 = {\n\tfd_close: function () {\n\t\treturn 8;\n\t},\n\tfd_seek: function () {\n\t\treturn 8;\n\t},\n\tfd_fdstat_get: function (fd, stat) {\n\t\t// needed for isatty() to enable line buffering for stdout\n\t\tvar heap = new DataView(instance.exports.memory.buffer);\n\t\theap.setUint8(stat, 2);\n\t\tfor (var i = 1; i < 24; ++i) heap.setUint8(stat + i, 0);\n\t\treturn 0;\n\t},\n\tfd_write: function (fd, iovs, iovs_len, nwritten) {\n\t\tvar heap = new DataView(instance.exports.memory.buffer);\n\t\tvar written = 0;\n\t\tvar str = '';\n\n\t\tfor (var i = 0; i < iovs_len; ++i) {\n\t\t\tvar buf = heap.getUint32(iovs + 8 * i + 0, true);\n\t\t\tvar buf_len = heap.getUint32(iovs + 8 * i + 4, true);\n\n\t\t\tvar buf_data = new Uint8Array(heap.buffer, buf, buf_len);\n\n\t\t\tfor (var j = 0; j < buf_data.length; ++j) {\n\t\t\t\tstr += String.fromCharCode(buf_data[j]);\n\t\t\t}\n\t\t\twritten += buf_len;\n\t\t}\n\n\t\tconsole.log(str);\n\n\t\theap.setUint32(nwritten, written, true);\n\t\treturn 0;\n\t},\n};\n\nvar wasi_trace = {\n\twasi_snapshot_preview1,\n\n\tinit: function (inst) {\n\t\tinstance = inst;\n\t},\n};\n\nexport { wasi_trace };\n"
  },
  {
    "path": "src/allocator.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#ifdef MESHOPTIMIZER_ALLOC_EXPORT\nmeshopt_Allocator::Storage& meshopt_Allocator::storage()\n{\n\tstatic Storage s = {::operator new, ::operator delete };\n\treturn s;\n}\n#endif\n\nvoid meshopt_setAllocator(void* (MESHOPTIMIZER_ALLOC_CALLCONV* allocate)(size_t), void (MESHOPTIMIZER_ALLOC_CALLCONV* deallocate)(void*))\n{\n\tmeshopt_Allocator::Storage& s = meshopt_Allocator::storage();\n\ts.allocate = allocate;\n\ts.deallocate = deallocate;\n}\n"
  },
  {
    "path": "src/clusterizer.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <float.h>\n#include <math.h>\n#include <string.h>\n\n// The block below auto-detects SIMD ISA that can be used on the target platform\n#ifndef MESHOPTIMIZER_NO_SIMD\n#if defined(__SSE2__) || (defined(_MSC_VER) && defined(_M_X64) && !defined(_M_ARM64EC))\n#define SIMD_SSE\n#include <emmintrin.h>\n#elif defined(__aarch64__) || (defined(_MSC_VER) && (defined(_M_ARM64) || defined(_M_ARM64EC)) && _MSC_VER >= 1922)\n#define SIMD_NEON\n#include <arm_neon.h>\n#endif\n#endif // !MESHOPTIMIZER_NO_SIMD\n\n// This work is based on:\n// Graham Wihlidal. Optimizing the Graphics Pipeline with Compute. 2016\n// Ingo Wald, Vlastimil Havran. On building fast kd-Trees for Ray Tracing, and on doing that in O(N log N). 2006\nnamespace meshopt\n{\n\n// We keep a limited number of seed triangles and add a few triangles per finished meshlet\nconst size_t kMeshletMaxSeeds = 256;\nconst size_t kMeshletAddSeeds = 4;\n\n// To avoid excessive recursion for malformed inputs, we limit the maximum depth of the tree\nconst int kMeshletMaxTreeDepth = 50;\n\nstruct TriangleAdjacency2\n{\n\tunsigned int* counts;\n\tunsigned int* offsets;\n\tunsigned int* data;\n};\n\nstatic void buildTriangleAdjacency(TriangleAdjacency2& adjacency, const unsigned int* indices, size_t index_count, size_t vertex_count, meshopt_Allocator& allocator)\n{\n\tsize_t face_count = index_count / 3;\n\n\t// allocate arrays\n\tadjacency.counts = allocator.allocate<unsigned int>(vertex_count);\n\tadjacency.offsets = allocator.allocate<unsigned int>(vertex_count);\n\tadjacency.data = allocator.allocate<unsigned int>(index_count);\n\n\t// fill triangle counts\n\tmemset(adjacency.counts, 0, vertex_count * sizeof(unsigned int));\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tassert(indices[i] < vertex_count);\n\n\t\tadjacency.counts[indices[i]]++;\n\t}\n\n\t// fill offset table\n\tunsigned int offset = 0;\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tadjacency.offsets[i] = offset;\n\t\toffset += adjacency.counts[i];\n\t}\n\n\tassert(offset == index_count);\n\n\t// fill triangle data\n\tfor (size_t i = 0; i < face_count; ++i)\n\t{\n\t\tunsigned int a = indices[i * 3 + 0], b = indices[i * 3 + 1], c = indices[i * 3 + 2];\n\n\t\tadjacency.data[adjacency.offsets[a]++] = unsigned(i);\n\t\tadjacency.data[adjacency.offsets[b]++] = unsigned(i);\n\t\tadjacency.data[adjacency.offsets[c]++] = unsigned(i);\n\t}\n\n\t// fix offsets that have been disturbed by the previous pass\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tassert(adjacency.offsets[i] >= adjacency.counts[i]);\n\t\tadjacency.offsets[i] -= adjacency.counts[i];\n\t}\n}\n\nstatic void buildTriangleAdjacencySparse(TriangleAdjacency2& adjacency, const unsigned int* indices, size_t index_count, size_t vertex_count, meshopt_Allocator& allocator)\n{\n\tsize_t face_count = index_count / 3;\n\n\t// sparse mode can build adjacency more quickly by ignoring unused vertices, using a bit to mark visited vertices\n\tconst unsigned int sparse_seen = 1u << 31;\n\tassert(index_count < sparse_seen);\n\n\t// allocate arrays\n\tadjacency.counts = allocator.allocate<unsigned int>(vertex_count);\n\tadjacency.offsets = allocator.allocate<unsigned int>(vertex_count);\n\tadjacency.data = allocator.allocate<unsigned int>(index_count);\n\n\t// fill triangle counts\n\tfor (size_t i = 0; i < index_count; ++i)\n\t\tassert(indices[i] < vertex_count);\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t\tadjacency.counts[indices[i]] = 0;\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t\tadjacency.counts[indices[i]]++;\n\n\t// fill offset table; uses sparse_seen bit to tag visited vertices\n\tunsigned int offset = 0;\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int v = indices[i];\n\n\t\tif ((adjacency.counts[v] & sparse_seen) == 0)\n\t\t{\n\t\t\tadjacency.offsets[v] = offset;\n\t\t\toffset += adjacency.counts[v];\n\t\t\tadjacency.counts[v] |= sparse_seen;\n\t\t}\n\t}\n\n\tassert(offset == index_count);\n\n\t// fill triangle data\n\tfor (size_t i = 0; i < face_count; ++i)\n\t{\n\t\tunsigned int a = indices[i * 3 + 0], b = indices[i * 3 + 1], c = indices[i * 3 + 2];\n\n\t\tadjacency.data[adjacency.offsets[a]++] = unsigned(i);\n\t\tadjacency.data[adjacency.offsets[b]++] = unsigned(i);\n\t\tadjacency.data[adjacency.offsets[c]++] = unsigned(i);\n\t}\n\n\t// fix offsets that have been disturbed by the previous pass\n\t// also fix counts (that were marked with sparse_seen by the first pass)\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int v = indices[i];\n\n\t\tif (adjacency.counts[v] & sparse_seen)\n\t\t{\n\t\t\tadjacency.counts[v] &= ~sparse_seen;\n\n\t\t\tassert(adjacency.offsets[v] >= adjacency.counts[v]);\n\t\t\tadjacency.offsets[v] -= adjacency.counts[v];\n\t\t}\n\t}\n}\n\nstatic void clearUsed(short* used, size_t vertex_count, const unsigned int* indices, size_t index_count)\n{\n\t// for sparse inputs, it's faster to only clear vertices referenced by the index buffer\n\tif (vertex_count <= index_count)\n\t\tmemset(used, -1, vertex_count * sizeof(short));\n\telse\n\t\tfor (size_t i = 0; i < index_count; ++i)\n\t\t{\n\t\t\tassert(indices[i] < vertex_count);\n\t\t\tused[indices[i]] = -1;\n\t\t}\n}\n\nstruct Cone\n{\n\tfloat px, py, pz;\n\tfloat nx, ny, nz;\n};\n\nstatic float getMeshletScore(float distance, float spread, float cone_weight, float expected_radius)\n{\n\tfloat cone = 1.f - spread * cone_weight;\n\tfloat cone_clamped = cone < 1e-3f ? 1e-3f : cone;\n\n\treturn (1 + distance / expected_radius * (1 - cone_weight)) * cone_clamped;\n}\n\nstatic Cone getMeshletCone(const Cone& acc, unsigned int triangle_count)\n{\n\tCone result = acc;\n\n\tfloat center_scale = triangle_count == 0 ? 0.f : 1.f / float(triangle_count);\n\n\tresult.px *= center_scale;\n\tresult.py *= center_scale;\n\tresult.pz *= center_scale;\n\n\tfloat axis_length = result.nx * result.nx + result.ny * result.ny + result.nz * result.nz;\n\tfloat axis_scale = axis_length == 0.f ? 0.f : 1.f / sqrtf(axis_length);\n\n\tresult.nx *= axis_scale;\n\tresult.ny *= axis_scale;\n\tresult.nz *= axis_scale;\n\n\treturn result;\n}\n\nstatic float computeTriangleCones(Cone* triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\t(void)vertex_count;\n\n\tsize_t vertex_stride_float = vertex_positions_stride / sizeof(float);\n\tsize_t face_count = index_count / 3;\n\n\tfloat mesh_area = 0;\n\n\tfor (size_t i = 0; i < face_count; ++i)\n\t{\n\t\tunsigned int a = indices[i * 3 + 0], b = indices[i * 3 + 1], c = indices[i * 3 + 2];\n\t\tassert(a < vertex_count && b < vertex_count && c < vertex_count);\n\n\t\tconst float* p0 = vertex_positions + vertex_stride_float * a;\n\t\tconst float* p1 = vertex_positions + vertex_stride_float * b;\n\t\tconst float* p2 = vertex_positions + vertex_stride_float * c;\n\n\t\tfloat p10[3] = {p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]};\n\t\tfloat p20[3] = {p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]};\n\n\t\tfloat normalx = p10[1] * p20[2] - p10[2] * p20[1];\n\t\tfloat normaly = p10[2] * p20[0] - p10[0] * p20[2];\n\t\tfloat normalz = p10[0] * p20[1] - p10[1] * p20[0];\n\n\t\tfloat area = sqrtf(normalx * normalx + normaly * normaly + normalz * normalz);\n\t\tfloat invarea = (area == 0.f) ? 0.f : 1.f / area;\n\n\t\ttriangles[i].px = (p0[0] + p1[0] + p2[0]) / 3.f;\n\t\ttriangles[i].py = (p0[1] + p1[1] + p2[1]) / 3.f;\n\t\ttriangles[i].pz = (p0[2] + p1[2] + p2[2]) / 3.f;\n\n\t\ttriangles[i].nx = normalx * invarea;\n\t\ttriangles[i].ny = normaly * invarea;\n\t\ttriangles[i].nz = normalz * invarea;\n\n\t\tmesh_area += area;\n\t}\n\n\treturn mesh_area;\n}\n\nstatic bool appendMeshlet(meshopt_Meshlet& meshlet, unsigned int a, unsigned int b, unsigned int c, short* used, meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, size_t meshlet_offset, size_t max_vertices, size_t max_triangles, bool split = false)\n{\n\tshort& av = used[a];\n\tshort& bv = used[b];\n\tshort& cv = used[c];\n\n\tbool result = false;\n\n\tint used_extra = (av < 0) + (bv < 0) + (cv < 0);\n\n\tif (meshlet.vertex_count + used_extra > max_vertices || meshlet.triangle_count >= max_triangles || split)\n\t{\n\t\tmeshlets[meshlet_offset] = meshlet;\n\n\t\tfor (size_t j = 0; j < meshlet.vertex_count; ++j)\n\t\t\tused[meshlet_vertices[meshlet.vertex_offset + j]] = -1;\n\n\t\tmeshlet.vertex_offset += meshlet.vertex_count;\n\t\tmeshlet.triangle_offset += meshlet.triangle_count * 3;\n\t\tmeshlet.vertex_count = 0;\n\t\tmeshlet.triangle_count = 0;\n\n\t\tresult = true;\n\t}\n\n\tif (av < 0)\n\t{\n\t\tav = short(meshlet.vertex_count);\n\t\tmeshlet_vertices[meshlet.vertex_offset + meshlet.vertex_count++] = a;\n\t}\n\n\tif (bv < 0)\n\t{\n\t\tbv = short(meshlet.vertex_count);\n\t\tmeshlet_vertices[meshlet.vertex_offset + meshlet.vertex_count++] = b;\n\t}\n\n\tif (cv < 0)\n\t{\n\t\tcv = short(meshlet.vertex_count);\n\t\tmeshlet_vertices[meshlet.vertex_offset + meshlet.vertex_count++] = c;\n\t}\n\n\tmeshlet_triangles[meshlet.triangle_offset + meshlet.triangle_count * 3 + 0] = (unsigned char)av;\n\tmeshlet_triangles[meshlet.triangle_offset + meshlet.triangle_count * 3 + 1] = (unsigned char)bv;\n\tmeshlet_triangles[meshlet.triangle_offset + meshlet.triangle_count * 3 + 2] = (unsigned char)cv;\n\tmeshlet.triangle_count++;\n\n\treturn result;\n}\n\nstatic unsigned int getNeighborTriangle(const meshopt_Meshlet& meshlet, const Cone& meshlet_cone, const unsigned int* meshlet_vertices, const unsigned int* indices, const TriangleAdjacency2& adjacency, const Cone* triangles, const unsigned int* live_triangles, const short* used, float meshlet_expected_radius, float cone_weight)\n{\n\tunsigned int best_triangle = ~0u;\n\tint best_priority = 5;\n\tfloat best_score = FLT_MAX;\n\n\tfor (size_t i = 0; i < meshlet.vertex_count; ++i)\n\t{\n\t\tunsigned int index = meshlet_vertices[meshlet.vertex_offset + i];\n\n\t\tunsigned int* neighbors = &adjacency.data[0] + adjacency.offsets[index];\n\t\tsize_t neighbors_size = adjacency.counts[index];\n\n\t\tfor (size_t j = 0; j < neighbors_size; ++j)\n\t\t{\n\t\t\tunsigned int triangle = neighbors[j];\n\t\t\tunsigned int a = indices[triangle * 3 + 0], b = indices[triangle * 3 + 1], c = indices[triangle * 3 + 2];\n\n\t\t\tint extra = (used[a] < 0) + (used[b] < 0) + (used[c] < 0);\n\t\t\tassert(extra <= 2);\n\n\t\t\tint priority = -1;\n\n\t\t\t// triangles that don't add new vertices to meshlets are max. priority\n\t\t\tif (extra == 0)\n\t\t\t\tpriority = 0;\n\t\t\t// artificially increase the priority of dangling triangles as they're expensive to add to new meshlets\n\t\t\telse if (live_triangles[a] == 1 || live_triangles[b] == 1 || live_triangles[c] == 1)\n\t\t\t\tpriority = 1;\n\t\t\t// if two vertices have live count of 2, removing this triangle will make another triangle dangling which is good for overall flow\n\t\t\telse if ((live_triangles[a] == 2) + (live_triangles[b] == 2) + (live_triangles[c] == 2) >= 2)\n\t\t\t\tpriority = 1 + extra;\n\t\t\t// otherwise adjust priority to be after the above cases, 3 or 4 based on used[] count\n\t\t\telse\n\t\t\t\tpriority = 2 + extra;\n\n\t\t\t// since topology-based priority is always more important than the score, we can skip scoring in some cases\n\t\t\tif (priority > best_priority)\n\t\t\t\tcontinue;\n\n\t\t\tconst Cone& tri_cone = triangles[triangle];\n\n\t\t\tfloat dx = tri_cone.px - meshlet_cone.px, dy = tri_cone.py - meshlet_cone.py, dz = tri_cone.pz - meshlet_cone.pz;\n\t\t\tfloat distance = sqrtf(dx * dx + dy * dy + dz * dz);\n\t\t\tfloat spread = tri_cone.nx * meshlet_cone.nx + tri_cone.ny * meshlet_cone.ny + tri_cone.nz * meshlet_cone.nz;\n\n\t\t\tfloat score = getMeshletScore(distance, spread, cone_weight, meshlet_expected_radius);\n\n\t\t\t// note that topology-based priority is always more important than the score\n\t\t\t// this helps maintain reasonable effectiveness of meshlet data and reduces scoring cost\n\t\t\tif (priority < best_priority || score < best_score)\n\t\t\t{\n\t\t\t\tbest_triangle = triangle;\n\t\t\t\tbest_priority = priority;\n\t\t\t\tbest_score = score;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn best_triangle;\n}\n\nstatic size_t appendSeedTriangles(unsigned int* seeds, const meshopt_Meshlet& meshlet, const unsigned int* meshlet_vertices, const unsigned int* indices, const TriangleAdjacency2& adjacency, const Cone* triangles, const unsigned int* live_triangles, float cornerx, float cornery, float cornerz)\n{\n\tunsigned int best_seeds[kMeshletAddSeeds];\n\tunsigned int best_live[kMeshletAddSeeds];\n\tfloat best_score[kMeshletAddSeeds];\n\n\tfor (size_t i = 0; i < kMeshletAddSeeds; ++i)\n\t{\n\t\tbest_seeds[i] = ~0u;\n\t\tbest_live[i] = ~0u;\n\t\tbest_score[i] = FLT_MAX;\n\t}\n\n\tfor (size_t i = 0; i < meshlet.vertex_count; ++i)\n\t{\n\t\tunsigned int index = meshlet_vertices[meshlet.vertex_offset + i];\n\n\t\tunsigned int best_neighbor = ~0u;\n\t\tunsigned int best_neighbor_live = ~0u;\n\n\t\t// find the neighbor with the smallest live metric\n\t\tunsigned int* neighbors = &adjacency.data[0] + adjacency.offsets[index];\n\t\tsize_t neighbors_size = adjacency.counts[index];\n\n\t\tfor (size_t j = 0; j < neighbors_size; ++j)\n\t\t{\n\t\t\tunsigned int triangle = neighbors[j];\n\t\t\tunsigned int a = indices[triangle * 3 + 0], b = indices[triangle * 3 + 1], c = indices[triangle * 3 + 2];\n\n\t\t\tunsigned int live = live_triangles[a] + live_triangles[b] + live_triangles[c];\n\n\t\t\tif (live < best_neighbor_live)\n\t\t\t{\n\t\t\t\tbest_neighbor = triangle;\n\t\t\t\tbest_neighbor_live = live;\n\t\t\t}\n\t\t}\n\n\t\t// add the neighbor to the list of seeds; the list is unsorted and the replacement criteria is approximate\n\t\tif (best_neighbor == ~0u)\n\t\t\tcontinue;\n\n\t\tfloat dx = triangles[best_neighbor].px - cornerx, dy = triangles[best_neighbor].py - cornery, dz = triangles[best_neighbor].pz - cornerz;\n\t\tfloat best_neighbor_score = sqrtf(dx * dx + dy * dy + dz * dz);\n\n\t\tfor (size_t j = 0; j < kMeshletAddSeeds; ++j)\n\t\t{\n\t\t\t// non-strict comparison reduces the number of duplicate seeds (triangles adjacent to multiple vertices)\n\t\t\tif (best_neighbor_live < best_live[j] || (best_neighbor_live == best_live[j] && best_neighbor_score <= best_score[j]))\n\t\t\t{\n\t\t\t\tbest_seeds[j] = best_neighbor;\n\t\t\t\tbest_live[j] = best_neighbor_live;\n\t\t\t\tbest_score[j] = best_neighbor_score;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// add surviving seeds to the meshlet\n\tsize_t seed_count = 0;\n\n\tfor (size_t i = 0; i < kMeshletAddSeeds; ++i)\n\t\tif (best_seeds[i] != ~0u)\n\t\t\tseeds[seed_count++] = best_seeds[i];\n\n\treturn seed_count;\n}\n\nstatic size_t pruneSeedTriangles(unsigned int* seeds, size_t seed_count, const unsigned char* emitted_flags)\n{\n\tsize_t result = 0;\n\n\tfor (size_t i = 0; i < seed_count; ++i)\n\t{\n\t\tunsigned int index = seeds[i];\n\n\t\tseeds[result] = index;\n\t\tresult += emitted_flags[index] == 0;\n\t}\n\n\treturn result;\n}\n\nstatic unsigned int selectSeedTriangle(const unsigned int* seeds, size_t seed_count, const unsigned int* indices, const Cone* triangles, const unsigned int* live_triangles, float cornerx, float cornery, float cornerz)\n{\n\tunsigned int best_seed = ~0u;\n\tunsigned int best_live = ~0u;\n\tfloat best_score = FLT_MAX;\n\n\tfor (size_t i = 0; i < seed_count; ++i)\n\t{\n\t\tunsigned int index = seeds[i];\n\t\tunsigned int a = indices[index * 3 + 0], b = indices[index * 3 + 1], c = indices[index * 3 + 2];\n\n\t\tunsigned int live = live_triangles[a] + live_triangles[b] + live_triangles[c];\n\t\tfloat dx = triangles[index].px - cornerx, dy = triangles[index].py - cornery, dz = triangles[index].pz - cornerz;\n\t\tfloat score = sqrtf(dx * dx + dy * dy + dz * dz);\n\n\t\tif (live < best_live || (live == best_live && score < best_score))\n\t\t{\n\t\t\tbest_seed = index;\n\t\t\tbest_live = live;\n\t\t\tbest_score = score;\n\t\t}\n\t}\n\n\treturn best_seed;\n}\n\nstruct KDNode\n{\n\tunion\n\t{\n\t\tfloat split;\n\t\tunsigned int index;\n\t};\n\n\t// leaves: axis = 3, children = number of points including this one\n\t// branches: axis != 3, left subtree = skip 1, right subtree = skip 1+children\n\tunsigned int axis : 2;\n\tunsigned int children : 30;\n};\n\nstatic size_t kdtreePartition(unsigned int* indices, size_t count, const float* points, size_t stride, int axis, float pivot)\n{\n\tsize_t m = 0;\n\n\t// invariant: elements in range [0, m) are < pivot, elements in range [m, i) are >= pivot\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tfloat v = points[indices[i] * stride + axis];\n\n\t\t// swap(m, i) unconditionally\n\t\tunsigned int t = indices[m];\n\t\tindices[m] = indices[i];\n\t\tindices[i] = t;\n\n\t\t// when v >= pivot, we swap i with m without advancing it, preserving invariants\n\t\tm += v < pivot;\n\t}\n\n\treturn m;\n}\n\nstatic size_t kdtreeBuildLeaf(size_t offset, KDNode* nodes, size_t node_count, unsigned int* indices, size_t count)\n{\n\tassert(offset + count <= node_count);\n\t(void)node_count;\n\n\tKDNode& result = nodes[offset];\n\n\tresult.index = indices[0];\n\tresult.axis = 3;\n\tresult.children = unsigned(count);\n\n\t// all remaining points are stored in nodes immediately following the leaf\n\tfor (size_t i = 1; i < count; ++i)\n\t{\n\t\tKDNode& tail = nodes[offset + i];\n\n\t\ttail.index = indices[i];\n\t\ttail.axis = 3;\n\t\ttail.children = ~0u >> 2; // bogus value to prevent misuse\n\t}\n\n\treturn offset + count;\n}\n\nstatic size_t kdtreeBuild(size_t offset, KDNode* nodes, size_t node_count, const float* points, size_t stride, unsigned int* indices, size_t count, size_t leaf_size, int depth)\n{\n\tassert(count > 0);\n\tassert(offset < node_count);\n\n\tif (count <= leaf_size)\n\t\treturn kdtreeBuildLeaf(offset, nodes, node_count, indices, count);\n\n\tfloat mean[3] = {};\n\tfloat vars[3] = {};\n\tfloat runc = 1, runs = 1;\n\n\t// gather statistics on the points in the subtree using Welford's algorithm\n\tfor (size_t i = 0; i < count; ++i, runc += 1.f, runs = 1.f / runc)\n\t{\n\t\tconst float* point = points + indices[i] * stride;\n\n\t\tfor (int k = 0; k < 3; ++k)\n\t\t{\n\t\t\tfloat delta = point[k] - mean[k];\n\t\t\tmean[k] += delta * runs;\n\t\t\tvars[k] += delta * (point[k] - mean[k]);\n\t\t}\n\t}\n\n\t// split axis is one where the variance is largest\n\tint axis = (vars[0] >= vars[1] && vars[0] >= vars[2]) ? 0 : (vars[1] >= vars[2] ? 1 : 2);\n\n\tfloat split = mean[axis];\n\tsize_t middle = kdtreePartition(indices, count, points, stride, axis, split);\n\n\t// when the partition is degenerate simply consolidate the points into a single node\n\t// this also ensures recursion depth is bounded on pathological inputs\n\tif (middle <= leaf_size / 2 || middle >= count - leaf_size / 2 || depth >= kMeshletMaxTreeDepth)\n\t\treturn kdtreeBuildLeaf(offset, nodes, node_count, indices, count);\n\n\tKDNode& result = nodes[offset];\n\n\tresult.split = split;\n\tresult.axis = axis;\n\n\t// left subtree is right after our node\n\tsize_t next_offset = kdtreeBuild(offset + 1, nodes, node_count, points, stride, indices, middle, leaf_size, depth + 1);\n\n\t// distance to the right subtree is represented explicitly\n\tassert(next_offset - offset > 1);\n\tresult.children = unsigned(next_offset - offset - 1);\n\n\treturn kdtreeBuild(next_offset, nodes, node_count, points, stride, indices + middle, count - middle, leaf_size, depth + 1);\n}\n\nstatic void kdtreeNearest(KDNode* nodes, unsigned int root, const float* points, size_t stride, const unsigned char* emitted_flags, const float* position, unsigned int& result, float& limit)\n{\n\tconst KDNode& node = nodes[root];\n\n\tif (node.children == 0)\n\t\treturn;\n\n\tif (node.axis == 3)\n\t{\n\t\t// leaf\n\t\tbool inactive = true;\n\n\t\tfor (unsigned int i = 0; i < node.children; ++i)\n\t\t{\n\t\t\tunsigned int index = nodes[root + i].index;\n\n\t\t\tif (emitted_flags[index])\n\t\t\t\tcontinue;\n\n\t\t\tinactive = false;\n\n\t\t\tconst float* point = points + index * stride;\n\n\t\t\tfloat dx = point[0] - position[0], dy = point[1] - position[1], dz = point[2] - position[2];\n\t\t\tfloat distance = sqrtf(dx * dx + dy * dy + dz * dz);\n\n\t\t\tif (distance < limit)\n\t\t\t{\n\t\t\t\tresult = index;\n\t\t\t\tlimit = distance;\n\t\t\t}\n\t\t}\n\n\t\t// deactivate leaves that no longer have items to emit\n\t\tif (inactive)\n\t\t\tnodes[root].children = 0;\n\t}\n\telse\n\t{\n\t\t// branch; we order recursion to process the node that search position is in first\n\t\tfloat delta = position[node.axis] - node.split;\n\t\tunsigned int first = (delta <= 0) ? 0 : node.children;\n\t\tunsigned int second = first ^ node.children;\n\n\t\t// deactivate branches that no longer have items to emit to accelerate traversal\n\t\t// note that we do this *before* recursing which delays deactivation but keeps tail calls\n\t\tif ((nodes[root + 1 + first].children | nodes[root + 1 + second].children) == 0)\n\t\t\tnodes[root].children = 0;\n\n\t\t// recursion depth is bounded by tree depth (which is limited by construction)\n\t\tkdtreeNearest(nodes, root + 1 + first, points, stride, emitted_flags, position, result, limit);\n\n\t\t// only process the other node if it can have a match based on closest distance so far\n\t\tif (fabsf(delta) <= limit)\n\t\t\tkdtreeNearest(nodes, root + 1 + second, points, stride, emitted_flags, position, result, limit);\n\t}\n}\n\nstruct BVHBoxT\n{\n\tfloat min[4];\n\tfloat max[4];\n};\n\nstruct BVHBox\n{\n\tfloat min[3];\n\tfloat max[3];\n};\n\n#if defined(SIMD_SSE)\nstatic float boxMerge(BVHBoxT& box, const BVHBox& other)\n{\n\t__m128 min = _mm_loadu_ps(box.min);\n\t__m128 max = _mm_loadu_ps(box.max);\n\n\t// note: over-read is safe because BVHBox array is allocated with padding\n\tmin = _mm_min_ps(min, _mm_loadu_ps(other.min));\n\tmax = _mm_max_ps(max, _mm_loadu_ps(other.max));\n\n\t_mm_storeu_ps(box.min, min);\n\t_mm_storeu_ps(box.max, max);\n\n\t__m128 size = _mm_sub_ps(max, min);\n\t__m128 size_yzx = _mm_shuffle_ps(size, size, _MM_SHUFFLE(0, 0, 2, 1));\n\t__m128 mul = _mm_mul_ps(size, size_yzx);\n\t__m128 sum_xy = _mm_add_ss(mul, _mm_shuffle_ps(mul, mul, _MM_SHUFFLE(1, 1, 1, 1)));\n\t__m128 sum_xyz = _mm_add_ss(sum_xy, _mm_shuffle_ps(mul, mul, _MM_SHUFFLE(2, 2, 2, 2)));\n\n\treturn _mm_cvtss_f32(sum_xyz);\n}\n#elif defined(SIMD_NEON)\nstatic float boxMerge(BVHBoxT& box, const BVHBox& other)\n{\n\tfloat32x4_t min = vld1q_f32(box.min);\n\tfloat32x4_t max = vld1q_f32(box.max);\n\n\t// note: over-read is safe because BVHBox array is allocated with padding\n\tmin = vminq_f32(min, vld1q_f32(other.min));\n\tmax = vmaxq_f32(max, vld1q_f32(other.max));\n\n\tvst1q_f32(box.min, min);\n\tvst1q_f32(box.max, max);\n\n\tfloat32x4_t size = vsubq_f32(max, min);\n\tfloat32x4_t size_yzx = vextq_f32(vextq_f32(size, size, 3), size, 2);\n\tfloat32x4_t mul = vmulq_f32(size, size_yzx);\n\tfloat sum_xy = vgetq_lane_f32(mul, 0) + vgetq_lane_f32(mul, 1);\n\tfloat sum_xyz = sum_xy + vgetq_lane_f32(mul, 2);\n\n\treturn sum_xyz;\n}\n#else\nstatic float boxMerge(BVHBoxT& box, const BVHBox& other)\n{\n\tfor (int k = 0; k < 3; ++k)\n\t{\n\t\tbox.min[k] = other.min[k] < box.min[k] ? other.min[k] : box.min[k];\n\t\tbox.max[k] = other.max[k] > box.max[k] ? other.max[k] : box.max[k];\n\t}\n\n\tfloat sx = box.max[0] - box.min[0], sy = box.max[1] - box.min[1], sz = box.max[2] - box.min[2];\n\treturn sx * sy + sx * sz + sy * sz;\n}\n#endif\n\ninline unsigned int radixFloat(unsigned int v)\n{\n\t// if sign bit is 0, flip sign bit\n\t// if sign bit is 1, flip everything\n\tunsigned int mask = (int(v) >> 31) | 0x80000000;\n\treturn v ^ mask;\n}\n\nstatic void computeHistogram(unsigned int (&hist)[1024][3], const float* data, size_t count)\n{\n\tmemset(hist, 0, sizeof(hist));\n\n\tconst unsigned int* bits = reinterpret_cast<const unsigned int*>(data);\n\n\t// compute 3 10-bit histograms in parallel (dropping 2 LSB)\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned int id = radixFloat(bits[i]);\n\n\t\thist[(id >> 2) & 1023][0]++;\n\t\thist[(id >> 12) & 1023][1]++;\n\t\thist[(id >> 22) & 1023][2]++;\n\t}\n\n\tunsigned int sum0 = 0, sum1 = 0, sum2 = 0;\n\n\t// replace histogram data with prefix histogram sums in-place\n\tfor (int i = 0; i < 1024; ++i)\n\t{\n\t\tunsigned int hx = hist[i][0], hy = hist[i][1], hz = hist[i][2];\n\n\t\thist[i][0] = sum0;\n\t\thist[i][1] = sum1;\n\t\thist[i][2] = sum2;\n\n\t\tsum0 += hx;\n\t\tsum1 += hy;\n\t\tsum2 += hz;\n\t}\n\n\tassert(sum0 == count && sum1 == count && sum2 == count);\n}\n\nstatic void radixPass(unsigned int* destination, const unsigned int* source, const float* keys, size_t count, unsigned int (&hist)[1024][3], int pass)\n{\n\tconst unsigned int* bits = reinterpret_cast<const unsigned int*>(keys);\n\tint bitoff = pass * 10 + 2; // drop 2 LSB to be able to use 3 10-bit passes\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned int id = (radixFloat(bits[source[i]]) >> bitoff) & 1023;\n\n\t\tdestination[hist[id][pass]++] = source[i];\n\t}\n}\n\nstatic void bvhPrepare(BVHBox* boxes, float* centroids, const unsigned int* indices, size_t face_count, const float* vertex_positions, size_t vertex_count, size_t vertex_stride_float)\n{\n\t(void)vertex_count;\n\n\tfor (size_t i = 0; i < face_count; ++i)\n\t{\n\t\tunsigned int a = indices[i * 3 + 0], b = indices[i * 3 + 1], c = indices[i * 3 + 2];\n\t\tassert(a < vertex_count && b < vertex_count && c < vertex_count);\n\n\t\tconst float* va = vertex_positions + vertex_stride_float * a;\n\t\tconst float* vb = vertex_positions + vertex_stride_float * b;\n\t\tconst float* vc = vertex_positions + vertex_stride_float * c;\n\n\t\tBVHBox& box = boxes[i];\n\n\t\tfor (int k = 0; k < 3; ++k)\n\t\t{\n\t\t\tbox.min[k] = va[k] < vb[k] ? va[k] : vb[k];\n\t\t\tbox.min[k] = vc[k] < box.min[k] ? vc[k] : box.min[k];\n\n\t\t\tbox.max[k] = va[k] > vb[k] ? va[k] : vb[k];\n\t\t\tbox.max[k] = vc[k] > box.max[k] ? vc[k] : box.max[k];\n\n\t\t\tcentroids[i + face_count * k] = (box.min[k] + box.max[k]) / 2.f;\n\t\t}\n\t}\n}\n\nstatic size_t bvhCountVertices(const unsigned int* order, size_t count, short* used, const unsigned int* indices, unsigned int* out = NULL)\n{\n\t// count number of unique vertices\n\tsize_t used_vertices = 0;\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned int index = order[i];\n\t\tunsigned int a = indices[index * 3 + 0], b = indices[index * 3 + 1], c = indices[index * 3 + 2];\n\n\t\tused_vertices += (used[a] < 0) + (used[b] < 0) + (used[c] < 0);\n\t\tused[a] = used[b] = used[c] = 1;\n\n\t\tif (out)\n\t\t\tout[i] = unsigned(used_vertices);\n\t}\n\n\t// reset used[] for future invocations\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned int index = order[i];\n\t\tunsigned int a = indices[index * 3 + 0], b = indices[index * 3 + 1], c = indices[index * 3 + 2];\n\n\t\tused[a] = used[b] = used[c] = -1;\n\t}\n\n\treturn used_vertices;\n}\n\nstatic void bvhPackLeaf(unsigned char* boundary, size_t count)\n{\n\t// mark meshlet boundary for future reassembly\n\tassert(count > 0);\n\n\tboundary[0] = 1;\n\tmemset(boundary + 1, 0, count - 1);\n}\n\nstatic void bvhPackTail(unsigned char* boundary, const unsigned int* order, size_t count, short* used, const unsigned int* indices, size_t max_vertices, size_t max_triangles)\n{\n\tfor (size_t i = 0; i < count;)\n\t{\n\t\tsize_t chunk = i + max_triangles <= count ? max_triangles : count - i;\n\n\t\tif (bvhCountVertices(order + i, chunk, used, indices) <= max_vertices)\n\t\t{\n\t\t\tbvhPackLeaf(boundary + i, chunk);\n\t\t\ti += chunk;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// chunk is vertex bound, split it into smaller meshlets\n\t\tassert(chunk > max_vertices / 3);\n\n\t\tbvhPackLeaf(boundary + i, max_vertices / 3);\n\t\ti += max_vertices / 3;\n\t}\n}\n\nstatic bool bvhDivisible(size_t count, size_t min, size_t max)\n{\n\t// count is representable as a sum of values in [min..max] if if it in range of [k*min..k*min+k*(max-min)]\n\t// equivalent to ceil(count / max) <= floor(count / min), but the form below allows using idiv (see nv_cluster_builder)\n\t// we avoid expensive integer divisions in the common case where min is <= max/2\n\treturn min * 2 <= max ? count >= min : count % min <= (count / min) * (max - min);\n}\n\nstatic void bvhComputeArea(float* areas, const BVHBox* boxes, const unsigned int* order, size_t count)\n{\n\tBVHBoxT accuml = {{FLT_MAX, FLT_MAX, FLT_MAX, 0}, {-FLT_MAX, -FLT_MAX, -FLT_MAX, 0}};\n\tBVHBoxT accumr = accuml;\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tfloat larea = boxMerge(accuml, boxes[order[i]]);\n\t\tfloat rarea = boxMerge(accumr, boxes[order[count - 1 - i]]);\n\n\t\tareas[i] = larea;\n\t\tareas[i + count] = rarea;\n\t}\n}\n\nstatic size_t bvhPivot(const float* areas, const unsigned int* vertices, size_t count, size_t step, size_t min, size_t max, float fill, size_t maxfill, float* out_cost)\n{\n\tbool aligned = count >= min * 2 && bvhDivisible(count, min, max);\n\tsize_t end = aligned ? count - min : count - 1;\n\n\tfloat rmaxfill = 1.f / float(int(maxfill));\n\n\t// find best split that minimizes SAH\n\tsize_t bestsplit = 0;\n\tfloat bestcost = FLT_MAX;\n\n\tfor (size_t i = min - 1; i < end; i += step)\n\t{\n\t\tsize_t lsplit = i + 1, rsplit = count - (i + 1);\n\n\t\tif (!bvhDivisible(lsplit, min, max))\n\t\t\tcontinue;\n\t\tif (aligned && !bvhDivisible(rsplit, min, max))\n\t\t\tcontinue;\n\n\t\t// areas[x] = inclusive surface area of boxes[0..x]\n\t\t// areas[count-1-x] = inclusive surface area of boxes[x..count-1]\n\t\tfloat larea = areas[i], rarea = areas[(count - 1 - (i + 1)) + count];\n\t\tfloat cost = larea * float(int(lsplit)) + rarea * float(int(rsplit));\n\n\t\tif (cost > bestcost)\n\t\t\tcontinue;\n\n\t\t// use vertex fill when splitting vertex limited clusters; note that we use the same (left->right) vertex count\n\t\t// using bidirectional vertex counts is a little more expensive to compute and produces slightly worse results in practice\n\t\tsize_t lfill = vertices ? vertices[i] : lsplit;\n\t\tsize_t rfill = vertices ? vertices[i] : rsplit;\n\n\t\t// fill cost; use floating point math to round up to maxfill to avoid expensive integer modulo\n\t\tint lrest = int(float(int(lfill + maxfill - 1)) * rmaxfill) * int(maxfill) - int(lfill);\n\t\tint rrest = int(float(int(rfill + maxfill - 1)) * rmaxfill) * int(maxfill) - int(rfill);\n\n\t\tcost += fill * (float(lrest) * larea + float(rrest) * rarea);\n\n\t\tif (cost < bestcost)\n\t\t{\n\t\t\tbestcost = cost;\n\t\t\tbestsplit = i + 1;\n\t\t}\n\t}\n\n\t*out_cost = bestcost;\n\treturn bestsplit;\n}\n\nstatic void bvhPartition(unsigned int* target, const unsigned int* order, const unsigned char* sides, size_t split, size_t count)\n{\n\tsize_t l = 0, r = split;\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned char side = sides[order[i]];\n\t\ttarget[side ? r : l] = order[i];\n\t\tl += 1;\n\t\tl -= side;\n\t\tr += side;\n\t}\n\n\tassert(l == split && r == count);\n}\n\nstatic void bvhSplit(const BVHBox* boxes, unsigned int* orderx, unsigned int* ordery, unsigned int* orderz, unsigned char* boundary, size_t count, int depth, void* scratch, short* used, const unsigned int* indices, size_t max_vertices, size_t min_triangles, size_t max_triangles, float fill_weight)\n{\n\tif (count <= max_triangles && bvhCountVertices(orderx, count, used, indices) <= max_vertices)\n\t\treturn bvhPackLeaf(boundary, count);\n\n\tunsigned int* axes[3] = {orderx, ordery, orderz};\n\n\t// we can use step=1 unconditionally but to reduce the cost for min=max case we use step=max\n\tsize_t step = min_triangles == max_triangles && count > max_triangles ? max_triangles : 1;\n\n\t// if we could not pack the meshlet, we must be vertex bound\n\tsize_t mint = count <= max_triangles && max_vertices / 3 < min_triangles ? max_vertices / 3 : min_triangles;\n\tsize_t maxfill = count <= max_triangles ? max_vertices : max_triangles;\n\n\t// find best split that minimizes SAH\n\tint bestk = -1;\n\tsize_t bestsplit = 0;\n\tfloat bestcost = FLT_MAX;\n\n\tfor (int k = 0; k < 3; ++k)\n\t{\n\t\tfloat* areas = static_cast<float*>(scratch);\n\t\tunsigned int* vertices = NULL;\n\n\t\tbvhComputeArea(areas, boxes, axes[k], count);\n\n\t\tif (count <= max_triangles)\n\t\t{\n\t\t\t// for vertex bound clusters, count number of unique vertices for each split\n\t\t\tvertices = reinterpret_cast<unsigned int*>(areas + 2 * count);\n\t\t\tbvhCountVertices(axes[k], count, used, indices, vertices);\n\t\t}\n\n\t\tfloat axiscost = FLT_MAX;\n\t\tsize_t axissplit = bvhPivot(areas, vertices, count, step, mint, max_triangles, fill_weight, maxfill, &axiscost);\n\n\t\tif (axissplit && axiscost < bestcost)\n\t\t{\n\t\t\tbestk = k;\n\t\t\tbestcost = axiscost;\n\t\t\tbestsplit = axissplit;\n\t\t}\n\t}\n\n\t// this may happen if SAH costs along the admissible splits are NaN, or due to imbalanced splits on pathological inputs\n\tif (bestk < 0 || depth >= kMeshletMaxTreeDepth)\n\t\treturn bvhPackTail(boundary, orderx, count, used, indices, max_vertices, max_triangles);\n\n\t// mark sides of split for partitioning\n\tunsigned char* sides = static_cast<unsigned char*>(scratch) + count * sizeof(unsigned int);\n\n\tfor (size_t i = 0; i < bestsplit; ++i)\n\t\tsides[axes[bestk][i]] = 0;\n\n\tfor (size_t i = bestsplit; i < count; ++i)\n\t\tsides[axes[bestk][i]] = 1;\n\n\t// partition all axes into two sides, maintaining order\n\tunsigned int* temp = static_cast<unsigned int*>(scratch);\n\n\tfor (int k = 0; k < 3; ++k)\n\t{\n\t\tif (k == bestk)\n\t\t\tcontinue;\n\n\t\tunsigned int* axis = axes[k];\n\t\tmemcpy(temp, axis, sizeof(unsigned int) * count);\n\t\tbvhPartition(axis, temp, sides, bestsplit, count);\n\t}\n\n\t// recursion depth is bounded due to max depth check above\n\tbvhSplit(boxes, orderx, ordery, orderz, boundary, bestsplit, depth + 1, scratch, used, indices, max_vertices, min_triangles, max_triangles, fill_weight);\n\tbvhSplit(boxes, orderx + bestsplit, ordery + bestsplit, orderz + bestsplit, boundary + bestsplit, count - bestsplit, depth + 1, scratch, used, indices, max_vertices, min_triangles, max_triangles, fill_weight);\n}\n\n} // namespace meshopt\n\nsize_t meshopt_buildMeshletsBound(size_t index_count, size_t max_vertices, size_t max_triangles)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(max_vertices >= 3 && max_vertices <= 256);\n\tassert(max_triangles >= 1 && max_triangles <= 512);\n\n\t// meshlet construction is limited by max vertices and max triangles per meshlet\n\t// the worst case is that the input is an unindexed stream since this equally stresses both limits\n\t// note that we assume that in the worst case, we leave 2 vertices unpacked in each meshlet - if we have space for 3 we can pack any triangle\n\tsize_t max_vertices_conservative = max_vertices - 2;\n\tsize_t meshlet_limit_vertices = (index_count + max_vertices_conservative - 1) / max_vertices_conservative;\n\tsize_t meshlet_limit_triangles = (index_count / 3 + max_triangles - 1) / max_triangles;\n\n\treturn meshlet_limit_vertices > meshlet_limit_triangles ? meshlet_limit_vertices : meshlet_limit_triangles;\n}\n\nsize_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float cone_weight, float split_factor)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\n\tassert(max_vertices >= 3 && max_vertices <= 256);\n\tassert(min_triangles >= 1 && min_triangles <= max_triangles && max_triangles <= 512);\n\n\tassert(cone_weight >= 0 && cone_weight <= 1);\n\tassert(split_factor >= 0);\n\n\tif (index_count == 0)\n\t\treturn 0;\n\n\tmeshopt_Allocator allocator;\n\n\tTriangleAdjacency2 adjacency = {};\n\tif (vertex_count > index_count && index_count < (1u << 31))\n\t\tbuildTriangleAdjacencySparse(adjacency, indices, index_count, vertex_count, allocator);\n\telse\n\t\tbuildTriangleAdjacency(adjacency, indices, index_count, vertex_count, allocator);\n\n\t// live triangle counts; note, we alias adjacency.counts as we remove triangles after emitting them so the counts always match\n\tunsigned int* live_triangles = adjacency.counts;\n\n\tsize_t face_count = index_count / 3;\n\n\tunsigned char* emitted_flags = allocator.allocate<unsigned char>(face_count);\n\tmemset(emitted_flags, 0, face_count);\n\n\t// for each triangle, precompute centroid & normal to use for scoring\n\tCone* triangles = allocator.allocate<Cone>(face_count);\n\tfloat mesh_area = computeTriangleCones(triangles, indices, index_count, vertex_positions, vertex_count, vertex_positions_stride);\n\n\t// assuming each meshlet is a square patch, expected radius is sqrt(expected area)\n\tfloat triangle_area_avg = face_count == 0 ? 0.f : mesh_area / float(face_count) * 0.5f;\n\tfloat meshlet_expected_radius = sqrtf(triangle_area_avg * max_triangles) * 0.5f;\n\n\t// build a kd-tree for nearest neighbor lookup\n\tunsigned int* kdindices = allocator.allocate<unsigned int>(face_count);\n\tfor (size_t i = 0; i < face_count; ++i)\n\t\tkdindices[i] = unsigned(i);\n\n\tKDNode* nodes = allocator.allocate<KDNode>(face_count * 2);\n\tkdtreeBuild(0, nodes, face_count * 2, &triangles[0].px, sizeof(Cone) / sizeof(float), kdindices, face_count, /* leaf_size= */ 8, 0);\n\n\t// find a specific corner of the mesh to use as a starting point for meshlet flow\n\tfloat cornerx = FLT_MAX, cornery = FLT_MAX, cornerz = FLT_MAX;\n\n\tfor (size_t i = 0; i < face_count; ++i)\n\t{\n\t\tconst Cone& tri = triangles[i];\n\n\t\tcornerx = cornerx > tri.px ? tri.px : cornerx;\n\t\tcornery = cornery > tri.py ? tri.py : cornery;\n\t\tcornerz = cornerz > tri.pz ? tri.pz : cornerz;\n\t}\n\n\t// index of the vertex in the meshlet, -1 if the vertex isn't used\n\tshort* used = allocator.allocate<short>(vertex_count);\n\tclearUsed(used, vertex_count, indices, index_count);\n\n\t// initial seed triangle is the one closest to the corner\n\tunsigned int initial_seed = ~0u;\n\tfloat initial_score = FLT_MAX;\n\n\tfor (size_t i = 0; i < face_count; ++i)\n\t{\n\t\tconst Cone& tri = triangles[i];\n\n\t\tfloat dx = tri.px - cornerx, dy = tri.py - cornery, dz = tri.pz - cornerz;\n\t\tfloat score = sqrtf(dx * dx + dy * dy + dz * dz);\n\n\t\tif (initial_seed == ~0u || score < initial_score)\n\t\t{\n\t\t\tinitial_seed = unsigned(i);\n\t\t\tinitial_score = score;\n\t\t}\n\t}\n\n\t// seed triangles to continue meshlet flow\n\tunsigned int seeds[kMeshletMaxSeeds] = {};\n\tsize_t seed_count = 0;\n\n\tmeshopt_Meshlet meshlet = {};\n\tsize_t meshlet_offset = 0;\n\n\tCone meshlet_cone_acc = {};\n\n\tfor (;;)\n\t{\n\t\tCone meshlet_cone = getMeshletCone(meshlet_cone_acc, meshlet.triangle_count);\n\n\t\tunsigned int best_triangle = ~0u;\n\n\t\t// for the first triangle, we don't have a meshlet cone yet, so we use the initial seed\n\t\t// to continue the meshlet, we select an adjacent triangle based on connectivity and spatial scoring\n\t\tif (meshlet_offset == 0 && meshlet.triangle_count == 0)\n\t\t\tbest_triangle = initial_seed;\n\t\telse\n\t\t\tbest_triangle = getNeighborTriangle(meshlet, meshlet_cone, meshlet_vertices, indices, adjacency, triangles, live_triangles, used, meshlet_expected_radius, cone_weight);\n\n\t\tbool split = false;\n\n\t\t// when we run out of adjacent triangles we need to switch to spatial search; we currently just pick the closest triangle irrespective of connectivity\n\t\tif (best_triangle == ~0u)\n\t\t{\n\t\t\tfloat position[3] = {meshlet_cone.px, meshlet_cone.py, meshlet_cone.pz};\n\t\t\tunsigned int index = ~0u;\n\t\t\tfloat distance = FLT_MAX;\n\n\t\t\tkdtreeNearest(nodes, 0, &triangles[0].px, sizeof(Cone) / sizeof(float), emitted_flags, position, index, distance);\n\n\t\t\tbest_triangle = index;\n\t\t\tsplit = meshlet.triangle_count >= min_triangles && split_factor > 0 && distance > meshlet_expected_radius * split_factor;\n\t\t}\n\n\t\tif (best_triangle == ~0u)\n\t\t\tbreak;\n\n\t\tint best_extra = (used[indices[best_triangle * 3 + 0]] < 0) + (used[indices[best_triangle * 3 + 1]] < 0) + (used[indices[best_triangle * 3 + 2]] < 0);\n\n\t\t// if the best triangle doesn't fit into current meshlet, we re-select using seeds to maintain global flow\n\t\tif (split || (meshlet.vertex_count + best_extra > max_vertices || meshlet.triangle_count >= max_triangles))\n\t\t{\n\t\t\tseed_count = pruneSeedTriangles(seeds, seed_count, emitted_flags);\n\t\t\tseed_count = (seed_count + kMeshletAddSeeds <= kMeshletMaxSeeds) ? seed_count : kMeshletMaxSeeds - kMeshletAddSeeds;\n\t\t\tseed_count += appendSeedTriangles(seeds + seed_count, meshlet, meshlet_vertices, indices, adjacency, triangles, live_triangles, cornerx, cornery, cornerz);\n\n\t\t\tunsigned int best_seed = selectSeedTriangle(seeds, seed_count, indices, triangles, live_triangles, cornerx, cornery, cornerz);\n\n\t\t\t// we may not find a valid seed triangle if the mesh is disconnected as seeds are based on adjacency\n\t\t\tbest_triangle = best_seed != ~0u ? best_seed : best_triangle;\n\t\t}\n\n\t\tunsigned int a = indices[best_triangle * 3 + 0], b = indices[best_triangle * 3 + 1], c = indices[best_triangle * 3 + 2];\n\t\tassert(a < vertex_count && b < vertex_count && c < vertex_count);\n\n\t\t// add meshlet to the output; when the current meshlet is full we reset the accumulated bounds\n\t\tif (appendMeshlet(meshlet, a, b, c, used, meshlets, meshlet_vertices, meshlet_triangles, meshlet_offset, max_vertices, max_triangles, split))\n\t\t{\n\t\t\tmeshlet_offset++;\n\t\t\tmemset(&meshlet_cone_acc, 0, sizeof(meshlet_cone_acc));\n\t\t}\n\n\t\t// remove emitted triangle from adjacency data\n\t\t// this makes sure that we spend less time traversing these lists on subsequent iterations\n\t\t// live triangle counts are updated as a byproduct of these adjustments\n\t\tfor (size_t k = 0; k < 3; ++k)\n\t\t{\n\t\t\tunsigned int index = indices[best_triangle * 3 + k];\n\n\t\t\tunsigned int* neighbors = &adjacency.data[0] + adjacency.offsets[index];\n\t\t\tsize_t neighbors_size = adjacency.counts[index];\n\n\t\t\tfor (size_t i = 0; i < neighbors_size; ++i)\n\t\t\t{\n\t\t\t\tunsigned int tri = neighbors[i];\n\n\t\t\t\tif (tri == best_triangle)\n\t\t\t\t{\n\t\t\t\t\tneighbors[i] = neighbors[neighbors_size - 1];\n\t\t\t\t\tadjacency.counts[index]--;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// update aggregated meshlet cone data for scoring subsequent triangles\n\t\tmeshlet_cone_acc.px += triangles[best_triangle].px;\n\t\tmeshlet_cone_acc.py += triangles[best_triangle].py;\n\t\tmeshlet_cone_acc.pz += triangles[best_triangle].pz;\n\t\tmeshlet_cone_acc.nx += triangles[best_triangle].nx;\n\t\tmeshlet_cone_acc.ny += triangles[best_triangle].ny;\n\t\tmeshlet_cone_acc.nz += triangles[best_triangle].nz;\n\n\t\tassert(!emitted_flags[best_triangle]);\n\t\temitted_flags[best_triangle] = 1;\n\t}\n\n\tif (meshlet.triangle_count)\n\t\tmeshlets[meshlet_offset++] = meshlet;\n\n\tassert(meshlet_offset <= meshopt_buildMeshletsBound(index_count, max_vertices, min_triangles));\n\tassert(meshlet.triangle_offset + meshlet.triangle_count * 3 <= index_count && meshlet.vertex_offset + meshlet.vertex_count <= index_count);\n\treturn meshlet_offset;\n}\n\nsize_t meshopt_buildMeshlets(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight)\n{\n\treturn meshopt_buildMeshletsFlex(meshlets, meshlet_vertices, meshlet_triangles, indices, index_count, vertex_positions, vertex_count, vertex_positions_stride, max_vertices, max_triangles, max_triangles, cone_weight, 0.0f);\n}\n\nsize_t meshopt_buildMeshletsScan(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\n\tassert(max_vertices >= 3 && max_vertices <= 256);\n\tassert(max_triangles >= 1 && max_triangles <= 512);\n\n\tmeshopt_Allocator allocator;\n\n\t// index of the vertex in the meshlet, -1 if the vertex isn't used\n\tshort* used = allocator.allocate<short>(vertex_count);\n\tclearUsed(used, vertex_count, indices, index_count);\n\n\tmeshopt_Meshlet meshlet = {};\n\tsize_t meshlet_offset = 0;\n\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tunsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2];\n\t\tassert(a < vertex_count && b < vertex_count && c < vertex_count);\n\n\t\t// appends triangle to the meshlet and writes previous meshlet to the output if full\n\t\tmeshlet_offset += appendMeshlet(meshlet, a, b, c, used, meshlets, meshlet_vertices, meshlet_triangles, meshlet_offset, max_vertices, max_triangles);\n\t}\n\n\tif (meshlet.triangle_count)\n\t\tmeshlets[meshlet_offset++] = meshlet;\n\n\tassert(meshlet_offset <= meshopt_buildMeshletsBound(index_count, max_vertices, max_triangles));\n\tassert(meshlet.triangle_offset + meshlet.triangle_count * 3 <= index_count && meshlet.vertex_offset + meshlet.vertex_count <= index_count);\n\treturn meshlet_offset;\n}\n\nsize_t meshopt_buildMeshletsSpatial(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float fill_weight)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\n\tassert(max_vertices >= 3 && max_vertices <= 256);\n\tassert(min_triangles >= 1 && min_triangles <= max_triangles && max_triangles <= 512);\n\n\tif (index_count == 0)\n\t\treturn 0;\n\n\tsize_t face_count = index_count / 3;\n\tsize_t vertex_stride_float = vertex_positions_stride / sizeof(float);\n\n\tmeshopt_Allocator allocator;\n\n\t// 3 floats plus 1 uint for sorting, or\n\t// 2 floats plus 1 uint for pivoting, or\n\t// 1 uint plus 1 byte for partitioning\n\tfloat* scratch = allocator.allocate<float>(face_count * 4);\n\n\t// compute bounding boxes and centroids for sorting\n\tBVHBox* boxes = allocator.allocate<BVHBox>(face_count + 1); // padding for SIMD\n\tbvhPrepare(boxes, scratch, indices, face_count, vertex_positions, vertex_count, vertex_stride_float);\n\tmemset(boxes + face_count, 0, sizeof(BVHBox));\n\n\tunsigned int* axes = allocator.allocate<unsigned int>(face_count * 3);\n\tunsigned int* temp = reinterpret_cast<unsigned int*>(scratch) + face_count * 3;\n\n\tfor (int k = 0; k < 3; ++k)\n\t{\n\t\tunsigned int* order = axes + k * face_count;\n\t\tconst float* keys = scratch + k * face_count;\n\n\t\tunsigned int hist[1024][3];\n\t\tcomputeHistogram(hist, keys, face_count);\n\n\t\t// 3-pass radix sort computes the resulting order into axes\n\t\tfor (size_t i = 0; i < face_count; ++i)\n\t\t\ttemp[i] = unsigned(i);\n\n\t\tradixPass(order, temp, keys, face_count, hist, 0);\n\t\tradixPass(temp, order, keys, face_count, hist, 1);\n\t\tradixPass(order, temp, keys, face_count, hist, 2);\n\t}\n\n\t// index of the vertex in the meshlet, -1 if the vertex isn't used\n\tshort* used = allocator.allocate<short>(vertex_count);\n\tclearUsed(used, vertex_count, indices, index_count);\n\n\tunsigned char* boundary = allocator.allocate<unsigned char>(face_count);\n\n\tbvhSplit(boxes, &axes[0], &axes[face_count], &axes[face_count * 2], boundary, face_count, 0, scratch, used, indices, max_vertices, min_triangles, max_triangles, fill_weight);\n\n\t// compute the desired number of meshlets; note that on some meshes with a lot of vertex bound clusters this might go over the bound\n\tsize_t meshlet_count = 0;\n\tfor (size_t i = 0; i < face_count; ++i)\n\t{\n\t\tassert(boundary[i] <= 1);\n\t\tmeshlet_count += boundary[i];\n\t}\n\n\tsize_t meshlet_bound = meshopt_buildMeshletsBound(index_count, max_vertices, min_triangles);\n\n\t// pack triangles into meshlets according to the order and boundaries marked by bvhSplit\n\tmeshopt_Meshlet meshlet = {};\n\tsize_t meshlet_offset = 0;\n\tsize_t meshlet_pending = meshlet_count;\n\n\tfor (size_t i = 0; i < face_count; ++i)\n\t{\n\t\tassert(boundary[i] <= 1);\n\t\tbool split = i > 0 && boundary[i] == 1;\n\n\t\t// while we are over the limit, we ignore boundary[] data and disable splits until we free up enough space\n\t\tif (split && meshlet_count > meshlet_bound && meshlet_offset + meshlet_pending >= meshlet_bound)\n\t\t\tsplit = false;\n\n\t\tunsigned int index = axes[i];\n\t\tassert(index < face_count);\n\n\t\tunsigned int a = indices[index * 3 + 0], b = indices[index * 3 + 1], c = indices[index * 3 + 2];\n\n\t\t// appends triangle to the meshlet and writes previous meshlet to the output if full\n\t\tmeshlet_offset += appendMeshlet(meshlet, a, b, c, used, meshlets, meshlet_vertices, meshlet_triangles, meshlet_offset, max_vertices, max_triangles, split);\n\t\tmeshlet_pending -= boundary[i];\n\t}\n\n\tif (meshlet.triangle_count)\n\t\tmeshlets[meshlet_offset++] = meshlet;\n\n\tassert(meshlet_offset <= meshlet_bound);\n\tassert(meshlet.triangle_offset + meshlet.triangle_count * 3 <= index_count && meshlet.vertex_offset + meshlet.vertex_count <= index_count);\n\treturn meshlet_offset;\n}\n\n#undef SIMD_SSE\n#undef SIMD_NEON\n"
  },
  {
    "path": "src/indexanalyzer.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <string.h>\n\nmeshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int cache_size, unsigned int warp_size, unsigned int primgroup_size)\n{\n\tassert(index_count % 3 == 0);\n\tassert(cache_size >= 3);\n\tassert(warp_size == 0 || warp_size >= 3);\n\n\tmeshopt_Allocator allocator;\n\n\tmeshopt_VertexCacheStatistics result = {};\n\n\tunsigned int warp_offset = 0;\n\tunsigned int primgroup_offset = 0;\n\n\tunsigned int* cache_timestamps = allocator.allocate<unsigned int>(vertex_count);\n\tmemset(cache_timestamps, 0, vertex_count * sizeof(unsigned int));\n\n\tunsigned int timestamp = cache_size + 1;\n\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tunsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2];\n\t\tassert(a < vertex_count && b < vertex_count && c < vertex_count);\n\n\t\tbool ac = (timestamp - cache_timestamps[a]) > cache_size;\n\t\tbool bc = (timestamp - cache_timestamps[b]) > cache_size;\n\t\tbool cc = (timestamp - cache_timestamps[c]) > cache_size;\n\n\t\t// flush cache if triangle doesn't fit into warp or into the primitive buffer\n\t\tif ((primgroup_size && primgroup_offset == primgroup_size) || (warp_size && warp_offset + ac + bc + cc > warp_size))\n\t\t{\n\t\t\tresult.warps_executed += warp_offset > 0;\n\n\t\t\twarp_offset = 0;\n\t\t\tprimgroup_offset = 0;\n\n\t\t\t// reset cache\n\t\t\ttimestamp += cache_size + 1;\n\t\t}\n\n\t\t// update cache and add vertices to warp\n\t\tfor (int j = 0; j < 3; ++j)\n\t\t{\n\t\t\tunsigned int index = indices[i + j];\n\n\t\t\tif (timestamp - cache_timestamps[index] > cache_size)\n\t\t\t{\n\t\t\t\tcache_timestamps[index] = timestamp++;\n\t\t\t\tresult.vertices_transformed++;\n\t\t\t\twarp_offset++;\n\t\t\t}\n\t\t}\n\n\t\tprimgroup_offset++;\n\t}\n\n\tsize_t unique_vertex_count = 0;\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tunique_vertex_count += cache_timestamps[i] > 0;\n\n\tresult.warps_executed += warp_offset > 0;\n\n\tresult.acmr = index_count == 0 ? 0 : float(result.vertices_transformed) / float(index_count / 3);\n\tresult.atvr = unique_vertex_count == 0 ? 0 : float(result.vertices_transformed) / float(unique_vertex_count);\n\n\treturn result;\n}\n\nmeshopt_VertexFetchStatistics meshopt_analyzeVertexFetch(const unsigned int* indices, size_t index_count, size_t vertex_count, size_t vertex_size)\n{\n\tassert(index_count % 3 == 0);\n\tassert(vertex_size > 0 && vertex_size <= 256);\n\n\tmeshopt_Allocator allocator;\n\n\tmeshopt_VertexFetchStatistics result = {};\n\n\tunsigned char* vertex_visited = allocator.allocate<unsigned char>(vertex_count);\n\tmemset(vertex_visited, 0, vertex_count);\n\n\tconst size_t kCacheLine = 64;\n\tconst size_t kCacheSize = 128 * 1024;\n\n\t// simple direct mapped cache; on typical mesh data this is close to 4-way cache, and this model is a gross approximation anyway\n\tsize_t cache[kCacheSize / kCacheLine] = {};\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int index = indices[i];\n\t\tassert(index < vertex_count);\n\n\t\tvertex_visited[index] = 1;\n\n\t\tsize_t start_address = index * vertex_size;\n\t\tsize_t end_address = start_address + vertex_size;\n\n\t\tsize_t start_tag = start_address / kCacheLine;\n\t\tsize_t end_tag = (end_address + kCacheLine - 1) / kCacheLine;\n\n\t\tassert(start_tag < end_tag);\n\n\t\tfor (size_t tag = start_tag; tag < end_tag; ++tag)\n\t\t{\n\t\t\tsize_t line = tag % (sizeof(cache) / sizeof(cache[0]));\n\n\t\t\t// we store +1 since cache is filled with 0 by default\n\t\t\tresult.bytes_fetched += (cache[line] != tag + 1) * kCacheLine;\n\t\t\tcache[line] = tag + 1;\n\t\t}\n\t}\n\n\tsize_t unique_vertex_count = 0;\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tunique_vertex_count += vertex_visited[i];\n\n\tresult.overfetch = unique_vertex_count == 0 ? 0 : float(result.bytes_fetched) / float(unique_vertex_count * vertex_size);\n\n\treturn result;\n}\n"
  },
  {
    "path": "src/indexcodec.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <string.h>\n\n// This work is based on:\n// Fabian Giesen. Simple lossless index buffer compression & follow-up. 2013\n// Conor Stokes. Vertex Cache Optimised Index Buffer Compression. 2014\nnamespace meshopt\n{\n\nconst unsigned char kIndexHeader = 0xe0;\nconst unsigned char kSequenceHeader = 0xd0;\n\nstatic int gEncodeIndexVersion = 1;\nconst int kDecodeIndexVersion = 1;\n\ntypedef unsigned int VertexFifo[16];\ntypedef unsigned int EdgeFifo[16][2];\n\nstatic const unsigned char kCodeAuxEncodingTable[16] = {\n    0x00, 0x76, 0x87, 0x56, 0x67, 0x78, 0xa9, 0x86, 0x65, 0x89, 0x68, 0x98, 0x01, 0x69,\n    0, 0, // last two entries aren't used for encoding\n};\n\nstatic int rotateTriangle(unsigned int a, unsigned int b, unsigned int c, unsigned int next)\n{\n\t(void)a;\n\n\treturn (b == next) ? 1 : (c == next ? 2 : 0);\n}\n\nstatic int getEdgeFifo(EdgeFifo fifo, unsigned int a, unsigned int b, unsigned int c, size_t offset)\n{\n\tfor (int i = 0; i < 16; ++i)\n\t{\n\t\tsize_t index = (offset - 1 - i) & 15;\n\n\t\tunsigned int e0 = fifo[index][0];\n\t\tunsigned int e1 = fifo[index][1];\n\n\t\tif (e0 == a && e1 == b)\n\t\t\treturn (i << 2) | 0;\n\t\tif (e0 == b && e1 == c)\n\t\t\treturn (i << 2) | 1;\n\t\tif (e0 == c && e1 == a)\n\t\t\treturn (i << 2) | 2;\n\t}\n\n\treturn -1;\n}\n\nstatic void pushEdgeFifo(EdgeFifo fifo, unsigned int a, unsigned int b, size_t& offset)\n{\n\tfifo[offset][0] = a;\n\tfifo[offset][1] = b;\n\toffset = (offset + 1) & 15;\n}\n\nstatic int getVertexFifo(VertexFifo fifo, unsigned int v, size_t offset)\n{\n\tfor (int i = 0; i < 16; ++i)\n\t{\n\t\tsize_t index = (offset - 1 - i) & 15;\n\n\t\tif (fifo[index] == v)\n\t\t\treturn i;\n\t}\n\n\treturn -1;\n}\n\nstatic void pushVertexFifo(VertexFifo fifo, unsigned int v, size_t& offset, int cond = 1)\n{\n\tfifo[offset] = v;\n\toffset = (offset + cond) & 15;\n}\n\nstatic void encodeVByte(unsigned char*& data, unsigned int v)\n{\n\t// encode 32-bit value in up to 5 7-bit groups\n\tdo\n\t{\n\t\t*data++ = (v & 127) | (v > 127 ? 128 : 0);\n\t\tv >>= 7;\n\t} while (v);\n}\n\nstatic unsigned int decodeVByte(const unsigned char*& data)\n{\n\tunsigned char lead = *data++;\n\n\t// fast path: single byte\n\tif (lead < 128)\n\t\treturn lead;\n\n\t// slow path: up to 4 extra bytes\n\t// note that this loop always terminates, which is important for malformed data\n\tunsigned int result = lead & 127;\n\tunsigned int shift = 7;\n\n\tfor (int i = 0; i < 4; ++i)\n\t{\n\t\tunsigned char group = *data++;\n\t\tresult |= unsigned(group & 127) << shift;\n\t\tshift += 7;\n\n\t\tif (group < 128)\n\t\t\tbreak;\n\t}\n\n\treturn result;\n}\n\nstatic void encodeIndex(unsigned char*& data, unsigned int index, unsigned int last)\n{\n\tunsigned int d = index - last;\n\tunsigned int v = (d << 1) ^ (int(d) >> 31);\n\n\tencodeVByte(data, v);\n}\n\nstatic unsigned int decodeIndex(const unsigned char*& data, unsigned int last)\n{\n\tunsigned int v = decodeVByte(data);\n\tunsigned int d = (v >> 1) ^ -int(v & 1);\n\n\treturn last + d;\n}\n\nstatic int getCodeAuxIndex(unsigned char v, const unsigned char* table)\n{\n\tfor (int i = 0; i < 16; ++i)\n\t\tif (table[i] == v)\n\t\t\treturn i;\n\n\treturn -1;\n}\n\nstatic void writeTriangle(void* destination, size_t offset, size_t index_size, unsigned int a, unsigned int b, unsigned int c)\n{\n\tif (index_size == 2)\n\t{\n\t\tstatic_cast<unsigned short*>(destination)[offset + 0] = (unsigned short)(a);\n\t\tstatic_cast<unsigned short*>(destination)[offset + 1] = (unsigned short)(b);\n\t\tstatic_cast<unsigned short*>(destination)[offset + 2] = (unsigned short)(c);\n\t}\n\telse\n\t{\n\t\tstatic_cast<unsigned int*>(destination)[offset + 0] = a;\n\t\tstatic_cast<unsigned int*>(destination)[offset + 1] = b;\n\t\tstatic_cast<unsigned int*>(destination)[offset + 2] = c;\n\t}\n}\n\n} // namespace meshopt\n\nsize_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_size, const unsigned int* indices, size_t index_count)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\n\t// the minimum valid encoding is header, 1 byte per triangle and a 16-byte codeaux table\n\tif (buffer_size < 1 + index_count / 3 + 16)\n\t\treturn 0;\n\n\tint version = gEncodeIndexVersion;\n\n\tbuffer[0] = (unsigned char)(kIndexHeader | version);\n\n\tEdgeFifo edgefifo;\n\tmemset(edgefifo, -1, sizeof(edgefifo));\n\n\tVertexFifo vertexfifo;\n\tmemset(vertexfifo, -1, sizeof(vertexfifo));\n\n\tsize_t edgefifooffset = 0;\n\tsize_t vertexfifooffset = 0;\n\n\tunsigned int next = 0;\n\tunsigned int last = 0;\n\n\tunsigned char* code = buffer + 1;\n\tunsigned char* data = code + index_count / 3;\n\tunsigned char* data_safe_end = buffer + buffer_size - 16;\n\n\tint fecmax = version >= 1 ? 13 : 15;\n\n\tstatic const int rotations[] = {0, 1, 2, 0, 1};\n\n\t// use static encoding table; it's possible to pack the result and then build an optimal table and repack\n\t// for now we keep it simple and use the table that has been generated based on symbol frequency on a training mesh set\n\tconst unsigned char* codeaux_table = kCodeAuxEncodingTable;\n\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\t// make sure we have enough space to write a triangle\n\t\t// each triangle writes at most 16 bytes: 1b for codeaux and 5b for each free index\n\t\t// after this we can be sure we can write without extra bounds checks\n\t\tif (data > data_safe_end)\n\t\t\treturn 0;\n\n\t\tint fer = getEdgeFifo(edgefifo, indices[i + 0], indices[i + 1], indices[i + 2], edgefifooffset);\n\n\t\tif (fer >= 0 && (fer >> 2) < 15)\n\t\t{\n\t\t\t// note: getEdgeFifo implicitly rotates triangles by matching a/b to existing edge\n\t\t\tconst int* order = rotations + (fer & 3);\n\n\t\t\tunsigned int a = indices[i + order[0]], b = indices[i + order[1]], c = indices[i + order[2]];\n\n\t\t\t// encode edge index and vertex fifo index, next or free index\n\t\t\tint fe = fer >> 2;\n\t\t\tint fc = getVertexFifo(vertexfifo, c, vertexfifooffset);\n\n\t\t\tint fec = (fc >= 1 && fc < fecmax) ? fc : (c == next ? (next++, 0) : 15);\n\n\t\t\tif (fec == 15 && version >= 1)\n\t\t\t{\n\t\t\t\t// encode last-1 and last+1 to optimize strip-like sequences\n\t\t\t\tif (c + 1 == last)\n\t\t\t\t\tfec = 13, last = c;\n\t\t\t\tif (c == last + 1)\n\t\t\t\t\tfec = 14, last = c;\n\t\t\t}\n\n\t\t\t*code++ = (unsigned char)((fe << 4) | fec);\n\n\t\t\t// note that we need to update the last index since free indices are delta-encoded\n\t\t\tif (fec == 15)\n\t\t\t\tencodeIndex(data, c, last), last = c;\n\n\t\t\t// we only need to push third vertex since first two are likely already in the vertex fifo\n\t\t\tif (fec == 0 || fec >= fecmax)\n\t\t\t\tpushVertexFifo(vertexfifo, c, vertexfifooffset);\n\n\t\t\t// we only need to push two new edges to edge fifo since the third one is already there\n\t\t\tpushEdgeFifo(edgefifo, c, b, edgefifooffset);\n\t\t\tpushEdgeFifo(edgefifo, a, c, edgefifooffset);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tint rotation = rotateTriangle(indices[i + 0], indices[i + 1], indices[i + 2], next);\n\t\t\tconst int* order = rotations + rotation;\n\n\t\t\tunsigned int a = indices[i + order[0]], b = indices[i + order[1]], c = indices[i + order[2]];\n\n\t\t\t// if a/b/c are 0/1/2, we emit a reset code\n\t\t\tbool reset = false;\n\n\t\t\tif (a == 0 && b == 1 && c == 2 && next > 0 && version >= 1)\n\t\t\t{\n\t\t\t\treset = true;\n\t\t\t\tnext = 0;\n\n\t\t\t\t// reset vertex fifo to make sure we don't accidentally reference vertices from that in the future\n\t\t\t\t// this makes sure next continues to get incremented instead of being stuck\n\t\t\t\tmemset(vertexfifo, -1, sizeof(vertexfifo));\n\t\t\t}\n\n\t\t\tint fb = getVertexFifo(vertexfifo, b, vertexfifooffset);\n\t\t\tint fc = getVertexFifo(vertexfifo, c, vertexfifooffset);\n\n\t\t\t// after rotation, a is almost always equal to next, so we don't waste bits on FIFO encoding for a\n\t\t\t// note: decoder implicitly assumes that if feb=fec=0, then fea=0 (reset code); this is enforced by rotation\n\t\t\tint fea = (a == next) ? (next++, 0) : 15;\n\t\t\tint feb = (fb >= 0 && fb < 14) ? fb + 1 : (b == next ? (next++, 0) : 15);\n\t\t\tint fec = (fc >= 0 && fc < 14) ? fc + 1 : (c == next ? (next++, 0) : 15);\n\n\t\t\t// we encode feb & fec in 4 bits using a table if possible, and as a full byte otherwise\n\t\t\tunsigned char codeaux = (unsigned char)((feb << 4) | fec);\n\t\t\tint codeauxindex = getCodeAuxIndex(codeaux, codeaux_table);\n\n\t\t\t// <14 encodes an index into codeaux table, 14 encodes fea=0, 15 encodes fea=15\n\t\t\tif (fea == 0 && codeauxindex >= 0 && codeauxindex < 14 && !reset)\n\t\t\t{\n\t\t\t\t*code++ = (unsigned char)((15 << 4) | codeauxindex);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t*code++ = (unsigned char)((15 << 4) | 14 | fea);\n\t\t\t\t*data++ = codeaux;\n\t\t\t}\n\n\t\t\t// note that we need to update the last index since free indices are delta-encoded\n\t\t\tif (fea == 15)\n\t\t\t\tencodeIndex(data, a, last), last = a;\n\n\t\t\tif (feb == 15)\n\t\t\t\tencodeIndex(data, b, last), last = b;\n\n\t\t\tif (fec == 15)\n\t\t\t\tencodeIndex(data, c, last), last = c;\n\n\t\t\t// only push vertices that weren't already in fifo\n\t\t\tif (fea == 0 || fea == 15)\n\t\t\t\tpushVertexFifo(vertexfifo, a, vertexfifooffset);\n\n\t\t\tif (feb == 0 || feb == 15)\n\t\t\t\tpushVertexFifo(vertexfifo, b, vertexfifooffset);\n\n\t\t\tif (fec == 0 || fec == 15)\n\t\t\t\tpushVertexFifo(vertexfifo, c, vertexfifooffset);\n\n\t\t\t// all three edges aren't in the fifo; pushing all of them is important so that we can match them for later triangles\n\t\t\tpushEdgeFifo(edgefifo, b, a, edgefifooffset);\n\t\t\tpushEdgeFifo(edgefifo, c, b, edgefifooffset);\n\t\t\tpushEdgeFifo(edgefifo, a, c, edgefifooffset);\n\t\t}\n\t}\n\n\t// make sure we have enough space to write codeaux table\n\tif (data > data_safe_end)\n\t\treturn 0;\n\n\t// add codeaux encoding table to the end of the stream; this is used for decoding codeaux *and* as padding\n\t// we need padding for decoding to be able to assume that each triangle is encoded as <= 16 bytes of extra data\n\t// this is enough space for aux byte + 5 bytes per varint index which is the absolute worst case for any input\n\tfor (size_t i = 0; i < 16; ++i)\n\t{\n\t\t// decoder assumes that table entries never refer to separately encoded indices\n\t\tassert((codeaux_table[i] & 0xf) != 0xf && (codeaux_table[i] >> 4) != 0xf);\n\n\t\t*data++ = codeaux_table[i];\n\t}\n\n\t// since we encode restarts as codeaux without a table reference, we need to make sure 00 is encoded as a table reference\n\tassert(codeaux_table[0] == 0);\n\n\tassert(data >= buffer + index_count / 3 + 16);\n\tassert(data <= buffer + buffer_size);\n\n\treturn data - buffer;\n}\n\nsize_t meshopt_encodeIndexBufferBound(size_t index_count, size_t vertex_count)\n{\n\tassert(index_count % 3 == 0);\n\n\t// compute number of bits required for each index\n\tunsigned int vertex_bits = 1;\n\n\twhile (vertex_bits < 32 && vertex_count > size_t(1) << vertex_bits)\n\t\tvertex_bits++;\n\n\t// worst-case encoding is 2 header bytes + 3 varint-7 encoded index deltas\n\tunsigned int vertex_groups = (vertex_bits + 1 + 6) / 7;\n\n\treturn 1 + (index_count / 3) * (2 + 3 * vertex_groups) + 16;\n}\n\nvoid meshopt_encodeIndexVersion(int version)\n{\n\tassert(unsigned(version) <= unsigned(meshopt::kDecodeIndexVersion));\n\n\tmeshopt::gEncodeIndexVersion = version;\n}\n\nint meshopt_decodeIndexVersion(const unsigned char* buffer, size_t buffer_size)\n{\n\tif (buffer_size < 1)\n\t\treturn -1;\n\n\tunsigned char header = buffer[0];\n\n\tif ((header & 0xf0) != meshopt::kIndexHeader && (header & 0xf0) != meshopt::kSequenceHeader)\n\t\treturn -1;\n\n\tint version = header & 0x0f;\n\tif (version > meshopt::kDecodeIndexVersion)\n\t\treturn -1;\n\n\treturn version;\n}\n\nint meshopt_decodeIndexBuffer(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(index_size == 2 || index_size == 4);\n\n\t// the minimum valid encoding is header, 1 byte per triangle and a 16-byte codeaux table\n\tif (buffer_size < 1 + index_count / 3 + 16)\n\t\treturn -2;\n\n\tif ((buffer[0] & 0xf0) != kIndexHeader)\n\t\treturn -1;\n\n\tint version = buffer[0] & 0x0f;\n\tif (version > kDecodeIndexVersion)\n\t\treturn -1;\n\n\tEdgeFifo edgefifo;\n\tmemset(edgefifo, -1, sizeof(edgefifo));\n\n\tVertexFifo vertexfifo;\n\tmemset(vertexfifo, -1, sizeof(vertexfifo));\n\n\tsize_t edgefifooffset = 0;\n\tsize_t vertexfifooffset = 0;\n\n\tunsigned int next = 0;\n\tunsigned int last = 0;\n\n\tint fecmax = version >= 1 ? 13 : 15;\n\n\t// since we store 16-byte codeaux table at the end, triangle data has to begin before data_safe_end\n\tconst unsigned char* code = buffer + 1;\n\tconst unsigned char* data = code + index_count / 3;\n\tconst unsigned char* data_safe_end = buffer + buffer_size - 16;\n\n\tconst unsigned char* codeaux_table = data_safe_end;\n\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\t// make sure we have enough data to read for a triangle\n\t\t// each triangle reads at most 16 bytes of data: 1b for codeaux and 5b for each free index\n\t\t// after this we can be sure we can read without extra bounds checks\n\t\tif (data > data_safe_end)\n\t\t\treturn -2;\n\n\t\tunsigned char codetri = *code++;\n\n\t\tif (codetri < 0xf0)\n\t\t{\n\t\t\tint fe = codetri >> 4;\n\n\t\t\t// fifo reads are wrapped around 16 entry buffer\n\t\t\tunsigned int a = edgefifo[(edgefifooffset - 1 - fe) & 15][0];\n\t\t\tunsigned int b = edgefifo[(edgefifooffset - 1 - fe) & 15][1];\n\t\t\tunsigned int c = 0;\n\n\t\t\tint fec = codetri & 15;\n\n\t\t\t// note: this is the most common path in the entire decoder\n\t\t\t// inside this if we try to stay branchless (by using cmov/etc.) since these aren't predictable\n\t\t\tif (fec < fecmax)\n\t\t\t{\n\t\t\t\t// fifo reads are wrapped around 16 entry buffer\n\t\t\t\tunsigned int cf = vertexfifo[(vertexfifooffset - 1 - fec) & 15];\n\t\t\t\tc = (fec == 0) ? next : cf;\n\n\t\t\t\tint fec0 = fec == 0;\n\t\t\t\tnext += fec0;\n\n\t\t\t\t// push vertex fifo must match the encoding step *exactly* otherwise the data will not be decoded correctly\n\t\t\t\tpushVertexFifo(vertexfifo, c, vertexfifooffset, fec0);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// fec - (fec ^ 3) decodes 13, 14 into -1, 1\n\t\t\t\t// note that we need to update the last index since free indices are delta-encoded\n\t\t\t\tlast = c = (fec != 15) ? last + (fec - (fec ^ 3)) : decodeIndex(data, last);\n\n\t\t\t\t// push vertex/edge fifo must match the encoding step *exactly* otherwise the data will not be decoded correctly\n\t\t\t\tpushVertexFifo(vertexfifo, c, vertexfifooffset);\n\t\t\t}\n\n\t\t\t// push edge fifo must match the encoding step *exactly* otherwise the data will not be decoded correctly\n\t\t\tpushEdgeFifo(edgefifo, c, b, edgefifooffset);\n\t\t\tpushEdgeFifo(edgefifo, a, c, edgefifooffset);\n\n\t\t\t// output triangle\n\t\t\twriteTriangle(destination, i, index_size, a, b, c);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// fast path: read codeaux from the table\n\t\t\tif (codetri < 0xfe)\n\t\t\t{\n\t\t\t\tunsigned char codeaux = codeaux_table[codetri & 15];\n\n\t\t\t\t// note: table can't contain feb/fec=15\n\t\t\t\tint feb = codeaux >> 4;\n\t\t\t\tint fec = codeaux & 15;\n\n\t\t\t\t// fifo reads are wrapped around 16 entry buffer\n\t\t\t\t// also note that we increment next for all three vertices before decoding indices - this matches encoder behavior\n\t\t\t\tunsigned int a = next++;\n\n\t\t\t\tunsigned int bf = vertexfifo[(vertexfifooffset - feb) & 15];\n\t\t\t\tunsigned int b = (feb == 0) ? next : bf;\n\n\t\t\t\tint feb0 = feb == 0;\n\t\t\t\tnext += feb0;\n\n\t\t\t\tunsigned int cf = vertexfifo[(vertexfifooffset - fec) & 15];\n\t\t\t\tunsigned int c = (fec == 0) ? next : cf;\n\n\t\t\t\tint fec0 = fec == 0;\n\t\t\t\tnext += fec0;\n\n\t\t\t\t// output triangle\n\t\t\t\twriteTriangle(destination, i, index_size, a, b, c);\n\n\t\t\t\t// push vertex/edge fifo must match the encoding step *exactly* otherwise the data will not be decoded correctly\n\t\t\t\tpushVertexFifo(vertexfifo, a, vertexfifooffset);\n\t\t\t\tpushVertexFifo(vertexfifo, b, vertexfifooffset, feb0);\n\t\t\t\tpushVertexFifo(vertexfifo, c, vertexfifooffset, fec0);\n\n\t\t\t\tpushEdgeFifo(edgefifo, b, a, edgefifooffset);\n\t\t\t\tpushEdgeFifo(edgefifo, c, b, edgefifooffset);\n\t\t\t\tpushEdgeFifo(edgefifo, a, c, edgefifooffset);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// slow path: read a full byte for codeaux instead of using a table lookup\n\t\t\t\tunsigned char codeaux = *data++;\n\n\t\t\t\tint fea = codetri == 0xfe ? 0 : 15;\n\t\t\t\tint feb = codeaux >> 4;\n\t\t\t\tint fec = codeaux & 15;\n\n\t\t\t\t// reset: codeaux is 0 but encoded as not-a-table\n\t\t\t\tif (codeaux == 0)\n\t\t\t\t\tnext = 0;\n\n\t\t\t\t// fifo reads are wrapped around 16 entry buffer\n\t\t\t\t// also note that we increment next for all three vertices before decoding indices - this matches encoder behavior\n\t\t\t\tunsigned int a = (fea == 0) ? next++ : 0;\n\t\t\t\tunsigned int b = (feb == 0) ? next++ : vertexfifo[(vertexfifooffset - feb) & 15];\n\t\t\t\tunsigned int c = (fec == 0) ? next++ : vertexfifo[(vertexfifooffset - fec) & 15];\n\n\t\t\t\t// note that we need to update the last index since free indices are delta-encoded\n\t\t\t\tif (fea == 15)\n\t\t\t\t\tlast = a = decodeIndex(data, last);\n\n\t\t\t\tif (feb == 15)\n\t\t\t\t\tlast = b = decodeIndex(data, last);\n\n\t\t\t\tif (fec == 15)\n\t\t\t\t\tlast = c = decodeIndex(data, last);\n\n\t\t\t\t// output triangle\n\t\t\t\twriteTriangle(destination, i, index_size, a, b, c);\n\n\t\t\t\t// push vertex/edge fifo must match the encoding step *exactly* otherwise the data will not be decoded correctly\n\t\t\t\tpushVertexFifo(vertexfifo, a, vertexfifooffset);\n\t\t\t\tpushVertexFifo(vertexfifo, b, vertexfifooffset, (feb == 0) | (feb == 15));\n\t\t\t\tpushVertexFifo(vertexfifo, c, vertexfifooffset, (fec == 0) | (fec == 15));\n\n\t\t\t\tpushEdgeFifo(edgefifo, b, a, edgefifooffset);\n\t\t\t\tpushEdgeFifo(edgefifo, c, b, edgefifooffset);\n\t\t\t\tpushEdgeFifo(edgefifo, a, c, edgefifooffset);\n\t\t\t}\n\t\t}\n\t}\n\n\t// we should've read all data bytes and stopped at the boundary between data and codeaux table\n\tif (data != data_safe_end)\n\t\treturn -3;\n\n\treturn 0;\n}\n\nsize_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const unsigned int* indices, size_t index_count)\n{\n\tusing namespace meshopt;\n\n\t// the minimum valid encoding is header, 1 byte per index and a 4-byte tail\n\tif (buffer_size < 1 + index_count + 4)\n\t\treturn 0;\n\n\tint version = gEncodeIndexVersion;\n\n\tbuffer[0] = (unsigned char)(kSequenceHeader | version);\n\n\tunsigned int last[2] = {};\n\tunsigned int current = 0;\n\n\tunsigned char* data = buffer + 1;\n\tunsigned char* data_safe_end = buffer + buffer_size - 4;\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\t// make sure we have enough data to write\n\t\t// each index writes at most 5 bytes of data; there's a 4 byte tail after data_safe_end\n\t\t// after this we can be sure we can write without extra bounds checks\n\t\tif (data >= data_safe_end)\n\t\t\treturn 0;\n\n\t\tunsigned int index = indices[i];\n\n\t\t// this is a heuristic that switches between baselines when the delta grows too large\n\t\t// we want the encoded delta to fit into one byte (7 bits), but 2 bits are used for sign and baseline index\n\t\t// for now we immediately switch the baseline when delta grows too large - this can be adjusted arbitrarily\n\t\tint cd = int(index - last[current]);\n\t\tcurrent ^= ((cd < 0 ? -cd : cd) >= 30);\n\n\t\t// encode delta from the last index\n\t\tunsigned int d = index - last[current];\n\t\tunsigned int v = (d << 1) ^ (int(d) >> 31);\n\n\t\t// note: low bit encodes the index of the last baseline which will be used for reconstruction\n\t\tencodeVByte(data, (v << 1) | current);\n\n\t\t// update last for the next iteration that uses it\n\t\tlast[current] = index;\n\t}\n\n\t// make sure we have enough space to write tail\n\tif (data > data_safe_end)\n\t\treturn 0;\n\n\tfor (int k = 0; k < 4; ++k)\n\t\t*data++ = 0;\n\n\treturn data - buffer;\n}\n\nsize_t meshopt_encodeIndexSequenceBound(size_t index_count, size_t vertex_count)\n{\n\t// compute number of bits required for each index\n\tunsigned int vertex_bits = 1;\n\n\twhile (vertex_bits < 32 && vertex_count > size_t(1) << vertex_bits)\n\t\tvertex_bits++;\n\n\t// worst-case encoding is 1 varint-7 encoded index delta for a K bit value and an extra bit\n\tunsigned int vertex_groups = (vertex_bits + 1 + 1 + 6) / 7;\n\n\treturn 1 + index_count * vertex_groups + 4;\n}\n\nint meshopt_decodeIndexSequence(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size)\n{\n\tusing namespace meshopt;\n\n\t// the minimum valid encoding is header, 1 byte per index and a 4-byte tail\n\tif (buffer_size < 1 + index_count + 4)\n\t\treturn -2;\n\n\tif ((buffer[0] & 0xf0) != kSequenceHeader)\n\t\treturn -1;\n\n\tint version = buffer[0] & 0x0f;\n\tif (version > kDecodeIndexVersion)\n\t\treturn -1;\n\n\tconst unsigned char* data = buffer + 1;\n\tconst unsigned char* data_safe_end = buffer + buffer_size - 4;\n\n\tunsigned int last[2] = {};\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\t// make sure we have enough data to read\n\t\t// each index reads at most 5 bytes of data; there's a 4 byte tail after data_safe_end\n\t\t// after this we can be sure we can read without extra bounds checks\n\t\tif (data >= data_safe_end)\n\t\t\treturn -2;\n\n\t\tunsigned int v = decodeVByte(data);\n\n\t\t// decode the index of the last baseline\n\t\tunsigned int current = v & 1;\n\t\tv >>= 1;\n\n\t\t// reconstruct index as a delta\n\t\tunsigned int d = (v >> 1) ^ -int(v & 1);\n\t\tunsigned int index = last[current] + d;\n\n\t\t// update last for the next iteration that uses it\n\t\tlast[current] = index;\n\n\t\tif (index_size == 2)\n\t\t{\n\t\t\tstatic_cast<unsigned short*>(destination)[i] = (unsigned short)(index);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tstatic_cast<unsigned int*>(destination)[i] = index;\n\t\t}\n\t}\n\n\t// we should've read all data bytes and stopped at the boundary between data and tail\n\tif (data != data_safe_end)\n\t\treturn -3;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/indexgenerator.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <string.h>\n\n// This work is based on:\n// Matthias Teschner, Bruno Heidelberger, Matthias Mueller, Danat Pomeranets, Markus Gross. Optimized Spatial Hashing for Collision Detection of Deformable Objects. 2003\n// John McDonald, Mark Kilgard. Crack-Free Point-Normal Triangles using Adjacent Edge Normals. 2010\n// John Hable. Variable Rate Shading with Visibility Buffer Rendering. 2024\nnamespace meshopt\n{\n\nstatic unsigned int hashUpdate4(unsigned int h, const unsigned char* key, size_t len)\n{\n\t// MurmurHash2\n\tconst unsigned int m = 0x5bd1e995;\n\tconst int r = 24;\n\n\twhile (len >= 4)\n\t{\n\t\tunsigned int k = *reinterpret_cast<const unsigned int*>(key);\n\n\t\tk *= m;\n\t\tk ^= k >> r;\n\t\tk *= m;\n\n\t\th *= m;\n\t\th ^= k;\n\n\t\tkey += 4;\n\t\tlen -= 4;\n\t}\n\n\treturn h;\n}\n\nstruct VertexHasher\n{\n\tconst unsigned char* vertices;\n\tsize_t vertex_size;\n\tsize_t vertex_stride;\n\n\tsize_t hash(unsigned int index) const\n\t{\n\t\treturn hashUpdate4(0, vertices + index * vertex_stride, vertex_size);\n\t}\n\n\tbool equal(unsigned int lhs, unsigned int rhs) const\n\t{\n\t\treturn memcmp(vertices + lhs * vertex_stride, vertices + rhs * vertex_stride, vertex_size) == 0;\n\t}\n};\n\nstruct VertexStreamHasher\n{\n\tconst meshopt_Stream* streams;\n\tsize_t stream_count;\n\n\tsize_t hash(unsigned int index) const\n\t{\n\t\tunsigned int h = 0;\n\n\t\tfor (size_t i = 0; i < stream_count; ++i)\n\t\t{\n\t\t\tconst meshopt_Stream& s = streams[i];\n\t\t\tconst unsigned char* data = static_cast<const unsigned char*>(s.data);\n\n\t\t\th = hashUpdate4(h, data + index * s.stride, s.size);\n\t\t}\n\n\t\treturn h;\n\t}\n\n\tbool equal(unsigned int lhs, unsigned int rhs) const\n\t{\n\t\tfor (size_t i = 0; i < stream_count; ++i)\n\t\t{\n\t\t\tconst meshopt_Stream& s = streams[i];\n\t\t\tconst unsigned char* data = static_cast<const unsigned char*>(s.data);\n\n\t\t\tif (memcmp(data + lhs * s.stride, data + rhs * s.stride, s.size) != 0)\n\t\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n};\n\nstruct VertexCustomHasher\n{\n\tconst float* vertex_positions;\n\tsize_t vertex_stride_float;\n\n\tint (*callback)(void*, unsigned int, unsigned int);\n\tvoid* context;\n\n\tsize_t hash(unsigned int index) const\n\t{\n\t\tconst unsigned int* key = reinterpret_cast<const unsigned int*>(vertex_positions + index * vertex_stride_float);\n\n\t\tunsigned int x = key[0], y = key[1], z = key[2];\n\n\t\t// replace negative zero with zero\n\t\tx = (x == 0x80000000) ? 0 : x;\n\t\ty = (y == 0x80000000) ? 0 : y;\n\t\tz = (z == 0x80000000) ? 0 : z;\n\n\t\t// scramble bits to make sure that integer coordinates have entropy in lower bits\n\t\tx ^= x >> 17;\n\t\ty ^= y >> 17;\n\t\tz ^= z >> 17;\n\n\t\t// Optimized Spatial Hashing for Collision Detection of Deformable Objects\n\t\treturn (x * 73856093) ^ (y * 19349663) ^ (z * 83492791);\n\t}\n\n\tbool equal(unsigned int lhs, unsigned int rhs) const\n\t{\n\t\tconst float* lp = vertex_positions + lhs * vertex_stride_float;\n\t\tconst float* rp = vertex_positions + rhs * vertex_stride_float;\n\n\t\tif (lp[0] != rp[0] || lp[1] != rp[1] || lp[2] != rp[2])\n\t\t\treturn false;\n\n\t\treturn callback ? callback(context, lhs, rhs) : true;\n\t}\n};\n\nstruct EdgeHasher\n{\n\tconst unsigned int* remap;\n\n\tsize_t hash(unsigned long long edge) const\n\t{\n\t\tunsigned int e0 = unsigned(edge >> 32);\n\t\tunsigned int e1 = unsigned(edge);\n\n\t\tunsigned int h1 = remap[e0];\n\t\tunsigned int h2 = remap[e1];\n\n\t\tconst unsigned int m = 0x5bd1e995;\n\n\t\t// MurmurHash64B finalizer\n\t\th1 ^= h2 >> 18;\n\t\th1 *= m;\n\t\th2 ^= h1 >> 22;\n\t\th2 *= m;\n\t\th1 ^= h2 >> 17;\n\t\th1 *= m;\n\t\th2 ^= h1 >> 19;\n\t\th2 *= m;\n\n\t\treturn h2;\n\t}\n\n\tbool equal(unsigned long long lhs, unsigned long long rhs) const\n\t{\n\t\tunsigned int l0 = unsigned(lhs >> 32);\n\t\tunsigned int l1 = unsigned(lhs);\n\n\t\tunsigned int r0 = unsigned(rhs >> 32);\n\t\tunsigned int r1 = unsigned(rhs);\n\n\t\treturn remap[l0] == remap[r0] && remap[l1] == remap[r1];\n\t}\n};\n\nstatic size_t hashBuckets(size_t count)\n{\n\tsize_t buckets = 1;\n\twhile (buckets < count + count / 4)\n\t\tbuckets *= 2;\n\n\treturn buckets;\n}\n\ntemplate <typename T, typename Hash>\nstatic T* hashLookup(T* table, size_t buckets, const Hash& hash, const T& key, const T& empty)\n{\n\tassert(buckets > 0);\n\tassert((buckets & (buckets - 1)) == 0);\n\n\tsize_t hashmod = buckets - 1;\n\tsize_t bucket = hash.hash(key) & hashmod;\n\n\tfor (size_t probe = 0; probe <= hashmod; ++probe)\n\t{\n\t\tT& item = table[bucket];\n\n\t\tif (item == empty)\n\t\t\treturn &item;\n\n\t\tif (hash.equal(item, key))\n\t\t\treturn &item;\n\n\t\t// hash collision, quadratic probing\n\t\tbucket = (bucket + probe + 1) & hashmod;\n\t}\n\n\tassert(false && \"Hash table is full\"); // unreachable\n\treturn NULL;\n}\n\nstatic void buildPositionRemap(unsigned int* remap, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, meshopt_Allocator& allocator)\n{\n\tVertexHasher vertex_hasher = {reinterpret_cast<const unsigned char*>(vertex_positions), 3 * sizeof(float), vertex_positions_stride};\n\n\tsize_t vertex_table_size = hashBuckets(vertex_count);\n\tunsigned int* vertex_table = allocator.allocate<unsigned int>(vertex_table_size);\n\tmemset(vertex_table, -1, vertex_table_size * sizeof(unsigned int));\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tunsigned int index = unsigned(i);\n\t\tunsigned int* entry = hashLookup(vertex_table, vertex_table_size, vertex_hasher, index, ~0u);\n\n\t\tif (*entry == ~0u)\n\t\t\t*entry = index;\n\n\t\tremap[index] = *entry;\n\t}\n\n\tallocator.deallocate(vertex_table);\n}\n\ntemplate <typename Hash>\nstatic size_t generateVertexRemap(unsigned int* remap, const unsigned int* indices, size_t index_count, size_t vertex_count, const Hash& hash, meshopt_Allocator& allocator)\n{\n\tmemset(remap, -1, vertex_count * sizeof(unsigned int));\n\n\tsize_t table_size = hashBuckets(vertex_count);\n\tunsigned int* table = allocator.allocate<unsigned int>(table_size);\n\tmemset(table, -1, table_size * sizeof(unsigned int));\n\n\tunsigned int next_vertex = 0;\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int index = indices ? indices[i] : unsigned(i);\n\t\tassert(index < vertex_count);\n\n\t\tif (remap[index] != ~0u)\n\t\t\tcontinue;\n\n\t\tunsigned int* entry = hashLookup(table, table_size, hash, index, ~0u);\n\n\t\tif (*entry == ~0u)\n\t\t{\n\t\t\t*entry = index;\n\t\t\tremap[index] = next_vertex++;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tassert(remap[*entry] != ~0u);\n\t\t\tremap[index] = remap[*entry];\n\t\t}\n\t}\n\n\tassert(next_vertex <= vertex_count);\n\treturn next_vertex;\n}\n\ntemplate <size_t BlockSize>\nstatic void remapVertices(void* destination, const void* vertices, size_t vertex_count, size_t vertex_size, const unsigned int* remap)\n{\n\tsize_t block_size = BlockSize == 0 ? vertex_size : BlockSize;\n\tassert(block_size == vertex_size);\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tif (remap[i] != ~0u)\n\t\t{\n\t\t\tassert(remap[i] < vertex_count);\n\t\t\tmemcpy(static_cast<unsigned char*>(destination) + remap[i] * block_size, static_cast<const unsigned char*>(vertices) + i * block_size, block_size);\n\t\t}\n}\n\ntemplate <typename Hash>\nstatic void generateShadowBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const Hash& hash, meshopt_Allocator& allocator)\n{\n\tunsigned int* remap = allocator.allocate<unsigned int>(vertex_count);\n\tmemset(remap, -1, vertex_count * sizeof(unsigned int));\n\n\tsize_t table_size = hashBuckets(vertex_count);\n\tunsigned int* table = allocator.allocate<unsigned int>(table_size);\n\tmemset(table, -1, table_size * sizeof(unsigned int));\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int index = indices[i];\n\t\tassert(index < vertex_count);\n\n\t\tif (remap[index] == ~0u)\n\t\t{\n\t\t\tunsigned int* entry = hashLookup(table, table_size, hash, index, ~0u);\n\n\t\t\tif (*entry == ~0u)\n\t\t\t\t*entry = index;\n\n\t\t\tremap[index] = *entry;\n\t\t}\n\n\t\tdestination[i] = remap[index];\n\t}\n}\n\n} // namespace meshopt\n\nsize_t meshopt_generateVertexRemap(unsigned int* destination, const unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size)\n{\n\tusing namespace meshopt;\n\n\tassert(indices || index_count == vertex_count);\n\tassert(!indices || index_count % 3 == 0);\n\tassert(vertex_size > 0 && vertex_size <= 256);\n\n\tmeshopt_Allocator allocator;\n\tVertexHasher hasher = {static_cast<const unsigned char*>(vertices), vertex_size, vertex_size};\n\n\treturn generateVertexRemap(destination, indices, index_count, vertex_count, hasher, allocator);\n}\n\nsize_t meshopt_generateVertexRemapMulti(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream* streams, size_t stream_count)\n{\n\tusing namespace meshopt;\n\n\tassert(indices || index_count == vertex_count);\n\tassert(!indices || index_count % 3 == 0);\n\tassert(stream_count > 0 && stream_count <= 16);\n\n\tfor (size_t i = 0; i < stream_count; ++i)\n\t{\n\t\tassert(streams[i].size > 0 && streams[i].size <= 256);\n\t\tassert(streams[i].size <= streams[i].stride);\n\t}\n\n\tmeshopt_Allocator allocator;\n\tVertexStreamHasher hasher = {streams, stream_count};\n\n\treturn generateVertexRemap(destination, indices, index_count, vertex_count, hasher, allocator);\n}\n\nsize_t meshopt_generateVertexRemapCustom(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, int (*callback)(void*, unsigned int, unsigned int), void* context)\n{\n\tusing namespace meshopt;\n\n\tassert(indices || index_count == vertex_count);\n\tassert(!indices || index_count % 3 == 0);\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\n\tmeshopt_Allocator allocator;\n\tVertexCustomHasher hasher = {vertex_positions, vertex_positions_stride / sizeof(float), callback, context};\n\n\treturn generateVertexRemap(destination, indices, index_count, vertex_count, hasher, allocator);\n}\n\nvoid meshopt_remapVertexBuffer(void* destination, const void* vertices, size_t vertex_count, size_t vertex_size, const unsigned int* remap)\n{\n\tusing namespace meshopt;\n\n\tassert(vertex_size > 0 && vertex_size <= 256);\n\n\tmeshopt_Allocator allocator;\n\n\t// support in-place remap\n\tif (destination == vertices)\n\t{\n\t\tunsigned char* vertices_copy = allocator.allocate<unsigned char>(vertex_count * vertex_size);\n\t\tmemcpy(vertices_copy, vertices, vertex_count * vertex_size);\n\t\tvertices = vertices_copy;\n\t}\n\n\t// specialize the loop for common vertex sizes to ensure memcpy is compiled as an inlined intrinsic\n\tswitch (vertex_size)\n\t{\n\tcase 4:\n\t\treturn remapVertices<4>(destination, vertices, vertex_count, vertex_size, remap);\n\n\tcase 8:\n\t\treturn remapVertices<8>(destination, vertices, vertex_count, vertex_size, remap);\n\n\tcase 12:\n\t\treturn remapVertices<12>(destination, vertices, vertex_count, vertex_size, remap);\n\n\tcase 16:\n\t\treturn remapVertices<16>(destination, vertices, vertex_count, vertex_size, remap);\n\n\tdefault:\n\t\treturn remapVertices<0>(destination, vertices, vertex_count, vertex_size, remap);\n\t}\n}\n\nvoid meshopt_remapIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const unsigned int* remap)\n{\n\tassert(index_count % 3 == 0);\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int index = indices ? indices[i] : unsigned(i);\n\t\tassert(remap[index] != ~0u);\n\n\t\tdestination[i] = remap[index];\n\t}\n}\n\nvoid meshopt_generateShadowIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride)\n{\n\tusing namespace meshopt;\n\n\tassert(indices);\n\tassert(index_count % 3 == 0);\n\tassert(vertex_size > 0 && vertex_size <= 256);\n\tassert(vertex_size <= vertex_stride);\n\n\tmeshopt_Allocator allocator;\n\tVertexHasher hasher = {static_cast<const unsigned char*>(vertices), vertex_size, vertex_stride};\n\n\tgenerateShadowBuffer(destination, indices, index_count, vertex_count, hasher, allocator);\n}\n\nvoid meshopt_generateShadowIndexBufferMulti(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream* streams, size_t stream_count)\n{\n\tusing namespace meshopt;\n\n\tassert(indices);\n\tassert(index_count % 3 == 0);\n\tassert(stream_count > 0 && stream_count <= 16);\n\n\tfor (size_t i = 0; i < stream_count; ++i)\n\t{\n\t\tassert(streams[i].size > 0 && streams[i].size <= 256);\n\t\tassert(streams[i].size <= streams[i].stride);\n\t}\n\n\tmeshopt_Allocator allocator;\n\tVertexStreamHasher hasher = {streams, stream_count};\n\n\tgenerateShadowBuffer(destination, indices, index_count, vertex_count, hasher, allocator);\n}\n\nvoid meshopt_generatePositionRemap(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tusing namespace meshopt;\n\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\n\tmeshopt_Allocator allocator;\n\tVertexCustomHasher hasher = {vertex_positions, vertex_positions_stride / sizeof(float), NULL, NULL};\n\n\tsize_t table_size = hashBuckets(vertex_count);\n\tunsigned int* table = allocator.allocate<unsigned int>(table_size);\n\tmemset(table, -1, table_size * sizeof(unsigned int));\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tunsigned int* entry = hashLookup(table, table_size, hasher, unsigned(i), ~0u);\n\n\t\tif (*entry == ~0u)\n\t\t\t*entry = unsigned(i);\n\n\t\tdestination[i] = *entry;\n\t}\n}\n\nvoid meshopt_generateAdjacencyIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\n\tmeshopt_Allocator allocator;\n\n\tstatic const int next[4] = {1, 2, 0, 1};\n\n\t// build position remap: for each vertex, which other (canonical) vertex does it map to?\n\tunsigned int* remap = allocator.allocate<unsigned int>(vertex_count);\n\tbuildPositionRemap(remap, vertex_positions, vertex_count, vertex_positions_stride, allocator);\n\n\t// build edge set; this stores all triangle edges but we can look these up by any other wedge\n\tEdgeHasher edge_hasher = {remap};\n\n\tsize_t edge_table_size = hashBuckets(index_count);\n\tunsigned long long* edge_table = allocator.allocate<unsigned long long>(edge_table_size);\n\tunsigned int* edge_vertex_table = allocator.allocate<unsigned int>(edge_table_size);\n\n\tmemset(edge_table, -1, edge_table_size * sizeof(unsigned long long));\n\tmemset(edge_vertex_table, -1, edge_table_size * sizeof(unsigned int));\n\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tfor (int e = 0; e < 3; ++e)\n\t\t{\n\t\t\tunsigned int i0 = indices[i + e];\n\t\t\tunsigned int i1 = indices[i + next[e]];\n\t\t\tunsigned int i2 = indices[i + next[e + 1]];\n\t\t\tassert(i0 < vertex_count && i1 < vertex_count && i2 < vertex_count);\n\n\t\t\tunsigned long long edge = ((unsigned long long)i0 << 32) | i1;\n\t\t\tunsigned long long* entry = hashLookup(edge_table, edge_table_size, edge_hasher, edge, ~0ull);\n\n\t\t\tif (*entry == ~0ull)\n\t\t\t{\n\t\t\t\t*entry = edge;\n\n\t\t\t\t// store vertex opposite to the edge\n\t\t\t\tedge_vertex_table[entry - edge_table] = i2;\n\t\t\t}\n\t\t}\n\t}\n\n\t// build resulting index buffer: 6 indices for each input triangle\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tunsigned int patch[6];\n\n\t\tfor (int e = 0; e < 3; ++e)\n\t\t{\n\t\t\tunsigned int i0 = indices[i + e];\n\t\t\tunsigned int i1 = indices[i + next[e]];\n\t\t\tassert(i0 < vertex_count && i1 < vertex_count);\n\n\t\t\t// note: this refers to the opposite edge!\n\t\t\tunsigned long long edge = ((unsigned long long)i1 << 32) | i0;\n\t\t\tunsigned long long* oppe = hashLookup(edge_table, edge_table_size, edge_hasher, edge, ~0ull);\n\n\t\t\tpatch[e * 2 + 0] = i0;\n\t\t\tpatch[e * 2 + 1] = (*oppe == ~0ull) ? i0 : edge_vertex_table[oppe - edge_table];\n\t\t}\n\n\t\tmemcpy(destination + i * 2, patch, sizeof(patch));\n\t}\n}\n\nvoid meshopt_generateTessellationIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\n\tmeshopt_Allocator allocator;\n\n\tstatic const int next[3] = {1, 2, 0};\n\n\t// build position remap: for each vertex, which other (canonical) vertex does it map to?\n\tunsigned int* remap = allocator.allocate<unsigned int>(vertex_count);\n\tbuildPositionRemap(remap, vertex_positions, vertex_count, vertex_positions_stride, allocator);\n\n\t// build edge set; this stores all triangle edges but we can look these up by any other wedge\n\tEdgeHasher edge_hasher = {remap};\n\n\tsize_t edge_table_size = hashBuckets(index_count);\n\tunsigned long long* edge_table = allocator.allocate<unsigned long long>(edge_table_size);\n\tmemset(edge_table, -1, edge_table_size * sizeof(unsigned long long));\n\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tfor (int e = 0; e < 3; ++e)\n\t\t{\n\t\t\tunsigned int i0 = indices[i + e];\n\t\t\tunsigned int i1 = indices[i + next[e]];\n\t\t\tassert(i0 < vertex_count && i1 < vertex_count);\n\n\t\t\tunsigned long long edge = ((unsigned long long)i0 << 32) | i1;\n\t\t\tunsigned long long* entry = hashLookup(edge_table, edge_table_size, edge_hasher, edge, ~0ull);\n\n\t\t\tif (*entry == ~0ull)\n\t\t\t\t*entry = edge;\n\t\t}\n\t}\n\n\t// build resulting index buffer: 12 indices for each input triangle\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tunsigned int patch[12];\n\n\t\tfor (int e = 0; e < 3; ++e)\n\t\t{\n\t\t\tunsigned int i0 = indices[i + e];\n\t\t\tunsigned int i1 = indices[i + next[e]];\n\t\t\tassert(i0 < vertex_count && i1 < vertex_count);\n\n\t\t\t// note: this refers to the opposite edge!\n\t\t\tunsigned long long edge = ((unsigned long long)i1 << 32) | i0;\n\t\t\tunsigned long long oppe = *hashLookup(edge_table, edge_table_size, edge_hasher, edge, ~0ull);\n\n\t\t\t// use the same edge if opposite edge doesn't exist (border)\n\t\t\toppe = (oppe == ~0ull) ? edge : oppe;\n\n\t\t\t// triangle index (0, 1, 2)\n\t\t\tpatch[e] = i0;\n\n\t\t\t// opposite edge (3, 4; 5, 6; 7, 8)\n\t\t\tpatch[3 + e * 2 + 0] = unsigned(oppe);\n\t\t\tpatch[3 + e * 2 + 1] = unsigned(oppe >> 32);\n\n\t\t\t// dominant vertex (9, 10, 11)\n\t\t\tpatch[9 + e] = remap[i0];\n\t\t}\n\n\t\tmemcpy(destination + i * 4, patch, sizeof(patch));\n\t}\n}\n\nsize_t meshopt_generateProvokingIndexBuffer(unsigned int* destination, unsigned int* reorder, const unsigned int* indices, size_t index_count, size_t vertex_count)\n{\n\tassert(index_count % 3 == 0);\n\n\tmeshopt_Allocator allocator;\n\n\tunsigned int* remap = allocator.allocate<unsigned int>(vertex_count);\n\tmemset(remap, -1, vertex_count * sizeof(unsigned int));\n\n\t// compute vertex valence; this is used to prioritize least used corner\n\t// note: we use 8-bit counters for performance; for outlier vertices the valence is incorrect but that just affects the heuristic\n\tunsigned char* valence = allocator.allocate<unsigned char>(vertex_count);\n\tmemset(valence, 0, vertex_count);\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int index = indices[i];\n\t\tassert(index < vertex_count);\n\n\t\tvalence[index]++;\n\t}\n\n\tunsigned int reorder_offset = 0;\n\n\t// assign provoking vertices; leave the rest for the next pass\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tunsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2];\n\t\tassert(a < vertex_count && b < vertex_count && c < vertex_count);\n\n\t\t// try to rotate triangle such that provoking vertex hasn't been seen before\n\t\t// if multiple vertices are new, prioritize the one with least valence\n\t\t// this reduces the risk that a future triangle will have all three vertices seen\n\t\tunsigned int va = remap[a] == ~0u ? valence[a] : ~0u;\n\t\tunsigned int vb = remap[b] == ~0u ? valence[b] : ~0u;\n\t\tunsigned int vc = remap[c] == ~0u ? valence[c] : ~0u;\n\n\t\tif (vb != ~0u && vb <= va && vb <= vc)\n\t\t{\n\t\t\t// abc -> bca\n\t\t\tunsigned int t = a;\n\t\t\ta = b, b = c, c = t;\n\t\t}\n\t\telse if (vc != ~0u && vc <= va && vc <= vb)\n\t\t{\n\t\t\t// abc -> cab\n\t\t\tunsigned int t = c;\n\t\t\tc = b, b = a, a = t;\n\t\t}\n\n\t\tunsigned int newidx = reorder_offset;\n\n\t\t// now remap[a] = ~0u or all three vertices are old\n\t\t// recording remap[a] makes it possible to remap future references to the same index, conserving space\n\t\tif (remap[a] == ~0u)\n\t\t\tremap[a] = newidx;\n\n\t\t// we need to clone the provoking vertex to get a unique index\n\t\t// if all three are used the choice is arbitrary since no future triangle will be able to reuse any of these\n\t\treorder[reorder_offset++] = a;\n\n\t\t// note: first vertex is final, the other two will be fixed up in next pass\n\t\tdestination[i + 0] = newidx;\n\t\tdestination[i + 1] = b;\n\t\tdestination[i + 2] = c;\n\n\t\t// update vertex valences for corner heuristic\n\t\tvalence[a]--;\n\t\tvalence[b]--;\n\t\tvalence[c]--;\n\t}\n\n\t// remap or clone non-provoking vertices (iterating to skip provoking vertices)\n\tint step = 1;\n\n\tfor (size_t i = 1; i < index_count; i += step, step ^= 3)\n\t{\n\t\tunsigned int index = destination[i];\n\n\t\tif (remap[index] == ~0u)\n\t\t{\n\t\t\t// we haven't seen the vertex before as a provoking vertex\n\t\t\t// to maintain the reference to the original vertex we need to clone it\n\t\t\tunsigned int newidx = reorder_offset;\n\n\t\t\tremap[index] = newidx;\n\t\t\treorder[reorder_offset++] = index;\n\t\t}\n\n\t\tdestination[i] = remap[index];\n\t}\n\n\tassert(reorder_offset <= vertex_count + index_count / 3);\n\treturn reorder_offset;\n}\n"
  },
  {
    "path": "src/meshletcodec.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <string.h>\n\n// The block below auto-detects SIMD ISA that can be used on the target platform\n#ifndef MESHOPTIMIZER_NO_SIMD\n\n// The SIMD implementation requires SSE4.1, which can be enabled unconditionally through compiler settings\n#if defined(__AVX__) || defined(__SSE4_1__)\n#define SIMD_SSE\n#endif\n\n// MSVC supports compiling SSE4.1 code regardless of compile options; we use a cpuid-based scalar fallback\n#if !defined(SIMD_SSE) && defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC)))\n#define SIMD_SSE\n#define SIMD_FALLBACK\n#endif\n\n// GCC 4.9+ and clang 3.8+ support targeting SIMD ISA from individual functions; we use a cpuid-based scalar fallback\n#if !defined(SIMD_SSE) && ((defined(__clang__) && __clang_major__ * 100 + __clang_minor__ >= 308) || (defined(__GNUC__) && __GNUC__ * 100 + __GNUC_MINOR__ >= 409)) && (defined(__i386__) || defined(__x86_64__))\n#define SIMD_SSE\n#define SIMD_FALLBACK\n#define SIMD_TARGET __attribute__((target(\"sse4.1\")))\n#endif\n\n// When targeting AArch64, enable NEON SIMD unconditionally; we do not support SIMD decoding for 32-bit ARM\n#if defined(__aarch64__) || (defined(_MSC_VER) && (defined(_M_ARM64) || defined(_M_ARM64EC)) && _MSC_VER >= 1922)\n#define SIMD_NEON\n#endif\n\n#if defined(_MSC_VER) && _MSC_VER > 1930\n#define SIMD_FLATTEN [[msvc::flatten]]\n#elif defined(__GNUC__) || defined(__clang__)\n#define SIMD_FLATTEN __attribute__((flatten))\n#else\n#define SIMD_FLATTEN\n#endif\n\n#ifndef SIMD_TARGET\n#define SIMD_TARGET\n#endif\n\n#endif // !MESHOPTIMIZER_NO_SIMD\n\n#ifdef SIMD_SSE\n#include <smmintrin.h>\n#endif\n\n#ifdef SIMD_NEON\n#include <arm_neon.h>\n#endif\n\n#if defined(SIMD_SSE) && defined(SIMD_FALLBACK)\n#ifdef _MSC_VER\n#include <intrin.h> // __cpuid\n#else\n#include <cpuid.h> // __cpuid\n#endif\n#endif\n\n#ifndef TRACE\n#define TRACE 0\n#endif\n\n#if TRACE\n#include <stdio.h>\n#endif\n\nnamespace meshopt\n{\n\ntypedef unsigned int EdgeFifo8[8][2];\n\nstatic int rotateTriangle(unsigned int a, unsigned int b, unsigned int c)\n{\n\treturn (a > b && a > c) ? 1 : (b > c ? 2 : 0);\n}\n\nstatic int getEdgeFifo8(EdgeFifo8 fifo, unsigned int a, unsigned int b, unsigned int c, size_t offset)\n{\n\tfor (int i = 0; i < 8; ++i)\n\t{\n\t\tsize_t index = (offset - 1 - i) & 7;\n\n\t\tunsigned int e0 = fifo[index][0];\n\t\tunsigned int e1 = fifo[index][1];\n\n\t\tif (e0 == a && e1 == b)\n\t\t\treturn (i << 2) | 0;\n\t\tif (e0 == b && e1 == c)\n\t\t\treturn (i << 2) | 1;\n\t\tif (e0 == c && e1 == a)\n\t\t\treturn (i << 2) | 2;\n\t}\n\n\treturn -1;\n}\n\nstatic void pushEdgeFifo8(EdgeFifo8 fifo, unsigned int a, unsigned int b, size_t& offset)\n{\n\tfifo[offset][0] = a;\n\tfifo[offset][1] = b;\n\toffset = (offset + 1) & 7;\n}\n\nstatic size_t encodeTriangles(unsigned char* codes, unsigned char* extra, const unsigned char* triangles, size_t triangle_count)\n{\n\tEdgeFifo8 edgefifo;\n\tmemset(edgefifo, -1, sizeof(edgefifo));\n\n\tsize_t edgefifooffset = 0;\n\n\tunsigned int next = 0;\n\n\t// 4-bit triangle codes give us 16 options that we use as follows:\n\t// 3*2 edge reuse (2 edges * 3 last triangles) * 2 next/explicit = 12 options\n\t// 4 remaining options = next bits; 000, 001, 011, 111.\n\t// triangles are rotated to make next bits line up.\n\tmemset(codes, 0, (triangle_count + 1) / 2);\n\n\tstatic const int rotations[] = {0, 1, 2, 0, 1};\n\n\tunsigned char* start = extra;\n\n\tfor (size_t i = 0; i < triangle_count; ++i)\n\t{\n#if TRACE > 1\n\t\tunsigned int last = next;\n#endif\n\n\t\tint fer = getEdgeFifo8(edgefifo, triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2], edgefifooffset);\n\n\t\tif (fer >= 0 && (fer >> 2) < 6)\n\t\t{\n\t\t\t// note: getEdgeFifo8 implicitly rotates triangles by matching a/b to existing edge\n\t\t\tconst int* order = rotations + (fer & 3);\n\n\t\t\tunsigned int a = triangles[i * 3 + order[0]], b = triangles[i * 3 + order[1]], c = triangles[i * 3 + order[2]];\n\n\t\t\tint fec = (c == next) ? (next++, 0) : 1;\n\n#if TRACE > 1\n\t\t\tprintf(\"%3d+ | %3d %3d %3d | edge: e%d c%d\\n\", last, a, b, c, fer >> 2, fec);\n#endif\n\n\t\t\tunsigned int code = (fer >> 2) * 2 + fec;\n\n\t\t\tcodes[i / 2] |= (unsigned char)(code << ((i & 1) * 4));\n\n\t\t\tif (fec)\n\t\t\t\t*extra++ = (unsigned char)c;\n\n\t\t\tpushEdgeFifo8(edgefifo, c, b, edgefifooffset);\n\t\t\tpushEdgeFifo8(edgefifo, a, c, edgefifooffset);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// rotate triangles to minimize the need for extra vertices\n\t\t\tint rotation = rotateTriangle(triangles[i * 3 + 0], triangles[i * 3 + 1], triangles[i * 3 + 2]);\n\t\t\tconst int* order = rotations + rotation;\n\n\t\t\tunsigned int a = triangles[i * 3 + order[0]], b = triangles[i * 3 + order[1]], c = triangles[i * 3 + order[2]];\n\n\t\t\t// fe must be continuous: once a vertex is encoded with next, further vertices must also be encoded with next\n\t\t\tint fea = (a == next && b == next + 1 && c == next + 2) ? (next++, 0) : 1;\n\t\t\tint feb = (b == next && c == next + 1) ? (next++, 0) : 1;\n\t\t\tint fec = (c == next) ? (next++, 0) : 1;\n\n\t\t\tassert(fea == 1 || feb == 0);\n\t\t\tassert(feb == 1 || fec == 0);\n\n#if TRACE > 1\n\t\t\tprintf(\"%3d+ | %3d %3d %3d | restart: %d%d%d\\n\", last, a, b, c, fea, feb, fec);\n#endif\n\n\t\t\tunsigned int code = 12 + (fea + feb + fec);\n\n\t\t\tcodes[i / 2] |= (unsigned char)(code << ((i & 1) * 4));\n\n\t\t\tif (fea)\n\t\t\t\t*extra++ = (unsigned char)a;\n\t\t\tif (feb)\n\t\t\t\t*extra++ = (unsigned char)b;\n\t\t\tif (fec)\n\t\t\t\t*extra++ = (unsigned char)c;\n\n\t\t\tpushEdgeFifo8(edgefifo, c, b, edgefifooffset);\n\t\t\tpushEdgeFifo8(edgefifo, a, c, edgefifooffset);\n\t\t}\n\t}\n\n\treturn extra - start;\n}\n\nstatic size_t encodeVertices(unsigned char* ctrl, unsigned char* data, const unsigned int* vertices, size_t vertex_count)\n{\n\t// grouped varint, 2 bit per value to indicate 0/1/2/3 byte deltas, with per-group 4-byte fallback\n\tmemset(ctrl, 0, (vertex_count + 3) / 4);\n\n\tunsigned char* start = data;\n\n\tunsigned int last = ~0u;\n\n\tfor (size_t i = 0; i < vertex_count; i += 4)\n\t{\n\t\tunsigned int gv[4] = {};\n\n\t\tfor (int k = 0; k < 4 && i + k < vertex_count; ++k)\n\t\t{\n\t\t\tunsigned int d = vertices[i + k] - last - 1;\n\t\t\tunsigned int v = (d << 1) ^ (int(d) >> 31);\n\n\t\t\tgv[k] = v;\n\t\t\tlast = vertices[i + k];\n\t\t}\n\n\t\t// if any value needs 4 bytes, or if *all* values need 3 bytes, we use 4 bytes for all values\n\t\t// this allows us to encode most 3-byte deltas with 3 bytes which saves space overall\n\t\tbool use4 = (gv[0] | gv[1] | gv[2] | gv[3]) > 0xffffff || (gv[0] > 0xffff && gv[1] > 0xffff && gv[2] > 0xffff && gv[3] > 0xffff);\n\n\t\tfor (int k = 0; k < 4; ++k)\n\t\t{\n\t\t\tunsigned int v = gv[k];\n\n\t\t\t// 0/1/2/3 bytes per value, or all 4 values use 4 bytes\n\t\t\tint code = use4 ? 3 : (v == 0 ? 0 : (v < 256 ? 1 : (v < 65536 ? 2 : 3)));\n\n\t\t\tif (code > 0)\n\t\t\t\t*data++ = (unsigned char)(v & 0xff);\n\t\t\tif (code > 1)\n\t\t\t\t*data++ = (unsigned char)((v >> 8) & 0xff);\n\t\t\tif (code > 2)\n\t\t\t\t*data++ = (unsigned char)((v >> 16) & 0xff);\n\t\t\tif (use4)\n\t\t\t\t*data++ = (unsigned char)((v >> 24) & 0xff);\n\n\t\t\t// split low and high bits into two nibbles for better packing\n\t\t\tctrl[i / 4] |= ((code & 1) << k) | ((code >> 1) << (k + 4));\n\t\t}\n\t}\n\n\treturn data - start;\n}\n\n#if defined(SIMD_FALLBACK) || (!defined(SIMD_SSE) && !defined(SIMD_NEON))\ninline void writeTriangle(unsigned int* triangles, size_t i, unsigned int fifo)\n{\n\t// output triangle is stored without extra edge vertex (0xcbac => 0xcba)\n\ttriangles[i] = fifo >> 8;\n}\n\ninline void writeTriangle(unsigned char* triangles, size_t i, unsigned int fifo)\n{\n\ttriangles[i * 3 + 0] = (unsigned char)(fifo >> 8);\n\ttriangles[i * 3 + 1] = (unsigned char)(fifo >> 16);\n\ttriangles[i * 3 + 2] = (unsigned char)(fifo >> 24);\n}\n\ntemplate <typename T>\nstatic const unsigned char* decodeTriangles(T* triangles, const unsigned char* codes, const unsigned char* extra, const unsigned char* bound, size_t triangle_count)\n{\n\t// branchlessly read next or extra vertex and advance pointers\n#define NEXT(var, ec) \\\n\te = *extra; \\\n\tunsigned int var = (ec) ? e : next; \\\n\textra += (ec), next += 1 - (ec)\n\n\tunsigned int next = 0;\n\tunsigned int fifo[3] = {}; // two edge fifo entries in one uint: 0xcbac\n\n\tfor (size_t i = 0; i < triangle_count; ++i)\n\t{\n\t\tif (extra > bound)\n\t\t\treturn NULL;\n\n\t\tunsigned int code = (codes[i / 2] >> ((i & 1) * 4)) & 0xF;\n\t\tunsigned int tri;\n\n\t\tif (code < 12)\n\t\t{\n\t\t\t// reuse\n\t\t\tunsigned int edge = fifo[code / 4];\n\t\t\tedge >>= (code << 3) & 16; // shift by 16 if bit 1 is set (odd edge for each triangle)\n\n\t\t\t// 0-1 extra vertices\n\t\t\tunsigned int e;\n\t\t\tNEXT(c, code & 1);\n\n\t\t\t// repack triangle into edge format (0xcbac)\n\t\t\ttri = ((edge & 0xff) << 16) | (edge & 0xff00) | c | (c << 24);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// restart\n\t\t\tint fea = code > 12;\n\t\t\tint feb = code > 13;\n\t\t\tint fec = code > 14;\n\n\t\t\t// 0-3 extra vertices\n\t\t\tunsigned int e;\n\t\t\tNEXT(a, fea);\n\t\t\tNEXT(b, feb);\n\t\t\tNEXT(c, fec);\n\n\t\t\t// repack triangle into edge format (0xcbac)\n\t\t\ttri = c | (a << 8) | (b << 16) | (c << 24);\n\t\t}\n\n\t\twriteTriangle(triangles, i, tri);\n\n\t\tfifo[2] = fifo[1];\n\t\tfifo[1] = fifo[0];\n\t\tfifo[0] = tri;\n\t}\n\n\treturn extra;\n\n#undef NEXT\n}\n\ntemplate <typename V>\nstatic const unsigned char* decodeVertices(V* vertices, const unsigned char* ctrl, const unsigned char* data, const unsigned char* bound, size_t vertex_count)\n{\n\tunsigned int last = ~0u;\n\n\tfor (size_t i = 0; i < vertex_count; i += 4)\n\t{\n\t\tif (data > bound)\n\t\t\treturn NULL;\n\n\t\tunsigned char code4 = ctrl[i / 4];\n\n\t\tfor (int k = 0; k < 4; ++k)\n\t\t{\n\t\t\tint code = ((code4 >> k) & 1) | ((code4 >> (k + 3)) & 2);\n\t\t\tint length = code4 == 0xff ? 4 : code;\n\n\t\t\t// branchlessly read up to 4 bytes\n\t\t\tunsigned int mask = (length == 4) ? ~0u : (1 << (8 * length)) - 1;\n\t\t\tunsigned int v = (data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)) & mask;\n\n\t\t\t// unzigzag + 1\n\t\t\tunsigned int d = (v >> 1) ^ -int(v & 1);\n\t\t\tunsigned int r = last + d + 1;\n\n\t\t\tif (i + k < vertex_count)\n\t\t\t\tvertices[i + k] = V(r);\n\n\t\t\tdata += length;\n\t\t\tlast = r;\n\t\t}\n\t}\n\n\treturn data;\n}\n\nstatic int decodeMeshlet(void* vertices, void* triangles, const unsigned char* codes, const unsigned char* ctrl, const unsigned char* data, const unsigned char* bound, size_t vertex_count, size_t triangle_count, size_t vertex_size, size_t triangle_size)\n{\n\tif (vertex_size == 4)\n\t\tdata = decodeVertices(static_cast<unsigned int*>(vertices), ctrl, data, bound, vertex_count);\n\telse\n\t\tdata = decodeVertices(static_cast<unsigned short*>(vertices), ctrl, data, bound, vertex_count);\n\tif (!data)\n\t\treturn -2;\n\n\tif (triangle_size == 4)\n\t\tdata = decodeTriangles(static_cast<unsigned int*>(triangles), codes, data, bound, triangle_count);\n\telse\n\t\tdata = decodeTriangles(static_cast<unsigned char*>(triangles), codes, data, bound, triangle_count);\n\tif (!data)\n\t\treturn -2;\n\n\treturn (data == bound) ? 0 : -3;\n}\n#endif\n\n#if defined(SIMD_SSE) || defined(SIMD_NEON)\n// SIMD state is stored in a single 16b register as follows:\n// 0..5: 6 next extra bytes\n// 6..14: 9 bytes = 3 triangles worth of index data\n// 15: 'next' byte\n\n// upon reading each triangle pair we need to transform this state such that the 9 bytes with triangle data contain the newly decoded triangles,\n// which is a permutation of original state modulo per-element additions\n// this transform can be chained to decode second triangle from original state; we create tables for 256 combinations of two 4-bit triangle codes\n// the actual decoding becomes shuffle+add per triangle pair, plus management of extra bytes\nstatic unsigned char kDecodeTableMasks[256][16];\nstatic unsigned char kDecodeTableExtra[256];\n\n// for SIMD vertex decoding we need to unpack 4 values with 0-4 bytes in each\n// this can be done with a single control-dependent shuffle per group\nstatic unsigned char kDecodeTableVerts[256][16];\nstatic unsigned char kDecodeTableLength[256];\n\nstatic bool decodeBuildTables()\n{\n#define NEXT(var, ec) \\\n\tshuf[var] = (ec) ? (unsigned char)extra : 15; \\\n\tnext[var] = (ec) ? 0 : (unsigned char)nextoff; \\\n\textra += (ec), nextoff += 1 - (ec)\n\n\t// check for SSE4.1 support if we have a fallback path\n#if defined(SIMD_SSE) && defined(SIMD_FALLBACK)\n\tint cpuinfo[4] = {};\n#ifdef _MSC_VER\n\t__cpuid(cpuinfo, 1);\n#else\n\t__cpuid(1, cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]);\n#endif\n\t// bit 19 = SSE4.1\n\tif ((cpuinfo[2] & (1 << 19)) == 0)\n\t\treturn false;\n#endif\n\n\t// fill triangle decoding tables for each combination of two triangle codes\n\tfor (int code = 0; code < 256; ++code)\n\t{\n\t\tunsigned char shuf[16] = {};\n\t\tunsigned char next[16] = {};\n\t\tint extra = 0;\n\t\tint nextoff = 0;\n\n\t\t// state 0..5 will be refilled every iteration, so we ignore that\n\t\t// state 6..8 will always contain the last decoded triangle because every triangle shifts fifo equally, so we can decode it independently\n\t\tshuf[6] = 12;\n\t\tshuf[7] = 13;\n\t\tshuf[8] = 14;\n\n\t\t// state 15 will contain next (potentially incremented a few times)\n\t\tshuf[15] = 15;\n\n\t\t// state 9..11 will contain the first decoded triangle (tri0), which can refer to extra/next and the original triangle history\n\t\t// state 12..14 will contain the second decoded triangle (tri1); when decoding edge reuse, we need to handle edge 0/1 specially as it was just decoded earlier\n\t\tfor (int k = 0; k < 2; ++k)\n\t\t{\n\t\t\tint tri = (code >> (k * 4)) & 0xf;\n\n\t\t\tif (tri < 12)\n\t\t\t{\n\t\t\t\tif (k == 1 && tri / 4 == 0)\n\t\t\t\t{\n\t\t\t\t\t// we need to decode one of two edges from the triangle we just decoded earlier\n\t\t\t\t\t// for that we simply need to copy shuf/next values for the two decoded indices\n\t\t\t\t\tshuf[9 + k * 3] = shuf[9 + ((tri & 2) ? 2 : 0)];\n\t\t\t\t\tnext[9 + k * 3] = next[9 + ((tri & 2) ? 2 : 0)];\n\n\t\t\t\t\tshuf[10 + k * 3] = shuf[9 + ((tri & 2) ? 1 : 2)];\n\t\t\t\t\tnext[10 + k * 3] = next[9 + ((tri & 2) ? 1 : 2)];\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// reuse: edge comes from the history based on edge index\n\t\t\t\t\t// note: we reuse with an offset because last triangle in the original history was consumed by tri0\n\t\t\t\t\tint trioff = 6 + k * 3 + (2 - tri / 4) * 3;\n\n\t\t\t\t\t// edge cb or ac\n\t\t\t\t\tshuf[9 + k * 3] = (unsigned char)(trioff + ((tri & 2) ? 2 : 0));\n\t\t\t\t\tshuf[10 + k * 3] = (unsigned char)(trioff + ((tri & 2) ? 1 : 2));\n\t\t\t\t}\n\n\t\t\t\t// third vertex is either next or comes from extra\n\t\t\t\tNEXT(11 + k * 3, tri & 1);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// restart: three vertices, each comes from next or extra\n\t\t\t\tint fea = tri > 12;\n\t\t\t\tint feb = tri > 13;\n\t\t\t\tint fec = tri > 14;\n\n\t\t\t\tNEXT(9 + k * 3, fea);\n\t\t\t\tNEXT(10 + k * 3, feb);\n\t\t\t\tNEXT(11 + k * 3, fec);\n\t\t\t}\n\t\t}\n\n\t\t// next needs to advance\n\t\tnext[15] = (unsigned char)nextoff;\n\n\t\t// next[0..8] = 0 trivially (never written to); next[9] must also be 0 because nextoff is 0 initially\n\t\t// shuf[0..5] is not used, which allows us to pack next[10..15] + shuf[6..15] into a single 16-byte entry\n\t\tassert(next[9] == 0);\n\t\tmemcpy(&kDecodeTableMasks[code][0], &next[10], 6);\n\t\tmemcpy(&kDecodeTableMasks[code][6], &shuf[6], 10);\n\t\tkDecodeTableExtra[code] = (unsigned char)extra;\n\t}\n\n\t// fill vertex decoding tables for each combination of four vertex references\n\tfor (unsigned int i = 0; i < 256; ++i)\n\t{\n\t\tunsigned char shuf[16] = {};\n\t\tint offset = 0;\n\n\t\tfor (int k = 0; k < 4; ++k)\n\t\t{\n\t\t\tint code = ((i >> k) & 1) | ((i >> (k + 3)) & 2);\n\t\t\tint length = i == 0xff ? 4 : code; // 0/1/2/3 bytes, or all 4 bytes if code==0xff\n\n\t\t\tshuf[k * 4 + 0] = (length > 0) ? (unsigned char)(offset + 0) : 0x80;\n\t\t\tshuf[k * 4 + 1] = (length > 1) ? (unsigned char)(offset + 1) : 0x80;\n\t\t\tshuf[k * 4 + 2] = (length > 2) ? (unsigned char)(offset + 2) : 0x80;\n\t\t\tshuf[k * 4 + 3] = (length > 3) ? (unsigned char)(offset + 3) : 0x80;\n\n\t\t\toffset += length;\n\t\t}\n\n\t\tmemcpy(kDecodeTableVerts[i], shuf, sizeof(shuf));\n\t\tkDecodeTableLength[i] = (unsigned char)offset;\n\t}\n\n\treturn true;\n\n#undef NEXT\n}\n\nstatic bool gDecodeTablesInitialized = decodeBuildTables();\n#endif\n\n#if defined(SIMD_SSE)\nSIMD_TARGET\ninline __m128i decodeTriangleGroup(__m128i state, unsigned char code, const unsigned char*& extra)\n{\n\t__m128i shuf = _mm_loadu_si128(reinterpret_cast<const __m128i*>(kDecodeTableMasks[code]));\n\t__m128i next = _mm_slli_si128(shuf, 10);\n\n\t// patch first 6 bytes with current extra and roll state forward\n\t__m128i ext = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(extra));\n\tstate = _mm_blend_epi16(state, ext, 7);\n\tstate = _mm_add_epi8(_mm_shuffle_epi8(state, shuf), next);\n\n\textra += kDecodeTableExtra[code];\n\n\treturn state;\n}\n\nSIMD_TARGET\ninline __m128i decodeVertexGroup(__m128i last, unsigned char code, const unsigned char*& data)\n{\n\t__m128i word = _mm_loadu_si128(reinterpret_cast<const __m128i*>(data));\n\t__m128i shuf = _mm_loadu_si128(reinterpret_cast<const __m128i*>(kDecodeTableVerts[code]));\n\n\t__m128i v = _mm_shuffle_epi8(word, shuf);\n\n\t// unzigzag+1\n\t__m128i xl = _mm_sub_epi32(_mm_setzero_si128(), _mm_and_si128(v, _mm_set1_epi32(1)));\n\t__m128i xr = _mm_srli_epi32(v, 1);\n\t__m128i x = _mm_add_epi32(_mm_xor_si128(xl, xr), _mm_set1_epi32(1));\n\n\t// prefix sum\n\tx = _mm_add_epi32(x, _mm_slli_si128(x, 8));\n\tx = _mm_add_epi32(x, _mm_slli_si128(x, 4));\n\tx = _mm_add_epi32(x, _mm_shuffle_epi32(last, 0xff));\n\n\tdata += kDecodeTableLength[code];\n\n\treturn x;\n}\n#endif\n\n#if defined(SIMD_NEON)\nSIMD_TARGET\ninline uint8x16_t decodeTriangleGroup(uint8x16_t state, unsigned char code, const unsigned char*& extra)\n{\n\tuint8x16_t shuf = vld1q_u8(kDecodeTableMasks[code]);\n\tuint8x16_t next = vextq_u8(vdupq_n_u8(0), shuf, 6);\n\n\t// patch first 6 bytes with current extra and roll state forward\n\tuint8x8_t extl = vld1_u8(extra);\n\tuint8x16_t ext = vcombine_u8(extl, vdup_n_u8(0));\n\tstate = vbslq_u8(vcombine_u8(vcreate_u8(0xffffffffffffull), vdup_n_u8(0)), ext, state);\n\tstate = vaddq_u8(vqtbl1q_u8(state, shuf), next);\n\n\textra += kDecodeTableExtra[code];\n\n\treturn state;\n}\n\nSIMD_TARGET\ninline uint32x4_t decodeVertexGroup(uint32x4_t last, unsigned char code, const unsigned char*& data)\n{\n\tuint8x16_t word = vld1q_u8(data);\n\tuint8x16_t shuf = vld1q_u8(kDecodeTableVerts[code]);\n\n\tuint32x4_t v = vreinterpretq_u32_u8(vqtbl1q_u8(word, shuf));\n\n\t// unzigzag+1\n\tuint32x4_t xl = vsubq_u32(vdupq_n_u32(0), vandq_u32(v, vdupq_n_u32(1)));\n\tuint32x4_t xr = vshrq_n_u32(v, 1);\n\tuint32x4_t x = vaddq_u32(veorq_u32(xl, xr), vdupq_n_u32(1));\n\n\t// prefix sum\n\tx = vaddq_u32(x, vextq_u32(vdupq_n_u32(0), x, 2));\n\tx = vaddq_u32(x, vextq_u32(vdupq_n_u32(0), x, 3));\n\tx = vaddq_u32(x, vdupq_n_u32(vgetq_lane_u32(last, 3)));\n\n\tdata += kDecodeTableLength[code];\n\n\treturn x;\n}\n#endif\n\n#if defined(SIMD_SSE)\n#ifdef __GNUC__\ntypedef int __attribute__((aligned(1))) unaligned_int;\n#else\ntypedef int unaligned_int;\n#endif\n#endif\n\n#if defined(SIMD_SSE) || defined(SIMD_NEON)\nSIMD_TARGET\nstatic const unsigned char* decodeTrianglesSimd(unsigned int* triangles, const unsigned char* codes, const unsigned char* extra, const unsigned char* bound, size_t triangle_count)\n{\n#if defined(SIMD_SSE)\n\t__m128i repack = _mm_setr_epi8(9, 10, 11, -1, 12, 13, 14, -1, 0, 0, 0, 0, 0, 0, 0, 0);\n\t__m128i state = _mm_setzero_si128();\n#elif defined(SIMD_NEON)\n\tuint8x8_t repack = vcreate_u8(0xff0e0d0cff0b0a09ull);\n\tuint8x16_t state = vdupq_n_u8(0);\n#endif\n\n\tsize_t groups = triangle_count / 2;\n\n\t// process all complete groups\n\tfor (size_t i = 0; i < groups; ++i)\n\t{\n\t\tunsigned char code = *codes++;\n\n\t\tif (extra > bound)\n\t\t\treturn NULL;\n\n\t\tstate = decodeTriangleGroup(state, code, extra);\n\n\t\t// write 6 bytes of new triangle data into output, formatted as 8 bytes with 0 padding\n#if defined(SIMD_SSE)\n\t\t__m128i r = _mm_shuffle_epi8(state, repack);\n\t\t_mm_storel_epi64(reinterpret_cast<__m128i*>(&triangles[i * 2]), r);\n#elif defined(SIMD_NEON)\n\t\tuint32x2_t r = vreinterpret_u32_u8(vqtbl1_u8(state, repack));\n\t\tvst1_u32(&triangles[i * 2], r);\n#endif\n\t}\n\n\t// process a 1 triangle tail; to maintain the memory safety guarantee we have to write a 32-bit element\n\tif (triangle_count & 1)\n\t{\n\t\tunsigned char code = *codes++;\n\n\t\tif (extra > bound)\n\t\t\treturn NULL;\n\n\t\tstate = decodeTriangleGroup(state, code, extra);\n\n\t\tunsigned int* tail = &triangles[triangle_count & ~1u];\n\n#if defined(SIMD_SSE)\n\t\t__m128i r = _mm_shuffle_epi8(state, repack);\n\t\t*tail = unsigned(_mm_cvtsi128_si32(r));\n#elif defined(SIMD_NEON)\n\t\tuint32x2_t r = vreinterpret_u32_u8(vqtbl1_u8(state, repack));\n\t\tvst1_lane_u32(tail, r, 0);\n#endif\n\t}\n\n\treturn extra;\n}\n\nSIMD_TARGET\nstatic const unsigned char* decodeTrianglesSimd(unsigned char* triangles, const unsigned char* codes, const unsigned char* extra, const unsigned char* bound, size_t triangle_count)\n{\n#if defined(SIMD_SSE)\n\t__m128i state = _mm_setzero_si128();\n#elif defined(SIMD_NEON)\n\tuint8x16_t state = vdupq_n_u8(0);\n#endif\n\n\t// because the output buffer is guaranteed to have 32-bit aligned size available, we can optimize writes and tail processing\n\t// instead of processing triangles 2 at a time, we process 2 *pairs* at a time (12-byte write) followed by a tail pair, if present\n\t// if the number of triangles mod 4 is 3, we'd normally need to write 12k+9 bytes, but we can instead overwrite up to 3 bytes in the main loop\n\tsize_t groups = (triangle_count + 1) / 4;\n\n\t// process all complete groups\n\tfor (size_t i = 0; i < groups; ++i)\n\t{\n\t\tunsigned char code0 = *codes++;\n\t\tunsigned char code1 = *codes++;\n\n\t\t// each triangle pair reads <=6 bytes from extra, so two pairs need <=12 bytes and gap guarantees 16 byte of overread\n\t\tif (extra > bound)\n\t\t\treturn NULL;\n\n\t\tstate = decodeTriangleGroup(state, code0, extra);\n\n\t\t// write first decoded triangle and first index of second decoded triangle\n#if defined(SIMD_SSE)\n\t\t__m128i r0 = _mm_srli_si128(state, 9);\n\t\t*reinterpret_cast<unaligned_int*>(&triangles[i * 12]) = _mm_cvtsi128_si32(r0);\n#elif defined(SIMD_NEON)\n\t\tuint8x16_t r0 = vextq_u8(state, vdupq_n_u8(0), 9);\n\t\tvst1q_lane_u32(reinterpret_cast<unsigned int*>(&triangles[i * 12]), vreinterpretq_u32_u8(r0), 0);\n#endif\n\n\t\tstate = decodeTriangleGroup(state, code1, extra);\n\n\t\t// write last two indices of second decoded triangle that we didn't write above plus two new ones\n\t\t// note that the second decoded triangle has shifted down to 6-8 bytes, hence shift by 7\n#if defined(SIMD_SSE)\n\t\t__m128i r1 = _mm_srli_si128(state, 7);\n\t\t_mm_storel_epi64(reinterpret_cast<__m128i*>(&triangles[i * 12 + 4]), r1);\n#elif defined(SIMD_NEON)\n\t\tuint8x16_t r1 = vextq_u8(state, vdupq_n_u8(0), 7);\n\t\tvst1_u8(&triangles[i * 12 + 4], vget_low_u8(r1));\n#endif\n\t}\n\n\t// process a 1-2 triangle tail; to maintain the memory safety guarantee we have to write 1-2 32-bit elements\n\tif (groups * 4 < triangle_count)\n\t{\n\t\tunsigned char code = *codes++;\n\n\t\tif (extra > bound)\n\t\t\treturn NULL;\n\n\t\tstate = decodeTriangleGroup(state, code, extra);\n\n\t\tunsigned char* tail = &triangles[(triangle_count & ~3u) * 3];\n\n#if defined(SIMD_SSE)\n\t\t__m128i r = _mm_srli_si128(state, 9);\n\n\t\t*reinterpret_cast<unaligned_int*>(tail) = _mm_cvtsi128_si32(r);\n\t\tif ((triangle_count & 3) > 1)\n\t\t\t*reinterpret_cast<unaligned_int*>(tail + 4) = _mm_extract_epi32(r, 1);\n#elif defined(SIMD_NEON)\n\t\tuint8x16_t r = vextq_u8(state, vdupq_n_u8(0), 9);\n\n\t\tvst1q_lane_u32(reinterpret_cast<unsigned int*>(tail), vreinterpretq_u32_u8(r), 0);\n\t\tif ((triangle_count & 3) > 1)\n\t\t\tvst1q_lane_u32(reinterpret_cast<unsigned int*>(tail + 4), vreinterpretq_u32_u8(r), 1);\n#endif\n\t}\n\n\treturn extra;\n}\n\nSIMD_TARGET\nstatic const unsigned char* decodeVerticesSimd(unsigned int* vertices, const unsigned char* ctrl, const unsigned char* data, const unsigned char* bound, size_t vertex_count)\n{\n#if defined(SIMD_SSE)\n\t__m128i last = _mm_set1_epi32(-1);\n#elif defined(SIMD_NEON)\n\tuint32x4_t last = vdupq_n_u32(~0u);\n#endif\n\n\tsize_t groups = vertex_count / 4;\n\n\t// process all complete groups\n\tfor (size_t i = 0; i < groups; ++i)\n\t{\n\t\tunsigned char code = *ctrl++;\n\t\tif (data > bound)\n\t\t\treturn NULL;\n\n\t\tlast = decodeVertexGroup(last, code, data);\n\n#if defined(SIMD_SSE)\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(&vertices[i * 4]), last);\n#elif defined(SIMD_NEON)\n\t\tvst1q_u32(&vertices[i * 4], last);\n#endif\n\t}\n\n\t// process a 1-3 vertex tail; to maintain the memory safety guarantee we have to write individual elements\n\tif (vertex_count & 3)\n\t{\n\t\tunsigned char code = *ctrl++;\n\n\t\tif (data > bound)\n\t\t\treturn NULL;\n\n\t\tlast = decodeVertexGroup(last, code, data);\n\n\t\tunsigned int* tail = &vertices[vertex_count & ~3u];\n\n#if defined(SIMD_SSE)\n\t\ttail[0] = _mm_cvtsi128_si32(last);\n\t\tif ((vertex_count & 3) > 1)\n\t\t\ttail[1] = _mm_extract_epi32(last, 1);\n\t\tif ((vertex_count & 3) > 2)\n\t\t\ttail[2] = _mm_extract_epi32(last, 2);\n#elif defined(SIMD_NEON)\n\t\tvst1q_lane_u32(&tail[0], last, 0);\n\t\tif ((vertex_count & 3) > 1)\n\t\t\tvst1q_lane_u32(&tail[1], last, 1);\n\t\tif ((vertex_count & 3) > 2)\n\t\t\tvst1q_lane_u32(&tail[2], last, 2);\n#endif\n\t}\n\n\treturn data;\n}\n\nSIMD_TARGET\nstatic const unsigned char* decodeVerticesSimd(unsigned short* vertices, const unsigned char* ctrl, const unsigned char* data, const unsigned char* bound, size_t vertex_count)\n{\n#if defined(SIMD_SSE)\n\t__m128i repack = _mm_setr_epi8(0, 1, 4, 5, 8, 9, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0);\n\t__m128i last = _mm_set1_epi32(-1);\n#elif defined(SIMD_NEON)\n\tuint32x4_t last = vdupq_n_u32(~0u);\n#endif\n\n\t// because the output buffer is guaranteed to have 32-bit aligned size available, we can simplify tail processing\n\t// if the number of vertices mod 4 is 3, we'd normally need to write 8+6 bytes, but we can instead overwrite up to 2 bytes in the main loop\n\tsize_t groups = (vertex_count + 1) / 4;\n\n\t// process all complete groups\n\tfor (size_t i = 0; i < groups; ++i)\n\t{\n\t\tunsigned char code = *ctrl++;\n\n\t\tif (data > bound)\n\t\t\treturn NULL;\n\n\t\tlast = decodeVertexGroup(last, code, data);\n\n#if defined(SIMD_SSE)\n\t\t__m128i r = _mm_shuffle_epi8(last, repack);\n\t\t_mm_storel_epi64(reinterpret_cast<__m128i*>(&vertices[i * 4]), r);\n#elif defined(SIMD_NEON)\n\t\tuint16x4_t r = vmovn_u32(last);\n\t\tvst1_u16(&vertices[i * 4], r);\n#endif\n\t}\n\n\t// process a 1-2 vertex tail; to maintain the memory safety guarantee we have to write a 32-bit element\n\tif (groups * 4 < vertex_count)\n\t{\n\t\tunsigned char code = *ctrl++;\n\n\t\tif (data > bound)\n\t\t\treturn NULL;\n\n\t\tlast = decodeVertexGroup(last, code, data);\n\n\t\tunsigned short* tail = &vertices[vertex_count & ~3u];\n\n#if defined(SIMD_SSE)\n\t\t__m128i r = _mm_shufflelo_epi16(last, 8);\n\t\t*reinterpret_cast<unaligned_int*>(tail) = _mm_cvtsi128_si32(r);\n#elif defined(SIMD_NEON)\n\t\tuint16x4_t r = vmovn_u32(last);\n\t\tvst1_lane_u32(reinterpret_cast<unsigned int*>(tail), vreinterpret_u32_u16(r), 0);\n#endif\n\t}\n\n\treturn data;\n}\n\ntemplate <int Raw>\nSIMD_TARGET SIMD_FLATTEN static int\ndecodeMeshletSimd(void* vertices, void* triangles, const unsigned char* codes, const unsigned char* ctrl, const unsigned char* data, const unsigned char* bound, size_t vertex_count, size_t triangle_count, size_t vertex_size, size_t triangle_size)\n{\n\tassert(gDecodeTablesInitialized);\n\t(void)gDecodeTablesInitialized;\n\n#ifdef __clang__\n\t// data is guaranteed to be non-null initially; if decode loops never hit bounds errors, it remains non-null\n\t__builtin_assume(data);\n#endif\n\n\t// decodes 4 vertices at a time with tail processing; writes up to align(vertex_size * vertex_count, 4)\n\t// raw decoding skips tail processing by rounding up vertex count; it's safe because output buffer is guaranteed to have extra space, and tail control data is 0\n\tif (vertex_size == 4 || Raw)\n\t\tdata = decodeVerticesSimd(static_cast<unsigned int*>(vertices), ctrl, data, bound, Raw ? (vertex_count + 3) & ~3 : vertex_count);\n\telse\n\t\tdata = decodeVerticesSimd(static_cast<unsigned short*>(vertices), ctrl, data, bound, vertex_count);\n\tif (!data)\n\t\treturn -2;\n\n\t// decodes 2/4 triangles at a time with tail processing; writes up to align(triangle_size * triangle_count, 4)\n\t// raw decoding skips tail processing by rounding up triangle count; it's safe because output buffer is guaranteed to have extra space, and tail code data is 0\n\tif (triangle_size == 4 || Raw)\n\t\tdata = decodeTrianglesSimd(static_cast<unsigned int*>(triangles), codes, data, bound, Raw ? (triangle_count + 1) & ~1 : triangle_count);\n\telse\n\t\tdata = decodeTrianglesSimd(static_cast<unsigned char*>(triangles), codes, data, bound, triangle_count);\n\tif (!data)\n\t\treturn -2;\n\n\treturn (data == bound) ? 0 : -3;\n}\n#endif\n\n} // namespace meshopt\n\nsize_t meshopt_encodeMeshletBound(size_t max_vertices, size_t max_triangles)\n{\n\tsize_t codes_size = (max_triangles + 1) / 2;\n\tsize_t extra_size = max_triangles * 3;\n\n\tsize_t ctrl_size = (max_vertices + 3) / 4;\n\tsize_t data_size = (max_vertices + 3) / 4 * 16; // worst case: 16 bytes per vertex group\n\n\tsize_t gap_size = (codes_size + ctrl_size < 16) ? 16 - (codes_size + ctrl_size) : 0;\n\n\treturn codes_size + extra_size + ctrl_size + data_size + gap_size;\n}\n\nsize_t meshopt_encodeMeshlet(unsigned char* buffer, size_t buffer_size, const unsigned int* vertices, size_t vertex_count, const unsigned char* triangles, size_t triangle_count)\n{\n\tusing namespace meshopt;\n\n\tassert(triangle_count <= 256 && vertex_count <= 256);\n\n\t// 4 bits per triangle + up to three bytes of extra data\n\tunsigned char codes[256 / 2];\n\tunsigned char extra[256 * 3];\n\tsize_t codes_size = (triangle_count + 1) / 2;\n\tsize_t extra_size = encodeTriangles(codes, extra, triangles, triangle_count);\n\tassert(extra_size <= sizeof(extra));\n\n\t// 2 bits per vertex + up to 4 bytes of actual data\n\tunsigned char ctrl[256 / 4];\n\tunsigned char data[256 * 4];\n\tsize_t ctrl_size = (vertex_count + 3) / 4;\n\tsize_t data_size = encodeVertices(ctrl, data, vertices, vertex_count);\n\tassert(data_size <= sizeof(data));\n\n\t// we need to ensure that up to 16 bytes after extra+data are available for SIMD decoding\n\t// to minimize overhead, we place fixed-size codes+control at the end of the buffer\n\tsize_t gap_size = (codes_size + ctrl_size < 16) ? 16 - (codes_size + ctrl_size) : 0;\n\n\tsize_t result = codes_size + extra_size + ctrl_size + data_size + gap_size;\n\n\tif (result > buffer_size)\n\t\treturn 0;\n\n\t// variable-size data first\n\tmemcpy(buffer, data, data_size);\n\tbuffer += data_size;\n\tmemcpy(buffer, extra, extra_size);\n\tbuffer += extra_size;\n\n\t// gap (for accelerated decoding) separates variable-size and fixed-size data\n\tmemset(buffer, 0, gap_size);\n\tbuffer += gap_size;\n\n\t// fixed-size data last; it can be located from buffer end during decoding\n\tmemcpy(buffer, ctrl, ctrl_size);\n\tbuffer += ctrl_size;\n\tmemcpy(buffer, codes, codes_size);\n\tbuffer += codes_size;\n\n#if TRACE > 1\n\tprintf(\"extra:\");\n\tfor (size_t i = 0; i < extra_size; ++i)\n\t\tprintf(\" %d\", extra[i]);\n\tprintf(\"\\n\");\n\n\tunsigned int minv = ~0u;\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tminv = minv < vertices[i] ? minv : vertices[i];\n\n\tprintf(\"vertices: [%d+]\", minv);\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tprintf(\" %d\", vertices[i] - minv);\n\tprintf(\"\\n\");\n#endif\n\n#if TRACE\n\tprintf(\"stats: %d vertices, %d triangles => %d bytes (triangles: %d codes, %d extra; vertices: %d control, %d data; %d gap)\\n\",\n\t    int(vertex_count), int(triangle_count), int(result),\n\t    int(codes_size), int(extra_size), int(ctrl_size), int(data_size), int(gap_size));\n#endif\n\n\treturn result;\n}\n\nint meshopt_decodeMeshlet(void* vertices, size_t vertex_count, size_t vertex_size, void* triangles, size_t triangle_count, size_t triangle_size, const unsigned char* buffer, size_t buffer_size)\n{\n\tusing namespace meshopt;\n\n\tassert(triangle_count <= 256 && vertex_count <= 256);\n\tassert(vertex_size == 4 || vertex_size == 2);\n\tassert(triangle_size == 4 || triangle_size == 3);\n\n\t// layout must match encoding\n\tsize_t codes_size = (triangle_count + 1) / 2;\n\tsize_t ctrl_size = (vertex_count + 3) / 4;\n\tsize_t gap_size = (codes_size + ctrl_size < 16) ? 16 - (codes_size + ctrl_size) : 0;\n\n\tif (buffer_size < codes_size + ctrl_size + gap_size)\n\t\treturn -2;\n\n\tconst unsigned char* end = buffer + buffer_size;\n\tconst unsigned char* codes = end - codes_size;\n\tconst unsigned char* ctrl = codes - ctrl_size;\n\tconst unsigned char* data = buffer;\n\n\t// gap ensures we have at least 16 bytes available after bound; this allows SIMD decoders to over-read safely\n\tconst unsigned char* bound = ctrl - gap_size;\n\tassert(bound >= buffer && bound + 16 <= buffer + buffer_size);\n\n#if defined(SIMD_FALLBACK)\n\treturn (gDecodeTablesInitialized ? decodeMeshletSimd<0> : decodeMeshlet)(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, vertex_size, triangle_size);\n#elif defined(SIMD_SSE) || defined(SIMD_NEON)\n\treturn decodeMeshletSimd<0>(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, vertex_size, triangle_size);\n#else\n\treturn decodeMeshlet(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, vertex_size, triangle_size);\n#endif\n}\n\nint meshopt_decodeMeshletRaw(unsigned int* vertices, size_t vertex_count, unsigned int* triangles, size_t triangle_count, const unsigned char* buffer, size_t buffer_size)\n{\n\tusing namespace meshopt;\n\n\tassert(triangle_count <= 256 && vertex_count <= 256);\n\n\t// layout must match encoding\n\tsize_t codes_size = (triangle_count + 1) / 2;\n\tsize_t ctrl_size = (vertex_count + 3) / 4;\n\tsize_t gap_size = (codes_size + ctrl_size < 16) ? 16 - (codes_size + ctrl_size) : 0;\n\n\tif (buffer_size < codes_size + ctrl_size + gap_size)\n\t\treturn -2;\n\n\tconst unsigned char* end = buffer + buffer_size;\n\tconst unsigned char* codes = end - codes_size;\n\tconst unsigned char* ctrl = codes - ctrl_size;\n\tconst unsigned char* data = buffer;\n\n\t// gap ensures we have at least 16 bytes available after bound; this allows SIMD decoders to over-read safely\n\tconst unsigned char* bound = ctrl - gap_size;\n\tassert(bound >= buffer && bound + 16 <= buffer + buffer_size);\n\n#if defined(SIMD_FALLBACK)\n\treturn (gDecodeTablesInitialized ? decodeMeshletSimd<1> : decodeMeshlet)(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, 4, 4);\n#elif defined(SIMD_SSE) || defined(SIMD_NEON)\n\treturn decodeMeshletSimd<1>(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, 4, 4);\n#else\n\treturn decodeMeshlet(vertices, triangles, codes, ctrl, data, bound, vertex_count, triangle_count, 4, 4);\n#endif\n}\n\n#undef SIMD_SSE\n#undef SIMD_NEON\n#undef SIMD_FALLBACK\n#undef SIMD_FLATTEN\n#undef SIMD_TARGET\n"
  },
  {
    "path": "src/meshletutils.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <float.h>\n#include <math.h>\n#include <string.h>\n\n// This work is based on:\n// Matthaeus Chajdas. GeometryFX 1.2 - Cluster Culling. 2016\n// Jack Ritter. An Efficient Bounding Sphere. 1990\n// Thomas Larsson. Fast and Tight Fitting Bounding Spheres. 2008\nnamespace meshopt\n{\n\n// This must be <= 256 since meshlet indices are stored as bytes\nconst size_t kMeshletMaxVertices = 256;\n\n// A reasonable limit is around 2*max_vertices or less\nconst size_t kMeshletMaxTriangles = 512;\n\nstatic void computeBoundingSphere(float result[4], const float* points, size_t count, size_t points_stride, const float* radii, size_t radii_stride, size_t axis_count, const unsigned int* indices = NULL)\n{\n\tstatic const float axes[7][3] = {\n\t    // X, Y, Z\n\t    {1, 0, 0},\n\t    {0, 1, 0},\n\t    {0, 0, 1},\n\n\t    // XYZ, -XYZ, X-YZ, XY-Z; normalized to unit length\n\t    {0.57735026f, 0.57735026f, 0.57735026f},\n\t    {-0.57735026f, 0.57735026f, 0.57735026f},\n\t    {0.57735026f, -0.57735026f, 0.57735026f},\n\t    {0.57735026f, 0.57735026f, -0.57735026f},\n\t};\n\n\tassert(count > 0);\n\tassert(axis_count <= sizeof(axes) / sizeof(axes[0]));\n\n\tsize_t points_stride_float = points_stride / sizeof(float);\n\tsize_t radii_stride_float = radii_stride / sizeof(float);\n\n\t// find extremum points along all axes; for each axis we get a pair of points with min/max coordinates\n\tunsigned int pmin[7], pmax[7];\n\tfloat tmin[7], tmax[7];\n\n\tfor (size_t axis = 0; axis < axis_count; ++axis)\n\t{\n\t\tpmin[axis] = pmax[axis] = 0;\n\t\ttmin[axis] = FLT_MAX;\n\t\ttmax[axis] = -FLT_MAX;\n\t}\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned int v = indices ? indices[i] : unsigned(i);\n\t\tconst float* p = points + v * points_stride_float;\n\t\tfloat r = radii[v * radii_stride_float];\n\n\t\tfor (size_t axis = 0; axis < axis_count; ++axis)\n\t\t{\n\t\t\tconst float* ax = axes[axis];\n\n\t\t\tfloat tp = ax[0] * p[0] + ax[1] * p[1] + ax[2] * p[2];\n\t\t\tfloat tpmin = tp - r, tpmax = tp + r;\n\n\t\t\tpmin[axis] = (tpmin < tmin[axis]) ? v : pmin[axis];\n\t\t\tpmax[axis] = (tpmax > tmax[axis]) ? v : pmax[axis];\n\t\t\ttmin[axis] = (tpmin < tmin[axis]) ? tpmin : tmin[axis];\n\t\t\ttmax[axis] = (tpmax > tmax[axis]) ? tpmax : tmax[axis];\n\t\t}\n\t}\n\n\t// find the pair of points with largest distance\n\tsize_t paxis = 0;\n\tfloat paxisdr = 0;\n\n\tfor (size_t axis = 0; axis < axis_count; ++axis)\n\t{\n\t\tconst float* p1 = points + pmin[axis] * points_stride_float;\n\t\tconst float* p2 = points + pmax[axis] * points_stride_float;\n\t\tfloat r1 = radii[pmin[axis] * radii_stride_float];\n\t\tfloat r2 = radii[pmax[axis] * radii_stride_float];\n\n\t\tfloat d2 = (p2[0] - p1[0]) * (p2[0] - p1[0]) + (p2[1] - p1[1]) * (p2[1] - p1[1]) + (p2[2] - p1[2]) * (p2[2] - p1[2]);\n\t\tfloat dr = sqrtf(d2) + r1 + r2;\n\n\t\tif (dr > paxisdr)\n\t\t{\n\t\t\tpaxisdr = dr;\n\t\t\tpaxis = axis;\n\t\t}\n\t}\n\n\t// use the longest segment as the initial sphere diameter\n\tconst float* p1 = points + pmin[paxis] * points_stride_float;\n\tconst float* p2 = points + pmax[paxis] * points_stride_float;\n\tfloat r1 = radii[pmin[paxis] * radii_stride_float];\n\tfloat r2 = radii[pmax[paxis] * radii_stride_float];\n\n\tfloat paxisd = sqrtf((p2[0] - p1[0]) * (p2[0] - p1[0]) + (p2[1] - p1[1]) * (p2[1] - p1[1]) + (p2[2] - p1[2]) * (p2[2] - p1[2]));\n\tfloat paxisk = paxisd > 0 ? (paxisd + r2 - r1) / (2 * paxisd) : 0.f;\n\n\tfloat center[3] = {p1[0] + (p2[0] - p1[0]) * paxisk, p1[1] + (p2[1] - p1[1]) * paxisk, p1[2] + (p2[2] - p1[2]) * paxisk};\n\tfloat radius = paxisdr / 2;\n\n\t// iteratively adjust the sphere up until all points fit\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned int v = indices ? indices[i] : unsigned(i);\n\t\tconst float* p = points + v * points_stride_float;\n\t\tfloat r = radii[v * radii_stride_float];\n\n\t\tfloat d2 = (p[0] - center[0]) * (p[0] - center[0]) + (p[1] - center[1]) * (p[1] - center[1]) + (p[2] - center[2]) * (p[2] - center[2]);\n\t\tfloat d = sqrtf(d2);\n\n\t\tif (d + r > radius)\n\t\t{\n\t\t\tfloat k = d > 0 ? (d + r - radius) / (2 * d) : 0.f;\n\n\t\t\tcenter[0] += k * (p[0] - center[0]);\n\t\t\tcenter[1] += k * (p[1] - center[1]);\n\t\t\tcenter[2] += k * (p[2] - center[2]);\n\t\t\tradius = (radius + d + r) / 2;\n\t\t}\n\t}\n\n\tresult[0] = center[0];\n\tresult[1] = center[1];\n\tresult[2] = center[2];\n\tresult[3] = radius;\n}\n\nstatic meshopt_Bounds computeClusterBounds(const unsigned int* indices, size_t index_count, const unsigned int* corners, size_t corner_count, const float* vertex_positions, size_t vertex_positions_stride)\n{\n\tsize_t vertex_stride_float = vertex_positions_stride / sizeof(float);\n\n\t// compute triangle normals (.w completes plane equation)\n\tfloat normals[kMeshletMaxTriangles][4];\n\tsize_t triangles = 0;\n\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tunsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2];\n\n\t\tconst float* p0 = vertex_positions + vertex_stride_float * a;\n\t\tconst float* p1 = vertex_positions + vertex_stride_float * b;\n\t\tconst float* p2 = vertex_positions + vertex_stride_float * c;\n\n\t\tfloat p10[3] = {p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]};\n\t\tfloat p20[3] = {p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]};\n\n\t\tfloat normalx = p10[1] * p20[2] - p10[2] * p20[1];\n\t\tfloat normaly = p10[2] * p20[0] - p10[0] * p20[2];\n\t\tfloat normalz = p10[0] * p20[1] - p10[1] * p20[0];\n\n\t\tfloat area = sqrtf(normalx * normalx + normaly * normaly + normalz * normalz);\n\n\t\t// no need to include degenerate triangles - they will be invisible anyway\n\t\tif (area == 0.f)\n\t\t\tcontinue;\n\n\t\tnormalx /= area;\n\t\tnormaly /= area;\n\t\tnormalz /= area;\n\n\t\t// record triangle normals; normal and corner 0 define a plane equation\n\t\tnormals[triangles][0] = normalx;\n\t\tnormals[triangles][1] = normaly;\n\t\tnormals[triangles][2] = normalz;\n\t\tnormals[triangles][3] = -(normalx * p0[0] + normaly * p0[1] + normalz * p0[2]);\n\t\ttriangles++;\n\t}\n\n\tmeshopt_Bounds bounds = {};\n\n\t// degenerate cluster, no valid triangles => trivial reject (cone data is 0)\n\tif (triangles == 0)\n\t\treturn bounds;\n\n\tconst float rzero = 0.f;\n\n\t// compute cluster bounding sphere; we'll use the center to determine normal cone apex as well\n\tfloat psphere[4] = {};\n\tcomputeBoundingSphere(psphere, vertex_positions, corner_count, vertex_positions_stride, &rzero, 0, 7, corners);\n\n\tfloat center[3] = {psphere[0], psphere[1], psphere[2]};\n\n\t// treating triangle normals as points, find the bounding sphere - the sphere center determines the optimal cone axis\n\tfloat nsphere[4] = {};\n\tcomputeBoundingSphere(nsphere, normals[0], triangles, sizeof(float) * 4, &rzero, 0, 3);\n\n\tfloat axis[3] = {nsphere[0], nsphere[1], nsphere[2]};\n\tfloat axislength = sqrtf(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]);\n\tfloat invaxislength = axislength == 0.f ? 0.f : 1.f / axislength;\n\n\taxis[0] *= invaxislength;\n\taxis[1] *= invaxislength;\n\taxis[2] *= invaxislength;\n\n\t// compute a tight cone around all normals, mindp = cos(angle/2)\n\tfloat mindp = 1.f;\n\n\tfor (size_t i = 0; i < triangles; ++i)\n\t{\n\t\tfloat dp = normals[i][0] * axis[0] + normals[i][1] * axis[1] + normals[i][2] * axis[2];\n\n\t\tmindp = (dp < mindp) ? dp : mindp;\n\t}\n\n\t// fill bounding sphere info; note that below we can return bounds without cone information for degenerate cones\n\tbounds.center[0] = center[0];\n\tbounds.center[1] = center[1];\n\tbounds.center[2] = center[2];\n\tbounds.radius = psphere[3];\n\n\t// degenerate cluster, normal cone is larger than a hemisphere => trivial accept\n\t// note that if mindp is positive but close to 0, the triangle intersection code below gets less stable\n\t// we arbitrarily decide that if a normal cone is ~168 degrees wide or more, the cone isn't useful\n\tif (mindp <= 0.1f)\n\t{\n\t\tbounds.cone_cutoff = 1;\n\t\tbounds.cone_cutoff_s8 = 127;\n\t\treturn bounds;\n\t}\n\n\tfloat maxt = 0;\n\n\t// we need to find the point on center-t*axis ray that lies in negative half-space of all triangles\n\tfor (size_t i = 0; i < triangles; ++i)\n\t{\n\t\t// dot(center-t*axis-corner, trinormal) = 0\n\t\t// dot(center-corner, trinormal) - t * dot(axis, trinormal) = 0\n\t\tfloat dc = center[0] * normals[i][0] + center[1] * normals[i][1] + center[2] * normals[i][2] + normals[i][3];\n\t\tfloat dn = axis[0] * normals[i][0] + axis[1] * normals[i][1] + axis[2] * normals[i][2];\n\n\t\t// dn should be larger than mindp cutoff above\n\t\tassert(dn > 0.f);\n\t\tfloat t = dc / dn;\n\n\t\tmaxt = (t > maxt) ? t : maxt;\n\t}\n\n\t// cone apex should be in the negative half-space of all cluster triangles by construction\n\tbounds.cone_apex[0] = center[0] - axis[0] * maxt;\n\tbounds.cone_apex[1] = center[1] - axis[1] * maxt;\n\tbounds.cone_apex[2] = center[2] - axis[2] * maxt;\n\n\t// note: this axis is the axis of the normal cone, but our test for perspective camera effectively negates the axis\n\tbounds.cone_axis[0] = axis[0];\n\tbounds.cone_axis[1] = axis[1];\n\tbounds.cone_axis[2] = axis[2];\n\n\t// cos(a) for normal cone is mindp; we need to add 90 degrees on both sides and invert the cone\n\t// which gives us -cos(a+90) = -(-sin(a)) = sin(a) = sqrt(1 - cos^2(a))\n\tbounds.cone_cutoff = sqrtf(1 - mindp * mindp);\n\n\t// quantize axis & cutoff to 8-bit SNORM format\n\tbounds.cone_axis_s8[0] = (signed char)(meshopt_quantizeSnorm(bounds.cone_axis[0], 8));\n\tbounds.cone_axis_s8[1] = (signed char)(meshopt_quantizeSnorm(bounds.cone_axis[1], 8));\n\tbounds.cone_axis_s8[2] = (signed char)(meshopt_quantizeSnorm(bounds.cone_axis[2], 8));\n\n\t// for the 8-bit test to be conservative, we need to adjust the cutoff by measuring the max. error\n\tfloat cone_axis_s8_e0 = fabsf(bounds.cone_axis_s8[0] / 127.f - bounds.cone_axis[0]);\n\tfloat cone_axis_s8_e1 = fabsf(bounds.cone_axis_s8[1] / 127.f - bounds.cone_axis[1]);\n\tfloat cone_axis_s8_e2 = fabsf(bounds.cone_axis_s8[2] / 127.f - bounds.cone_axis[2]);\n\n\t// note that we need to round this up instead of rounding to nearest, hence +1\n\tint cone_cutoff_s8 = int(127 * (bounds.cone_cutoff + cone_axis_s8_e0 + cone_axis_s8_e1 + cone_axis_s8_e2) + 1);\n\n\tbounds.cone_cutoff_s8 = (cone_cutoff_s8 > 127) ? 127 : (signed char)(cone_cutoff_s8);\n\n\treturn bounds;\n}\n\n} // namespace meshopt\n\nmeshopt_Bounds meshopt_computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(index_count / 3 <= kMeshletMaxTriangles);\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\n\t(void)vertex_count;\n\n\tunsigned int cache[512];\n\tmemset(cache, -1, sizeof(cache));\n\n\tunsigned int corners[kMeshletMaxTriangles * 3 + 1]; // +1 for branchless slot\n\tsize_t corner_count = 0;\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int v = indices[i];\n\t\tassert(v < vertex_count);\n\n\t\tunsigned int& c = cache[v & (sizeof(cache) / sizeof(cache[0]) - 1)];\n\n\t\t// branchless append if vertex isn't in cache\n\t\tcorners[corner_count] = v;\n\t\tcorner_count += (c != v);\n\t\tc = v;\n\t}\n\n\treturn computeClusterBounds(indices, index_count, corners, corner_count, vertex_positions, vertex_positions_stride);\n}\n\nmeshopt_Bounds meshopt_computeMeshletBounds(const unsigned int* meshlet_vertices, const unsigned char* meshlet_triangles, size_t triangle_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tusing namespace meshopt;\n\n\tassert(triangle_count <= kMeshletMaxTriangles);\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\n\t(void)vertex_count;\n\n\tunsigned int indices[kMeshletMaxTriangles * 3];\n\tsize_t corner_count = 0;\n\n\tfor (size_t i = 0; i < triangle_count * 3; ++i)\n\t{\n\t\tunsigned char t = meshlet_triangles[i];\n\t\tunsigned int index = meshlet_vertices[t];\n\t\tassert(index < vertex_count);\n\n\t\tindices[i] = index;\n\n\t\t// meshlet_vertices[] slice should only contain vertices used by triangle indices, which is the case for any well formed meshlet\n\t\tcorner_count = t >= corner_count ? t + 1 : corner_count;\n\t}\n\n\treturn computeClusterBounds(indices, triangle_count * 3, meshlet_vertices, corner_count, vertex_positions, vertex_positions_stride);\n}\n\nmeshopt_Bounds meshopt_computeSphereBounds(const float* positions, size_t count, size_t positions_stride, const float* radii, size_t radii_stride)\n{\n\tusing namespace meshopt;\n\n\tassert(positions_stride >= 12 && positions_stride <= 256);\n\tassert(positions_stride % sizeof(float) == 0);\n\tassert((radii_stride >= 4 && radii_stride <= 256) || radii == NULL);\n\tassert(radii_stride % sizeof(float) == 0);\n\n\tmeshopt_Bounds bounds = {};\n\n\tif (count == 0)\n\t\treturn bounds;\n\n\tconst float rzero = 0.f;\n\n\tfloat psphere[4] = {};\n\tcomputeBoundingSphere(psphere, positions, count, positions_stride, radii ? radii : &rzero, radii ? radii_stride : 0, 7);\n\n\tbounds.center[0] = psphere[0];\n\tbounds.center[1] = psphere[1];\n\tbounds.center[2] = psphere[2];\n\tbounds.radius = psphere[3];\n\n\treturn bounds;\n}\n\nvoid meshopt_optimizeMeshlet(unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, size_t triangle_count, size_t vertex_count)\n{\n\tusing namespace meshopt;\n\n\tassert(triangle_count <= kMeshletMaxTriangles);\n\tassert(vertex_count <= kMeshletMaxVertices);\n\n\tunsigned char* indices = meshlet_triangles;\n\tunsigned int* vertices = meshlet_vertices;\n\n\t// cache tracks vertex timestamps (corresponding to triangle index! all 3 vertices are added at the same time and never removed)\n\tunsigned char cache[kMeshletMaxVertices];\n\tmemset(cache, 0, vertex_count);\n\n\t// note that we start from a value that means all vertices aren't in cache\n\tunsigned char cache_last = 128;\n\tconst unsigned char cache_cutoff = 3; // 3 triangles = ~5..9 vertices depending on reuse\n\n\tfor (size_t i = 0; i < triangle_count; ++i)\n\t{\n\t\tint next = -1;\n\t\tint next_score = -1;\n\n\t\tfor (size_t j = i; j < triangle_count; ++j)\n\t\t{\n\t\t\tunsigned char a = indices[j * 3 + 0], b = indices[j * 3 + 1], c = indices[j * 3 + 2];\n\t\t\tassert(a < vertex_count && b < vertex_count && c < vertex_count);\n\n\t\t\t// compute cache distance using unsigned 8-bit subtraction, so cache timestamp overflow is handled gracefully\n\t\t\tunsigned char ad = (unsigned char)(cache_last - cache[a]);\n\t\t\tunsigned char bd = (unsigned char)(cache_last - cache[b]);\n\t\t\tunsigned char cd = (unsigned char)(cache_last - cache[c]);\n\n\t\t\t// we currently score purely by how many vertices are in the cache window\n\t\t\t// future heuristics for compressibility could include minimizing distance (ad+bd+cd)\n\t\t\t// however, that requires scanning the entire candidate set, making the early out below impossible\n\t\t\tint match = (ad < cache_cutoff) + (bd < cache_cutoff) + (cd < cache_cutoff);\n\t\t\tint score = match;\n\n\t\t\tnext = (score > next_score) ? int(j) : next;\n\t\t\tnext_score = (score > next_score) ? score : next_score;\n\n\t\t\t// for now we settle for a first edge match, which makes the function ~linear in practice\n\t\t\tif (match >= 2)\n\t\t\t\tbreak;\n\t\t}\n\n\t\tassert(next >= 0);\n\n\t\tunsigned char a = indices[next * 3 + 0], b = indices[next * 3 + 1], c = indices[next * 3 + 2];\n\n\t\t// shift triangles before the next one forward so that we always keep an ordered partition\n\t\t// note: this could have swapped triangles [i] and [next] but that distorts the order and may skew the output sequence\n\t\tmemmove(indices + (i + 1) * 3, indices + i * 3, (next - i) * 3 * sizeof(unsigned char));\n\n\t\tindices[i * 3 + 0] = a;\n\t\tindices[i * 3 + 1] = b;\n\t\tindices[i * 3 + 2] = c;\n\n\t\t// cache timestamp is the same between all vertices of each triangle to reduce overflow\n\t\tcache_last++;\n\t\tcache[a] = cache_last;\n\t\tcache[b] = cache_last;\n\t\tcache[c] = cache_last;\n\t}\n\n\t// rotate triangles to maximize compressibility\n\tmemset(cache, 0, vertex_count);\n\n\tfor (size_t i = 0; i < triangle_count; ++i)\n\t{\n\t\tunsigned char a = indices[i * 3 + 0], b = indices[i * 3 + 1], c = indices[i * 3 + 2];\n\n\t\t// if only the middle vertex has been used, rotate triangle to ensure new vertices are always sequential\n\t\tif (!cache[a] && cache[b] && !cache[c])\n\t\t{\n\t\t\t// abc -> bca\n\t\t\tunsigned char t = a;\n\t\t\ta = b, b = c, c = t;\n\t\t}\n\t\telse if (!cache[a] && !cache[b] && !cache[c])\n\t\t{\n\t\t\t// out of three edges, the edge ab can not be reused by subsequent triangles in some encodings\n\t\t\t// if subsequent triangles don't share edges ca or bc, we can rotate the triangle to fix this\n\t\t\tbool needab = false, needbc = false, needca = false;\n\n\t\t\tfor (size_t j = i + 1; j < triangle_count && j <= i + 3; ++j)\n\t\t\t{\n\t\t\t\tunsigned char oa = indices[j * 3 + 0], ob = indices[j * 3 + 1], oc = indices[j * 3 + 2];\n\n\t\t\t\t// note: edge comparisons are reversed as reused edges are flipped\n\t\t\t\tneedab |= (oa == b && ob == a) || (ob == b && oc == a) || (oc == b && oa == a);\n\t\t\t\tneedbc |= (oa == c && ob == b) || (ob == c && oc == b) || (oc == c && oa == b);\n\t\t\t\tneedca |= (oa == a && ob == c) || (ob == a && oc == c) || (oc == a && oa == c);\n\t\t\t}\n\n\t\t\tif (needab && !needbc)\n\t\t\t{\n\t\t\t\t// abc -> bca\n\t\t\t\tunsigned char t = a;\n\t\t\t\ta = b, b = c, c = t;\n\t\t\t}\n\t\t\telse if (needab && !needca)\n\t\t\t{\n\t\t\t\t// abc -> cab\n\t\t\t\tunsigned char t = c;\n\t\t\t\tc = b, b = a, a = t;\n\t\t\t}\n\t\t}\n\n\t\tindices[i * 3 + 0] = a, indices[i * 3 + 1] = b, indices[i * 3 + 2] = c;\n\n\t\tcache[a] = cache[b] = cache[c] = 1;\n\t}\n\n\t// reorder meshlet vertices for access locality assuming index buffer is scanned sequentially\n\tunsigned int order[kMeshletMaxVertices];\n\n\tshort remap[kMeshletMaxVertices];\n\tmemset(remap, -1, vertex_count * sizeof(short));\n\n\tsize_t vertex_offset = 0;\n\n\tfor (size_t i = 0; i < triangle_count * 3; ++i)\n\t{\n\t\tshort& r = remap[indices[i]];\n\n\t\tif (r < 0)\n\t\t{\n\t\t\tr = short(vertex_offset);\n\t\t\torder[vertex_offset] = vertices[indices[i]];\n\t\t\tvertex_offset++;\n\t\t}\n\n\t\tindices[i] = (unsigned char)r;\n\t}\n\n\tassert(vertex_offset <= vertex_count);\n\tmemcpy(vertices, order, vertex_offset * sizeof(unsigned int));\n}\n\nsize_t meshopt_extractMeshletIndices(unsigned int* vertices, unsigned char* triangles, const unsigned int* indices, size_t index_count)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(index_count / 3 <= kMeshletMaxTriangles);\n\n\tsize_t unique = 0;\n\n\t// direct mapped cache for fast lookups based on low index bits; inspired by vk_lod_clusters from NVIDIA\n\tshort cache[1024];\n\tmemset(cache, -1, sizeof(cache));\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int v = indices[i];\n\t\tunsigned int key = v & (sizeof(cache) / sizeof(cache[0]) - 1);\n\t\tshort c = cache[key];\n\n\t\t// fast path: vertex has been seen before\n\t\tif (c >= 0 && vertices[c] == v)\n\t\t{\n\t\t\ttriangles[i] = (unsigned char)c;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// fast path: vertex has never been seen before\n\t\tif (c < 0)\n\t\t{\n\t\t\tassert(unique < kMeshletMaxVertices);\n\t\t\tcache[key] = short(unique);\n\t\t\ttriangles[i] = (unsigned char)unique;\n\t\t\tvertices[unique++] = v;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// slow path: collision with a different vertex, so we need to look through all vertices\n\t\tint pos = -1;\n\t\tfor (size_t j = 0; j < unique; ++j)\n\t\t\tif (vertices[j] == v)\n\t\t\t{\n\t\t\t\tpos = int(j);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\tif (pos < 0)\n\t\t{\n\t\t\tassert(unique < kMeshletMaxVertices);\n\t\t\tpos = int(unique);\n\t\t\tvertices[unique++] = v;\n\t\t}\n\n\t\tcache[key] = short(pos);\n\t\ttriangles[i] = (unsigned char)pos;\n\t}\n\n\tassert(unique <= kMeshletMaxVertices);\n\treturn unique;\n}\n"
  },
  {
    "path": "src/meshoptimizer.h",
    "content": "/**\n * meshoptimizer - version 1.0\n *\n * Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\n * Report bugs and download new versions at https://github.com/zeux/meshoptimizer\n *\n * This library is distributed under the MIT License. See notice at the end of this file.\n */\n#pragma once\n\n#include <assert.h>\n#include <stddef.h>\n\n/* Version macro; major * 1000 + minor * 10 + patch */\n#define MESHOPTIMIZER_VERSION 1000 /* 1.0 */\n\n/* If no API is defined, assume default */\n#ifndef MESHOPTIMIZER_API\n#define MESHOPTIMIZER_API\n#endif\n\n/* Set the calling-convention for alloc/dealloc function pointers */\n#ifndef MESHOPTIMIZER_ALLOC_CALLCONV\n#ifdef _MSC_VER\n#define MESHOPTIMIZER_ALLOC_CALLCONV __cdecl\n#else\n#define MESHOPTIMIZER_ALLOC_CALLCONV\n#endif\n#endif\n\n/* Experimental APIs have unstable interface and might have implementation that's not fully tested or optimized */\n#ifndef MESHOPTIMIZER_EXPERIMENTAL\n#define MESHOPTIMIZER_EXPERIMENTAL MESHOPTIMIZER_API\n#endif\n\n/* C interface */\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\n/**\n * Vertex attribute stream\n * Each element takes size bytes, beginning at data, with stride controlling the spacing between successive elements (stride >= size).\n */\nstruct meshopt_Stream\n{\n\tconst void* data;\n\tsize_t size;\n\tsize_t stride;\n};\n\n/**\n * Generates a vertex remap table from the vertex buffer and an optional index buffer and returns number of unique vertices\n * As a result, all vertices that are binary equivalent map to the same (new) location, with no gaps in the resulting sequence.\n * Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer/meshopt_remapIndexBuffer.\n * Note that binary equivalence considers all vertex_size bytes, including padding which should be zero-initialized.\n *\n * destination must contain enough space for the resulting remap table (vertex_count elements)\n * indices can be NULL if the input is unindexed\n */\nMESHOPTIMIZER_API size_t meshopt_generateVertexRemap(unsigned int* destination, const unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size);\n\n/**\n * Generates a vertex remap table from multiple vertex streams and an optional index buffer and returns number of unique vertices\n * As a result, all vertices that are binary equivalent map to the same (new) location, with no gaps in the resulting sequence.\n * Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer/meshopt_remapIndexBuffer.\n * To remap vertex buffers, you will need to call meshopt_remapVertexBuffer for each vertex stream.\n * Note that binary equivalence considers all size bytes in each stream, including padding which should be zero-initialized.\n *\n * destination must contain enough space for the resulting remap table (vertex_count elements)\n * indices can be NULL if the input is unindexed\n * stream_count must be <= 16\n */\nMESHOPTIMIZER_API size_t meshopt_generateVertexRemapMulti(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream* streams, size_t stream_count);\n\n/**\n * Generates a vertex remap table from the vertex buffer and an optional index buffer and returns number of unique vertices\n * As a result, all vertices that are equivalent map to the same (new) location, with no gaps in the resulting sequence.\n * Equivalence is checked in two steps: vertex positions are compared for equality, and then the user-specified equality function is called (if provided).\n * Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer/meshopt_remapIndexBuffer.\n *\n * destination must contain enough space for the resulting remap table (vertex_count elements)\n * indices can be NULL if the input is unindexed\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n * callback can be NULL if no additional equality check is needed; otherwise, it should return 1 if vertices with specified indices are equivalent and 0 if they are not\n */\nMESHOPTIMIZER_API size_t meshopt_generateVertexRemapCustom(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, int (*callback)(void*, unsigned int, unsigned int), void* context);\n\n/**\n * Generates vertex buffer from the source vertex buffer and remap table generated by meshopt_generateVertexRemap\n *\n * destination must contain enough space for the resulting vertex buffer (unique_vertex_count elements, returned by meshopt_generateVertexRemap)\n * vertex_count should be the initial vertex count and not the value returned by meshopt_generateVertexRemap\n */\nMESHOPTIMIZER_API void meshopt_remapVertexBuffer(void* destination, const void* vertices, size_t vertex_count, size_t vertex_size, const unsigned int* remap);\n\n/**\n * Generate index buffer from the source index buffer and remap table generated by meshopt_generateVertexRemap\n *\n * destination must contain enough space for the resulting index buffer (index_count elements)\n * indices can be NULL if the input is unindexed\n */\nMESHOPTIMIZER_API void meshopt_remapIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const unsigned int* remap);\n\n/**\n * Generate index buffer that can be used for more efficient rendering when only a subset of the vertex attributes is necessary\n * All vertices that are binary equivalent (wrt first vertex_size bytes) map to the first vertex in the original vertex buffer.\n * This makes it possible to use the index buffer for Z pre-pass or shadowmap rendering, while using the original index buffer for regular rendering.\n * Note that binary equivalence considers all vertex_size bytes, including padding which should be zero-initialized.\n *\n * destination must contain enough space for the resulting index buffer (index_count elements)\n */\nMESHOPTIMIZER_API void meshopt_generateShadowIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride);\n\n/**\n * Generate index buffer that can be used for more efficient rendering when only a subset of the vertex attributes is necessary\n * All vertices that are binary equivalent (wrt specified streams) map to the first vertex in the original vertex buffer.\n * This makes it possible to use the index buffer for Z pre-pass or shadowmap rendering, while using the original index buffer for regular rendering.\n * Note that binary equivalence considers all size bytes in each stream, including padding which should be zero-initialized.\n *\n * destination must contain enough space for the resulting index buffer (index_count elements)\n * stream_count must be <= 16\n */\nMESHOPTIMIZER_API void meshopt_generateShadowIndexBufferMulti(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream* streams, size_t stream_count);\n\n/**\n * Generates a remap table that maps all vertices with the same position to the same (existing) index.\n * Similarly to meshopt_generateShadowIndexBuffer, this can be helpful to pre-process meshes for position-only rendering.\n * This can also be used to implement algorithms that require positional-only connectivity, such as hierarchical simplification.\n *\n * destination must contain enough space for the resulting remap table (vertex_count elements)\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n */\nMESHOPTIMIZER_API void meshopt_generatePositionRemap(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\n\n/**\n * Generate index buffer that can be used as a geometry shader input with triangle adjacency topology\n * Each triangle is converted into a 6-vertex patch with the following layout:\n * - 0, 2, 4: original triangle vertices\n * - 1, 3, 5: vertices adjacent to edges 02, 24 and 40\n * The resulting patch can be rendered with geometry shaders using e.g. VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY.\n * This can be used to implement algorithms like silhouette detection/expansion and other forms of GS-driven rendering.\n *\n * destination must contain enough space for the resulting index buffer (index_count*2 elements)\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n */\nMESHOPTIMIZER_API void meshopt_generateAdjacencyIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\n\n/**\n * Generate index buffer that can be used for PN-AEN tessellation with crack-free displacement\n * Each triangle is converted into a 12-vertex patch with the following layout:\n * - 0, 1, 2: original triangle vertices\n * - 3, 4: opposing edge for edge 0, 1\n * - 5, 6: opposing edge for edge 1, 2\n * - 7, 8: opposing edge for edge 2, 0\n * - 9, 10, 11: dominant vertices for corners 0, 1, 2\n * The resulting patch can be rendered with hardware tessellation using PN-AEN and displacement mapping.\n * See \"Tessellation on Any Budget\" (John McDonald, GDC 2011) for implementation details.\n *\n * destination must contain enough space for the resulting index buffer (index_count*4 elements)\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n */\nMESHOPTIMIZER_API void meshopt_generateTessellationIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\n\n/**\n * Generate index buffer that can be used for visibility buffer rendering and returns the size of the reorder table\n * Each triangle's provoking vertex index is equal to primitive id; this allows passing it to the fragment shader using flat/nointerpolation attribute.\n * This is important for performance on hardware where primitive id can't be accessed efficiently in fragment shader.\n * The reorder table stores the original vertex id for each vertex in the new index buffer, and should be used in the vertex shader to load vertex data.\n * The provoking vertex is assumed to be the first vertex in the triangle; if this is not the case (OpenGL), rotate each triangle (abc -> bca) before rendering.\n * For maximum efficiency the input index buffer should be optimized for vertex cache first.\n *\n * destination must contain enough space for the resulting index buffer (index_count elements)\n * reorder must contain enough space for the worst case reorder table (vertex_count + index_count/3 elements)\n */\nMESHOPTIMIZER_API size_t meshopt_generateProvokingIndexBuffer(unsigned int* destination, unsigned int* reorder, const unsigned int* indices, size_t index_count, size_t vertex_count);\n\n/**\n * Vertex transform cache optimizer\n * Reorders indices to reduce the number of GPU vertex shader invocations\n * If index buffer contains multiple ranges for multiple draw calls, this function needs to be called on each range individually.\n *\n * destination must contain enough space for the resulting index buffer (index_count elements)\n */\nMESHOPTIMIZER_API void meshopt_optimizeVertexCache(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count);\n\n/**\n * Vertex transform cache optimizer for strip-like caches\n * Produces inferior results to meshopt_optimizeVertexCache from the GPU vertex cache perspective\n * However, the resulting index order is more optimal if the goal is to reduce the triangle strip length or improve compression efficiency\n *\n * destination must contain enough space for the resulting index buffer (index_count elements)\n */\nMESHOPTIMIZER_API void meshopt_optimizeVertexCacheStrip(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count);\n\n/**\n * Vertex transform cache optimizer for FIFO caches\n * Reorders indices to reduce the number of GPU vertex shader invocations\n * Generally takes ~3x less time to optimize meshes but produces inferior results compared to meshopt_optimizeVertexCache\n * If index buffer contains multiple ranges for multiple draw calls, this function needs to be called on each range individually.\n *\n * destination must contain enough space for the resulting index buffer (index_count elements)\n * cache_size should be less than the actual GPU cache size to avoid cache thrashing\n */\nMESHOPTIMIZER_API void meshopt_optimizeVertexCacheFifo(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int cache_size);\n\n/**\n * Overdraw optimizer\n * Reorders indices to reduce the number of GPU vertex shader invocations and the pixel overdraw\n * If index buffer contains multiple ranges for multiple draw calls, this function needs to be called on each range individually.\n *\n * destination must contain enough space for the resulting index buffer (index_count elements)\n * indices must contain index data that is the result of meshopt_optimizeVertexCache (*not* the original mesh indices!)\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n * threshold indicates how much the overdraw optimizer can degrade vertex cache efficiency (1.05 = up to 5%) to reduce overdraw more efficiently\n */\nMESHOPTIMIZER_API void meshopt_optimizeOverdraw(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float threshold);\n\n/**\n * Vertex fetch cache optimizer\n * Reorders vertices and changes indices to reduce the amount of GPU memory fetches during vertex processing\n * Returns the number of unique vertices, which is the same as input vertex count unless some vertices are unused\n * This function works for a single vertex stream; for multiple vertex streams, use meshopt_optimizeVertexFetchRemap + meshopt_remapVertexBuffer for each stream.\n *\n * destination must contain enough space for the resulting vertex buffer (vertex_count elements)\n * indices is used both as an input and as an output index buffer\n */\nMESHOPTIMIZER_API size_t meshopt_optimizeVertexFetch(void* destination, unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size);\n\n/**\n * Vertex fetch cache optimizer\n * Generates vertex remap to reduce the amount of GPU memory fetches during vertex processing\n * Returns the number of unique vertices, which is the same as input vertex count unless some vertices are unused\n * The resulting remap table should be used to reorder vertex/index buffers using meshopt_remapVertexBuffer/meshopt_remapIndexBuffer\n *\n * destination must contain enough space for the resulting remap table (vertex_count elements)\n */\nMESHOPTIMIZER_API size_t meshopt_optimizeVertexFetchRemap(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count);\n\n/**\n * Index buffer encoder\n * Encodes index data into an array of bytes that is generally much smaller (<1.5 bytes/triangle) and compresses better (<1 bytes/triangle) compared to original.\n * Input index buffer must represent a triangle list.\n * Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space\n * For maximum efficiency the index buffer being encoded has to be optimized for vertex cache and vertex fetch first.\n *\n * buffer must contain enough space for the encoded index buffer (use meshopt_encodeIndexBufferBound to compute worst case size)\n */\nMESHOPTIMIZER_API size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_size, const unsigned int* indices, size_t index_count);\nMESHOPTIMIZER_API size_t meshopt_encodeIndexBufferBound(size_t index_count, size_t vertex_count);\n\n/**\n * Set index encoder format version (defaults to 1)\n *\n * version must specify the data format version to encode; valid values are 0 (decodable by all library versions) and 1 (decodable by 0.14+)\n */\nMESHOPTIMIZER_API void meshopt_encodeIndexVersion(int version);\n\n/**\n * Index buffer decoder\n * Decodes index data from an array of bytes generated by meshopt_encodeIndexBuffer\n * Returns 0 if decoding was successful, and an error code otherwise\n * The decoder is safe to use for untrusted input, but it may produce garbage data (e.g. out of range indices).\n *\n * destination must contain enough space for the resulting index buffer (index_count elements)\n */\nMESHOPTIMIZER_API int meshopt_decodeIndexBuffer(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size);\n\n/**\n * Get encoded index format version\n * Returns format version of the encoded index buffer/sequence, or -1 if the buffer header is invalid\n * Note that a non-negative value doesn't guarantee that the buffer will be decoded correctly if the input is malformed.\n */\nMESHOPTIMIZER_API int meshopt_decodeIndexVersion(const unsigned char* buffer, size_t buffer_size);\n\n/**\n * Index sequence encoder\n * Encodes index sequence into an array of bytes that is generally smaller and compresses better compared to original.\n * Input index sequence can represent arbitrary topology; for triangle lists meshopt_encodeIndexBuffer is likely to be better.\n * Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space\n *\n * buffer must contain enough space for the encoded index sequence (use meshopt_encodeIndexSequenceBound to compute worst case size)\n */\nMESHOPTIMIZER_API size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const unsigned int* indices, size_t index_count);\nMESHOPTIMIZER_API size_t meshopt_encodeIndexSequenceBound(size_t index_count, size_t vertex_count);\n\n/**\n * Index sequence decoder\n * Decodes index data from an array of bytes generated by meshopt_encodeIndexSequence\n * Returns 0 if decoding was successful, and an error code otherwise\n * The decoder is safe to use for untrusted input, but it may produce garbage data (e.g. out of range indices).\n *\n * destination must contain enough space for the resulting index sequence (index_count elements)\n */\nMESHOPTIMIZER_API int meshopt_decodeIndexSequence(void* destination, size_t index_count, size_t index_size, const unsigned char* buffer, size_t buffer_size);\n\n/**\n * Experimental: Meshlet encoder\n * Encodes meshlet data into an array of bytes that is generally smaller and compresses better compared to original.\n * Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space\n * This function encodes a single meshlet; when encoding multiple meshlets, additional headers may be necessary to store vertex/triangle count and encoded size.\n * For maximum efficiency the meshlet being encoded should be optimized using meshopt_optimizeMeshlet; additionally, vertex reference data should be optimized for locality (fetch).\n *\n * buffer must contain enough space for the encoded meshlet (use meshopt_encodeMeshletBound to compute worst case size)\n * vertices may be NULL, in which case vertex_count must be 0 and only triangle data is encoded\n * vertex_count and triangle_count must be <= 256.\n */\nMESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeMeshlet(unsigned char* buffer, size_t buffer_size, const unsigned int* vertices, size_t vertex_count, const unsigned char* triangles, size_t triangle_count);\nMESHOPTIMIZER_EXPERIMENTAL size_t meshopt_encodeMeshletBound(size_t max_vertices, size_t max_triangles);\n\n/**\n * Experimental: Meshlet decoder\n * Decodes meshlet data from an array of bytes generated by meshopt_encodeMeshlet\n * Returns 0 if decoding was successful, and an error code otherwise\n * The decoder is safe to use for untrusted input, but it may produce garbage data.\n *\n * vertices must contain enough space for the resulting vertex data, aligned to 4 bytes (align(vertex_count * vertex_size, 4) bytes)\n * vertex_size must be 2 (16-bit vertex references) or 4 (32-bit vertex references)\n * triangles must contain enough space for the resulting triangle data, aligned to 4 bytes (align(triangle_count * triangle_size, 4) bytes)\n * triangle_size must be 3 (8-bit triangle indices) or 4 (32-bit packed triangles, stored as (a) | (b << 8) | (c << 16))\n * vertex_count, triangle_count match those used during encoding exactly; buffer_size must be equal to the encoded size returned by meshopt_encodeMeshlet.\n * vertices may be NULL, in which case vertex_count must be 0 and the meshlet must contain just triangle data\n *\n * When using \"raw\" decoding (meshopt_decodeMeshletRaw), both vertices and triangles should have available space further aligned to 16 bytes for efficient SIMD decoding.\n */\nMESHOPTIMIZER_EXPERIMENTAL int meshopt_decodeMeshlet(void* vertices, size_t vertex_count, size_t vertex_size, void* triangles, size_t triangle_count, size_t triangle_size, const unsigned char* buffer, size_t buffer_size);\nMESHOPTIMIZER_EXPERIMENTAL int meshopt_decodeMeshletRaw(unsigned int* vertices, size_t vertex_count, unsigned int* triangles, size_t triangle_count, const unsigned char* buffer, size_t buffer_size);\n\n/**\n * Vertex buffer encoder\n * Encodes vertex data into an array of bytes that is generally smaller and compresses better compared to original.\n * Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space\n * This function works for a single vertex stream; for multiple vertex streams, call meshopt_encodeVertexBuffer for each stream.\n * Note that all vertex_size bytes of each vertex are encoded verbatim, including padding which should be zero-initialized.\n * For maximum efficiency the vertex buffer being encoded has to be quantized and optimized for locality of reference (cache/fetch) first.\n *\n * buffer must contain enough space for the encoded vertex buffer (use meshopt_encodeVertexBufferBound to compute worst case size)\n * vertex_size must be a multiple of 4 (and <= 256)\n */\nMESHOPTIMIZER_API size_t meshopt_encodeVertexBuffer(unsigned char* buffer, size_t buffer_size, const void* vertices, size_t vertex_count, size_t vertex_size);\nMESHOPTIMIZER_API size_t meshopt_encodeVertexBufferBound(size_t vertex_count, size_t vertex_size);\n\n/**\n * Vertex buffer encoder\n * Encodes vertex data just like meshopt_encodeVertexBuffer, but allows to override compression level.\n * For compression level to take effect, the vertex encoding version must be set to 1.\n * The default compression level implied by meshopt_encodeVertexBuffer is 2.\n *\n * buffer must contain enough space for the encoded vertex buffer (use meshopt_encodeVertexBufferBound to compute worst case size)\n * vertex_size must be a multiple of 4 (and <= 256)\n * level should be in the range [0, 3] with 0 being the fastest and 3 being the slowest and producing the best compression ratio.\n * version should be -1 to use the default version (specified via meshopt_encodeVertexVersion), or 0/1 to override the version; per above, level won't take effect if version is 0.\n */\nMESHOPTIMIZER_API size_t meshopt_encodeVertexBufferLevel(unsigned char* buffer, size_t buffer_size, const void* vertices, size_t vertex_count, size_t vertex_size, int level, int version);\n\n/**\n * Set vertex encoder format version (defaults to 1)\n *\n * version must specify the data format version to encode; valid values are 0 (decodable by all library versions) and 1 (decodable by 0.23+)\n */\nMESHOPTIMIZER_API void meshopt_encodeVertexVersion(int version);\n\n/**\n * Vertex buffer decoder\n * Decodes vertex data from an array of bytes generated by meshopt_encodeVertexBuffer\n * Returns 0 if decoding was successful, and an error code otherwise\n * The decoder is safe to use for untrusted input, but it may produce garbage data.\n *\n * destination must contain enough space for the resulting vertex buffer (vertex_count * vertex_size bytes)\n * vertex_size must be a multiple of 4 (and <= 256)\n */\nMESHOPTIMIZER_API int meshopt_decodeVertexBuffer(void* destination, size_t vertex_count, size_t vertex_size, const unsigned char* buffer, size_t buffer_size);\n\n/**\n * Get encoded vertex format version\n * Returns format version of the encoded vertex buffer, or -1 if the buffer header is invalid\n * Note that a non-negative value doesn't guarantee that the buffer will be decoded correctly if the input is malformed.\n */\nMESHOPTIMIZER_API int meshopt_decodeVertexVersion(const unsigned char* buffer, size_t buffer_size);\n\n/**\n * Vertex buffer filters\n * These functions can be used to filter output of meshopt_decodeVertexBuffer in-place.\n *\n * meshopt_decodeFilterOct decodes octahedral encoding of a unit vector with K-bit signed X/Y as an input; Z must store 1.0f.\n * Each component is stored as an 8-bit or 16-bit normalized integer; stride must be equal to 4 or 8. W is preserved as is.\n *\n * meshopt_decodeFilterQuat decodes 3-component quaternion encoding with K-bit component encoding and a 2-bit component index indicating which component to reconstruct.\n * Each component is stored as an 16-bit integer; stride must be equal to 8.\n *\n * meshopt_decodeFilterExp decodes exponential encoding of floating-point data with 8-bit exponent and 24-bit integer mantissa as 2^E*M.\n * Each 32-bit component is decoded in isolation; stride must be divisible by 4.\n *\n * meshopt_decodeFilterColor decodes RGBA colors from YCoCg (+A) color encoding where RGB is converted to YCoCg space with K-bit component encoding, and A is stored using K-1 bits.\n * Each component is stored as an 8-bit or 16-bit normalized integer; stride must be equal to 4 or 8.\n */\nMESHOPTIMIZER_API void meshopt_decodeFilterOct(void* buffer, size_t count, size_t stride);\nMESHOPTIMIZER_API void meshopt_decodeFilterQuat(void* buffer, size_t count, size_t stride);\nMESHOPTIMIZER_API void meshopt_decodeFilterExp(void* buffer, size_t count, size_t stride);\nMESHOPTIMIZER_API void meshopt_decodeFilterColor(void* buffer, size_t count, size_t stride);\n\n/**\n * Vertex buffer filter encoders\n * These functions can be used to encode data in a format that meshopt_decodeFilter can decode\n *\n * meshopt_encodeFilterOct encodes unit vectors with K-bit (2 <= K <= 16) signed X/Y as an output.\n * Each component is stored as an 8-bit or 16-bit normalized integer; stride must be equal to 4 or 8. Z will store 1.0f, W is preserved as is.\n * Input data must contain 4 floats for every vector (count*4 total).\n *\n * meshopt_encodeFilterQuat encodes unit quaternions with K-bit (4 <= K <= 16) component encoding.\n * Each component is stored as an 16-bit integer; stride must be equal to 8.\n * Input data must contain 4 floats for every quaternion (count*4 total).\n *\n * meshopt_encodeFilterExp encodes arbitrary (finite) floating-point data with 8-bit exponent and K-bit integer mantissa (1 <= K <= 24).\n * Exponent can be shared between all components of a given vector as defined by stride or all values of a given component; stride must be divisible by 4.\n * Input data must contain stride/4 floats for every vector (count*stride/4 total).\n *\n * meshopt_encodeFilterColor encodes RGBA color data by converting RGB to YCoCg color space with K-bit (2 <= K <= 16) component encoding; A is stored using K-1 bits.\n * Each component is stored as an 8-bit or 16-bit integer; stride must be equal to 4 or 8.\n * Input data must contain 4 floats for every color (count*4 total).\n */\nenum meshopt_EncodeExpMode\n{\n\t/* When encoding exponents, use separate values for each component (maximum quality) */\n\tmeshopt_EncodeExpSeparate,\n\t/* When encoding exponents, use shared value for all components of each vector (better compression) */\n\tmeshopt_EncodeExpSharedVector,\n\t/* When encoding exponents, use shared value for each component of all vectors (best compression) */\n\tmeshopt_EncodeExpSharedComponent,\n\t/* When encoding exponents, use separate values for each component, but clamp to 0 (good quality if very small values are not important) */\n\tmeshopt_EncodeExpClamped,\n};\n\nMESHOPTIMIZER_API void meshopt_encodeFilterOct(void* destination, size_t count, size_t stride, int bits, const float* data);\nMESHOPTIMIZER_API void meshopt_encodeFilterQuat(void* destination, size_t count, size_t stride, int bits, const float* data);\nMESHOPTIMIZER_API void meshopt_encodeFilterExp(void* destination, size_t count, size_t stride, int bits, const float* data, enum meshopt_EncodeExpMode mode);\nMESHOPTIMIZER_API void meshopt_encodeFilterColor(void* destination, size_t count, size_t stride, int bits, const float* data);\n\n/**\n * Simplification options\n */\nenum\n{\n\t/* Do not move vertices that are located on the topological border (vertices on triangle edges that don't have a paired triangle). Useful for simplifying portions of the larger mesh. */\n\tmeshopt_SimplifyLockBorder = 1 << 0,\n\t/* Improve simplification performance assuming input indices are a sparse subset of the mesh. Note that error becomes relative to subset extents. */\n\tmeshopt_SimplifySparse = 1 << 1,\n\t/* Treat error limit and resulting error as absolute instead of relative to mesh extents. */\n\tmeshopt_SimplifyErrorAbsolute = 1 << 2,\n\t/* Remove disconnected parts of the mesh during simplification incrementally, regardless of the topological restrictions inside components. */\n\tmeshopt_SimplifyPrune = 1 << 3,\n\t/* Produce more regular triangle sizes and shapes during simplification, at some cost to geometric and attribute quality. */\n\tmeshopt_SimplifyRegularize = 1 << 4,\n\t/* Experimental: Allow collapses across attribute discontinuities, except for vertices that are tagged with meshopt_SimplifyVertex_Protect in vertex_lock. */\n\tmeshopt_SimplifyPermissive = 1 << 5,\n};\n\n/**\n * Experimental: Simplification vertex flags/locks, for use in `vertex_lock` arrays in simplification APIs\n */\nenum\n{\n\t/* Do not move this vertex. */\n\tmeshopt_SimplifyVertex_Lock = 1 << 0,\n\t/* Protect attribute discontinuity at this vertex; must be used together with meshopt_SimplifyPermissive option. */\n\tmeshopt_SimplifyVertex_Protect = 1 << 1,\n};\n\n/**\n * Mesh simplifier\n * Reduces the number of triangles in the mesh, attempting to preserve mesh appearance as much as possible\n * The algorithm tries to preserve mesh topology and can stop short of the target goal based on topology constraints or target error.\n * If not all attributes from the input mesh are needed, it's recommended to reindex the mesh without them prior to simplification.\n * Returns the number of indices after simplification, with destination containing new index data\n *\n * The resulting index buffer references vertices from the original vertex buffer.\n * If the original vertex data isn't needed, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.\n *\n * destination must contain enough space for the target index buffer, worst case is index_count elements (*not* target_index_count)!\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation; value range [0..1]\n * options must be a bitmask composed of meshopt_SimplifyX options; 0 is a safe default\n * result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification\n */\nMESHOPTIMIZER_API size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options, float* result_error);\n\n/**\n * Mesh simplifier with attribute metric\n * Reduces the number of triangles in the mesh, attempting to preserve mesh appearance as much as possible.\n * Similar to meshopt_simplify, but incorporates attribute values into the error metric used to prioritize simplification order.\n * The algorithm tries to preserve mesh topology and can stop short of the target goal based on topology constraints or target error.\n * If not all attributes from the input mesh are needed, it's recommended to reindex the mesh without them prior to simplification.\n * Returns the number of indices after simplification, with destination containing new index data\n *\n * The resulting index buffer references vertices from the original vertex buffer.\n * If the original vertex data isn't needed, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.\n * Note that the number of attributes with non-zero weights affects memory requirements and running time.\n *\n * destination must contain enough space for the target index buffer, worst case is index_count elements (*not* target_index_count)!\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n * vertex_attributes should have attribute_count floats for each vertex\n * attribute_weights should have attribute_count floats in total; the weights determine relative priority of attributes between each other and wrt position\n * attribute_count must be <= 32\n * vertex_lock can be NULL; when it's not NULL, it should have a value for each vertex; 1 denotes vertices that can't be moved\n * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation; value range [0..1]\n * options must be a bitmask composed of meshopt_SimplifyX options; 0 is a safe default\n * result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification\n */\nMESHOPTIMIZER_API size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* result_error);\n\n/**\n * Mesh simplifier with position/attribute update\n * Reduces the number of triangles in the mesh, attempting to preserve mesh appearance as much as possible.\n * Similar to meshopt_simplifyWithAttributes, but destructively updates positions and attribute values for optimal appearance.\n * The algorithm tries to preserve mesh topology and can stop short of the target goal based on topology constraints or target error.\n * If not all attributes from the input mesh are needed, it's recommended to reindex the mesh without them prior to simplification.\n * Returns the number of indices after simplification, indices are destructively updated with new index data\n *\n * The updated index buffer references vertices from the original vertex buffer, however the vertex positions and attributes are updated in-place.\n * Creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended; if the original vertex data is needed, it should be copied before simplification.\n * Note that the number of attributes with non-zero weights affects memory requirements and running time. Attributes with zero weights are not updated.\n *\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n * vertex_attributes should have attribute_count floats for each vertex\n * attribute_weights should have attribute_count floats in total; the weights determine relative priority of attributes between each other and wrt position\n * attribute_count must be <= 32\n * vertex_lock can be NULL; when it's not NULL, it should have a value for each vertex; 1 denotes vertices that can't be moved\n * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation; value range [0..1]\n * options must be a bitmask composed of meshopt_SimplifyX options; 0 is a safe default\n * result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification\n */\nMESHOPTIMIZER_API size_t meshopt_simplifyWithUpdate(unsigned int* indices, size_t index_count, float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* result_error);\n\n/**\n * Mesh simplifier (sloppy)\n * Reduces the number of triangles in the mesh, sacrificing mesh appearance for simplification performance\n * The algorithm doesn't preserve mesh topology but can stop short of the target goal based on target error.\n * Returns the number of indices after simplification, with destination containing new index data\n * The resulting index buffer references vertices from the original vertex buffer.\n * If the original vertex data isn't needed, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.\n *\n * destination must contain enough space for the target index buffer, worst case is index_count elements (*not* target_index_count)!\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n * vertex_lock can be NULL; when it's not NULL, it should have a value for each vertex; vertices that can't be moved should set 1 consistently for all indices with the same position\n * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation; value range [0..1]\n * result_error can be NULL; when it's not NULL, it will contain the resulting (relative) error after simplification\n */\nMESHOPTIMIZER_API size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const unsigned char* vertex_lock, size_t target_index_count, float target_error, float* result_error);\n\n/**\n * Mesh simplifier (pruner)\n * Reduces the number of triangles in the mesh by removing small isolated parts of the mesh\n * Returns the number of indices after simplification, with destination containing new index data\n * The resulting index buffer references vertices from the original vertex buffer.\n * If the original vertex data isn't needed, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.\n *\n * destination must contain enough space for the target index buffer, worst case is index_count elements\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n * target_error represents the error relative to mesh extents that can be tolerated, e.g. 0.01 = 1% deformation; value range [0..1]\n */\nMESHOPTIMIZER_API size_t meshopt_simplifyPrune(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float target_error);\n\n/**\n * Point cloud simplifier\n * Reduces the number of points in the cloud to reach the given target\n * Returns the number of points after simplification, with destination containing new index data\n * The resulting index buffer references vertices from the original vertex buffer.\n * If the original vertex data isn't needed, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.\n *\n * destination must contain enough space for the target index buffer (target_vertex_count elements)\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n * vertex_colors can be NULL; when it's not NULL, it should have float3 color in the first 12 bytes of each vertex\n * color_weight determines relative priority of color wrt position; 1.0 is a safe default\n */\nMESHOPTIMIZER_API size_t meshopt_simplifyPoints(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_colors, size_t vertex_colors_stride, float color_weight, size_t target_vertex_count);\n\n/**\n * Returns the error scaling factor used by the simplifier to convert between absolute and relative extents\n *\n * Absolute error must be *divided* by the scaling factor before passing it to meshopt_simplify as target_error\n * Relative error returned by meshopt_simplify via result_error must be *multiplied* by the scaling factor to get absolute error.\n */\nMESHOPTIMIZER_API float meshopt_simplifyScale(const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\n\n/**\n * Mesh stripifier\n * Converts a previously vertex cache optimized triangle list to triangle strip, stitching strips using restart index or degenerate triangles\n * Returns the number of indices in the resulting strip, with destination containing new index data\n * For maximum efficiency the index buffer being converted has to be optimized for vertex cache first.\n * Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance.\n *\n * destination must contain enough space for the target index buffer, worst case can be computed with meshopt_stripifyBound\n * restart_index should be 0xffff or 0xffffffff depending on index size, or 0 to use degenerate triangles\n */\nMESHOPTIMIZER_API size_t meshopt_stripify(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int restart_index);\nMESHOPTIMIZER_API size_t meshopt_stripifyBound(size_t index_count);\n\n/**\n * Mesh unstripifier\n * Converts a triangle strip to a triangle list\n * Returns the number of indices in the resulting list, with destination containing new index data\n *\n * destination must contain enough space for the target index buffer, worst case can be computed with meshopt_unstripifyBound\n */\nMESHOPTIMIZER_API size_t meshopt_unstripify(unsigned int* destination, const unsigned int* indices, size_t index_count, unsigned int restart_index);\nMESHOPTIMIZER_API size_t meshopt_unstripifyBound(size_t index_count);\n\nstruct meshopt_VertexCacheStatistics\n{\n\tunsigned int vertices_transformed;\n\tunsigned int warps_executed;\n\tfloat acmr; /* transformed vertices / triangle count; best case 0.5, worst case 3.0, optimum depends on topology */\n\tfloat atvr; /* transformed vertices / vertex count; best case 1.0, worst case 6.0, optimum is 1.0 (each vertex is transformed once) */\n};\n\n/**\n * Vertex transform cache analyzer\n * Returns cache hit statistics using a simplified FIFO model\n * Results may not match actual GPU performance\n */\nMESHOPTIMIZER_API struct meshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int cache_size, unsigned int warp_size, unsigned int primgroup_size);\n\nstruct meshopt_VertexFetchStatistics\n{\n\tunsigned int bytes_fetched;\n\tfloat overfetch; /* fetched bytes / vertex buffer size; best case 1.0 (each byte is fetched once) */\n};\n\n/**\n * Vertex fetch cache analyzer\n * Returns cache hit statistics using a simplified direct mapped model\n * Results may not match actual GPU performance\n */\nMESHOPTIMIZER_API struct meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch(const unsigned int* indices, size_t index_count, size_t vertex_count, size_t vertex_size);\n\nstruct meshopt_OverdrawStatistics\n{\n\tunsigned int pixels_covered;\n\tunsigned int pixels_shaded;\n\tfloat overdraw; /* shaded pixels / covered pixels; best case 1.0 */\n};\n\n/**\n * Overdraw analyzer\n * Returns overdraw statistics using a software rasterizer\n * Results may not match actual GPU performance\n *\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n */\nMESHOPTIMIZER_API struct meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\n\nstruct meshopt_CoverageStatistics\n{\n\tfloat coverage[3];\n\tfloat extent; /* viewport size in mesh coordinates */\n};\n\n/**\n * Coverage analyzer\n * Returns coverage statistics (ratio of viewport pixels covered from each axis) using a software rasterizer\n *\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n */\nMESHOPTIMIZER_API struct meshopt_CoverageStatistics meshopt_analyzeCoverage(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\n\n/**\n * Meshlet is a small mesh cluster (subset) that consists of:\n * - triangles, an 8-bit micro triangle (index) buffer, that for each triangle specifies three local vertices to use;\n * - vertices, a 32-bit vertex indirection buffer, that for each local vertex specifies which mesh vertex to fetch vertex attributes from.\n *\n * For efficiency, meshlet triangles and vertices are packed into two large arrays; this structure contains offsets and counts to access the data.\n */\nstruct meshopt_Meshlet\n{\n\t/* offsets within meshlet_vertices and meshlet_triangles arrays with meshlet data */\n\tunsigned int vertex_offset;\n\tunsigned int triangle_offset;\n\n\t/* number of vertices and triangles used in the meshlet; data is stored in consecutive range [offset..offset+count) for vertices and [offset..offset+count*3) for triangles */\n\tunsigned int vertex_count;\n\tunsigned int triangle_count;\n};\n\n/**\n * Meshlet builder\n * Splits the mesh into a set of meshlets where each meshlet has a micro index buffer indexing into meshlet vertices that refer to the original vertex buffer\n * The resulting data can be used to render meshes using NVidia programmable mesh shading pipeline, or in other cluster-based renderers.\n * When targeting mesh shading hardware, for maximum efficiency meshlets should be further optimized using meshopt_optimizeMeshlet.\n * When using buildMeshlets, vertex positions need to be provided to minimize the size of the resulting clusters.\n * When using buildMeshletsScan, for maximum efficiency the index buffer being converted has to be optimized for vertex cache first.\n *\n * meshlets must contain enough space for all meshlets, worst case size can be computed with meshopt_buildMeshletsBound\n * meshlet_vertices must contain enough space for all meshlets, worst case is index_count elements (*not* vertex_count!)\n * meshlet_triangles must contain enough space for all meshlets, worst case is index_count elements\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n * max_vertices and max_triangles must not exceed implementation limits (max_vertices <= 256, max_triangles <= 512)\n * cone_weight should be set to 0 when cone culling is not used, and a value between 0 and 1 otherwise to balance between cluster size and cone culling efficiency\n */\nMESHOPTIMIZER_API size_t meshopt_buildMeshlets(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight);\nMESHOPTIMIZER_API size_t meshopt_buildMeshletsScan(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles);\nMESHOPTIMIZER_API size_t meshopt_buildMeshletsBound(size_t index_count, size_t max_vertices, size_t max_triangles);\n\n/**\n * Meshlet builder with flexible cluster sizes\n * Splits the mesh into a set of meshlets, similarly to meshopt_buildMeshlets, but allows to specify minimum and maximum number of triangles per meshlet.\n * Clusters between min and max triangle counts are split when the cluster size would have exceeded the expected cluster size by more than split_factor.\n *\n * meshlets must contain enough space for all meshlets, worst case size can be computed with meshopt_buildMeshletsBound using min_triangles (*not* max!)\n * meshlet_vertices must contain enough space for all meshlets, worst case is index_count elements (*not* vertex_count!)\n * meshlet_triangles must contain enough space for all meshlets, worst case is index_count elements\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n * max_vertices, min_triangles and max_triangles must not exceed implementation limits (max_vertices <= 256, max_triangles <= 512; min_triangles <= max_triangles)\n * cone_weight should be set to 0 when cone culling is not used, and a value between 0 and 1 otherwise to balance between cluster size and cone culling efficiency\n * split_factor should be set to a non-negative value; when greater than 0, clusters that have large bounds may be split unless they are under the min_triangles threshold\n */\nMESHOPTIMIZER_API size_t meshopt_buildMeshletsFlex(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float cone_weight, float split_factor);\n\n/**\n * Meshlet builder that produces clusters optimized for raytracing\n * Splits the mesh into a set of meshlets, similarly to meshopt_buildMeshlets, but optimizes cluster subdivision for raytracing and allows to specify minimum and maximum number of triangles per meshlet.\n *\n * meshlets must contain enough space for all meshlets, worst case size can be computed with meshopt_buildMeshletsBound using min_triangles (*not* max!)\n * meshlet_vertices must contain enough space for all meshlets, worst case is index_count elements (*not* vertex_count!)\n * meshlet_triangles must contain enough space for all meshlets, worst case is index_count elements\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n * max_vertices, min_triangles and max_triangles must not exceed implementation limits (max_vertices <= 256, max_triangles <= 512; min_triangles <= max_triangles)\n * fill_weight allows to prioritize clusters that are closer to maximum size at some cost to SAH quality; 0.5 is a safe default\n */\nMESHOPTIMIZER_API size_t meshopt_buildMeshletsSpatial(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float fill_weight);\n\n/**\n * Meshlet optimizer\n * Reorders meshlet vertices and triangles to maximize locality which can improve rasterizer throughput or ray tracing performance when using fast-build modes.\n *\n * meshlet_triangles and meshlet_vertices must refer to meshlet data; when buildMeshlets* is used, these need to be computed from meshlet's vertex_offset and triangle_offset\n * triangle_count and vertex_count must not exceed implementation limits (vertex_count <= 256, triangle_count <= 512)\n */\nMESHOPTIMIZER_API void meshopt_optimizeMeshlet(unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, size_t triangle_count, size_t vertex_count);\n\nstruct meshopt_Bounds\n{\n\t/* bounding sphere, useful for frustum and occlusion culling */\n\tfloat center[3];\n\tfloat radius;\n\n\t/* normal cone, useful for backface culling */\n\tfloat cone_apex[3];\n\tfloat cone_axis[3];\n\tfloat cone_cutoff; /* = cos(angle/2) */\n\n\t/* normal cone axis and cutoff, stored in 8-bit SNORM format; decode using x/127.0 */\n\tsigned char cone_axis_s8[3];\n\tsigned char cone_cutoff_s8;\n};\n\n/**\n * Cluster bounds generator\n * Creates bounding volumes that can be used for frustum, backface and occlusion culling.\n *\n * For backface culling with orthographic projection, use the following formula to reject backfacing clusters:\n *   dot(view, cone_axis) >= cone_cutoff\n *\n * For perspective projection, you can use the formula that needs cone apex in addition to axis & cutoff:\n *   dot(normalize(cone_apex - camera_position), cone_axis) >= cone_cutoff\n *\n * Alternatively, you can use the formula that doesn't need cone apex and uses bounding sphere instead:\n *   dot(normalize(center - camera_position), cone_axis) >= cone_cutoff + radius / length(center - camera_position)\n * or an equivalent formula that doesn't have a singularity at center = camera_position:\n *   dot(center - camera_position, cone_axis) >= cone_cutoff * length(center - camera_position) + radius\n *\n * The formula that uses the apex is slightly more accurate but needs the apex; if you are already using bounding sphere\n * to do frustum/occlusion culling, the formula that doesn't use the apex may be preferable (for derivation see\n * Real-Time Rendering 4th Edition, section 19.3).\n *\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n * vertex_count should specify the number of vertices in the entire mesh, not cluster or meshlet\n * indices should have at most 256 unique vertex indices\n * index_count/3 and triangle_count must not exceed implementation limits (<= 512)\n */\nMESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\nMESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeMeshletBounds(const unsigned int* meshlet_vertices, const unsigned char* meshlet_triangles, size_t triangle_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\n\n/**\n * Sphere bounds generator\n * Creates bounding sphere around a set of points or a set of spheres; returns the center and radius of the sphere, with other fields of the result set to 0.\n *\n * positions should have float3 position in the first 12 bytes of each element\n * radii can be NULL; when it's not NULL, it should have a non-negative float radius in the first 4 bytes of each element\n */\nMESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeSphereBounds(const float* positions, size_t count, size_t positions_stride, const float* radii, size_t radii_stride);\n\n/**\n * Experimental: Extract meshlet-local vertex and triangle indices from absolute cluster indices.\n * Fills triangles[] and vertices[] such that vertices[triangles[i]] == indices[i], and returns the number of unique vertices.\n *\n * indices should have at most 256 unique vertex indices\n * index_count/3 must not exceed implementation limits (<= 512)\n */\nMESHOPTIMIZER_EXPERIMENTAL size_t meshopt_extractMeshletIndices(unsigned int* vertices, unsigned char* triangles, const unsigned int* indices, size_t index_count);\n\n/**\n * Cluster partitioner\n * Partitions clusters into groups of similar size, prioritizing grouping clusters that share vertices or are close to each other.\n * When vertex positions are not provided, only clusters that share vertices will be grouped together, which may result in small partitions for some inputs.\n *\n * destination must contain enough space for the resulting partition data (cluster_count elements)\n * destination[i] will contain the partition id for cluster i, with the total number of partitions returned by the function\n * cluster_indices should have the vertex indices referenced by each cluster, stored sequentially\n * cluster_index_counts should have the number of indices in each cluster; sum of all cluster_index_counts must be equal to total_index_count\n * vertex_positions can be NULL; when it's not NULL, it should have float3 position in the first 12 bytes of each vertex\n * target_partition_size is a target size for each partition, in clusters; the resulting partitions may be smaller or larger (up to target + target/3)\n */\nMESHOPTIMIZER_API size_t meshopt_partitionClusters(unsigned int* destination, const unsigned int* cluster_indices, size_t total_index_count, const unsigned int* cluster_index_counts, size_t cluster_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_partition_size);\n\n/**\n * Spatial sorter\n * Generates a remap table that can be used to reorder points for spatial locality.\n * Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer.\n *\n * destination must contain enough space for the resulting remap table (vertex_count elements)\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n */\nMESHOPTIMIZER_API void meshopt_spatialSortRemap(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\n\n/**\n * Spatial sorter\n * Reorders triangles for spatial locality, and generates a new index buffer. The resulting index buffer can be used with other functions like optimizeVertexCache.\n *\n * destination must contain enough space for the resulting index buffer (index_count elements)\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n */\nMESHOPTIMIZER_API void meshopt_spatialSortTriangles(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\n\n/**\n * Spatial clusterizer\n * Reorders points into clusters optimized for spatial locality, and generates a new index buffer.\n * Ensures the output can be split into cluster_size chunks where each chunk has good positional locality. Only the last chunk will be smaller than cluster_size.\n *\n * destination must contain enough space for the resulting index buffer (vertex_count elements)\n * vertex_positions should have float3 position in the first 12 bytes of each vertex\n */\nMESHOPTIMIZER_API void meshopt_spatialClusterPoints(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t cluster_size);\n\n/**\n * Experimental: Opacity micromap generator (measure)\n * Computes a subdivision level for each input triangle, as well as deduplicating the triangles that reference the same UVs to reduce rasterization requests.\n * Returns the number of OMM entries.\n *\n * levels and sources must contain enough space for the worst case output (index_count/3 elements, one per resulting OMM entry)\n * levels[i] will contain the subdivision level for entry i, with the total number of entries returned by the function; each entry should be rasterized from triangle index sources[i]\n * omm_indices must contain enough space for the resulting OMM indices (index_count/3 elements, one per triangle)\n * vertex_uvs should have float2 texture coordinate in the first 8 bytes of each vertex\n * max_level specifies the maximum subdivision level (0..12)\n * target_edge can be 0; when >0, triangle subdivision is adaptive and targets target_edge^2 texel area\n */\nMESHOPTIMIZER_EXPERIMENTAL size_t meshopt_opacityMapMeasure(unsigned char* levels, unsigned int* sources, int* omm_indices, const unsigned int* indices, size_t index_count, const float* vertex_uvs, size_t vertex_count, size_t vertex_uvs_stride, unsigned int texture_width, unsigned int texture_height, int max_level, float target_edge);\n\n/**\n * Experimental: Opacity micromap generator (rasterize)\n * Rasterizes opacity state for a single triangle entry by sampling the alpha texture, using bilinear filtering and 0.5 alpha cutoff.\n *\n * result should contain enough space for the output opacity data (which can be computed using meshopt_opacityMapEntrySize)\n * level specifies the subdivision level (0..12)\n * states should be 2 for 2-state format (opaque/transparent) and 4 for 4-state format (opaque/transparent/unknown)\n * uv0/uv1/uv2 should refer to a float2 texture coordinate for each triangle corner; note that micromap data is sensitive to the corner order\n * texture_data should point to the alpha channel of the first pixel, encoded as UNORM8\n * texture_stride specifies the distance in bytes between consecutive pixels, e.g. 4 for RGBA input\n * texture_pitch specifies the distance in bytes between consecutive rows, e.g. 4*texture_width for tightly packed RGBA input\n */\nMESHOPTIMIZER_EXPERIMENTAL void meshopt_opacityMapRasterize(unsigned char* result, int level, int states, const float* uv0, const float* uv1, const float* uv2, const unsigned char* texture_data, size_t texture_stride, size_t texture_pitch, unsigned int texture_width, unsigned int texture_height);\nMESHOPTIMIZER_EXPERIMENTAL size_t meshopt_opacityMapEntrySize(int level, int states);\n\n/**\n * Experimental: Opacity micromap generator (compact)\n * Compacts and deduplicates opacity data, merging identical micromap entries and replacing micromap states with special indices (-4..-1) when possible.\n * Returns the number of OMM entries after compaction; the data array should be trimmed using the last offset/size.\n *\n * data should contain opacity data for all input/output entries\n * levels should contain subdivision levels for all input/output entries\n * offsets should contain offset into data[] for each entry\n * levels[i] and offsets[i] will be updated with post-compaction level/offset for entry i, with the total number of entries returned by the function\n * omm_indices should contain indices into the original OMM data, and will be updated with a new index or a special index (-4..-1) when possible\n * states should be 2 for 2-state format (opaque/transparent) and 4 for 4-state format (opaque/transparent/unknown)\n */\nMESHOPTIMIZER_EXPERIMENTAL size_t meshopt_opacityMapCompact(unsigned char* data, size_t data_size, unsigned char* levels, unsigned int* offsets, size_t omm_count, int* omm_indices, size_t triangle_count, int states);\n\n/**\n * Quantize a float into half-precision (as defined by IEEE-754 fp16) floating point value\n * Generates +-inf for overflow, preserves NaN, flushes denormals to zero, rounds to nearest\n * Representable magnitude range: [6e-5; 65504]\n * Maximum relative reconstruction error: 5e-4\n */\nMESHOPTIMIZER_API unsigned short meshopt_quantizeHalf(float v);\n\n/**\n * Quantize a float into a floating point value with a limited number of significant mantissa bits, preserving the IEEE-754 fp32 binary representation\n * Preserves infinities/NaN, flushes denormals to zero, rounds to nearest\n * Assumes N is in a valid mantissa precision range, which is 1..23\n */\nMESHOPTIMIZER_API float meshopt_quantizeFloat(float v, int N);\n\n/**\n * Reverse quantization of a half-precision (as defined by IEEE-754 fp16) floating point value\n * Preserves Inf/NaN, flushes denormals to zero\n */\nMESHOPTIMIZER_API float meshopt_dequantizeHalf(unsigned short h);\n\n/**\n * Set allocation callbacks\n * These callbacks will be used instead of the default operator new/operator delete for all temporary allocations in the library.\n * Note that all algorithms only allocate memory for temporary use.\n * allocate/deallocate are always called in a stack-like order - last pointer to be allocated is deallocated first.\n */\nMESHOPTIMIZER_API void meshopt_setAllocator(void* (MESHOPTIMIZER_ALLOC_CALLCONV* allocate)(size_t), void (MESHOPTIMIZER_ALLOC_CALLCONV* deallocate)(void*));\n\n#ifdef __cplusplus\n} /* extern \"C\" */\n#endif\n\n/* Quantization into fixed point normalized formats; these are only available as inline C++ functions */\n#ifdef __cplusplus\n/**\n * Quantize a float in [0..1] range into an N-bit fixed point unorm value\n * Assumes reconstruction function (q / (2^N-1)), which is the case for fixed-function normalized fixed point conversion\n * Maximum reconstruction error: 1/2^(N+1)\n */\ninline int meshopt_quantizeUnorm(float v, int N);\n\n/**\n * Quantize a float in [-1..1] range into an N-bit fixed point snorm value\n * Assumes reconstruction function (q / (2^(N-1)-1)), which is the case for fixed-function normalized fixed point conversion (except early OpenGL versions)\n * Maximum reconstruction error: 1/2^N\n */\ninline int meshopt_quantizeSnorm(float v, int N);\n#endif\n\n/**\n * C++ template interface\n *\n * These functions mirror the C interface the library provides, providing template-based overloads so that\n * the caller can use an arbitrary type for the index data, both for input and output.\n * When the supplied type is the same size as that of unsigned int, the wrappers are zero-cost; when it's not,\n * the wrappers end up allocating memory and copying index data to convert from one type to another.\n */\n#if defined(__cplusplus) && !defined(MESHOPTIMIZER_NO_WRAPPERS)\ntemplate <typename T>\ninline size_t meshopt_generateVertexRemap(unsigned int* destination, const T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size);\ntemplate <typename T>\ninline size_t meshopt_generateVertexRemapMulti(unsigned int* destination, const T* indices, size_t index_count, size_t vertex_count, const meshopt_Stream* streams, size_t stream_count);\ntemplate <typename F>\ninline size_t meshopt_generateVertexRemapCustom(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, F callback);\ntemplate <typename T, typename F>\ninline size_t meshopt_generateVertexRemapCustom(unsigned int* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, F callback);\ntemplate <typename T>\ninline void meshopt_remapIndexBuffer(T* destination, const T* indices, size_t index_count, const unsigned int* remap);\ntemplate <typename T>\ninline void meshopt_generateShadowIndexBuffer(T* destination, const T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride);\ntemplate <typename T>\ninline void meshopt_generateShadowIndexBufferMulti(T* destination, const T* indices, size_t index_count, size_t vertex_count, const meshopt_Stream* streams, size_t stream_count);\ntemplate <typename T>\ninline void meshopt_generateAdjacencyIndexBuffer(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\ntemplate <typename T>\ninline void meshopt_generateTessellationIndexBuffer(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\ntemplate <typename T>\ninline size_t meshopt_generateProvokingIndexBuffer(T* destination, unsigned int* reorder, const T* indices, size_t index_count, size_t vertex_count);\ntemplate <typename T>\ninline void meshopt_optimizeVertexCache(T* destination, const T* indices, size_t index_count, size_t vertex_count);\ntemplate <typename T>\ninline void meshopt_optimizeVertexCacheStrip(T* destination, const T* indices, size_t index_count, size_t vertex_count);\ntemplate <typename T>\ninline void meshopt_optimizeVertexCacheFifo(T* destination, const T* indices, size_t index_count, size_t vertex_count, unsigned int cache_size);\ntemplate <typename T>\ninline void meshopt_optimizeOverdraw(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float threshold);\ntemplate <typename T>\ninline size_t meshopt_optimizeVertexFetchRemap(unsigned int* destination, const T* indices, size_t index_count, size_t vertex_count);\ntemplate <typename T>\ninline size_t meshopt_optimizeVertexFetch(void* destination, T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size);\ntemplate <typename T>\ninline size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_size, const T* indices, size_t index_count);\ntemplate <typename T>\ninline int meshopt_decodeIndexBuffer(T* destination, size_t index_count, const unsigned char* buffer, size_t buffer_size);\ntemplate <typename T>\ninline size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const T* indices, size_t index_count);\ntemplate <typename T>\ninline int meshopt_decodeIndexSequence(T* destination, size_t index_count, const unsigned char* buffer, size_t buffer_size);\ntemplate <typename V, typename T>\ninline int meshopt_decodeMeshlet(V* vertices, size_t vertex_count, T* triangles, size_t triangle_count, const unsigned char* buffer, size_t buffer_size);\ninline size_t meshopt_encodeVertexBufferLevel(unsigned char* buffer, size_t buffer_size, const void* vertices, size_t vertex_count, size_t vertex_size, int level);\ntemplate <typename T>\ninline size_t meshopt_simplify(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options = 0, float* result_error = NULL);\ntemplate <typename T>\ninline size_t meshopt_simplifyWithAttributes(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options = 0, float* result_error = NULL);\ntemplate <typename T>\ninline size_t meshopt_simplifyWithUpdate(T* indices, size_t index_count, float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options = 0, float* result_error = NULL);\ntemplate <typename T>\ninline size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float* result_error = NULL);\ntemplate <typename T>\ninline size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const unsigned char* vertex_lock, size_t target_index_count, float target_error, float* result_error = NULL);\ntemplate <typename T>\ninline size_t meshopt_simplifyPrune(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float target_error);\ntemplate <typename T>\ninline size_t meshopt_stripify(T* destination, const T* indices, size_t index_count, size_t vertex_count, T restart_index);\ntemplate <typename T>\ninline size_t meshopt_unstripify(T* destination, const T* indices, size_t index_count, T restart_index);\ntemplate <typename T>\ninline meshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const T* indices, size_t index_count, size_t vertex_count, unsigned int cache_size, unsigned int warp_size, unsigned int primgroup_size);\ntemplate <typename T>\ninline meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch(const T* indices, size_t index_count, size_t vertex_count, size_t vertex_size);\ntemplate <typename T>\ninline meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\ntemplate <typename T>\ninline meshopt_CoverageStatistics meshopt_analyzeCoverage(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\ntemplate <typename T>\ninline size_t meshopt_buildMeshlets(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight);\ntemplate <typename T>\ninline size_t meshopt_buildMeshletsScan(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles);\ntemplate <typename T>\ninline size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float cone_weight, float split_factor);\ntemplate <typename T>\ninline size_t meshopt_buildMeshletsSpatial(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float fill_weight);\ntemplate <typename T>\ninline meshopt_Bounds meshopt_computeClusterBounds(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\ntemplate <typename T>\ninline size_t meshopt_partitionClusters(unsigned int* destination, const T* cluster_indices, size_t total_index_count, const unsigned int* cluster_index_counts, size_t cluster_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_partition_size);\ntemplate <typename T>\ninline void meshopt_spatialSortTriangles(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);\n#endif\n\n/* Inline implementation */\n#ifdef __cplusplus\ninline int meshopt_quantizeUnorm(float v, int N)\n{\n\tconst float scale = float((1 << N) - 1);\n\n\tv = (v >= 0) ? v : 0;\n\tv = (v <= 1) ? v : 1;\n\n\treturn int(v * scale + 0.5f);\n}\n\ninline int meshopt_quantizeSnorm(float v, int N)\n{\n\tconst float scale = float((1 << (N - 1)) - 1);\n\n\tfloat round = (v >= 0 ? 0.5f : -0.5f);\n\n\tv = (v >= -1) ? v : -1;\n\tv = (v <= +1) ? v : +1;\n\n\treturn int(v * scale + round);\n}\n#endif\n\n/* Internal implementation helpers */\n#ifdef __cplusplus\nclass meshopt_Allocator\n{\npublic:\n\tstruct Storage\n\t{\n\t\tvoid* (MESHOPTIMIZER_ALLOC_CALLCONV* allocate)(size_t);\n\t\tvoid (MESHOPTIMIZER_ALLOC_CALLCONV* deallocate)(void*);\n\t};\n\n#ifdef MESHOPTIMIZER_ALLOC_EXPORT\n\tMESHOPTIMIZER_API static Storage& storage();\n#else\n\tstatic Storage& storage()\n\t{\n\t\tstatic Storage s = {::operator new, ::operator delete };\n\t\treturn s;\n\t}\n#endif\n\n\tmeshopt_Allocator()\n\t    : blocks()\n\t    , count(0)\n\t{\n\t}\n\n\t~meshopt_Allocator()\n\t{\n\t\tfor (size_t i = count; i > 0; --i)\n\t\t\tstorage().deallocate(blocks[i - 1]);\n\t}\n\n\ttemplate <typename T>\n\tT* allocate(size_t size)\n\t{\n\t\tassert(count < sizeof(blocks) / sizeof(blocks[0]));\n\t\tT* result = static_cast<T*>(storage().allocate(size > size_t(-1) / sizeof(T) ? size_t(-1) : size * sizeof(T)));\n\t\tblocks[count++] = result;\n\t\treturn result;\n\t}\n\n\tvoid deallocate(void* ptr)\n\t{\n\t\tassert(count > 0 && blocks[count - 1] == ptr);\n\t\tstorage().deallocate(ptr);\n\t\tcount--;\n\t}\n\nprivate:\n\tvoid* blocks[24];\n\tsize_t count;\n};\n#endif\n\n/* Inline implementation for C++ templated wrappers */\n#if defined(__cplusplus) && !defined(MESHOPTIMIZER_NO_WRAPPERS)\ntemplate <typename T, bool ZeroCopy = sizeof(T) == sizeof(unsigned int)>\nstruct meshopt_IndexAdapter;\n\ntemplate <typename T>\nstruct meshopt_IndexAdapter<T, false>\n{\n\tT* result;\n\tunsigned int* data;\n\tsize_t count;\n\n\tmeshopt_IndexAdapter(T* result_, const T* input, size_t count_)\n\t    : result(result_)\n\t    , data(NULL)\n\t    , count(count_)\n\t{\n\t\tsize_t size = count > size_t(-1) / sizeof(unsigned int) ? size_t(-1) : count * sizeof(unsigned int);\n\n\t\tdata = static_cast<unsigned int*>(meshopt_Allocator::storage().allocate(size));\n\n\t\tif (input)\n\t\t{\n\t\t\tfor (size_t i = 0; i < count; ++i)\n\t\t\t\tdata[i] = input[i];\n\t\t}\n\t}\n\n\t~meshopt_IndexAdapter()\n\t{\n\t\tif (result)\n\t\t{\n\t\t\tfor (size_t i = 0; i < count; ++i)\n\t\t\t\tresult[i] = T(data[i]);\n\t\t}\n\n\t\tmeshopt_Allocator::storage().deallocate(data);\n\t}\n};\n\ntemplate <typename T>\nstruct meshopt_IndexAdapter<T, true>\n{\n\tunsigned int* data;\n\n\tmeshopt_IndexAdapter(T* result, const T* input, size_t)\n\t    : data(reinterpret_cast<unsigned int*>(result ? result : const_cast<T*>(input)))\n\t{\n\t}\n};\n\ntemplate <typename T>\ninline size_t meshopt_generateVertexRemap(unsigned int* destination, const T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, indices ? index_count : 0);\n\n\treturn meshopt_generateVertexRemap(destination, indices ? in.data : NULL, index_count, vertices, vertex_count, vertex_size);\n}\n\ntemplate <typename T>\ninline size_t meshopt_generateVertexRemapMulti(unsigned int* destination, const T* indices, size_t index_count, size_t vertex_count, const meshopt_Stream* streams, size_t stream_count)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, indices ? index_count : 0);\n\n\treturn meshopt_generateVertexRemapMulti(destination, indices ? in.data : NULL, index_count, vertex_count, streams, stream_count);\n}\n\ntemplate <typename F>\ninline size_t meshopt_generateVertexRemapCustom(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, F callback)\n{\n\tstruct Call\n\t{\n\t\tstatic int compare(void* context, unsigned int lhs, unsigned int rhs) { return (*static_cast<F*>(context))(lhs, rhs) ? 1 : 0; }\n\t};\n\n\treturn meshopt_generateVertexRemapCustom(destination, indices, index_count, vertex_positions, vertex_count, vertex_positions_stride, &Call::compare, &callback);\n}\n\ntemplate <typename T, typename F>\ninline size_t meshopt_generateVertexRemapCustom(unsigned int* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, F callback)\n{\n\tstruct Call\n\t{\n\t\tstatic int compare(void* context, unsigned int lhs, unsigned int rhs) { return (*static_cast<F*>(context))(lhs, rhs) ? 1 : 0; }\n\t};\n\n\tmeshopt_IndexAdapter<T> in(NULL, indices, indices ? index_count : 0);\n\n\treturn meshopt_generateVertexRemapCustom(destination, indices ? in.data : NULL, index_count, vertex_positions, vertex_count, vertex_positions_stride, &Call::compare, &callback);\n}\n\ntemplate <typename T>\ninline void meshopt_remapIndexBuffer(T* destination, const T* indices, size_t index_count, const unsigned int* remap)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, indices ? index_count : 0);\n\tmeshopt_IndexAdapter<T> out(destination, 0, index_count);\n\n\tmeshopt_remapIndexBuffer(out.data, indices ? in.data : NULL, index_count, remap);\n}\n\ntemplate <typename T>\ninline void meshopt_generateShadowIndexBuffer(T* destination, const T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, index_count);\n\n\tmeshopt_generateShadowIndexBuffer(out.data, in.data, index_count, vertices, vertex_count, vertex_size, vertex_stride);\n}\n\ntemplate <typename T>\ninline void meshopt_generateShadowIndexBufferMulti(T* destination, const T* indices, size_t index_count, size_t vertex_count, const meshopt_Stream* streams, size_t stream_count)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, index_count);\n\n\tmeshopt_generateShadowIndexBufferMulti(out.data, in.data, index_count, vertex_count, streams, stream_count);\n}\n\ntemplate <typename T>\ninline void meshopt_generateAdjacencyIndexBuffer(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, index_count * 2);\n\n\tmeshopt_generateAdjacencyIndexBuffer(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride);\n}\n\ntemplate <typename T>\ninline void meshopt_generateTessellationIndexBuffer(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, index_count * 4);\n\n\tmeshopt_generateTessellationIndexBuffer(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride);\n}\n\ntemplate <typename T>\ninline size_t meshopt_generateProvokingIndexBuffer(T* destination, unsigned int* reorder, const T* indices, size_t index_count, size_t vertex_count)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, index_count);\n\n\tsize_t bound = vertex_count + (index_count / 3);\n\tassert(size_t(T(bound - 1)) == bound - 1); // bound - 1 must fit in T\n\t(void)bound;\n\n\treturn meshopt_generateProvokingIndexBuffer(out.data, reorder, in.data, index_count, vertex_count);\n}\n\ntemplate <typename T>\ninline void meshopt_optimizeVertexCache(T* destination, const T* indices, size_t index_count, size_t vertex_count)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, index_count);\n\n\tmeshopt_optimizeVertexCache(out.data, in.data, index_count, vertex_count);\n}\n\ntemplate <typename T>\ninline void meshopt_optimizeVertexCacheStrip(T* destination, const T* indices, size_t index_count, size_t vertex_count)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, index_count);\n\n\tmeshopt_optimizeVertexCacheStrip(out.data, in.data, index_count, vertex_count);\n}\n\ntemplate <typename T>\ninline void meshopt_optimizeVertexCacheFifo(T* destination, const T* indices, size_t index_count, size_t vertex_count, unsigned int cache_size)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, index_count);\n\n\tmeshopt_optimizeVertexCacheFifo(out.data, in.data, index_count, vertex_count, cache_size);\n}\n\ntemplate <typename T>\ninline void meshopt_optimizeOverdraw(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float threshold)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, index_count);\n\n\tmeshopt_optimizeOverdraw(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, threshold);\n}\n\ntemplate <typename T>\ninline size_t meshopt_optimizeVertexFetchRemap(unsigned int* destination, const T* indices, size_t index_count, size_t vertex_count)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\n\treturn meshopt_optimizeVertexFetchRemap(destination, in.data, index_count, vertex_count);\n}\n\ntemplate <typename T>\ninline size_t meshopt_optimizeVertexFetch(void* destination, T* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size)\n{\n\tmeshopt_IndexAdapter<T> inout(indices, indices, index_count);\n\n\treturn meshopt_optimizeVertexFetch(destination, inout.data, index_count, vertices, vertex_count, vertex_size);\n}\n\ntemplate <typename T>\ninline size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_size, const T* indices, size_t index_count)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\n\treturn meshopt_encodeIndexBuffer(buffer, buffer_size, in.data, index_count);\n}\n\ntemplate <typename T>\ninline int meshopt_decodeIndexBuffer(T* destination, size_t index_count, const unsigned char* buffer, size_t buffer_size)\n{\n\tchar index_size_valid[sizeof(T) == 2 || sizeof(T) == 4 ? 1 : -1];\n\t(void)index_size_valid;\n\n\treturn meshopt_decodeIndexBuffer(destination, index_count, sizeof(T), buffer, buffer_size);\n}\n\ntemplate <typename T>\ninline size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_size, const T* indices, size_t index_count)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\n\treturn meshopt_encodeIndexSequence(buffer, buffer_size, in.data, index_count);\n}\n\ntemplate <typename T>\ninline int meshopt_decodeIndexSequence(T* destination, size_t index_count, const unsigned char* buffer, size_t buffer_size)\n{\n\tchar index_size_valid[sizeof(T) == 2 || sizeof(T) == 4 ? 1 : -1];\n\t(void)index_size_valid;\n\n\treturn meshopt_decodeIndexSequence(destination, index_count, sizeof(T), buffer, buffer_size);\n}\n\ntemplate <typename V, typename T>\ninline int meshopt_decodeMeshlet(V* vertices, size_t vertex_count, T* triangles, size_t triangle_count, const unsigned char* buffer, size_t buffer_size)\n{\n\tchar types_valid[(sizeof(V) == 2 || sizeof(V) == 4) && (sizeof(T) == 1 || sizeof(T) == 4) ? 1 : -1];\n\t(void)types_valid;\n\n\treturn meshopt_decodeMeshlet(vertices, vertex_count, sizeof(V), triangles, triangle_count, sizeof(T) == 1 ? 3 : 4, buffer, buffer_size);\n}\n\ninline size_t meshopt_encodeVertexBufferLevel(unsigned char* buffer, size_t buffer_size, const void* vertices, size_t vertex_count, size_t vertex_size, int level)\n{\n\treturn meshopt_encodeVertexBufferLevel(buffer, buffer_size, vertices, vertex_count, vertex_size, level, -1);\n}\n\ntemplate <typename T>\ninline size_t meshopt_simplify(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options, float* result_error)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, index_count);\n\n\treturn meshopt_simplify(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, target_index_count, target_error, options, result_error);\n}\n\ntemplate <typename T>\ninline size_t meshopt_simplifyWithAttributes(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* result_error)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, index_count);\n\n\treturn meshopt_simplifyWithAttributes(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, vertex_attributes, vertex_attributes_stride, attribute_weights, attribute_count, vertex_lock, target_index_count, target_error, options, result_error);\n}\n\ntemplate <typename T>\ninline size_t meshopt_simplifyWithUpdate(T* indices, size_t index_count, float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* result_error)\n{\n\tmeshopt_IndexAdapter<T> inout(indices, indices, index_count);\n\n\treturn meshopt_simplifyWithUpdate(inout.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, vertex_attributes, vertex_attributes_stride, attribute_weights, attribute_count, vertex_lock, target_index_count, target_error, options, result_error);\n}\n\ntemplate <typename T>\ninline size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float* result_error)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, index_count);\n\n\treturn meshopt_simplifySloppy(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, NULL, target_index_count, target_error, result_error);\n}\n\ntemplate <typename T>\ninline size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const unsigned char* vertex_lock, size_t target_index_count, float target_error, float* result_error)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, index_count);\n\n\treturn meshopt_simplifySloppy(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, vertex_lock, target_index_count, target_error, result_error);\n}\n\ntemplate <typename T>\ninline size_t meshopt_simplifyPrune(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float target_error)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, index_count);\n\n\treturn meshopt_simplifyPrune(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, target_error);\n}\n\ntemplate <typename T>\ninline size_t meshopt_stripify(T* destination, const T* indices, size_t index_count, size_t vertex_count, T restart_index)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, (index_count / 3) * 5);\n\n\treturn meshopt_stripify(out.data, in.data, index_count, vertex_count, unsigned(restart_index));\n}\n\ntemplate <typename T>\ninline size_t meshopt_unstripify(T* destination, const T* indices, size_t index_count, T restart_index)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, (index_count - 2) * 3);\n\n\treturn meshopt_unstripify(out.data, in.data, index_count, unsigned(restart_index));\n}\n\ntemplate <typename T>\ninline meshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const T* indices, size_t index_count, size_t vertex_count, unsigned int cache_size, unsigned int warp_size, unsigned int primgroup_size)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\n\treturn meshopt_analyzeVertexCache(in.data, index_count, vertex_count, cache_size, warp_size, primgroup_size);\n}\n\ntemplate <typename T>\ninline meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch(const T* indices, size_t index_count, size_t vertex_count, size_t vertex_size)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\n\treturn meshopt_analyzeVertexFetch(in.data, index_count, vertex_count, vertex_size);\n}\n\ntemplate <typename T>\ninline meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\n\treturn meshopt_analyzeOverdraw(in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride);\n}\n\ntemplate <typename T>\ninline meshopt_CoverageStatistics meshopt_analyzeCoverage(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\n\treturn meshopt_analyzeCoverage(in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride);\n}\n\ntemplate <typename T>\ninline size_t meshopt_buildMeshlets(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\n\treturn meshopt_buildMeshlets(meshlets, meshlet_vertices, meshlet_triangles, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, max_vertices, max_triangles, cone_weight);\n}\n\ntemplate <typename T>\ninline size_t meshopt_buildMeshletsScan(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\n\treturn meshopt_buildMeshletsScan(meshlets, meshlet_vertices, meshlet_triangles, in.data, index_count, vertex_count, max_vertices, max_triangles);\n}\n\ntemplate <typename T>\ninline size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float cone_weight, float split_factor)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\n\treturn meshopt_buildMeshletsFlex(meshlets, meshlet_vertices, meshlet_triangles, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, max_vertices, min_triangles, max_triangles, cone_weight, split_factor);\n}\n\ntemplate <typename T>\ninline size_t meshopt_buildMeshletsSpatial(meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t min_triangles, size_t max_triangles, float fill_weight)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\n\treturn meshopt_buildMeshletsSpatial(meshlets, meshlet_vertices, meshlet_triangles, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, max_vertices, min_triangles, max_triangles, fill_weight);\n}\n\ntemplate <typename T>\ninline meshopt_Bounds meshopt_computeClusterBounds(const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\n\treturn meshopt_computeClusterBounds(in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride);\n}\n\ntemplate <typename T>\ninline size_t meshopt_partitionClusters(unsigned int* destination, const T* cluster_indices, size_t total_index_count, const unsigned int* cluster_index_counts, size_t cluster_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_partition_size)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, cluster_indices, total_index_count);\n\n\treturn meshopt_partitionClusters(destination, in.data, total_index_count, cluster_index_counts, cluster_count, vertex_positions, vertex_count, vertex_positions_stride, target_partition_size);\n}\n\ntemplate <typename T>\ninline void meshopt_spatialSortTriangles(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tmeshopt_IndexAdapter<T> in(NULL, indices, index_count);\n\tmeshopt_IndexAdapter<T> out(destination, NULL, index_count);\n\n\tmeshopt_spatialSortTriangles(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride);\n}\n#endif\n\n/**\n * Copyright (c) 2016-2026 Arseny Kapoulkine\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n */\n"
  },
  {
    "path": "src/opacitymap.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <string.h>\n\nnamespace meshopt\n{\n\n// opacity micromaps use a \"bird\" traversal order which recursively subdivides the triangles:\n// https://docs.vulkan.org/spec/latest/_images/micromap-subd.svg\n// note that triangles 0 and 2 have the same winding as the source triangle, however triangles 1 (flipped)\n// and 3 (upright) have flipped winding; this is obvious from the level 2 subdivision in the diagram above\ninline size_t getLevelSize(int level, int states)\n{\n\t// 1-bit 2-state or 2-bit 4-state per micro triangle, rounded up to whole bytes\n\treturn ((1 << (level * 2)) * (states >> 1) + 7) >> 3;\n}\n\nstruct Texture\n{\n\tconst unsigned char* data;\n\tsize_t stride, pitch;\n\tunsigned int width, height;\n};\n\nstatic float sampleTexture(const Texture& texture, float u, float v)\n{\n\t// wrap texture coordinates; floor is expensive so only call it if we're outside of [0, 1] range (+eps)\n\tu = fabsf(u - 0.5f) > 0.5f ? u - floorf(u) : u;\n\tv = fabsf(v - 0.5f) > 0.5f ? v - floorf(v) : v;\n\n\t// convert from [0, 1] to pixel grid and shift so that texel centers are on an integer grid\n\tu = u * float(int(texture.width)) - 0.5f;\n\tv = v * float(int(texture.height)) - 0.5f;\n\n\t// clamp u/v along the left/top edge to avoid extrapolation since we don't interpolate across the edge\n\tu = u < 0 ? 0.f : u;\n\tv = v < 0 ? 0.f : v;\n\n\t// note: u/v is now in [0, size-0.5]; float->int->float is usually less expensive than floor\n\tint x = int(u);\n\tint y = int(v);\n\tfloat rx = u - float(x);\n\tfloat ry = v - float(y);\n\n\t// safeguard: this should not happen but if it ever does, ensure the accesses are inbounds\n\tif (unsigned(x) >= texture.width || unsigned(y) >= texture.height)\n\t\treturn 0.f;\n\n\t// clamp the offsets instead of wrapping for simplicity and performance\n\tsize_t offset = size_t(y) * texture.pitch + x * texture.stride;\n\tsize_t offsetx = (x + 1 < int(texture.width)) ? texture.stride : 0;\n\tsize_t offsety = (y + 1 < int(texture.height)) ? texture.pitch : 0;\n\n\tunsigned char a00 = texture.data[offset];\n\tunsigned char a10 = texture.data[offset + offsetx];\n\tunsigned char a01 = texture.data[offset + offsety];\n\tunsigned char a11 = texture.data[offset + offsetx + offsety];\n\n\t// bilinear interpolation; we do it partially in integer space, deferring full conversion to [0, 1] until the end\n\tfloat ax0 = float(a00) + float(a10 - a00) * rx;\n\tfloat ax1 = float(a01) + float(a11 - a01) * rx;\n\treturn (ax0 + (ax1 - ax0) * ry) * (1.f / 255.f);\n}\n\nstatic unsigned int hashUpdate4u(unsigned int h, const unsigned char* key, size_t len)\n{\n\t// MurmurHash2\n\tconst unsigned int m = 0x5bd1e995;\n\tconst int r = 24;\n\n\twhile (len >= 4)\n\t{\n\t\tunsigned int k;\n\t\tmemcpy(&k, key, sizeof(k));\n\n\t\tk *= m;\n\t\tk ^= k >> r;\n\t\tk *= m;\n\n\t\th *= m;\n\t\th ^= k;\n\n\t\tkey += 4;\n\t\tlen -= 4;\n\t}\n\n\treturn h;\n}\n\nstruct TriangleOMM\n{\n\tint uvs[6];\n\tint level;\n};\n\nstruct TriangleOMMHasher\n{\n\tconst TriangleOMM* data;\n\n\tsize_t hash(unsigned int index) const\n\t{\n\t\tconst TriangleOMM& tri = data[index];\n\n\t\treturn hashUpdate4u(tri.level, reinterpret_cast<const unsigned char*>(tri.uvs), sizeof(tri.uvs));\n\t}\n\n\tbool equal(unsigned int lhs, unsigned int rhs) const\n\t{\n\t\tconst TriangleOMM& lt = data[lhs];\n\t\tconst TriangleOMM& rt = data[rhs];\n\n\t\treturn lt.level == rt.level && memcmp(lt.uvs, rt.uvs, sizeof(lt.uvs)) == 0;\n\t}\n};\n\nstruct OMMHasher\n{\n\tconst unsigned char* data;\n\tconst unsigned int* offsets;\n\tconst unsigned char* levels;\n\tint states;\n\n\tsize_t hash(unsigned int index) const\n\t{\n\t\tconst unsigned char* key = data + offsets[index];\n\t\tsize_t size = getLevelSize(levels[index], states);\n\n\t\tunsigned int h = levels[index];\n\n\t\t// MurmurHash2 for large keys, simple fold for small; note that size is a power of two\n\t\tif (size < 4)\n\t\t\th ^= key[0] | (key[size - 1] << 8);\n\t\telse\n\t\t\th = hashUpdate4u(h, key, size);\n\n\t\t// MurmurHash2 finalizer\n\t\th ^= h >> 13;\n\t\th *= 0x5bd1e995;\n\t\th ^= h >> 15;\n\t\treturn h;\n\t}\n\n\tbool equal(unsigned int lhs, unsigned int rhs) const\n\t{\n\t\tsize_t size = getLevelSize(levels[lhs], states);\n\n\t\treturn levels[lhs] == levels[rhs] && memcmp(data + offsets[lhs], data + offsets[rhs], size) == 0;\n\t}\n};\n\nstatic size_t hashBuckets3(size_t count)\n{\n\tsize_t buckets = 1;\n\twhile (buckets < count + count / 4)\n\t\tbuckets *= 2;\n\n\treturn buckets;\n}\n\ntemplate <typename T, typename Hash>\nstatic T* hashLookup3(T* table, size_t buckets, const Hash& hash, const T& key, const T& empty)\n{\n\tassert(buckets > 0);\n\tassert((buckets & (buckets - 1)) == 0);\n\n\tsize_t hashmod = buckets - 1;\n\tsize_t bucket = hash.hash(key) & hashmod;\n\n\tfor (size_t probe = 0; probe <= hashmod; ++probe)\n\t{\n\t\tT& item = table[bucket];\n\n\t\tif (item == empty)\n\t\t\treturn &item;\n\n\t\tif (hash.equal(item, key))\n\t\t\treturn &item;\n\n\t\t// hash collision, quadratic probing\n\t\tbucket = (bucket + probe + 1) & hashmod;\n\t}\n\n\tassert(false && \"Hash table is full\"); // unreachable\n\treturn NULL;\n}\n\ninline int quantizeSubpixel(float v, unsigned int size)\n{\n\treturn int(v * float(int(size) * 4) + (v >= 0 ? 0.5f : -0.5f));\n}\n\nstatic int rasterizeEdge(float u0, float v0, float u1, float v1, int edgeres, const Texture& texture)\n{\n\tfloat edgestep = 1.f / float(edgeres + 1);\n\n\tfloat ud = (u1 - u0) * edgestep, vd = (v1 - v0) * edgestep;\n\tfloat u = u0, v = v0;\n\n\tint mask = 0;\n\tint count = 0;\n\n\tfor (int i = 0; i < edgeres; ++i)\n\t{\n\t\tu += ud;\n\t\tv += vd;\n\n\t\tfloat a = sampleTexture(texture, u, v);\n\t\tmask |= (a >= 0.5f) << i;\n\t\tcount += a >= 0.5f;\n\t}\n\n\treturn mask | (count << 16);\n}\n\ntemplate <int States>\nstatic void rasterizeOpacity0(unsigned char* result, size_t index, float a0, float a1, float a2, float ac, int e0, int e1, int e2, int edgeres)\n{\n\tint states = States;\n\n\t// basic coverage estimator from center and corner values; trained to minimize error\n\tfloat coverage = (a0 + a1 + a2) * 0.12f + ac * 0.64f;\n\n\tif (edgeres)\n\t{\n\t\tfloat edgescale = 1.f / edgeres;\n\n\t\t// if we have edge samples, we can get a better coverage estimate by including them; trained to minimize error\n\t\tcoverage = ac * 0.22f + float((e0 >> 16) + (e1 >> 16) + (e2 >> 16)) * edgescale * 0.23f + (a0 + a1 + a2) * 0.03f;\n\t}\n\n\tif (states == 2)\n\t{\n\t\tresult[index / 8] |= (coverage >= 0.5f) << (index % 8);\n\t\treturn;\n\t}\n\n\tint transp = (a0 < 0.5f) & (a1 < 0.5f) & (a2 < 0.5f) & (ac < 0.5f);\n\tint opaque = (a0 > 0.5f) & (a1 > 0.5f) & (a2 > 0.5f) & (ac > 0.5f);\n\n\t// treat state as known if thresholding of corners & centers against wider bounds is consistent\n\t// for unknown states, we currently use the same formula as the 2-state opacity for better consistency with forced 2-state\n\tint unknown = 2 + (coverage >= 0.5f);\n\tint state = (transp | opaque) ? opaque : unknown;\n\n\tif (edgeres && (transp | opaque))\n\t{\n\t\t// if we have edge samples, ensure they are consistent too, falling back to unknown if not\n\t\tint exp = opaque ? (1 << edgeres) - 1 : 0;\n\t\tint eok = ((e0 & 0xffff) == exp) & ((e1 & 0xffff) == exp) & ((e2 & 0xffff) == exp);\n\n\t\tstate = eok ? state : unknown;\n\t}\n\n\tresult[index / 4] |= state << ((index % 4) * 2);\n}\n\ntemplate <int States>\nstatic void rasterizeOpacity1(unsigned char* result, size_t index, int edgeres, const float* c0, const float* c1, const float* c2, const Texture& texture)\n{\n\t// compute each edge midpoint & sample\n\tfloat c01[3] = {(c0[0] + c1[0]) / 2, (c0[1] + c1[1]) / 2, 0.f};\n\tfloat c12[3] = {(c1[0] + c2[0]) / 2, (c1[1] + c2[1]) / 2, 0.f};\n\tfloat c20[3] = {(c2[0] + c0[0]) / 2, (c2[1] + c0[1]) / 2, 0.f};\n\n\tc01[2] = sampleTexture(texture, c01[0], c01[1]);\n\tc12[2] = sampleTexture(texture, c12[0], c12[1]);\n\tc20[2] = sampleTexture(texture, c20[0], c20[1]);\n\n\t// corner tables for each edge, and corner + edge tables for each triangle\n\t// edges are numbered counter clockwise, 6 outer first, 3 inner last; triangle vertex and edge references are in triangle winding order\n\tstatic const unsigned char edges[9][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 0}, {5, 1}, {1, 3}, {3, 5}};\n\tstatic const unsigned char triangles[4][6] = {{0, 1, 5, 0, 6, 5}, {5, 3, 1, 8, 7, 6}, {1, 2, 3, 1, 2, 7}, {3, 5, 4, 8, 4, 3}};\n\n\tconst float* points[] = {c0, c01, c1, c12, c2, c20};\n\n\tint em[9] = {};\n\n\t// sample additional points on the edges to improve state estimation\n\tif (edgeres > 0)\n\t\tfor (size_t i = 0; i < 9; ++i)\n\t\t\tem[i] = rasterizeEdge(points[edges[i][0]][0], points[edges[i][0]][1], points[edges[i][1]][0], points[edges[i][1]][1], edgeres, texture);\n\n\tfor (size_t i = 0; i < 4; ++i)\n\t{\n\t\tconst unsigned char* tri = triangles[i];\n\t\tconst float* p0 = points[tri[0]];\n\t\tconst float* p1 = points[tri[1]];\n\t\tconst float* p2 = points[tri[2]];\n\n\t\t// compute triangle center & sample\n\t\tfloat uc = (p0[0] + p1[0] + p2[0]) * (1.f / 3.f);\n\t\tfloat vc = (p0[1] + p1[1] + p2[1]) * (1.f / 3.f);\n\t\tfloat ac = sampleTexture(texture, uc, vc);\n\n\t\t// rasterize opacity state based on alpha values in corners and center (and optionally edges)\n\t\trasterizeOpacity0<States>(result, index * 4 + i, p0[2], p1[2], p2[2], ac, em[tri[3]], em[tri[4]], em[tri[5]], edgeres);\n\t}\n}\n\ntemplate <int States>\nstatic void rasterizeOpacityRec(unsigned char* result, size_t index, int level, int edgeres, const float* c0, const float* c1, const float* c2, const Texture& texture)\n{\n\tif (level == 0)\n\t{\n\t\t// compute triangle center & sample\n\t\tfloat uc = (c0[0] + c1[0] + c2[0]) * (1.f / 3.f);\n\t\tfloat vc = (c0[1] + c1[1] + c2[1]) * (1.f / 3.f);\n\t\tfloat ac = sampleTexture(texture, uc, vc);\n\n\t\tint e0 = 0, e1 = 0, e2 = 0;\n\n\t\tif (edgeres > 0)\n\t\t{\n\t\t\t// sample additional points on the edges to improve state estimation\n\t\t\te0 = rasterizeEdge(c0[0], c0[1], c1[0], c1[1], edgeres, texture);\n\t\t\te1 = rasterizeEdge(c1[0], c1[1], c2[0], c2[1], edgeres, texture);\n\t\t\te2 = rasterizeEdge(c2[0], c2[1], c0[0], c0[1], edgeres, texture);\n\t\t}\n\n\t\t// rasterize opacity state based on alpha values in corners and center (and optionally edges)\n\t\treturn rasterizeOpacity0<States>(result, index, c0[2], c1[2], c2[2], ac, e0, e1, e2, edgeres);\n\t}\n\n\t// fast path: equivalent to recursive rasterization, but reuses edge data to reduce sample count\n\tif (level == 1 && edgeres > 0)\n\t\treturn rasterizeOpacity1<States>(result, index, edgeres, c0, c1, c2, texture);\n\n\t// compute each edge midpoint & sample\n\tfloat c01[3] = {(c0[0] + c1[0]) / 2, (c0[1] + c1[1]) / 2, 0.f};\n\tfloat c12[3] = {(c1[0] + c2[0]) / 2, (c1[1] + c2[1]) / 2, 0.f};\n\tfloat c20[3] = {(c2[0] + c0[0]) / 2, (c2[1] + c0[1]) / 2, 0.f};\n\n\tc01[2] = sampleTexture(texture, c01[0], c01[1]);\n\tc12[2] = sampleTexture(texture, c12[0], c12[1]);\n\tc20[2] = sampleTexture(texture, c20[0], c20[1]);\n\n\t// recursively rasterize each triangle\n\t// note: triangles 1 and 3 have flipped winding, and 1 is flipped upside down\n\trasterizeOpacityRec<States>(result, index * 4 + 0, level - 1, edgeres, c0, c01, c20, texture);\n\trasterizeOpacityRec<States>(result, index * 4 + 1, level - 1, edgeres, c20, c12, c01, texture);\n\trasterizeOpacityRec<States>(result, index * 4 + 2, level - 1, edgeres, c01, c1, c12, texture);\n\trasterizeOpacityRec<States>(result, index * 4 + 3, level - 1, edgeres, c12, c20, c2, texture);\n}\n\nstatic int getSpecialIndex(const unsigned char* data, int level, int states)\n{\n\tint first = data[0] & (states == 2 ? 1 : 3);\n\tint special = -(1 + first);\n\n\t// at level 0, every micromap can be converted to a special index\n\tif (level == 0)\n\t\treturn special;\n\n\t// at level 1 with 2 states, the byte is partially filled so we need a separate check\n\tif (level == 1 && states == 2)\n\t\treturn (data[0] & 15) == ((-first) & 15) ? special : 0;\n\n\t// otherwise we need to check that all bytes are consistent with the first value and we can do this byte-wise\n\tint expected = first * (states == 2 ? 0xff : 0x55);\n\tsize_t size = getLevelSize(level, states);\n\n\tfor (size_t i = 0; i < size; ++i)\n\t\tif (data[i] != expected)\n\t\t\treturn 0;\n\n\treturn special;\n}\n\n} // namespace meshopt\n\nsize_t meshopt_opacityMapMeasure(unsigned char* levels, unsigned int* sources, int* omm_indices, const unsigned int* indices, size_t index_count, const float* vertex_uvs, size_t vertex_count, size_t vertex_uvs_stride, unsigned int texture_width, unsigned int texture_height, int max_level, float target_edge)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(vertex_uvs_stride >= 8 && vertex_uvs_stride <= 256);\n\tassert(vertex_uvs_stride % sizeof(float) == 0);\n\tassert(unsigned(texture_width - 1) < 16384 && unsigned(texture_height - 1) < 16384);\n\tassert(max_level >= 0 && max_level <= 12);\n\tassert(target_edge >= 0);\n\n\t(void)vertex_count;\n\n\tmeshopt_Allocator allocator;\n\n\tsize_t vertex_stride_float = vertex_uvs_stride / sizeof(float);\n\tfloat texture_area = float(texture_width) * float(texture_height);\n\n\t// hash map used to deduplicate triangle rasterization requests based on UV\n\tsize_t table_size = hashBuckets3(index_count / 3);\n\tunsigned int* table = allocator.allocate<unsigned int>(table_size);\n\tmemset(table, -1, table_size * sizeof(unsigned int));\n\n\tTriangleOMM* triangles = allocator.allocate<TriangleOMM>(index_count / 3);\n\tTriangleOMMHasher hasher = {triangles};\n\n\tsize_t result = 0;\n\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tunsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2];\n\t\tassert(a < vertex_count && b < vertex_count && c < vertex_count);\n\n\t\tfloat u0 = vertex_uvs[a * vertex_stride_float + 0], v0 = vertex_uvs[a * vertex_stride_float + 1];\n\t\tfloat u1 = vertex_uvs[b * vertex_stride_float + 0], v1 = vertex_uvs[b * vertex_stride_float + 1];\n\t\tfloat u2 = vertex_uvs[c * vertex_stride_float + 0], v2 = vertex_uvs[c * vertex_stride_float + 1];\n\n\t\tint level = max_level;\n\n\t\tif (target_edge > 0)\n\t\t{\n\t\t\t// compute ratio of edge length (in texels) to target and determine subdivision level\n\t\t\tfloat uvarea = fabsf((u1 - u0) * (v2 - v0) - (u2 - u0) * (v1 - v0)) * 0.5f * texture_area;\n\t\t\tfloat ratio = sqrtf(uvarea) / target_edge;\n\t\t\tfloat levelf = log2f(ratio > 1 ? ratio : 1);\n\n\t\t\t// round to nearest and clamp\n\t\t\tlevel = int(levelf + 0.5f);\n\t\t\tlevel = level < 0 ? 0 : level;\n\t\t\tlevel = level < max_level ? level : max_level;\n\t\t}\n\n\t\t// deduplicate rasterization requests based on UV\n\t\tint su0 = quantizeSubpixel(u0, texture_width), sv0 = quantizeSubpixel(v0, texture_height);\n\t\tint su1 = quantizeSubpixel(u1, texture_width), sv1 = quantizeSubpixel(v1, texture_height);\n\t\tint su2 = quantizeSubpixel(u2, texture_width), sv2 = quantizeSubpixel(v2, texture_height);\n\n\t\tTriangleOMM tri = {{su0, sv0, su1, sv1, su2, sv2}, level};\n\t\ttriangles[result] = tri; // speculatively write triangle data to give hasher a way to compare it\n\n\t\tunsigned int* entry = hashLookup3(table, table_size, hasher, unsigned(result), ~0u);\n\n\t\tif (*entry == ~0u)\n\t\t{\n\t\t\t*entry = unsigned(result);\n\t\t\tlevels[result] = (unsigned char)level;\n\t\t\tsources[result] = unsigned(i / 3);\n\t\t\tresult++;\n\t\t}\n\n\t\tomm_indices[i / 3] = int(*entry);\n\t}\n\n\treturn result;\n}\n\nsize_t meshopt_opacityMapEntrySize(int level, int states)\n{\n\tassert(level >= 0 && level <= 12);\n\tassert(states == 2 || states == 4);\n\n\treturn meshopt::getLevelSize(level, states);\n}\n\nvoid meshopt_opacityMapRasterize(unsigned char* result, int level, int states, const float* uv0, const float* uv1, const float* uv2, const unsigned char* texture_data, size_t texture_stride, size_t texture_pitch, unsigned int texture_width, unsigned int texture_height)\n{\n\tusing namespace meshopt;\n\n\tassert(level >= 0 && level <= 12);\n\tassert(states == 2 || states == 4);\n\tassert(unsigned(texture_width - 1) < 16384 && unsigned(texture_height - 1) < 16384);\n\tassert(texture_stride >= 1 && texture_stride <= 4);\n\tassert(texture_pitch >= texture_stride * texture_width);\n\n\tmemset(result, 0, getLevelSize(level, states));\n\n\tTexture texture = {texture_data, texture_stride, texture_pitch, texture_width, texture_height};\n\n\t// determine number of edge samples for conservative state estimation\n\tfloat texture_area = float(texture_width) * float(texture_height);\n\tfloat uvarea = fabsf((uv1[0] - uv0[0]) * (uv2[1] - uv0[1]) - (uv2[0] - uv0[0]) * (uv1[1] - uv0[1])) * 0.5f * texture_area;\n\tfloat uvedge = sqrtf(uvarea) / float(1 << level);\n\n\t// target ~2px distance between edge samples (assuming equilateral microtriangles)\n\tint edgeres = int(uvedge * 0.75f);\n\tedgeres = edgeres < 0 ? 0 : edgeres;\n\tedgeres = edgeres > 7 ? 7 : edgeres;\n\n\t// rasterize all micro triangles recursively, passing corner data down to reduce redundant sampling\n\tfloat c0[3] = {uv0[0], uv0[1], sampleTexture(texture, uv0[0], uv0[1])};\n\tfloat c1[3] = {uv1[0], uv1[1], sampleTexture(texture, uv1[0], uv1[1])};\n\tfloat c2[3] = {uv2[0], uv2[1], sampleTexture(texture, uv2[0], uv2[1])};\n\n\t(states == 2 ? rasterizeOpacityRec<2> : rasterizeOpacityRec<4>)(result, 0, level, edgeres, c0, c1, c2, texture);\n}\n\nsize_t meshopt_opacityMapCompact(unsigned char* data, size_t data_size, unsigned char* levels, unsigned int* offsets, size_t omm_count, int* omm_indices, size_t triangle_count, int states)\n{\n\tusing namespace meshopt;\n\n\tassert(states == 2 || states == 4);\n\n\tmeshopt_Allocator allocator;\n\n\tunsigned char* data_old = allocator.allocate<unsigned char>(data_size);\n\tmemcpy(data_old, data, data_size);\n\n\tsize_t table_size = hashBuckets3(omm_count);\n\tunsigned int* table = allocator.allocate<unsigned int>(table_size);\n\tmemset(table, -1, table_size * sizeof(unsigned int));\n\n\tOMMHasher hasher = {data, offsets, levels, states};\n\n\tint* remap = allocator.allocate<int>(omm_count);\n\n\tsize_t next = 0;\n\tsize_t offset = 0;\n\n\tfor (size_t i = 0; i < omm_count; ++i)\n\t{\n\t\tint level = levels[i];\n\t\tassert(level >= 0 && level <= 12);\n\n\t\tconst unsigned char* old = data_old + offsets[i];\n\t\tsize_t size = getLevelSize(level, states);\n\t\tassert(offsets[i] + size <= data_size);\n\n\t\t// try to convert to a special index if all micro-triangle states are the same\n\t\tint special = getSpecialIndex(old, level, states);\n\t\tif (special < 0)\n\t\t{\n\t\t\tremap[i] = special;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// speculatively write data to give hasher a way to compare it\n\t\tmemcpy(data + offset, old, size);\n\t\toffsets[next] = unsigned(offset);\n\t\tlevels[next] = (unsigned char)level;\n\n\t\tunsigned int* entry = hashLookup3(table, table_size, hasher, unsigned(next), ~0u);\n\n\t\tif (*entry == ~0u)\n\t\t{\n\t\t\t*entry = unsigned(next);\n\t\t\tnext++;\n\t\t\toffset += size;\n\t\t}\n\n\t\tremap[i] = int(*entry);\n\t}\n\n\t// remap triangle indices to new indices or special indices\n\tfor (size_t i = 0; i < triangle_count; ++i)\n\t{\n\t\tassert(omm_indices[i] < 0 || unsigned(omm_indices[i]) < omm_count);\n\t\tomm_indices[i] = omm_indices[i] < 0 ? omm_indices[i] : remap[omm_indices[i]];\n\t}\n\n\treturn next;\n}\n"
  },
  {
    "path": "src/overdrawoptimizer.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <string.h>\n\n// This work is based on:\n// Pedro Sander, Diego Nehab and Joshua Barczak. Fast Triangle Reordering for Vertex Locality and Reduced Overdraw. 2007\nnamespace meshopt\n{\n\nstatic void calculateSortData(float* sort_data, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const unsigned int* clusters, size_t cluster_count)\n{\n\tsize_t vertex_stride_float = vertex_positions_stride / sizeof(float);\n\n\tfloat mesh_centroid[3] = {};\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tconst float* p = vertex_positions + vertex_stride_float * i;\n\n\t\tmesh_centroid[0] += p[0];\n\t\tmesh_centroid[1] += p[1];\n\t\tmesh_centroid[2] += p[2];\n\t}\n\n\tmesh_centroid[0] /= float(vertex_count);\n\tmesh_centroid[1] /= float(vertex_count);\n\tmesh_centroid[2] /= float(vertex_count);\n\n\tfor (size_t cluster = 0; cluster < cluster_count; ++cluster)\n\t{\n\t\tsize_t cluster_begin = clusters[cluster] * 3;\n\t\tsize_t cluster_end = (cluster + 1 < cluster_count) ? clusters[cluster + 1] * 3 : index_count;\n\t\tassert(cluster_begin < cluster_end);\n\n\t\tfloat cluster_area = 0;\n\t\tfloat cluster_centroid[3] = {};\n\t\tfloat cluster_normal[3] = {};\n\n\t\tfor (size_t i = cluster_begin; i < cluster_end; i += 3)\n\t\t{\n\t\t\tconst float* p0 = vertex_positions + vertex_stride_float * indices[i + 0];\n\t\t\tconst float* p1 = vertex_positions + vertex_stride_float * indices[i + 1];\n\t\t\tconst float* p2 = vertex_positions + vertex_stride_float * indices[i + 2];\n\n\t\t\tfloat p10[3] = {p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]};\n\t\t\tfloat p20[3] = {p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]};\n\n\t\t\tfloat normalx = p10[1] * p20[2] - p10[2] * p20[1];\n\t\t\tfloat normaly = p10[2] * p20[0] - p10[0] * p20[2];\n\t\t\tfloat normalz = p10[0] * p20[1] - p10[1] * p20[0];\n\n\t\t\tfloat area = sqrtf(normalx * normalx + normaly * normaly + normalz * normalz);\n\n\t\t\tcluster_centroid[0] += (p0[0] + p1[0] + p2[0]) * (area / 3);\n\t\t\tcluster_centroid[1] += (p0[1] + p1[1] + p2[1]) * (area / 3);\n\t\t\tcluster_centroid[2] += (p0[2] + p1[2] + p2[2]) * (area / 3);\n\t\t\tcluster_normal[0] += normalx;\n\t\t\tcluster_normal[1] += normaly;\n\t\t\tcluster_normal[2] += normalz;\n\t\t\tcluster_area += area;\n\t\t}\n\n\t\tfloat inv_cluster_area = cluster_area == 0 ? 0 : 1 / cluster_area;\n\n\t\tcluster_centroid[0] *= inv_cluster_area;\n\t\tcluster_centroid[1] *= inv_cluster_area;\n\t\tcluster_centroid[2] *= inv_cluster_area;\n\n\t\tfloat cluster_normal_length = sqrtf(cluster_normal[0] * cluster_normal[0] + cluster_normal[1] * cluster_normal[1] + cluster_normal[2] * cluster_normal[2]);\n\t\tfloat inv_cluster_normal_length = cluster_normal_length == 0 ? 0 : 1 / cluster_normal_length;\n\n\t\tcluster_normal[0] *= inv_cluster_normal_length;\n\t\tcluster_normal[1] *= inv_cluster_normal_length;\n\t\tcluster_normal[2] *= inv_cluster_normal_length;\n\n\t\tfloat centroid_vector[3] = {cluster_centroid[0] - mesh_centroid[0], cluster_centroid[1] - mesh_centroid[1], cluster_centroid[2] - mesh_centroid[2]};\n\n\t\tsort_data[cluster] = centroid_vector[0] * cluster_normal[0] + centroid_vector[1] * cluster_normal[1] + centroid_vector[2] * cluster_normal[2];\n\t}\n}\n\nstatic void calculateSortOrderRadix(unsigned int* sort_order, const float* sort_data, unsigned short* sort_keys, size_t cluster_count)\n{\n\t// compute sort data bounds and renormalize, using fixed point snorm\n\tfloat sort_data_max = 1e-3f;\n\n\tfor (size_t i = 0; i < cluster_count; ++i)\n\t{\n\t\tfloat dpa = fabsf(sort_data[i]);\n\n\t\tsort_data_max = (sort_data_max < dpa) ? dpa : sort_data_max;\n\t}\n\n\tconst int sort_bits = 11;\n\n\tfor (size_t i = 0; i < cluster_count; ++i)\n\t{\n\t\t// note that we flip distribution since high dot product should come first\n\t\tfloat sort_key = 0.5f - 0.5f * (sort_data[i] / sort_data_max);\n\n\t\tsort_keys[i] = meshopt_quantizeUnorm(sort_key, sort_bits) & ((1 << sort_bits) - 1);\n\t}\n\n\t// fill histogram for counting sort\n\tunsigned int histogram[1 << sort_bits];\n\tmemset(histogram, 0, sizeof(histogram));\n\n\tfor (size_t i = 0; i < cluster_count; ++i)\n\t{\n\t\thistogram[sort_keys[i]]++;\n\t}\n\n\t// compute offsets based on histogram data\n\tsize_t histogram_sum = 0;\n\n\tfor (size_t i = 0; i < 1 << sort_bits; ++i)\n\t{\n\t\tsize_t count = histogram[i];\n\t\thistogram[i] = unsigned(histogram_sum);\n\t\thistogram_sum += count;\n\t}\n\n\tassert(histogram_sum == cluster_count);\n\n\t// compute sort order based on offsets\n\tfor (size_t i = 0; i < cluster_count; ++i)\n\t{\n\t\tsort_order[histogram[sort_keys[i]]++] = unsigned(i);\n\t}\n}\n\nstatic unsigned int updateCache(unsigned int a, unsigned int b, unsigned int c, unsigned int cache_size, unsigned int* cache_timestamps, unsigned int& timestamp)\n{\n\tunsigned int cache_misses = 0;\n\n\t// if vertex is not in cache, put it in cache\n\tif (timestamp - cache_timestamps[a] > cache_size)\n\t{\n\t\tcache_timestamps[a] = timestamp++;\n\t\tcache_misses++;\n\t}\n\n\tif (timestamp - cache_timestamps[b] > cache_size)\n\t{\n\t\tcache_timestamps[b] = timestamp++;\n\t\tcache_misses++;\n\t}\n\n\tif (timestamp - cache_timestamps[c] > cache_size)\n\t{\n\t\tcache_timestamps[c] = timestamp++;\n\t\tcache_misses++;\n\t}\n\n\treturn cache_misses;\n}\n\nstatic size_t generateHardBoundaries(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int cache_size, unsigned int* cache_timestamps)\n{\n\tmemset(cache_timestamps, 0, vertex_count * sizeof(unsigned int));\n\n\tunsigned int timestamp = cache_size + 1;\n\n\tsize_t face_count = index_count / 3;\n\n\tsize_t result = 0;\n\n\tfor (size_t i = 0; i < face_count; ++i)\n\t{\n\t\tunsigned int m = updateCache(indices[i * 3 + 0], indices[i * 3 + 1], indices[i * 3 + 2], cache_size, &cache_timestamps[0], timestamp);\n\n\t\t// when all three vertices are not in the cache it's usually relatively safe to assume that this is a new patch in the mesh\n\t\t// that is disjoint from previous vertices; sometimes it might come back to reference existing vertices but that frequently\n\t\t// suggests an inefficiency in the vertex cache optimization algorithm\n\t\t// usually the first triangle has 3 misses unless it's degenerate - thus we make sure the first cluster always starts with 0\n\t\tif (i == 0 || m == 3)\n\t\t{\n\t\t\tdestination[result++] = unsigned(i);\n\t\t}\n\t}\n\n\tassert(result <= index_count / 3);\n\n\treturn result;\n}\n\nstatic size_t generateSoftBoundaries(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const unsigned int* clusters, size_t cluster_count, unsigned int cache_size, float threshold, unsigned int* cache_timestamps)\n{\n\tmemset(cache_timestamps, 0, vertex_count * sizeof(unsigned int));\n\n\tunsigned int timestamp = 0;\n\n\tsize_t result = 0;\n\n\tfor (size_t it = 0; it < cluster_count; ++it)\n\t{\n\t\tsize_t start = clusters[it];\n\t\tsize_t end = (it + 1 < cluster_count) ? clusters[it + 1] : index_count / 3;\n\t\tassert(start < end);\n\n\t\t// reset cache\n\t\ttimestamp += cache_size + 1;\n\n\t\t// measure cluster ACMR\n\t\tunsigned int cluster_misses = 0;\n\n\t\tfor (size_t i = start; i < end; ++i)\n\t\t{\n\t\t\tunsigned int m = updateCache(indices[i * 3 + 0], indices[i * 3 + 1], indices[i * 3 + 2], cache_size, &cache_timestamps[0], timestamp);\n\n\t\t\tcluster_misses += m;\n\t\t}\n\n\t\tfloat cluster_threshold = threshold * (float(cluster_misses) / float(end - start));\n\n\t\t// first cluster always starts from the hard cluster boundary\n\t\tdestination[result++] = unsigned(start);\n\n\t\t// reset cache\n\t\ttimestamp += cache_size + 1;\n\n\t\tunsigned int running_misses = 0;\n\t\tunsigned int running_faces = 0;\n\n\t\tfor (size_t i = start; i < end; ++i)\n\t\t{\n\t\t\tunsigned int m = updateCache(indices[i * 3 + 0], indices[i * 3 + 1], indices[i * 3 + 2], cache_size, &cache_timestamps[0], timestamp);\n\n\t\t\trunning_misses += m;\n\t\t\trunning_faces += 1;\n\n\t\t\tif (float(running_misses) / float(running_faces) <= cluster_threshold)\n\t\t\t{\n\t\t\t\t// we have reached the target ACMR with the current triangle so we need to start a new cluster on the next one\n\t\t\t\t// note that this may mean that we add 'end` to destination for the last triangle, which will imply that the last\n\t\t\t\t// cluster is empty; however, the 'pop_back' after the loop will clean it up\n\t\t\t\tdestination[result++] = unsigned(i + 1);\n\n\t\t\t\t// reset cache\n\t\t\t\ttimestamp += cache_size + 1;\n\n\t\t\t\trunning_misses = 0;\n\t\t\t\trunning_faces = 0;\n\t\t\t}\n\t\t}\n\n\t\t// each time we reach the target ACMR we flush the cluster\n\t\t// this means that the last cluster is by definition not very good - there are frequent cases where we are left with a few triangles\n\t\t// in the last cluster, producing a very bad ACMR and significantly penalizing the overall results\n\t\t// thus we remove the last cluster boundary, merging the last complete cluster with the last incomplete one\n\t\t// there are sometimes cases when the last cluster is actually good enough - in which case the code above would have added 'end'\n\t\t// to the cluster boundary array which we need to remove anyway - this code will do that automatically\n\t\tif (destination[result - 1] != start)\n\t\t{\n\t\t\tresult--;\n\t\t}\n\t}\n\n\tassert(result >= cluster_count);\n\tassert(result <= index_count / 3);\n\n\treturn result;\n}\n\n} // namespace meshopt\n\nvoid meshopt_optimizeOverdraw(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float threshold)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\n\tmeshopt_Allocator allocator;\n\n\t// guard for empty meshes\n\tif (index_count == 0 || vertex_count == 0)\n\t\treturn;\n\n\t// support in-place optimization\n\tif (destination == indices)\n\t{\n\t\tunsigned int* indices_copy = allocator.allocate<unsigned int>(index_count);\n\t\tmemcpy(indices_copy, indices, index_count * sizeof(unsigned int));\n\t\tindices = indices_copy;\n\t}\n\n\tunsigned int cache_size = 16;\n\n\tunsigned int* cache_timestamps = allocator.allocate<unsigned int>(vertex_count);\n\n\t// generate hard boundaries from full-triangle cache misses\n\tunsigned int* hard_clusters = allocator.allocate<unsigned int>(index_count / 3);\n\tsize_t hard_cluster_count = generateHardBoundaries(hard_clusters, indices, index_count, vertex_count, cache_size, cache_timestamps);\n\n\t// generate soft boundaries\n\tunsigned int* soft_clusters = allocator.allocate<unsigned int>(index_count / 3 + 1);\n\tsize_t soft_cluster_count = generateSoftBoundaries(soft_clusters, indices, index_count, vertex_count, hard_clusters, hard_cluster_count, cache_size, threshold, cache_timestamps);\n\n\tconst unsigned int* clusters = soft_clusters;\n\tsize_t cluster_count = soft_cluster_count;\n\n\t// fill sort data\n\tfloat* sort_data = allocator.allocate<float>(cluster_count);\n\tcalculateSortData(sort_data, indices, index_count, vertex_positions, vertex_count, vertex_positions_stride, clusters, cluster_count);\n\n\t// sort clusters using sort data\n\tunsigned short* sort_keys = allocator.allocate<unsigned short>(cluster_count);\n\tunsigned int* sort_order = allocator.allocate<unsigned int>(cluster_count);\n\tcalculateSortOrderRadix(sort_order, sort_data, sort_keys, cluster_count);\n\n\t// fill output buffer\n\tsize_t offset = 0;\n\n\tfor (size_t it = 0; it < cluster_count; ++it)\n\t{\n\t\tunsigned int cluster = sort_order[it];\n\t\tassert(cluster < cluster_count);\n\n\t\tsize_t cluster_begin = clusters[cluster] * 3;\n\t\tsize_t cluster_end = (cluster + 1 < cluster_count) ? clusters[cluster + 1] * 3 : index_count;\n\t\tassert(cluster_begin < cluster_end);\n\n\t\tmemcpy(destination + offset, indices + cluster_begin, (cluster_end - cluster_begin) * sizeof(unsigned int));\n\t\toffset += cluster_end - cluster_begin;\n\t}\n\n\tassert(offset == index_count);\n}\n"
  },
  {
    "path": "src/partition.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <string.h>\n\n// This work is based on:\n// Takio Kurita. An efficient agglomerative clustering algorithm using a heap. 1991\nnamespace meshopt\n{\n\n// To avoid excessive recursion for malformed inputs, we switch to bisection after some depth\nconst int kMergeDepthCutoff = 40;\n\nstruct ClusterAdjacency\n{\n\tunsigned int* offsets;\n\tunsigned int* clusters;\n\tunsigned int* shared;\n};\n\nstatic void filterClusterIndices(unsigned int* data, unsigned int* offsets, const unsigned int* cluster_indices, const unsigned int* cluster_index_counts, size_t cluster_count, unsigned char* used, size_t vertex_count, size_t total_index_count)\n{\n\t(void)vertex_count;\n\t(void)total_index_count;\n\n\tsize_t cluster_start = 0;\n\tsize_t cluster_write = 0;\n\n\tfor (size_t i = 0; i < cluster_count; ++i)\n\t{\n\t\toffsets[i] = unsigned(cluster_write);\n\n\t\t// copy cluster indices, skipping duplicates\n\t\tfor (size_t j = 0; j < cluster_index_counts[i]; ++j)\n\t\t{\n\t\t\tunsigned int v = cluster_indices[cluster_start + j];\n\t\t\tassert(v < vertex_count);\n\n\t\t\tdata[cluster_write] = v;\n\t\t\tcluster_write += 1 - used[v];\n\t\t\tused[v] = 1;\n\t\t}\n\n\t\t// reset used flags for the next cluster\n\t\tfor (size_t j = offsets[i]; j < cluster_write; ++j)\n\t\t\tused[data[j]] = 0;\n\n\t\tcluster_start += cluster_index_counts[i];\n\t}\n\n\tassert(cluster_start == total_index_count);\n\tassert(cluster_write <= total_index_count);\n\toffsets[cluster_count] = unsigned(cluster_write);\n}\n\nstatic float computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_positions_stride, float* out_center)\n{\n\tsize_t vertex_stride_float = vertex_positions_stride / sizeof(float);\n\n\tfloat center[3] = {0, 0, 0};\n\n\t// approximate center of the cluster by averaging all vertex positions\n\tfor (size_t j = 0; j < index_count; ++j)\n\t{\n\t\tconst float* p = vertex_positions + indices[j] * vertex_stride_float;\n\n\t\tcenter[0] += p[0];\n\t\tcenter[1] += p[1];\n\t\tcenter[2] += p[2];\n\t}\n\n\t// note: technically clusters can't be empty per meshopt_partitionCluster but we check for a division by zero in case that changes\n\tif (index_count)\n\t{\n\t\tcenter[0] /= float(index_count);\n\t\tcenter[1] /= float(index_count);\n\t\tcenter[2] /= float(index_count);\n\t}\n\n\t// compute radius of the bounding sphere for each cluster\n\tfloat radiussq = 0;\n\n\tfor (size_t j = 0; j < index_count; ++j)\n\t{\n\t\tconst float* p = vertex_positions + indices[j] * vertex_stride_float;\n\n\t\tfloat d2 = (p[0] - center[0]) * (p[0] - center[0]) + (p[1] - center[1]) * (p[1] - center[1]) + (p[2] - center[2]) * (p[2] - center[2]);\n\n\t\tradiussq = radiussq < d2 ? d2 : radiussq;\n\t}\n\n\tmemcpy(out_center, center, sizeof(center));\n\treturn sqrtf(radiussq);\n}\n\nstatic void buildClusterAdjacency(ClusterAdjacency& adjacency, const unsigned int* cluster_indices, const unsigned int* cluster_offsets, size_t cluster_count, size_t vertex_count, meshopt_Allocator& allocator)\n{\n\tunsigned int* ref_offsets = allocator.allocate<unsigned int>(vertex_count + 1);\n\n\t// compute number of clusters referenced by each vertex\n\tmemset(ref_offsets, 0, vertex_count * sizeof(unsigned int));\n\n\tfor (size_t i = 0; i < cluster_count; ++i)\n\t{\n\t\tfor (size_t j = cluster_offsets[i]; j < cluster_offsets[i + 1]; ++j)\n\t\t\tref_offsets[cluster_indices[j]]++;\n\t}\n\n\t// compute (worst-case) number of adjacent clusters for each cluster\n\tsize_t total_adjacency = 0;\n\n\tfor (size_t i = 0; i < cluster_count; ++i)\n\t{\n\t\tsize_t count = 0;\n\n\t\t// worst case is every vertex has a disjoint cluster list\n\t\tfor (size_t j = cluster_offsets[i]; j < cluster_offsets[i + 1]; ++j)\n\t\t\tcount += ref_offsets[cluster_indices[j]] - 1;\n\n\t\t// ... but only every other cluster can be adjacent in the end\n\t\ttotal_adjacency += count < cluster_count - 1 ? count : cluster_count - 1;\n\t}\n\n\t// we can now allocate adjacency buffers\n\tadjacency.offsets = allocator.allocate<unsigned int>(cluster_count + 1);\n\tadjacency.clusters = allocator.allocate<unsigned int>(total_adjacency);\n\tadjacency.shared = allocator.allocate<unsigned int>(total_adjacency);\n\n\t// convert ref counts to offsets\n\tsize_t total_refs = 0;\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tsize_t count = ref_offsets[i];\n\t\tref_offsets[i] = unsigned(total_refs);\n\t\ttotal_refs += count;\n\t}\n\n\tunsigned int* ref_data = allocator.allocate<unsigned int>(total_refs);\n\n\t// fill cluster refs for each vertex\n\tfor (size_t i = 0; i < cluster_count; ++i)\n\t{\n\t\tfor (size_t j = cluster_offsets[i]; j < cluster_offsets[i + 1]; ++j)\n\t\t\tref_data[ref_offsets[cluster_indices[j]]++] = unsigned(i);\n\t}\n\n\t// after the previous pass, ref_offsets contain the end of the data for each vertex; shift it forward to get the start\n\tmemmove(ref_offsets + 1, ref_offsets, vertex_count * sizeof(unsigned int));\n\tref_offsets[0] = 0;\n\n\t// fill cluster adjacency for each cluster...\n\tadjacency.offsets[0] = 0;\n\n\tfor (size_t i = 0; i < cluster_count; ++i)\n\t{\n\t\tunsigned int* adj = adjacency.clusters + adjacency.offsets[i];\n\t\tunsigned int* shd = adjacency.shared + adjacency.offsets[i];\n\t\tsize_t count = 0;\n\n\t\tfor (size_t j = cluster_offsets[i]; j < cluster_offsets[i + 1]; ++j)\n\t\t{\n\t\t\tunsigned int v = cluster_indices[j];\n\n\t\t\t// merge the entire cluster list of each vertex into current list\n\t\t\tfor (size_t k = ref_offsets[v]; k < ref_offsets[v + 1]; ++k)\n\t\t\t{\n\t\t\t\tunsigned int c = ref_data[k];\n\t\t\t\tassert(c < cluster_count);\n\n\t\t\t\tif (c == unsigned(i))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// if the cluster is already in the list, increment the shared count\n\t\t\t\tbool found = false;\n\t\t\t\tfor (size_t l = 0; l < count; ++l)\n\t\t\t\t\tif (adj[l] == c)\n\t\t\t\t\t{\n\t\t\t\t\t\tfound = true;\n\t\t\t\t\t\tshd[l]++;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t// .. or append a new cluster\n\t\t\t\tif (!found)\n\t\t\t\t{\n\t\t\t\t\tadj[count] = c;\n\t\t\t\t\tshd[count] = 1;\n\t\t\t\t\tcount++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// mark the end of the adjacency list; the next cluster will start there as well\n\t\tadjacency.offsets[i + 1] = adjacency.offsets[i] + unsigned(count);\n\t}\n\n\tassert(adjacency.offsets[cluster_count] <= total_adjacency);\n\n\t// ref_offsets can't be deallocated as it was allocated before adjacency\n\tallocator.deallocate(ref_data);\n}\n\nstruct ClusterGroup\n{\n\tint group;\n\tint next;\n\tunsigned int size; // 0 unless root\n\tunsigned int vertices;\n\n\tfloat center[3];\n\tfloat radius;\n};\n\nstruct GroupOrder\n{\n\tunsigned int id;\n\tint order;\n};\n\nstatic void heapPush(GroupOrder* heap, size_t size, GroupOrder item)\n{\n\t// insert a new element at the end (breaks heap invariant)\n\theap[size++] = item;\n\n\t// bubble up the new element to its correct position\n\tsize_t i = size - 1;\n\twhile (i > 0 && heap[i].order < heap[(i - 1) / 2].order)\n\t{\n\t\tsize_t p = (i - 1) / 2;\n\n\t\tGroupOrder temp = heap[i];\n\t\theap[i] = heap[p];\n\t\theap[p] = temp;\n\t\ti = p;\n\t}\n}\n\nstatic GroupOrder heapPop(GroupOrder* heap, size_t size)\n{\n\tassert(size > 0);\n\tGroupOrder top = heap[0];\n\n\t// move the last element to the top (breaks heap invariant)\n\theap[0] = heap[--size];\n\n\t// bubble down the new top element to its correct position\n\tsize_t i = 0;\n\twhile (i * 2 + 1 < size)\n\t{\n\t\t// find the smallest child\n\t\tsize_t j = i * 2 + 1;\n\t\tj += (j + 1 < size && heap[j + 1].order < heap[j].order);\n\n\t\t// if the parent is already smaller than both children, we're done\n\t\tif (heap[j].order >= heap[i].order)\n\t\t\tbreak;\n\n\t\t// otherwise, swap the parent and child and continue\n\t\tGroupOrder temp = heap[i];\n\t\theap[i] = heap[j];\n\t\theap[j] = temp;\n\t\ti = j;\n\t}\n\n\treturn top;\n}\n\nstatic unsigned int countShared(const ClusterGroup* groups, int group1, int group2, const ClusterAdjacency& adjacency)\n{\n\tunsigned int total = 0;\n\n\tfor (int i1 = group1; i1 >= 0; i1 = groups[i1].next)\n\t\tfor (int i2 = group2; i2 >= 0; i2 = groups[i2].next)\n\t\t{\n\t\t\tfor (unsigned int adj = adjacency.offsets[i1]; adj < adjacency.offsets[i1 + 1]; ++adj)\n\t\t\t\tif (adjacency.clusters[adj] == unsigned(i2))\n\t\t\t\t{\n\t\t\t\t\ttotal += adjacency.shared[adj];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t}\n\n\treturn total;\n}\n\nstatic void mergeBounds(ClusterGroup& target, const ClusterGroup& source)\n{\n\tfloat r1 = target.radius, r2 = source.radius;\n\tfloat dx = source.center[0] - target.center[0], dy = source.center[1] - target.center[1], dz = source.center[2] - target.center[2];\n\tfloat d = sqrtf(dx * dx + dy * dy + dz * dz);\n\n\tif (d + r1 < r2)\n\t{\n\t\ttarget.center[0] = source.center[0];\n\t\ttarget.center[1] = source.center[1];\n\t\ttarget.center[2] = source.center[2];\n\t\ttarget.radius = source.radius;\n\t\treturn;\n\t}\n\n\tif (d + r2 > r1)\n\t{\n\t\tfloat k = d > 0 ? (d + r2 - r1) / (2 * d) : 0.f;\n\n\t\ttarget.center[0] += dx * k;\n\t\ttarget.center[1] += dy * k;\n\t\ttarget.center[2] += dz * k;\n\t\ttarget.radius = (d + r2 + r1) / 2;\n\t}\n}\n\nstatic float boundsScore(const ClusterGroup& target, const ClusterGroup& source)\n{\n\tfloat r1 = target.radius, r2 = source.radius;\n\tfloat dx = source.center[0] - target.center[0], dy = source.center[1] - target.center[1], dz = source.center[2] - target.center[2];\n\tfloat d = sqrtf(dx * dx + dy * dy + dz * dz);\n\n\tfloat mr = d + r1 < r2 ? r2 : (d + r2 < r1 ? r1 : (d + r2 + r1) / 2);\n\n\treturn mr > 0 ? r1 / mr : 0.f;\n}\n\nstatic int pickGroupToMerge(const ClusterGroup* groups, int id, const ClusterAdjacency& adjacency, size_t max_partition_size, bool use_bounds)\n{\n\tassert(groups[id].size > 0);\n\n\tfloat group_rsqrt = 1.f / sqrtf(float(int(groups[id].vertices)));\n\n\tint best_group = -1;\n\tfloat best_score = 0;\n\n\tfor (int ci = id; ci >= 0; ci = groups[ci].next)\n\t{\n\t\tfor (unsigned int adj = adjacency.offsets[ci]; adj != adjacency.offsets[ci + 1]; ++adj)\n\t\t{\n\t\t\tint other = groups[adjacency.clusters[adj]].group;\n\t\t\tif (other < 0)\n\t\t\t\tcontinue;\n\n\t\t\tassert(groups[other].size > 0);\n\t\t\tif (groups[id].size + groups[other].size > max_partition_size)\n\t\t\t\tcontinue;\n\n\t\t\tunsigned int shared = countShared(groups, id, other, adjacency);\n\t\t\tfloat other_rsqrt = 1.f / sqrtf(float(int(groups[other].vertices)));\n\n\t\t\t// normalize shared count by the expected boundary of each group (+ keeps scoring symmetric)\n\t\t\tfloat score = float(int(shared)) * (group_rsqrt + other_rsqrt);\n\n\t\t\t// incorporate spatial score to favor merging nearby groups\n\t\t\tif (use_bounds)\n\t\t\t\tscore *= 1.f + 0.4f * boundsScore(groups[id], groups[other]);\n\n\t\t\tif (score > best_score)\n\t\t\t{\n\t\t\t\tbest_group = other;\n\t\t\t\tbest_score = score;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn best_group;\n}\n\nstatic void mergeLeaf(ClusterGroup* groups, unsigned int* order, size_t count, size_t target_partition_size, size_t max_partition_size)\n{\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned int id = order[i];\n\t\tif (groups[id].size == 0 || groups[id].size >= target_partition_size)\n\t\t\tcontinue;\n\n\t\tfloat best_score = -1.f;\n\t\tint best_group = -1;\n\n\t\tfor (size_t j = 0; j < count; ++j)\n\t\t{\n\t\t\tunsigned int other = order[j];\n\t\t\tif (id == other || groups[other].size == 0)\n\t\t\t\tcontinue;\n\n\t\t\tif (groups[id].size + groups[other].size > max_partition_size)\n\t\t\t\tcontinue;\n\n\t\t\t// favor merging nearby groups\n\t\t\tfloat score = boundsScore(groups[id], groups[other]);\n\n\t\t\tif (score > best_score)\n\t\t\t{\n\t\t\t\tbest_score = score;\n\t\t\t\tbest_group = other;\n\t\t\t}\n\t\t}\n\n\t\t// merge id *into* best_group; that way, we may merge more groups into the same best_group, maximizing the chance of reaching target\n\t\tif (best_group != -1)\n\t\t{\n\t\t\t// combine groups by linking them together\n\t\t\tunsigned int tail = best_group;\n\t\t\twhile (groups[tail].next >= 0)\n\t\t\t\ttail = groups[tail].next;\n\n\t\t\tgroups[tail].next = id;\n\n\t\t\t// update group sizes; note, we omit vertices update for simplicity as it's not used for spatial merge\n\t\t\tgroups[best_group].size += groups[id].size;\n\t\t\tgroups[id].size = 0;\n\n\t\t\t// merge bounding spheres\n\t\t\tmergeBounds(groups[best_group], groups[id]);\n\t\t\tgroups[id].radius = 0.f;\n\t\t}\n\t}\n}\n\nstatic size_t mergePartition(unsigned int* order, size_t count, const ClusterGroup* groups, int axis, float pivot)\n{\n\tsize_t m = 0;\n\n\t// invariant: elements in range [0, m) are < pivot, elements in range [m, i) are >= pivot\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tfloat v = groups[order[i]].center[axis];\n\n\t\t// swap(m, i) unconditionally\n\t\tunsigned int t = order[m];\n\t\torder[m] = order[i];\n\t\torder[i] = t;\n\n\t\t// when v >= pivot, we swap i with m without advancing it, preserving invariants\n\t\tm += v < pivot;\n\t}\n\n\treturn m;\n}\n\nstatic void mergeSpatial(ClusterGroup* groups, unsigned int* order, size_t count, size_t target_partition_size, size_t max_partition_size, size_t leaf_size, int depth)\n{\n\tsize_t total = 0;\n\tfor (size_t i = 0; i < count; ++i)\n\t\ttotal += groups[order[i]].size;\n\n\tif (total <= max_partition_size || count <= leaf_size)\n\t\treturn mergeLeaf(groups, order, count, target_partition_size, max_partition_size);\n\n\tfloat mean[3] = {};\n\tfloat vars[3] = {};\n\tfloat runc = 1, runs = 1;\n\n\t// gather statistics on the points in the subtree using Welford's algorithm\n\tfor (size_t i = 0; i < count; ++i, runc += 1.f, runs = 1.f / runc)\n\t{\n\t\tconst float* point = groups[order[i]].center;\n\n\t\tfor (int k = 0; k < 3; ++k)\n\t\t{\n\t\t\tfloat delta = point[k] - mean[k];\n\t\t\tmean[k] += delta * runs;\n\t\t\tvars[k] += delta * (point[k] - mean[k]);\n\t\t}\n\t}\n\n\t// split axis is one where the variance is largest\n\tint axis = (vars[0] >= vars[1] && vars[0] >= vars[2]) ? 0 : (vars[1] >= vars[2] ? 1 : 2);\n\n\tfloat split = mean[axis];\n\tsize_t middle = mergePartition(order, count, groups, axis, split);\n\n\t// enforce balance for degenerate partitions\n\t// this also ensures recursion depth is bounded on pathological inputs\n\tif (middle <= leaf_size / 2 || count - middle <= leaf_size / 2 || depth >= kMergeDepthCutoff)\n\t\tmiddle = count / 2;\n\n\t// recursion depth is logarithmic and bounded due to max depth check above\n\tmergeSpatial(groups, order, middle, target_partition_size, max_partition_size, leaf_size, depth + 1);\n\tmergeSpatial(groups, order + middle, count - middle, target_partition_size, max_partition_size, leaf_size, depth + 1);\n}\n\n} // namespace meshopt\n\nsize_t meshopt_partitionClusters(unsigned int* destination, const unsigned int* cluster_indices, size_t total_index_count, const unsigned int* cluster_index_counts, size_t cluster_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_partition_size)\n{\n\tusing namespace meshopt;\n\n\tassert((vertex_positions == NULL || vertex_positions_stride >= 12) && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\tassert(target_partition_size > 0);\n\n\tsize_t max_partition_size = target_partition_size + target_partition_size / 3;\n\n\tmeshopt_Allocator allocator;\n\n\tunsigned char* used = allocator.allocate<unsigned char>(vertex_count);\n\tmemset(used, 0, vertex_count);\n\n\tunsigned int* cluster_newindices = allocator.allocate<unsigned int>(total_index_count);\n\tunsigned int* cluster_offsets = allocator.allocate<unsigned int>(cluster_count + 1);\n\n\t// make new cluster index list that filters out duplicate indices\n\tfilterClusterIndices(cluster_newindices, cluster_offsets, cluster_indices, cluster_index_counts, cluster_count, used, vertex_count, total_index_count);\n\tcluster_indices = cluster_newindices;\n\n\t// build cluster adjacency along with edge weights (shared vertex count)\n\tClusterAdjacency adjacency = {};\n\tbuildClusterAdjacency(adjacency, cluster_indices, cluster_offsets, cluster_count, vertex_count, allocator);\n\n\tClusterGroup* groups = allocator.allocate<ClusterGroup>(cluster_count);\n\tmemset(groups, 0, sizeof(ClusterGroup) * cluster_count);\n\n\tGroupOrder* order = allocator.allocate<GroupOrder>(cluster_count);\n\tsize_t pending = 0;\n\n\t// create a singleton group for each cluster and order them by priority\n\tfor (size_t i = 0; i < cluster_count; ++i)\n\t{\n\t\tgroups[i].group = int(i);\n\t\tgroups[i].next = -1;\n\t\tgroups[i].size = 1;\n\t\tgroups[i].vertices = cluster_offsets[i + 1] - cluster_offsets[i];\n\t\tassert(groups[i].vertices > 0);\n\n\t\t// compute bounding sphere for each cluster if positions are provided\n\t\tif (vertex_positions)\n\t\t\tgroups[i].radius = computeClusterBounds(cluster_indices + cluster_offsets[i], cluster_offsets[i + 1] - cluster_offsets[i], vertex_positions, vertex_positions_stride, groups[i].center);\n\n\t\tGroupOrder item = {};\n\t\titem.id = unsigned(i);\n\t\titem.order = groups[i].vertices;\n\n\t\theapPush(order, pending++, item);\n\t}\n\n\t// iteratively merge the smallest group with the best group\n\twhile (pending)\n\t{\n\t\tGroupOrder top = heapPop(order, pending--);\n\n\t\t// this group was merged into another group earlier\n\t\tif (groups[top.id].size == 0)\n\t\t\tcontinue;\n\n\t\t// disassociate clusters from the group to prevent them from being merged again; we will re-associate them if the group is reinserted\n\t\tfor (int i = top.id; i >= 0; i = groups[i].next)\n\t\t{\n\t\t\tassert(groups[i].group == int(top.id));\n\t\t\tgroups[i].group = -1;\n\t\t}\n\n\t\t// the group is large enough, emit as is\n\t\tif (groups[top.id].size >= target_partition_size)\n\t\t\tcontinue;\n\n\t\tint best_group = pickGroupToMerge(groups, top.id, adjacency, max_partition_size, /* use_bounds= */ vertex_positions);\n\n\t\t// we can't grow the group any more, emit as is\n\t\tif (best_group == -1)\n\t\t\tcontinue;\n\n\t\t// compute shared vertices to adjust the total vertices estimate after merging\n\t\tunsigned int shared = countShared(groups, top.id, best_group, adjacency);\n\n\t\t// combine groups by linking them together\n\t\tunsigned int tail = top.id;\n\t\twhile (groups[tail].next >= 0)\n\t\t\ttail = groups[tail].next;\n\n\t\tgroups[tail].next = best_group;\n\n\t\t// update group sizes; note, the vertex update is a O(1) approximation which avoids recomputing the true size\n\t\tgroups[top.id].size += groups[best_group].size;\n\t\tgroups[top.id].vertices += groups[best_group].vertices;\n\t\tgroups[top.id].vertices = (groups[top.id].vertices > shared) ? groups[top.id].vertices - shared : 1;\n\n\t\tgroups[best_group].size = 0;\n\t\tgroups[best_group].vertices = 0;\n\n\t\t// merge bounding spheres if bounds are available\n\t\tif (vertex_positions)\n\t\t{\n\t\t\tmergeBounds(groups[top.id], groups[best_group]);\n\t\t\tgroups[best_group].radius = 0;\n\t\t}\n\n\t\t// re-associate all clusters back to the merged group\n\t\tfor (int i = top.id; i >= 0; i = groups[i].next)\n\t\t\tgroups[i].group = int(top.id);\n\n\t\ttop.order = groups[top.id].vertices;\n\t\theapPush(order, pending++, top);\n\t}\n\n\t// if vertex positions are provided, we do a final pass to see if we can merge small groups based on spatial locality alone\n\tif (vertex_positions)\n\t{\n\t\tunsigned int* merge_order = reinterpret_cast<unsigned int*>(order);\n\t\tsize_t merge_offset = 0;\n\n\t\tfor (size_t i = 0; i < cluster_count; ++i)\n\t\t\tif (groups[i].size)\n\t\t\t\tmerge_order[merge_offset++] = unsigned(i);\n\n\t\tmergeSpatial(groups, merge_order, merge_offset, target_partition_size, max_partition_size, /* leaf_size= */ 8, 0);\n\t}\n\n\t// output each remaining group\n\tsize_t next_group = 0;\n\n\tfor (size_t i = 0; i < cluster_count; ++i)\n\t{\n\t\tif (groups[i].size == 0)\n\t\t\tcontinue;\n\n\t\tfor (int j = int(i); j >= 0; j = groups[j].next)\n\t\t\tdestination[j] = unsigned(next_group);\n\n\t\tnext_group++;\n\t}\n\n\tassert(next_group <= cluster_count);\n\treturn next_group;\n}\n"
  },
  {
    "path": "src/quantization.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n\nunion FloatBits\n{\n\tfloat f;\n\tunsigned int ui;\n};\n\nunsigned short meshopt_quantizeHalf(float v)\n{\n\tFloatBits u = {v};\n\tunsigned int ui = u.ui;\n\n\tint s = (ui >> 16) & 0x8000;\n\tint em = ui & 0x7fffffff;\n\n\t// bias exponent and round to nearest; 112 is relative exponent bias (127-15)\n\tint h = (em - (112 << 23) + (1 << 12)) >> 13;\n\n\t// underflow: flush to zero; 113 encodes exponent -14\n\th = (em < (113 << 23)) ? 0 : h;\n\n\t// overflow: infinity; 143 encodes exponent 16\n\th = (em >= (143 << 23)) ? 0x7c00 : h;\n\n\t// NaN; note that we convert all types of NaN to qNaN\n\th = (em > (255 << 23)) ? 0x7e00 : h;\n\n\treturn (unsigned short)(s | h);\n}\n\nfloat meshopt_quantizeFloat(float v, int N)\n{\n\tassert(N >= 0 && N <= 23);\n\n\tFloatBits u = {v};\n\tunsigned int ui = u.ui;\n\n\tconst int mask = (1 << (23 - N)) - 1;\n\tconst int round = (1 << (23 - N)) >> 1;\n\n\tint e = ui & 0x7f800000;\n\tunsigned int rui = (ui + round) & ~mask;\n\n\t// round all numbers except inf/nan; this is important to make sure nan doesn't overflow into -0\n\tui = e == 0x7f800000 ? ui : rui;\n\n\t// flush denormals to zero\n\tui = e == 0 ? 0 : ui;\n\n\tu.ui = ui;\n\treturn u.f;\n}\n\nfloat meshopt_dequantizeHalf(unsigned short h)\n{\n\tunsigned int s = unsigned(h & 0x8000) << 16;\n\tint em = h & 0x7fff;\n\n\t// bias exponent and pad mantissa with 0; 112 is relative exponent bias (127-15)\n\tint r = (em + (112 << 10)) << 13;\n\n\t// denormal: flush to zero\n\tr = (em < (1 << 10)) ? 0 : r;\n\n\t// infinity/NaN; note that we preserve NaN payload as a byproduct of unifying inf/nan cases\n\t// 112 is an exponent bias fixup; since we already applied it once, applying it twice converts 31 to 255\n\tr += (em >= (31 << 10)) ? (112 << 23) : 0;\n\n\tFloatBits u;\n\tu.ui = s | r;\n\treturn u.f;\n}\n"
  },
  {
    "path": "src/rasterizer.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <float.h>\n#include <string.h>\n\n// This work is based on:\n// Nicolas Capens. Advanced Rasterization. 2004\nnamespace meshopt\n{\n\nconst int kViewport = 256;\n\nstruct OverdrawBuffer\n{\n\tfloat z[kViewport][kViewport][2];\n\tunsigned int overdraw[kViewport][kViewport][2];\n};\n\nstatic float computeDepthGradients(float& dzdx, float& dzdy, float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3)\n{\n\t// z2 = z1 + dzdx * (x2 - x1) + dzdy * (y2 - y1)\n\t// z3 = z1 + dzdx * (x3 - x1) + dzdy * (y3 - y1)\n\t// (x2-x1 y2-y1)(dzdx) = (z2-z1)\n\t// (x3-x1 y3-y1)(dzdy)   (z3-z1)\n\t// we'll solve it with Cramer's rule\n\tfloat det = (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1);\n\tfloat invdet = (det == 0) ? 0 : 1 / det;\n\n\tdzdx = ((z2 - z1) * (y3 - y1) - (y2 - y1) * (z3 - z1)) * invdet;\n\tdzdy = ((x2 - x1) * (z3 - z1) - (z2 - z1) * (x3 - x1)) * invdet;\n\n\treturn det;\n}\n\n// half-space fixed point triangle rasterizer\nstatic void rasterize(OverdrawBuffer* buffer, float v1x, float v1y, float v1z, float v2x, float v2y, float v2z, float v3x, float v3y, float v3z)\n{\n\t// compute depth gradients\n\tfloat DZx, DZy;\n\tfloat det = computeDepthGradients(DZx, DZy, v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z);\n\tint sign = det > 0;\n\n\t// flip backfacing triangles to simplify rasterization logic\n\tif (sign)\n\t{\n\t\t// flipping v2 & v3 preserves depth gradients since they're based on v1; only v1z is used below\n\t\tfloat t;\n\t\tt = v2x, v2x = v3x, v3x = t;\n\t\tt = v2y, v2y = v3y, v3y = t;\n\n\t\t// flip depth since we rasterize backfacing triangles to second buffer with reverse Z; only v1z is used below\n\t\tv1z = kViewport - v1z;\n\t\tDZx = -DZx;\n\t\tDZy = -DZy;\n\t}\n\n\t// coordinates, 28.4 fixed point\n\tint X1 = int(16.0f * v1x + 0.5f);\n\tint X2 = int(16.0f * v2x + 0.5f);\n\tint X3 = int(16.0f * v3x + 0.5f);\n\n\tint Y1 = int(16.0f * v1y + 0.5f);\n\tint Y2 = int(16.0f * v2y + 0.5f);\n\tint Y3 = int(16.0f * v3y + 0.5f);\n\n\t// bounding rectangle, clipped against viewport\n\t// since we rasterize pixels with covered centers, min >0.5 should round up\n\t// as for max, due to top-left filling convention we will never rasterize right/bottom edges\n\t// so max >= 0.5 should round down for inclusive bounds, and up for exclusive (in our case)\n\tint minx = X1 < X2 ? X1 : X2;\n\tminx = minx < X3 ? minx : X3;\n\tminx = (minx + 7) >> 4;\n\tminx = minx < 0 ? 0 : minx;\n\n\tint miny = Y1 < Y2 ? Y1 : Y2;\n\tminy = miny < Y3 ? miny : Y3;\n\tminy = (miny + 7) >> 4;\n\tminy = miny < 0 ? 0 : miny;\n\n\tint maxx = X1 > X2 ? X1 : X2;\n\tmaxx = maxx > X3 ? maxx : X3;\n\tmaxx = (maxx + 7) >> 4;\n\tmaxx = maxx > kViewport ? kViewport : maxx;\n\n\tint maxy = Y1 > Y2 ? Y1 : Y2;\n\tmaxy = maxy > Y3 ? maxy : Y3;\n\tmaxy = (maxy + 7) >> 4;\n\tmaxy = maxy > kViewport ? kViewport : maxy;\n\n\t// deltas, 28.4 fixed point\n\tint DX12 = X1 - X2;\n\tint DX23 = X2 - X3;\n\tint DX31 = X3 - X1;\n\n\tint DY12 = Y1 - Y2;\n\tint DY23 = Y2 - Y3;\n\tint DY31 = Y3 - Y1;\n\n\t// fill convention correction\n\tint TL1 = DY12 < 0 || (DY12 == 0 && DX12 > 0);\n\tint TL2 = DY23 < 0 || (DY23 == 0 && DX23 > 0);\n\tint TL3 = DY31 < 0 || (DY31 == 0 && DX31 > 0);\n\n\t// half edge equations, 24.8 fixed point\n\t// note that we offset minx/miny by half pixel since we want to rasterize pixels with covered centers\n\tint FX = (minx << 4) + 8;\n\tint FY = (miny << 4) + 8;\n\tint CY1 = DX12 * (FY - Y1) - DY12 * (FX - X1) + TL1 - 1;\n\tint CY2 = DX23 * (FY - Y2) - DY23 * (FX - X2) + TL2 - 1;\n\tint CY3 = DX31 * (FY - Y3) - DY31 * (FX - X3) + TL3 - 1;\n\tfloat ZY = v1z + (DZx * float(FX - X1) + DZy * float(FY - Y1)) * (1 / 16.f);\n\n\tfor (int y = miny; y < maxy; y++)\n\t{\n\t\tint CX1 = CY1;\n\t\tint CX2 = CY2;\n\t\tint CX3 = CY3;\n\t\tfloat ZX = ZY;\n\n\t\tfor (int x = minx; x < maxx; x++)\n\t\t{\n\t\t\t// check if all CXn are non-negative\n\t\t\tif ((CX1 | CX2 | CX3) >= 0)\n\t\t\t{\n\t\t\t\tif (ZX >= buffer->z[y][x][sign])\n\t\t\t\t{\n\t\t\t\t\tbuffer->z[y][x][sign] = ZX;\n\t\t\t\t\tbuffer->overdraw[y][x][sign]++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// signed left shift is UB for negative numbers so use unsigned-signed casts\n\t\t\tCX1 -= int(unsigned(DY12) << 4);\n\t\t\tCX2 -= int(unsigned(DY23) << 4);\n\t\t\tCX3 -= int(unsigned(DY31) << 4);\n\t\t\tZX += DZx;\n\t\t}\n\n\t\t// signed left shift is UB for negative numbers so use unsigned-signed casts\n\t\tCY1 += int(unsigned(DX12) << 4);\n\t\tCY2 += int(unsigned(DX23) << 4);\n\t\tCY3 += int(unsigned(DX31) << 4);\n\t\tZY += DZy;\n\t}\n}\n\nstatic float transformTriangles(float* triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tsize_t vertex_stride_float = vertex_positions_stride / sizeof(float);\n\n\tfloat minv[3] = {FLT_MAX, FLT_MAX, FLT_MAX};\n\tfloat maxv[3] = {-FLT_MAX, -FLT_MAX, -FLT_MAX};\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tconst float* v = vertex_positions + i * vertex_stride_float;\n\n\t\tfor (int j = 0; j < 3; ++j)\n\t\t{\n\t\t\tfloat vj = v[j];\n\n\t\t\tminv[j] = minv[j] > vj ? vj : minv[j];\n\t\t\tmaxv[j] = maxv[j] < vj ? vj : maxv[j];\n\t\t}\n\t}\n\n\tfloat extent = 0.f;\n\n\textent = (maxv[0] - minv[0]) < extent ? extent : (maxv[0] - minv[0]);\n\textent = (maxv[1] - minv[1]) < extent ? extent : (maxv[1] - minv[1]);\n\textent = (maxv[2] - minv[2]) < extent ? extent : (maxv[2] - minv[2]);\n\n\tfloat scale = kViewport / extent;\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int index = indices[i];\n\t\tassert(index < vertex_count);\n\n\t\tconst float* v = vertex_positions + index * vertex_stride_float;\n\n\t\ttriangles[i * 3 + 0] = (v[0] - minv[0]) * scale;\n\t\ttriangles[i * 3 + 1] = (v[1] - minv[1]) * scale;\n\t\ttriangles[i * 3 + 2] = (v[2] - minv[2]) * scale;\n\t}\n\n\treturn extent;\n}\n\nstatic void rasterizeTriangles(OverdrawBuffer* buffer, const float* triangles, size_t index_count, int axis)\n{\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tconst float* vn0 = &triangles[3 * (i + 0)];\n\t\tconst float* vn1 = &triangles[3 * (i + 1)];\n\t\tconst float* vn2 = &triangles[3 * (i + 2)];\n\n\t\tswitch (axis)\n\t\t{\n\t\tcase 0:\n\t\t\trasterize(buffer, vn0[2], vn0[1], vn0[0], vn1[2], vn1[1], vn1[0], vn2[2], vn2[1], vn2[0]);\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\trasterize(buffer, vn0[0], vn0[2], vn0[1], vn1[0], vn1[2], vn1[1], vn2[0], vn2[2], vn2[1]);\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\trasterize(buffer, vn0[1], vn0[0], vn0[2], vn1[1], vn1[0], vn1[2], vn2[1], vn2[0], vn2[2]);\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n} // namespace meshopt\n\nmeshopt_OverdrawStatistics meshopt_analyzeOverdraw(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\n\tmeshopt_Allocator allocator;\n\n\tmeshopt_OverdrawStatistics result = {};\n\n\tfloat* triangles = allocator.allocate<float>(index_count * 3);\n\ttransformTriangles(triangles, indices, index_count, vertex_positions, vertex_count, vertex_positions_stride);\n\n\tOverdrawBuffer* buffer = allocator.allocate<OverdrawBuffer>(1);\n\n\tfor (int axis = 0; axis < 3; ++axis)\n\t{\n\t\tmemset(buffer, 0, sizeof(OverdrawBuffer));\n\t\trasterizeTriangles(buffer, triangles, index_count, axis);\n\n\t\tfor (int y = 0; y < kViewport; ++y)\n\t\t\tfor (int x = 0; x < kViewport; ++x)\n\t\t\t\tfor (int s = 0; s < 2; ++s)\n\t\t\t\t{\n\t\t\t\t\tunsigned int overdraw = buffer->overdraw[y][x][s];\n\n\t\t\t\t\tresult.pixels_covered += overdraw > 0;\n\t\t\t\t\tresult.pixels_shaded += overdraw;\n\t\t\t\t}\n\t}\n\n\tresult.overdraw = result.pixels_covered ? float(result.pixels_shaded) / float(result.pixels_covered) : 0.f;\n\n\treturn result;\n}\n\nmeshopt_CoverageStatistics meshopt_analyzeCoverage(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\n\tmeshopt_Allocator allocator;\n\n\tmeshopt_CoverageStatistics result = {};\n\n\tfloat* triangles = allocator.allocate<float>(index_count * 3);\n\tfloat extent = transformTriangles(triangles, indices, index_count, vertex_positions, vertex_count, vertex_positions_stride);\n\n\tOverdrawBuffer* buffer = allocator.allocate<OverdrawBuffer>(1);\n\n\tfor (int axis = 0; axis < 3; ++axis)\n\t{\n\t\tmemset(buffer, 0, sizeof(OverdrawBuffer));\n\t\trasterizeTriangles(buffer, triangles, index_count, axis);\n\n\t\tunsigned int covered = 0;\n\n\t\tfor (int y = 0; y < kViewport; ++y)\n\t\t\tfor (int x = 0; x < kViewport; ++x)\n\t\t\t\tcovered += (buffer->overdraw[y][x][0] | buffer->overdraw[y][x][1]) > 0;\n\n\t\tresult.coverage[axis] = float(covered) / float(kViewport * kViewport);\n\t}\n\n\tresult.extent = extent;\n\n\treturn result;\n}\n"
  },
  {
    "path": "src/simplifier.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <float.h>\n#include <math.h>\n#include <string.h>\n\n#ifndef TRACE\n#define TRACE 0\n#endif\n\n#if TRACE\n#include <stdio.h>\n#endif\n\n#if TRACE\n#define TRACESTATS(i) stats[i]++;\n#else\n#define TRACESTATS(i) (void)0\n#endif\n\n// This work is based on:\n// Michael Garland and Paul S. Heckbert. Surface simplification using quadric error metrics. 1997\n// Michael Garland. Quadric-based polygonal surface simplification. 1999\n// Peter Lindstrom. Out-of-Core Simplification of Large Polygonal Models. 2000\n// Matthias Teschner, Bruno Heidelberger, Matthias Mueller, Danat Pomeranets, Markus Gross. Optimized Spatial Hashing for Collision Detection of Deformable Objects. 2003\n// Peter Van Sandt, Yannis Chronis, Jignesh M. Patel. Efficiently Searching In-Memory Sorted Arrays: Revenge of the Interpolation Search? 2019\n// Hugues Hoppe. New Quadric Metric for Simplifying Meshes with Appearance Attributes. 1999\n// Hugues Hoppe, Steve Marschner. Efficient Minimization of New Quadric Metric for Simplifying Meshes with Appearance Attributes. 2000\nnamespace meshopt\n{\n\nstruct EdgeAdjacency\n{\n\tstruct Edge\n\t{\n\t\tunsigned int next;\n\t\tunsigned int prev;\n\t};\n\n\tunsigned int* offsets;\n\tEdge* data;\n};\n\nstatic void prepareEdgeAdjacency(EdgeAdjacency& adjacency, size_t index_count, size_t vertex_count, meshopt_Allocator& allocator)\n{\n\tadjacency.offsets = allocator.allocate<unsigned int>(vertex_count + 1);\n\tadjacency.data = allocator.allocate<EdgeAdjacency::Edge>(index_count);\n}\n\nstatic void updateEdgeAdjacency(EdgeAdjacency& adjacency, const unsigned int* indices, size_t index_count, size_t vertex_count, const unsigned int* remap)\n{\n\tsize_t face_count = index_count / 3;\n\tunsigned int* offsets = adjacency.offsets + 1;\n\tEdgeAdjacency::Edge* data = adjacency.data;\n\n\t// fill edge counts\n\tmemset(offsets, 0, vertex_count * sizeof(unsigned int));\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int v = remap ? remap[indices[i]] : indices[i];\n\t\tassert(v < vertex_count);\n\n\t\toffsets[v]++;\n\t}\n\n\t// fill offset table\n\tunsigned int offset = 0;\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tunsigned int count = offsets[i];\n\t\toffsets[i] = offset;\n\t\toffset += count;\n\t}\n\n\tassert(offset == index_count);\n\n\t// fill edge data\n\tfor (size_t i = 0; i < face_count; ++i)\n\t{\n\t\tunsigned int a = indices[i * 3 + 0], b = indices[i * 3 + 1], c = indices[i * 3 + 2];\n\n\t\tif (remap)\n\t\t{\n\t\t\ta = remap[a];\n\t\t\tb = remap[b];\n\t\t\tc = remap[c];\n\t\t}\n\n\t\tdata[offsets[a]].next = b;\n\t\tdata[offsets[a]].prev = c;\n\t\toffsets[a]++;\n\n\t\tdata[offsets[b]].next = c;\n\t\tdata[offsets[b]].prev = a;\n\t\toffsets[b]++;\n\n\t\tdata[offsets[c]].next = a;\n\t\tdata[offsets[c]].prev = b;\n\t\toffsets[c]++;\n\t}\n\n\t// finalize offsets\n\tadjacency.offsets[0] = 0;\n\tassert(adjacency.offsets[vertex_count] == index_count);\n}\n\nstruct PositionHasher\n{\n\tconst float* vertex_positions;\n\tsize_t vertex_stride_float;\n\tconst unsigned int* sparse_remap;\n\n\tsize_t hash(unsigned int index) const\n\t{\n\t\tunsigned int ri = sparse_remap ? sparse_remap[index] : index;\n\t\tconst unsigned int* key = reinterpret_cast<const unsigned int*>(vertex_positions + ri * vertex_stride_float);\n\n\t\tunsigned int x = key[0], y = key[1], z = key[2];\n\n\t\t// replace negative zero with zero\n\t\tx = (x == 0x80000000) ? 0 : x;\n\t\ty = (y == 0x80000000) ? 0 : y;\n\t\tz = (z == 0x80000000) ? 0 : z;\n\n\t\t// scramble bits to make sure that integer coordinates have entropy in lower bits\n\t\tx ^= x >> 17;\n\t\ty ^= y >> 17;\n\t\tz ^= z >> 17;\n\n\t\t// Optimized Spatial Hashing for Collision Detection of Deformable Objects\n\t\treturn (x * 73856093) ^ (y * 19349663) ^ (z * 83492791);\n\t}\n\n\tbool equal(unsigned int lhs, unsigned int rhs) const\n\t{\n\t\tunsigned int li = sparse_remap ? sparse_remap[lhs] : lhs;\n\t\tunsigned int ri = sparse_remap ? sparse_remap[rhs] : rhs;\n\n\t\tconst float* lv = vertex_positions + li * vertex_stride_float;\n\t\tconst float* rv = vertex_positions + ri * vertex_stride_float;\n\n\t\treturn lv[0] == rv[0] && lv[1] == rv[1] && lv[2] == rv[2];\n\t}\n};\n\nstruct RemapHasher\n{\n\tunsigned int* remap;\n\n\tsize_t hash(unsigned int id) const\n\t{\n\t\treturn id * 0x5bd1e995;\n\t}\n\n\tbool equal(unsigned int lhs, unsigned int rhs) const\n\t{\n\t\treturn remap[lhs] == rhs;\n\t}\n};\n\nstatic size_t hashBuckets2(size_t count)\n{\n\tsize_t buckets = 1;\n\twhile (buckets < count + count / 4)\n\t\tbuckets *= 2;\n\n\treturn buckets;\n}\n\ntemplate <typename T, typename Hash>\nstatic T* hashLookup2(T* table, size_t buckets, const Hash& hash, const T& key, const T& empty)\n{\n\tassert(buckets > 0);\n\tassert((buckets & (buckets - 1)) == 0);\n\n\tsize_t hashmod = buckets - 1;\n\tsize_t bucket = hash.hash(key) & hashmod;\n\n\tfor (size_t probe = 0; probe <= hashmod; ++probe)\n\t{\n\t\tT& item = table[bucket];\n\n\t\tif (item == empty)\n\t\t\treturn &item;\n\n\t\tif (hash.equal(item, key))\n\t\t\treturn &item;\n\n\t\t// hash collision, quadratic probing\n\t\tbucket = (bucket + probe + 1) & hashmod;\n\t}\n\n\tassert(false && \"Hash table is full\"); // unreachable\n\treturn NULL;\n}\n\nstatic void buildPositionRemap(unsigned int* remap, unsigned int* wedge, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, const unsigned int* sparse_remap, meshopt_Allocator& allocator)\n{\n\tPositionHasher hasher = {vertex_positions_data, vertex_positions_stride / sizeof(float), sparse_remap};\n\n\tsize_t table_size = hashBuckets2(vertex_count);\n\tunsigned int* table = allocator.allocate<unsigned int>(table_size);\n\tmemset(table, -1, table_size * sizeof(unsigned int));\n\n\t// build forward remap: for each vertex, which other (canonical) vertex does it map to?\n\t// we use position equivalence for this, and remap vertices to other existing vertices\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tunsigned int index = unsigned(i);\n\t\tunsigned int* entry = hashLookup2(table, table_size, hasher, index, ~0u);\n\n\t\tif (*entry == ~0u)\n\t\t\t*entry = index;\n\n\t\tremap[index] = *entry;\n\t}\n\n\tallocator.deallocate(table);\n\n\tif (!wedge)\n\t\treturn;\n\n\t// build wedge table: for each vertex, which other vertex is the next wedge that also maps to the same vertex?\n\t// entries in table form a (cyclic) wedge loop per vertex; for manifold vertices, wedge[i] == remap[i] == i\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\twedge[i] = unsigned(i);\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tif (remap[i] != i)\n\t\t{\n\t\t\tunsigned int r = remap[i];\n\n\t\t\twedge[i] = wedge[r];\n\t\t\twedge[r] = unsigned(i);\n\t\t}\n}\n\nstatic unsigned int* buildSparseRemap(unsigned int* indices, size_t index_count, size_t vertex_count, size_t* out_vertex_count, meshopt_Allocator& allocator)\n{\n\t// use a bit set to compute the precise number of unique vertices\n\tunsigned char* filter = allocator.allocate<unsigned char>((vertex_count + 7) / 8);\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int index = indices[i];\n\t\tassert(index < vertex_count);\n\t\tfilter[index / 8] = 0;\n\t}\n\n\tsize_t unique = 0;\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int index = indices[i];\n\t\tunique += (filter[index / 8] & (1 << (index % 8))) == 0;\n\t\tfilter[index / 8] |= 1 << (index % 8);\n\t}\n\n\tunsigned int* remap = allocator.allocate<unsigned int>(unique);\n\tsize_t offset = 0;\n\n\t// temporary map dense => sparse; we allocate it last so that we can deallocate it\n\tsize_t revremap_size = hashBuckets2(unique);\n\tunsigned int* revremap = allocator.allocate<unsigned int>(revremap_size);\n\tmemset(revremap, -1, revremap_size * sizeof(unsigned int));\n\n\t// fill remap, using revremap as a helper, and rewrite indices in the same pass\n\tRemapHasher hasher = {remap};\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int index = indices[i];\n\t\tunsigned int* entry = hashLookup2(revremap, revremap_size, hasher, index, ~0u);\n\n\t\tif (*entry == ~0u)\n\t\t{\n\t\t\tremap[offset] = index;\n\t\t\t*entry = unsigned(offset);\n\t\t\toffset++;\n\t\t}\n\n\t\tindices[i] = *entry;\n\t}\n\n\tallocator.deallocate(revremap);\n\n\tassert(offset == unique);\n\t*out_vertex_count = unique;\n\n\treturn remap;\n}\n\nenum VertexKind\n{\n\tKind_Manifold, // not on an attribute seam, not on any boundary\n\tKind_Border,   // not on an attribute seam, has exactly two open edges\n\tKind_Seam,     // on an attribute seam with exactly two attribute seam edges\n\tKind_Complex,  // none of the above; these vertices can move as long as all wedges move to the target vertex\n\tKind_Locked,   // none of the above; these vertices can't move\n\n\tKind_Count\n};\n\n// manifold vertices can collapse onto anything\n// border/seam vertices can collapse onto border/seam respectively, or locked\n// complex vertices can collapse onto complex/locked\n// a rule of thumb is that collapsing kind A into kind B preserves the kind B in the target vertex\n// for example, while we could collapse Complex into Manifold, this would mean the target vertex isn't Manifold anymore\nconst unsigned char kCanCollapse[Kind_Count][Kind_Count] = {\n    {1, 1, 1, 1, 1},\n    {0, 1, 0, 0, 1},\n    {0, 0, 1, 0, 1},\n    {0, 0, 0, 1, 1},\n    {0, 0, 0, 0, 0},\n};\n\n// if a vertex is manifold or seam, adjoining edges are guaranteed to have an opposite edge\n// note that for seam edges, the opposite edge isn't present in the attribute-based topology\n// but is present if you consider a position-only mesh variant\n// while many complex collapses have the opposite edge, since complex vertices collapse to the\n// same wedge, keeping opposite edges separate improves the quality by considering both targets\nconst unsigned char kHasOpposite[Kind_Count][Kind_Count] = {\n    {1, 1, 1, 1, 1},\n    {1, 0, 1, 0, 0},\n    {1, 1, 1, 0, 1},\n    {1, 0, 0, 0, 0},\n    {1, 0, 1, 0, 0},\n};\n\nstatic bool hasEdge(const EdgeAdjacency& adjacency, unsigned int a, unsigned int b)\n{\n\tunsigned int count = adjacency.offsets[a + 1] - adjacency.offsets[a];\n\tconst EdgeAdjacency::Edge* edges = adjacency.data + adjacency.offsets[a];\n\n\tfor (size_t i = 0; i < count; ++i)\n\t\tif (edges[i].next == b)\n\t\t\treturn true;\n\n\treturn false;\n}\n\nstatic bool hasEdge(const EdgeAdjacency& adjacency, unsigned int a, unsigned int b, const unsigned int* remap, const unsigned int* wedge)\n{\n\tunsigned int v = a;\n\n\tdo\n\t{\n\t\tunsigned int count = adjacency.offsets[v + 1] - adjacency.offsets[v];\n\t\tconst EdgeAdjacency::Edge* edges = adjacency.data + adjacency.offsets[v];\n\n\t\tfor (size_t i = 0; i < count; ++i)\n\t\t\tif (remap[edges[i].next] == remap[b])\n\t\t\t\treturn true;\n\n\t\tv = wedge[v];\n\t} while (v != a);\n\n\treturn false;\n}\n\nstatic void classifyVertices(unsigned char* result, unsigned int* loop, unsigned int* loopback, size_t vertex_count, const EdgeAdjacency& adjacency, const unsigned int* remap, const unsigned int* wedge, const unsigned char* vertex_lock, const unsigned int* sparse_remap, unsigned int options)\n{\n\tmemset(loop, -1, vertex_count * sizeof(unsigned int));\n\tmemset(loopback, -1, vertex_count * sizeof(unsigned int));\n\n\t// incoming & outgoing open edges: ~0u if no open edges, i if there are more than 1\n\t// note that this is the same data as required in loop[] arrays; loop[] data is only used for border/seam by default\n\t// in permissive mode we also use it to guide complex-complex collapses, so we fill it for all vertices\n\tunsigned int* openinc = loopback;\n\tunsigned int* openout = loop;\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tunsigned int vertex = unsigned(i);\n\n\t\tunsigned int count = adjacency.offsets[vertex + 1] - adjacency.offsets[vertex];\n\t\tconst EdgeAdjacency::Edge* edges = adjacency.data + adjacency.offsets[vertex];\n\n\t\tfor (size_t j = 0; j < count; ++j)\n\t\t{\n\t\t\tunsigned int target = edges[j].next;\n\n\t\t\tif (target == vertex)\n\t\t\t{\n\t\t\t\t// degenerate triangles have two distinct edges instead of three, and the self edge\n\t\t\t\t// is bi-directional by definition; this can break border/seam classification by \"closing\"\n\t\t\t\t// the open edge from another triangle and falsely marking the vertex as manifold\n\t\t\t\t// instead we mark the vertex as having >1 open edges which turns it into locked/complex\n\t\t\t\topeninc[vertex] = openout[vertex] = vertex;\n\t\t\t}\n\t\t\telse if (!hasEdge(adjacency, target, vertex))\n\t\t\t{\n\t\t\t\topeninc[target] = (openinc[target] == ~0u) ? vertex : target;\n\t\t\t\topenout[vertex] = (openout[vertex] == ~0u) ? target : vertex;\n\t\t\t}\n\t\t}\n\t}\n\n#if TRACE\n\tsize_t stats[4] = {};\n#endif\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tif (remap[i] == i)\n\t\t{\n\t\t\tif (wedge[i] == i)\n\t\t\t{\n\t\t\t\t// no attribute seam, need to check if it's manifold\n\t\t\t\tunsigned int openi = openinc[i], openo = openout[i];\n\n\t\t\t\t// note: we classify any vertices with no open edges as manifold\n\t\t\t\t// this is technically incorrect - if 4 triangles share an edge, we'll classify vertices as manifold\n\t\t\t\t// it's unclear if this is a problem in practice\n\t\t\t\tif (openi == ~0u && openo == ~0u)\n\t\t\t\t{\n\t\t\t\t\tresult[i] = Kind_Manifold;\n\t\t\t\t}\n\t\t\t\telse if (openi != ~0u && openo != ~0u && remap[openi] == remap[openo] && openi != i)\n\t\t\t\t{\n\t\t\t\t\t// classify half-seams as seams (the branch below would mis-classify them as borders)\n\t\t\t\t\t// half-seam is a single vertex that connects to both vertices of a potential seam\n\t\t\t\t\t// treating these as seams allows collapsing the \"full\" seam vertex onto them\n\t\t\t\t\tresult[i] = Kind_Seam;\n\t\t\t\t}\n\t\t\t\telse if (openi != i && openo != i)\n\t\t\t\t{\n\t\t\t\t\tresult[i] = Kind_Border;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tresult[i] = Kind_Locked;\n\t\t\t\t\tTRACESTATS(0);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (wedge[wedge[i]] == i)\n\t\t\t{\n\t\t\t\t// attribute seam; need to distinguish between Seam and Locked\n\t\t\t\tunsigned int w = wedge[i];\n\t\t\t\tunsigned int openiv = openinc[i], openov = openout[i];\n\t\t\t\tunsigned int openiw = openinc[w], openow = openout[w];\n\n\t\t\t\t// seam should have one open half-edge for each vertex, and the edges need to \"connect\" - point to the same vertex post-remap\n\t\t\t\tif (openiv != ~0u && openiv != i && openov != ~0u && openov != i &&\n\t\t\t\t    openiw != ~0u && openiw != w && openow != ~0u && openow != w)\n\t\t\t\t{\n\t\t\t\t\tif (remap[openiv] == remap[openow] && remap[openov] == remap[openiw] && remap[openiv] != remap[openov])\n\t\t\t\t\t{\n\t\t\t\t\t\tresult[i] = Kind_Seam;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tresult[i] = Kind_Locked;\n\t\t\t\t\t\tTRACESTATS(1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tresult[i] = Kind_Locked;\n\t\t\t\t\tTRACESTATS(2);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// more than one vertex maps to this one; we don't have classification available\n\t\t\t\tresult[i] = Kind_Locked;\n\t\t\t\tTRACESTATS(3);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tassert(remap[i] < i);\n\n\t\t\tresult[i] = result[remap[i]];\n\t\t}\n\t}\n\n\tif (options & meshopt_SimplifyPermissive)\n\t\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\t\tif (result[i] == Kind_Seam || result[i] == Kind_Locked)\n\t\t\t{\n\t\t\t\tif (remap[i] != i)\n\t\t\t\t{\n\t\t\t\t\t// only process primary vertices; wedges will be updated to match the primary vertex\n\t\t\t\t\tresult[i] = result[remap[i]];\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tbool protect = false;\n\n\t\t\t\t// vertex_lock may protect any wedge, not just the primary vertex, so we switch to complex only if no wedges are protected\n\t\t\t\tunsigned int v = unsigned(i);\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tunsigned int rv = sparse_remap ? sparse_remap[v] : v;\n\t\t\t\t\tprotect |= vertex_lock && (vertex_lock[rv] & meshopt_SimplifyVertex_Protect) != 0;\n\t\t\t\t\tv = wedge[v];\n\t\t\t\t} while (v != i);\n\n\t\t\t\t// protect if any adjoining edge doesn't have an opposite edge (indicating vertex is on the border)\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tconst EdgeAdjacency::Edge* edges = &adjacency.data[adjacency.offsets[v]];\n\t\t\t\t\tsize_t count = adjacency.offsets[v + 1] - adjacency.offsets[v];\n\n\t\t\t\t\tfor (size_t j = 0; j < count; ++j)\n\t\t\t\t\t\tprotect |= !hasEdge(adjacency, edges[j].next, v, remap, wedge);\n\t\t\t\t\tv = wedge[v];\n\t\t\t\t} while (v != i);\n\n\t\t\t\tresult[i] = protect ? result[i] : int(Kind_Complex);\n\t\t\t}\n\n\tif (vertex_lock)\n\t{\n\t\t// vertex_lock may lock any wedge, not just the primary vertex, so we need to lock the primary vertex and relock any wedges\n\t\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\t{\n\t\t\tunsigned int ri = sparse_remap ? sparse_remap[i] : unsigned(i);\n\n\t\t\tif (vertex_lock[ri] & meshopt_SimplifyVertex_Lock)\n\t\t\t\tresult[remap[i]] = Kind_Locked;\n\t\t}\n\n\t\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\t\tif (result[remap[i]] == Kind_Locked)\n\t\t\t\tresult[i] = Kind_Locked;\n\t}\n\n\tif (options & meshopt_SimplifyLockBorder)\n\t\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\t\tif (result[i] == Kind_Border)\n\t\t\t\tresult[i] = Kind_Locked;\n\n#if TRACE\n\tprintf(\"locked: many open edges %d, disconnected seam %d, many seam edges %d, many wedges %d\\n\",\n\t    int(stats[0]), int(stats[1]), int(stats[2]), int(stats[3]));\n#endif\n}\n\nstruct Vector3\n{\n\tfloat x, y, z;\n};\n\nstatic float rescalePositions(Vector3* result, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, const unsigned int* sparse_remap = NULL, float* out_offset = NULL)\n{\n\tsize_t vertex_stride_float = vertex_positions_stride / sizeof(float);\n\n\tfloat minv[3] = {FLT_MAX, FLT_MAX, FLT_MAX};\n\tfloat maxv[3] = {-FLT_MAX, -FLT_MAX, -FLT_MAX};\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tunsigned int ri = sparse_remap ? sparse_remap[i] : unsigned(i);\n\t\tconst float* v = vertex_positions_data + ri * vertex_stride_float;\n\n\t\tif (result)\n\t\t{\n\t\t\tresult[i].x = v[0];\n\t\t\tresult[i].y = v[1];\n\t\t\tresult[i].z = v[2];\n\t\t}\n\n\t\tfor (int j = 0; j < 3; ++j)\n\t\t{\n\t\t\tfloat vj = v[j];\n\n\t\t\tminv[j] = minv[j] > vj ? vj : minv[j];\n\t\t\tmaxv[j] = maxv[j] < vj ? vj : maxv[j];\n\t\t}\n\t}\n\n\tfloat extent = 0.f;\n\n\textent = (maxv[0] - minv[0]) < extent ? extent : (maxv[0] - minv[0]);\n\textent = (maxv[1] - minv[1]) < extent ? extent : (maxv[1] - minv[1]);\n\textent = (maxv[2] - minv[2]) < extent ? extent : (maxv[2] - minv[2]);\n\n\tif (result)\n\t{\n\t\tfloat scale = extent == 0 ? 0.f : 1.f / extent;\n\n\t\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\t{\n\t\t\tresult[i].x = (result[i].x - minv[0]) * scale;\n\t\t\tresult[i].y = (result[i].y - minv[1]) * scale;\n\t\t\tresult[i].z = (result[i].z - minv[2]) * scale;\n\t\t}\n\t}\n\n\tif (out_offset)\n\t{\n\t\tout_offset[0] = minv[0];\n\t\tout_offset[1] = minv[1];\n\t\tout_offset[2] = minv[2];\n\t}\n\n\treturn extent;\n}\n\nstatic void rescaleAttributes(float* result, const float* vertex_attributes_data, size_t vertex_count, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned int* attribute_remap, const unsigned int* sparse_remap)\n{\n\tsize_t vertex_attributes_stride_float = vertex_attributes_stride / sizeof(float);\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tunsigned int ri = sparse_remap ? sparse_remap[i] : unsigned(i);\n\n\t\tfor (size_t k = 0; k < attribute_count; ++k)\n\t\t{\n\t\t\tunsigned int rk = attribute_remap[k];\n\t\t\tfloat a = vertex_attributes_data[ri * vertex_attributes_stride_float + rk];\n\n\t\t\tresult[i * attribute_count + k] = a * attribute_weights[rk];\n\t\t}\n\t}\n}\n\nstatic void finalizeVertices(float* vertex_positions_data, size_t vertex_positions_stride, float* vertex_attributes_data, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, size_t vertex_count, const Vector3* vertex_positions, const float* vertex_attributes, const unsigned int* sparse_remap, const unsigned int* attribute_remap, float vertex_scale, const float* vertex_offset, const unsigned char* vertex_kind, const unsigned char* vertex_update, const unsigned char* vertex_lock)\n{\n\tsize_t vertex_positions_stride_float = vertex_positions_stride / sizeof(float);\n\tsize_t vertex_attributes_stride_float = vertex_attributes_stride / sizeof(float);\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tif (!vertex_update[i])\n\t\t\tcontinue;\n\n\t\tunsigned int ri = sparse_remap ? sparse_remap[i] : unsigned(i);\n\n\t\t// updating externally locked vertices is not allowed\n\t\tif (vertex_lock && (vertex_lock[ri] & meshopt_SimplifyVertex_Lock) != 0)\n\t\t\tcontinue;\n\n\t\t// moving locked vertices may result in floating point drift\n\t\tif (vertex_kind[i] != Kind_Locked)\n\t\t{\n\t\t\tconst Vector3& p = vertex_positions[i];\n\t\t\tfloat* v = vertex_positions_data + ri * vertex_positions_stride_float;\n\n\t\t\tv[0] = p.x * vertex_scale + vertex_offset[0];\n\t\t\tv[1] = p.y * vertex_scale + vertex_offset[1];\n\t\t\tv[2] = p.z * vertex_scale + vertex_offset[2];\n\t\t}\n\n\t\tif (attribute_count)\n\t\t{\n\t\t\tconst float* sa = vertex_attributes + i * attribute_count;\n\t\t\tfloat* va = vertex_attributes_data + ri * vertex_attributes_stride_float;\n\n\t\t\tfor (size_t k = 0; k < attribute_count; ++k)\n\t\t\t{\n\t\t\t\tunsigned int rk = attribute_remap[k];\n\n\t\t\t\tva[rk] = sa[k] / attribute_weights[rk];\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic const size_t kMaxAttributes = 32;\n\nstruct Quadric\n{\n\t// a00*x^2 + a11*y^2 + a22*z^2 + 2*a10*xy + 2*a20*xz + 2*a21*yz + 2*b0*x + 2*b1*y + 2*b2*z + c\n\tfloat a00, a11, a22;\n\tfloat a10, a20, a21;\n\tfloat b0, b1, b2, c;\n\tfloat w;\n};\n\nstruct QuadricGrad\n{\n\t// gx*x + gy*y + gz*z + gw\n\tfloat gx, gy, gz, gw;\n};\n\nstruct Reservoir\n{\n\tfloat x, y, z;\n\tfloat r, g, b;\n\tfloat w;\n};\n\nstruct Collapse\n{\n\tunsigned int v0;\n\tunsigned int v1;\n\n\tunion\n\t{\n\t\tunsigned int bidi;\n\t\tfloat error;\n\t\tunsigned int errorui;\n\t};\n};\n\nstatic float normalize(Vector3& v)\n{\n\tfloat length = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);\n\n\tif (length > 0)\n\t{\n\t\tv.x /= length;\n\t\tv.y /= length;\n\t\tv.z /= length;\n\t}\n\n\treturn length;\n}\n\nstatic void quadricAdd(Quadric& Q, const Quadric& R)\n{\n\tQ.a00 += R.a00;\n\tQ.a11 += R.a11;\n\tQ.a22 += R.a22;\n\tQ.a10 += R.a10;\n\tQ.a20 += R.a20;\n\tQ.a21 += R.a21;\n\tQ.b0 += R.b0;\n\tQ.b1 += R.b1;\n\tQ.b2 += R.b2;\n\tQ.c += R.c;\n\tQ.w += R.w;\n}\n\nstatic void quadricAdd(QuadricGrad& G, const QuadricGrad& R)\n{\n\tG.gx += R.gx;\n\tG.gy += R.gy;\n\tG.gz += R.gz;\n\tG.gw += R.gw;\n}\n\nstatic void quadricAdd(QuadricGrad* G, const QuadricGrad* R, size_t attribute_count)\n{\n\tfor (size_t k = 0; k < attribute_count; ++k)\n\t{\n\t\tG[k].gx += R[k].gx;\n\t\tG[k].gy += R[k].gy;\n\t\tG[k].gz += R[k].gz;\n\t\tG[k].gw += R[k].gw;\n\t}\n}\n\nstatic float quadricEval(const Quadric& Q, const Vector3& v)\n{\n\tfloat rx = Q.b0;\n\tfloat ry = Q.b1;\n\tfloat rz = Q.b2;\n\n\trx += Q.a10 * v.y;\n\try += Q.a21 * v.z;\n\trz += Q.a20 * v.x;\n\n\trx *= 2;\n\try *= 2;\n\trz *= 2;\n\n\trx += Q.a00 * v.x;\n\try += Q.a11 * v.y;\n\trz += Q.a22 * v.z;\n\n\tfloat r = Q.c;\n\tr += rx * v.x;\n\tr += ry * v.y;\n\tr += rz * v.z;\n\n\treturn r;\n}\n\nstatic float quadricError(const Quadric& Q, const Vector3& v)\n{\n\tfloat r = quadricEval(Q, v);\n\tfloat s = Q.w == 0.f ? 0.f : 1.f / Q.w;\n\n\treturn fabsf(r) * s;\n}\n\nstatic float quadricError(const Quadric& Q, const QuadricGrad* G, size_t attribute_count, const Vector3& v, const float* va)\n{\n\tfloat r = quadricEval(Q, v);\n\n\t// see quadricFromAttributes for general derivation; here we need to add the parts of (eval(pos) - attr)^2 that depend on attr\n\tfor (size_t k = 0; k < attribute_count; ++k)\n\t{\n\t\tfloat a = va[k];\n\t\tfloat g = v.x * G[k].gx + v.y * G[k].gy + v.z * G[k].gz + G[k].gw;\n\n\t\tr += a * (a * Q.w - 2 * g);\n\t}\n\n\t// note: unlike position error, we do not normalize by Q.w to retain edge scaling as described in quadricFromAttributes\n\treturn fabsf(r);\n}\n\nstatic void quadricFromPlane(Quadric& Q, float a, float b, float c, float d, float w)\n{\n\tfloat aw = a * w;\n\tfloat bw = b * w;\n\tfloat cw = c * w;\n\tfloat dw = d * w;\n\n\tQ.a00 = a * aw;\n\tQ.a11 = b * bw;\n\tQ.a22 = c * cw;\n\tQ.a10 = a * bw;\n\tQ.a20 = a * cw;\n\tQ.a21 = b * cw;\n\tQ.b0 = a * dw;\n\tQ.b1 = b * dw;\n\tQ.b2 = c * dw;\n\tQ.c = d * dw;\n\tQ.w = w;\n}\n\nstatic void quadricFromPoint(Quadric& Q, float x, float y, float z, float w)\n{\n\tQ.a00 = Q.a11 = Q.a22 = w;\n\tQ.a10 = Q.a20 = Q.a21 = 0;\n\tQ.b0 = -x * w;\n\tQ.b1 = -y * w;\n\tQ.b2 = -z * w;\n\tQ.c = (x * x + y * y + z * z) * w;\n\tQ.w = w;\n}\n\nstatic void quadricFromTriangle(Quadric& Q, const Vector3& p0, const Vector3& p1, const Vector3& p2, float weight)\n{\n\tVector3 p10 = {p1.x - p0.x, p1.y - p0.y, p1.z - p0.z};\n\tVector3 p20 = {p2.x - p0.x, p2.y - p0.y, p2.z - p0.z};\n\n\t// normal = cross(p1 - p0, p2 - p0)\n\tVector3 normal = {p10.y * p20.z - p10.z * p20.y, p10.z * p20.x - p10.x * p20.z, p10.x * p20.y - p10.y * p20.x};\n\tfloat area = normalize(normal);\n\n\tfloat distance = normal.x * p0.x + normal.y * p0.y + normal.z * p0.z;\n\n\t// we use sqrtf(area) so that the error is scaled linearly; this tends to improve silhouettes\n\tquadricFromPlane(Q, normal.x, normal.y, normal.z, -distance, sqrtf(area) * weight);\n}\n\nstatic void quadricFromTriangleEdge(Quadric& Q, const Vector3& p0, const Vector3& p1, const Vector3& p2, float weight)\n{\n\tVector3 p10 = {p1.x - p0.x, p1.y - p0.y, p1.z - p0.z};\n\n\t// edge length; keep squared length around for projection correction\n\tfloat lengthsq = p10.x * p10.x + p10.y * p10.y + p10.z * p10.z;\n\tfloat length = sqrtf(lengthsq);\n\n\t// p20p = length of projection of p2-p0 onto p1-p0; note that p10 is unnormalized so we need to correct it later\n\tVector3 p20 = {p2.x - p0.x, p2.y - p0.y, p2.z - p0.z};\n\tfloat p20p = p20.x * p10.x + p20.y * p10.y + p20.z * p10.z;\n\n\t// perp = perpendicular vector from p2 to line segment p1-p0\n\t// note: since p10 is unnormalized we need to correct the projection; we scale p20 instead to take advantage of normalize below\n\tVector3 perp = {p20.x * lengthsq - p10.x * p20p, p20.y * lengthsq - p10.y * p20p, p20.z * lengthsq - p10.z * p20p};\n\tnormalize(perp);\n\n\tfloat distance = perp.x * p0.x + perp.y * p0.y + perp.z * p0.z;\n\n\t// note: the weight is scaled linearly with edge length; this has to match the triangle weight\n\tquadricFromPlane(Q, perp.x, perp.y, perp.z, -distance, length * weight);\n}\n\nstatic void quadricFromAttributes(Quadric& Q, QuadricGrad* G, const Vector3& p0, const Vector3& p1, const Vector3& p2, const float* va0, const float* va1, const float* va2, size_t attribute_count)\n{\n\t// for each attribute we want to encode the following function into the quadric:\n\t// (eval(pos) - attr)^2\n\t// where eval(pos) interpolates attribute across the triangle like so:\n\t// eval(pos) = pos.x * gx + pos.y * gy + pos.z * gz + gw\n\t// where gx/gy/gz/gw are gradients\n\tVector3 p10 = {p1.x - p0.x, p1.y - p0.y, p1.z - p0.z};\n\tVector3 p20 = {p2.x - p0.x, p2.y - p0.y, p2.z - p0.z};\n\n\t// normal = cross(p1 - p0, p2 - p0)\n\tVector3 normal = {p10.y * p20.z - p10.z * p20.y, p10.z * p20.x - p10.x * p20.z, p10.x * p20.y - p10.y * p20.x};\n\tfloat area = sqrtf(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z) * 0.5f;\n\n\t// quadric is weighted with the square of edge length (= area)\n\t// this equalizes the units with the positional error (which, after normalization, is a square of distance)\n\t// as a result, a change in weighted attribute of 1 along distance d is approximately equivalent to a change in position of d\n\tfloat w = area;\n\n\t// we compute gradients using barycentric coordinates; barycentric coordinates can be computed as follows:\n\t// v = (d11 * d20 - d01 * d21) / denom\n\t// w = (d00 * d21 - d01 * d20) / denom\n\t// u = 1 - v - w\n\t// here v0, v1 are triangle edge vectors, v2 is a vector from point to triangle corner, and dij = dot(vi, vj)\n\t// note: v2 and d20/d21 can not be evaluated here as v2 is effectively an unknown variable; we need these only as variables for derivation of gradients\n\tconst Vector3& v0 = p10;\n\tconst Vector3& v1 = p20;\n\tfloat d00 = v0.x * v0.x + v0.y * v0.y + v0.z * v0.z;\n\tfloat d01 = v0.x * v1.x + v0.y * v1.y + v0.z * v1.z;\n\tfloat d11 = v1.x * v1.x + v1.y * v1.y + v1.z * v1.z;\n\tfloat denom = d00 * d11 - d01 * d01;\n\tfloat denomr = denom == 0 ? 0.f : 1.f / denom;\n\n\t// precompute gradient factors\n\t// these are derived by directly computing derivative of eval(pos) = a0 * u + a1 * v + a2 * w and factoring out expressions that are shared between attributes\n\tfloat gx1 = (d11 * v0.x - d01 * v1.x) * denomr;\n\tfloat gx2 = (d00 * v1.x - d01 * v0.x) * denomr;\n\tfloat gy1 = (d11 * v0.y - d01 * v1.y) * denomr;\n\tfloat gy2 = (d00 * v1.y - d01 * v0.y) * denomr;\n\tfloat gz1 = (d11 * v0.z - d01 * v1.z) * denomr;\n\tfloat gz2 = (d00 * v1.z - d01 * v0.z) * denomr;\n\n\tmemset(&Q, 0, sizeof(Quadric));\n\n\tQ.w = w;\n\n\tfor (size_t k = 0; k < attribute_count; ++k)\n\t{\n\t\tfloat a0 = va0[k], a1 = va1[k], a2 = va2[k];\n\n\t\t// compute gradient of eval(pos) for x/y/z/w\n\t\t// the formulas below are obtained by directly computing derivative of eval(pos) = a0 * u + a1 * v + a2 * w\n\t\tfloat gx = gx1 * (a1 - a0) + gx2 * (a2 - a0);\n\t\tfloat gy = gy1 * (a1 - a0) + gy2 * (a2 - a0);\n\t\tfloat gz = gz1 * (a1 - a0) + gz2 * (a2 - a0);\n\t\tfloat gw = a0 - p0.x * gx - p0.y * gy - p0.z * gz;\n\n\t\t// quadric encodes (eval(pos)-attr)^2; this means that the resulting expansion needs to compute, for example, pos.x * pos.y * K\n\t\t// since quadrics already encode factors for pos.x * pos.y, we can accumulate almost everything in basic quadric fields\n\t\t// note: for simplicity we scale all factors by weight here instead of outside the loop\n\t\tQ.a00 += w * (gx * gx);\n\t\tQ.a11 += w * (gy * gy);\n\t\tQ.a22 += w * (gz * gz);\n\n\t\tQ.a10 += w * (gy * gx);\n\t\tQ.a20 += w * (gz * gx);\n\t\tQ.a21 += w * (gz * gy);\n\n\t\tQ.b0 += w * (gx * gw);\n\t\tQ.b1 += w * (gy * gw);\n\t\tQ.b2 += w * (gz * gw);\n\n\t\tQ.c += w * (gw * gw);\n\n\t\t// the only remaining sum components are ones that depend on attr; these will be addded during error evaluation, see quadricError\n\t\tG[k].gx = w * gx;\n\t\tG[k].gy = w * gy;\n\t\tG[k].gz = w * gz;\n\t\tG[k].gw = w * gw;\n\t}\n}\n\nstatic void quadricVolumeGradient(QuadricGrad& G, const Vector3& p0, const Vector3& p1, const Vector3& p2)\n{\n\tVector3 p10 = {p1.x - p0.x, p1.y - p0.y, p1.z - p0.z};\n\tVector3 p20 = {p2.x - p0.x, p2.y - p0.y, p2.z - p0.z};\n\n\t// normal = cross(p1 - p0, p2 - p0)\n\tVector3 normal = {p10.y * p20.z - p10.z * p20.y, p10.z * p20.x - p10.x * p20.z, p10.x * p20.y - p10.y * p20.x};\n\tfloat area = normalize(normal) * 0.5f;\n\n\tG.gx = normal.x * area;\n\tG.gy = normal.y * area;\n\tG.gz = normal.z * area;\n\tG.gw = (-p0.x * normal.x - p0.y * normal.y - p0.z * normal.z) * area;\n}\n\nstatic bool quadricSolve(Vector3& p, const Quadric& Q, const QuadricGrad& GV)\n{\n\t// solve A*p = -b where A is the quadric matrix and b is the linear term\n\tfloat a00 = Q.a00, a11 = Q.a11, a22 = Q.a22;\n\tfloat a10 = Q.a10, a20 = Q.a20, a21 = Q.a21;\n\tfloat x0 = -Q.b0, x1 = -Q.b1, x2 = -Q.b2;\n\n\tfloat eps = 1e-6f * Q.w;\n\n\t// LDL decomposition: A = LDL^T\n\tfloat d0 = a00;\n\tfloat l10 = a10 / d0;\n\tfloat l20 = a20 / d0;\n\n\tfloat d1 = a11 - a10 * l10;\n\tfloat dl21 = a21 - a20 * l10;\n\tfloat l21 = dl21 / d1;\n\n\tfloat d2 = a22 - a20 * l20 - dl21 * l21;\n\n\t// solve L*y = x\n\tfloat y0 = x0;\n\tfloat y1 = x1 - l10 * y0;\n\tfloat y2 = x2 - l20 * y0 - l21 * y1;\n\n\t// solve D*z = y\n\tfloat z0 = y0 / d0;\n\tfloat z1 = y1 / d1;\n\tfloat z2 = y2 / d2;\n\n\t// augment system with linear constraint GV using Lagrange multiplier\n\tfloat a30 = GV.gx, a31 = GV.gy, a32 = GV.gz;\n\tfloat x3 = -GV.gw;\n\n\tfloat l30 = a30 / d0;\n\tfloat dl31 = a31 - a30 * l10;\n\tfloat l31 = dl31 / d1;\n\tfloat dl32 = a32 - a30 * l20 - dl31 * l21;\n\tfloat l32 = dl32 / d2;\n\tfloat d3 = 0.f - a30 * l30 - dl31 * l31 - dl32 * l32;\n\n\tfloat y3 = x3 - l30 * y0 - l31 * y1 - l32 * y2;\n\tfloat z3 = fabsf(d3) > eps ? y3 / d3 : 0.f; // if d3 is zero, we can ignore the constraint\n\n\t// substitute L^T*p = z\n\tfloat lambda = z3;\n\tfloat pz = z2 - l32 * lambda;\n\tfloat py = z1 - l21 * pz - l31 * lambda;\n\tfloat px = z0 - l10 * py - l20 * pz - l30 * lambda;\n\n\tp.x = px;\n\tp.y = py;\n\tp.z = pz;\n\n\treturn fabsf(d0) > eps && fabsf(d1) > eps && fabsf(d2) > eps;\n}\n\nstatic void quadricReduceAttributes(Quadric& Q, const Quadric& A, const QuadricGrad* G, size_t attribute_count)\n{\n\t// update vertex quadric with attribute quadric; multiply by vertex weight to minimize normalized error\n\tQ.a00 += A.a00 * Q.w;\n\tQ.a11 += A.a11 * Q.w;\n\tQ.a22 += A.a22 * Q.w;\n\tQ.a10 += A.a10 * Q.w;\n\tQ.a20 += A.a20 * Q.w;\n\tQ.a21 += A.a21 * Q.w;\n\tQ.b0 += A.b0 * Q.w;\n\tQ.b1 += A.b1 * Q.w;\n\tQ.b2 += A.b2 * Q.w;\n\n\tfloat iaw = A.w == 0 ? 0.f : Q.w / A.w;\n\n\t// update linear system based on attribute gradients (BB^T/a)\n\tfor (size_t k = 0; k < attribute_count; ++k)\n\t{\n\t\tconst QuadricGrad& g = G[k];\n\n\t\tQ.a00 -= (g.gx * g.gx) * iaw;\n\t\tQ.a11 -= (g.gy * g.gy) * iaw;\n\t\tQ.a22 -= (g.gz * g.gz) * iaw;\n\t\tQ.a10 -= (g.gx * g.gy) * iaw;\n\t\tQ.a20 -= (g.gx * g.gz) * iaw;\n\t\tQ.a21 -= (g.gy * g.gz) * iaw;\n\n\t\tQ.b0 -= (g.gx * g.gw) * iaw;\n\t\tQ.b1 -= (g.gy * g.gw) * iaw;\n\t\tQ.b2 -= (g.gz * g.gw) * iaw;\n\t}\n}\n\nstatic void fillFaceQuadrics(Quadric* vertex_quadrics, QuadricGrad* volume_gradients, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const unsigned int* remap)\n{\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tunsigned int i0 = indices[i + 0];\n\t\tunsigned int i1 = indices[i + 1];\n\t\tunsigned int i2 = indices[i + 2];\n\n\t\tQuadric Q;\n\t\tquadricFromTriangle(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], 1.f);\n\n\t\tquadricAdd(vertex_quadrics[remap[i0]], Q);\n\t\tquadricAdd(vertex_quadrics[remap[i1]], Q);\n\t\tquadricAdd(vertex_quadrics[remap[i2]], Q);\n\n\t\tif (volume_gradients)\n\t\t{\n\t\t\tQuadricGrad GV;\n\t\t\tquadricVolumeGradient(GV, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2]);\n\n\t\t\tquadricAdd(volume_gradients[remap[i0]], GV);\n\t\t\tquadricAdd(volume_gradients[remap[i1]], GV);\n\t\t\tquadricAdd(volume_gradients[remap[i2]], GV);\n\t\t}\n\t}\n}\n\nstatic void fillVertexQuadrics(Quadric* vertex_quadrics, const Vector3* vertex_positions, size_t vertex_count, const unsigned int* remap, unsigned int options)\n{\n\t// by default, we use a very small weight to improve triangulation and numerical stability without affecting the shape or error\n\tfloat factor = (options & meshopt_SimplifyRegularize) ? 1e-1f : 1e-7f;\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tif (remap[i] != i)\n\t\t\tcontinue;\n\n\t\tconst Vector3& p = vertex_positions[i];\n\t\tfloat w = vertex_quadrics[i].w * factor;\n\n\t\tQuadric Q;\n\t\tquadricFromPoint(Q, p.x, p.y, p.z, w);\n\n\t\tquadricAdd(vertex_quadrics[i], Q);\n\t}\n}\n\nstatic void fillEdgeQuadrics(Quadric* vertex_quadrics, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const unsigned int* remap, const unsigned char* vertex_kind, const unsigned int* loop, const unsigned int* loopback)\n{\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tstatic const int next[4] = {1, 2, 0, 1};\n\n\t\tfor (int e = 0; e < 3; ++e)\n\t\t{\n\t\t\tunsigned int i0 = indices[i + e];\n\t\t\tunsigned int i1 = indices[i + next[e]];\n\n\t\t\tunsigned char k0 = vertex_kind[i0];\n\t\t\tunsigned char k1 = vertex_kind[i1];\n\n\t\t\t// check that either i0 or i1 are border/seam and are on the same edge loop\n\t\t\t// note that we need to add the error even for edged that connect e.g. border & locked\n\t\t\t// if we don't do that, the adjacent border->border edge won't have correct errors for corners\n\t\t\tif (k0 != Kind_Border && k0 != Kind_Seam && k1 != Kind_Border && k1 != Kind_Seam)\n\t\t\t\tcontinue;\n\n\t\t\tif ((k0 == Kind_Border || k0 == Kind_Seam) && loop[i0] != i1)\n\t\t\t\tcontinue;\n\n\t\t\tif ((k1 == Kind_Border || k1 == Kind_Seam) && loopback[i1] != i0)\n\t\t\t\tcontinue;\n\n\t\t\tunsigned int i2 = indices[i + next[e + 1]];\n\n\t\t\t// we try hard to maintain border edge geometry; seam edges can move more freely\n\t\t\t// due to topological restrictions on collapses, seam quadrics slightly improves collapse structure but aren't critical\n\t\t\tconst float kEdgeWeightSeam = 0.5f; // applied twice due to opposite edges\n\t\t\tconst float kEdgeWeightBorder = 10.f;\n\n\t\t\tfloat edgeWeight = (k0 == Kind_Border || k1 == Kind_Border) ? kEdgeWeightBorder : kEdgeWeightSeam;\n\n\t\t\tQuadric Q;\n\t\t\tquadricFromTriangleEdge(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], edgeWeight);\n\n\t\t\tQuadric QT;\n\t\t\tquadricFromTriangle(QT, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], edgeWeight);\n\n\t\t\t// mix edge quadric with triangle quadric to stabilize collapses in both directions; both quadrics inherit edge weight so that their error is added\n\t\t\tQT.w = 0;\n\t\t\tquadricAdd(Q, QT);\n\n\t\t\tquadricAdd(vertex_quadrics[remap[i0]], Q);\n\t\t\tquadricAdd(vertex_quadrics[remap[i1]], Q);\n\t\t}\n\t}\n}\n\nstatic void fillAttributeQuadrics(Quadric* attribute_quadrics, QuadricGrad* attribute_gradients, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const float* vertex_attributes, size_t attribute_count)\n{\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tunsigned int i0 = indices[i + 0];\n\t\tunsigned int i1 = indices[i + 1];\n\t\tunsigned int i2 = indices[i + 2];\n\n\t\tQuadric QA;\n\t\tQuadricGrad G[kMaxAttributes];\n\t\tquadricFromAttributes(QA, G, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], &vertex_attributes[i0 * attribute_count], &vertex_attributes[i1 * attribute_count], &vertex_attributes[i2 * attribute_count], attribute_count);\n\n\t\tquadricAdd(attribute_quadrics[i0], QA);\n\t\tquadricAdd(attribute_quadrics[i1], QA);\n\t\tquadricAdd(attribute_quadrics[i2], QA);\n\n\t\tquadricAdd(&attribute_gradients[i0 * attribute_count], G, attribute_count);\n\t\tquadricAdd(&attribute_gradients[i1 * attribute_count], G, attribute_count);\n\t\tquadricAdd(&attribute_gradients[i2 * attribute_count], G, attribute_count);\n\t}\n}\n\n// does triangle ABC flip when C is replaced with D?\nstatic bool hasTriangleFlip(const Vector3& a, const Vector3& b, const Vector3& c, const Vector3& d)\n{\n\tVector3 eb = {b.x - a.x, b.y - a.y, b.z - a.z};\n\tVector3 ec = {c.x - a.x, c.y - a.y, c.z - a.z};\n\tVector3 ed = {d.x - a.x, d.y - a.y, d.z - a.z};\n\n\tVector3 nbc = {eb.y * ec.z - eb.z * ec.y, eb.z * ec.x - eb.x * ec.z, eb.x * ec.y - eb.y * ec.x};\n\tVector3 nbd = {eb.y * ed.z - eb.z * ed.y, eb.z * ed.x - eb.x * ed.z, eb.x * ed.y - eb.y * ed.x};\n\n\tfloat ndp = nbc.x * nbd.x + nbc.y * nbd.y + nbc.z * nbd.z;\n\tfloat abc = nbc.x * nbc.x + nbc.y * nbc.y + nbc.z * nbc.z;\n\tfloat abd = nbd.x * nbd.x + nbd.y * nbd.y + nbd.z * nbd.z;\n\n\t// scale is cos(angle); somewhat arbitrarily set to ~75 degrees\n\t// note that the \"pure\" check is ndp <= 0 (90 degree cutoff) but that allows flipping through a series of close-to-90 collapses\n\treturn ndp <= 0.25f * sqrtf(abc * abd);\n}\n\nstatic bool hasTriangleFlips(const EdgeAdjacency& adjacency, const Vector3* vertex_positions, const unsigned int* collapse_remap, unsigned int i0, unsigned int i1)\n{\n\tassert(collapse_remap[i0] == i0);\n\tassert(collapse_remap[i1] == i1);\n\n\tconst Vector3& v0 = vertex_positions[i0];\n\tconst Vector3& v1 = vertex_positions[i1];\n\n\tconst EdgeAdjacency::Edge* edges = &adjacency.data[adjacency.offsets[i0]];\n\tsize_t count = adjacency.offsets[i0 + 1] - adjacency.offsets[i0];\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned int a = collapse_remap[edges[i].next];\n\t\tunsigned int b = collapse_remap[edges[i].prev];\n\n\t\t// skip triangles that will get collapsed by i0->i1 collapse or already got collapsed previously\n\t\tif (a == i1 || b == i1 || a == b)\n\t\t\tcontinue;\n\n\t\t// early-out when at least one triangle flips due to a collapse\n\t\tif (hasTriangleFlip(vertex_positions[a], vertex_positions[b], v0, v1))\n\t\t{\n#if TRACE >= 2\n\t\t\tprintf(\"edge block %d -> %d: flip welded %d %d %d\\n\", i0, i1, a, i0, b);\n#endif\n\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nstatic bool hasTriangleFlips(const EdgeAdjacency& adjacency, const Vector3* vertex_positions, unsigned int i0, const Vector3& v1)\n{\n\tconst Vector3& v0 = vertex_positions[i0];\n\n\tconst EdgeAdjacency::Edge* edges = &adjacency.data[adjacency.offsets[i0]];\n\tsize_t count = adjacency.offsets[i0 + 1] - adjacency.offsets[i0];\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned int a = edges[i].next, b = edges[i].prev;\n\n\t\tif (hasTriangleFlip(vertex_positions[a], vertex_positions[b], v0, v1))\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic float getNeighborhoodRadius(const EdgeAdjacency& adjacency, const Vector3* vertex_positions, unsigned int i0)\n{\n\tconst Vector3& v0 = vertex_positions[i0];\n\n\tconst EdgeAdjacency::Edge* edges = &adjacency.data[adjacency.offsets[i0]];\n\tsize_t count = adjacency.offsets[i0 + 1] - adjacency.offsets[i0];\n\n\tfloat result = 0.f;\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned int a = edges[i].next, b = edges[i].prev;\n\n\t\tconst Vector3& va = vertex_positions[a];\n\t\tconst Vector3& vb = vertex_positions[b];\n\n\t\tfloat da = (va.x - v0.x) * (va.x - v0.x) + (va.y - v0.y) * (va.y - v0.y) + (va.z - v0.z) * (va.z - v0.z);\n\t\tfloat db = (vb.x - v0.x) * (vb.x - v0.x) + (vb.y - v0.y) * (vb.y - v0.y) + (vb.z - v0.z) * (vb.z - v0.z);\n\n\t\tresult = result < da ? da : result;\n\t\tresult = result < db ? db : result;\n\t}\n\n\treturn sqrtf(result);\n}\n\nstatic unsigned int getComplexTarget(unsigned int v, unsigned int target, const unsigned int* remap, const unsigned int* loop, const unsigned int* loopback)\n{\n\tunsigned int r = remap[target];\n\n\t// use loop metadata to guide complex collapses towards the correct wedge\n\t// this works for edges on attribute discontinuities because loop/loopback track the single half-edge without a pair, similar to seams\n\tif (loop[v] != ~0u && remap[loop[v]] == r)\n\t\treturn loop[v];\n\telse if (loopback[v] != ~0u && remap[loopback[v]] == r)\n\t\treturn loopback[v];\n\telse\n\t\treturn target;\n}\n\nstatic size_t boundEdgeCollapses(const EdgeAdjacency& adjacency, size_t vertex_count, size_t index_count, unsigned char* vertex_kind)\n{\n\tsize_t dual_count = 0;\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tunsigned char k = vertex_kind[i];\n\t\tunsigned int e = adjacency.offsets[i + 1] - adjacency.offsets[i];\n\n\t\tdual_count += (k == Kind_Manifold || k == Kind_Seam) ? e : 0;\n\t}\n\n\tassert(dual_count <= index_count);\n\n\t// pad capacity by 3 so that we can check for overflow once per triangle instead of once per edge\n\treturn (index_count - dual_count / 2) + 3;\n}\n\nstatic size_t pickEdgeCollapses(Collapse* collapses, size_t collapse_capacity, const unsigned int* indices, size_t index_count, const unsigned int* remap, const unsigned char* vertex_kind, const unsigned int* loop, const unsigned int* loopback)\n{\n\tsize_t collapse_count = 0;\n\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tstatic const int next[3] = {1, 2, 0};\n\n\t\t// this should never happen as boundEdgeCollapses should give an upper bound for the collapse count, but in an unlikely event it does we can just drop extra collapses\n\t\tif (collapse_count + 3 > collapse_capacity)\n\t\t\tbreak;\n\n\t\tfor (int e = 0; e < 3; ++e)\n\t\t{\n\t\t\tunsigned int i0 = indices[i + e];\n\t\t\tunsigned int i1 = indices[i + next[e]];\n\n\t\t\t// this can happen either when input has a zero-length edge, or when we perform collapses for complex\n\t\t\t// topology w/seams and collapse a manifold vertex that connects to both wedges onto one of them\n\t\t\t// we leave edges like this alone since they may be important for preserving mesh integrity\n\t\t\tif (remap[i0] == remap[i1])\n\t\t\t\tcontinue;\n\n\t\t\tunsigned char k0 = vertex_kind[i0];\n\t\t\tunsigned char k1 = vertex_kind[i1];\n\n\t\t\t// the edge has to be collapsible in at least one direction\n\t\t\tif (!(kCanCollapse[k0][k1] | kCanCollapse[k1][k0]))\n\t\t\t\tcontinue;\n\n\t\t\t// manifold and seam edges should occur twice (i0->i1 and i1->i0) - skip redundant edges\n\t\t\tif (kHasOpposite[k0][k1] && remap[i1] > remap[i0])\n\t\t\t\tcontinue;\n\n\t\t\t// two vertices are on a border or a seam, but there's no direct edge between them\n\t\t\t// this indicates that they belong to two different edge loops and we should not collapse this edge\n\t\t\t// loop[] and loopback[] track half edges so we only need to check one of them\n\t\t\tif ((k0 == Kind_Border || k0 == Kind_Seam) && k1 != Kind_Manifold && loop[i0] != i1)\n\t\t\t\tcontinue;\n\t\t\tif ((k1 == Kind_Border || k1 == Kind_Seam) && k0 != Kind_Manifold && loopback[i1] != i0)\n\t\t\t\tcontinue;\n\n\t\t\t// edge can be collapsed in either direction - we will pick the one with minimum error\n\t\t\t// note: we evaluate error later during collapse ranking, here we just tag the edge as bidirectional\n\t\t\tif (kCanCollapse[k0][k1] & kCanCollapse[k1][k0])\n\t\t\t{\n\t\t\t\tCollapse c = {i0, i1, {/* bidi= */ 1}};\n\t\t\t\tcollapses[collapse_count++] = c;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// edge can only be collapsed in one direction\n\t\t\t\tunsigned int e0 = kCanCollapse[k0][k1] ? i0 : i1;\n\t\t\t\tunsigned int e1 = kCanCollapse[k0][k1] ? i1 : i0;\n\n\t\t\t\tCollapse c = {e0, e1, {/* bidi= */ 0}};\n\t\t\t\tcollapses[collapse_count++] = c;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn collapse_count;\n}\n\nstatic void rankEdgeCollapses(Collapse* collapses, size_t collapse_count, const Vector3* vertex_positions, const float* vertex_attributes, const Quadric* vertex_quadrics, const Quadric* attribute_quadrics, const QuadricGrad* attribute_gradients, size_t attribute_count, const unsigned int* remap, const unsigned int* wedge, const unsigned char* vertex_kind, const unsigned int* loop, const unsigned int* loopback)\n{\n\tfor (size_t i = 0; i < collapse_count; ++i)\n\t{\n\t\tCollapse& c = collapses[i];\n\n\t\tunsigned int i0 = c.v0;\n\t\tunsigned int i1 = c.v1;\n\t\tbool bidi = c.bidi;\n\n\t\tfloat ei = quadricError(vertex_quadrics[remap[i0]], vertex_positions[i1]);\n\t\tfloat ej = bidi ? quadricError(vertex_quadrics[remap[i1]], vertex_positions[i0]) : FLT_MAX;\n\n#if TRACE >= 3\n\t\tfloat di = ei, dj = ej;\n#endif\n\n\t\tif (attribute_count)\n\t\t{\n\t\t\tei += quadricError(attribute_quadrics[i0], &attribute_gradients[i0 * attribute_count], attribute_count, vertex_positions[i1], &vertex_attributes[i1 * attribute_count]);\n\t\t\tej += bidi ? quadricError(attribute_quadrics[i1], &attribute_gradients[i1 * attribute_count], attribute_count, vertex_positions[i0], &vertex_attributes[i0 * attribute_count]) : 0;\n\n\t\t\t// seam edges need to aggregate attribute errors between primary and secondary edges, as attribute quadrics are separate\n\t\t\tif (vertex_kind[i0] == Kind_Seam)\n\t\t\t{\n\t\t\t\t// for seam collapses we need to find the seam pair; this is a bit tricky since we need to rely on edge loops as target vertex may be locked (and thus have more than two wedges)\n\t\t\t\tunsigned int s0 = wedge[i0];\n\t\t\t\tunsigned int s1 = loop[i0] == i1 ? loopback[s0] : loop[s0];\n\n\t\t\t\tassert(wedge[s0] == i0); // s0 may be equal to i0 for half-seams\n\t\t\t\tassert(s1 != ~0u && remap[s1] == remap[i1]);\n\n\t\t\t\t// note: this should never happen due to the assertion above, but when disabled if we ever hit this case we'll get a memory safety issue; for now play it safe\n\t\t\t\ts1 = (s1 != ~0u) ? s1 : wedge[i1];\n\n\t\t\t\tei += quadricError(attribute_quadrics[s0], &attribute_gradients[s0 * attribute_count], attribute_count, vertex_positions[s1], &vertex_attributes[s1 * attribute_count]);\n\t\t\t\tej += bidi ? quadricError(attribute_quadrics[s1], &attribute_gradients[s1 * attribute_count], attribute_count, vertex_positions[s0], &vertex_attributes[s0 * attribute_count]) : 0;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// complex edges can have multiple wedges, so we need to aggregate errors for all wedges based on the selected target\n\t\t\t\tif (vertex_kind[i0] == Kind_Complex)\n\t\t\t\t\tfor (unsigned int v = wedge[i0]; v != i0; v = wedge[v])\n\t\t\t\t\t{\n\t\t\t\t\t\tunsigned int t = getComplexTarget(v, i1, remap, loop, loopback);\n\n\t\t\t\t\t\tei += quadricError(attribute_quadrics[v], &attribute_gradients[v * attribute_count], attribute_count, vertex_positions[t], &vertex_attributes[t * attribute_count]);\n\t\t\t\t\t}\n\n\t\t\t\tif (vertex_kind[i1] == Kind_Complex && bidi)\n\t\t\t\t\tfor (unsigned int v = wedge[i1]; v != i1; v = wedge[v])\n\t\t\t\t\t{\n\t\t\t\t\t\tunsigned int t = getComplexTarget(v, i0, remap, loop, loopback);\n\n\t\t\t\t\t\tej += quadricError(attribute_quadrics[v], &attribute_gradients[v * attribute_count], attribute_count, vertex_positions[t], &vertex_attributes[t * attribute_count]);\n\t\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// pick edge direction with minimal error (branchless)\n\t\tbool rev = bidi & (ej < ei);\n\n\t\tc.v0 = rev ? i1 : i0;\n\t\tc.v1 = rev ? i0 : i1;\n\t\tc.error = ej < ei ? ej : ei;\n\n#if TRACE >= 3\n\t\tif (bidi)\n\t\t\tprintf(\"edge eval %d -> %d: error %f (pos %f, attr %f); reverse %f (pos %f, attr %f)\\n\",\n\t\t\t    rev ? i1 : i0, rev ? i0 : i1,\n\t\t\t    sqrtf(rev ? ej : ei), sqrtf(rev ? dj : di), sqrtf(rev ? ej - dj : ei - di),\n\t\t\t    sqrtf(rev ? ei : ej), sqrtf(rev ? di : dj), sqrtf(rev ? ei - di : ej - dj));\n\t\telse\n\t\t\tprintf(\"edge eval %d -> %d: error %f (pos %f, attr %f)\\n\", i0, i1, sqrtf(c.error), sqrtf(di), sqrtf(ei - di));\n#endif\n\t}\n}\n\nstatic void sortEdgeCollapses(unsigned int* sort_order, const Collapse* collapses, size_t collapse_count)\n{\n\t// we use counting sort to order collapses by error; since the exact sort order is not as critical,\n\t// only top 12 bits of exponent+mantissa (8 bits of exponent and 4 bits of mantissa) are used.\n\t// to avoid excessive stack usage, we clamp the exponent range as collapses with errors much higher than 1 are not useful.\n\tconst unsigned int sort_bits = 12;\n\tconst unsigned int sort_bins = 2048 + 512; // exponent range [-127, 32)\n\n\t// fill histogram for counting sort\n\tunsigned int histogram[sort_bins];\n\tmemset(histogram, 0, sizeof(histogram));\n\n\tfor (size_t i = 0; i < collapse_count; ++i)\n\t{\n\t\t// skip sign bit since error is non-negative\n\t\tunsigned int error = collapses[i].errorui;\n\t\tunsigned int key = (error << 1) >> (32 - sort_bits);\n\t\tkey = key < sort_bins ? key : sort_bins - 1;\n\n\t\thistogram[key]++;\n\t}\n\n\t// compute offsets based on histogram data\n\tsize_t histogram_sum = 0;\n\n\tfor (size_t i = 0; i < sort_bins; ++i)\n\t{\n\t\tsize_t count = histogram[i];\n\t\thistogram[i] = unsigned(histogram_sum);\n\t\thistogram_sum += count;\n\t}\n\n\tassert(histogram_sum == collapse_count);\n\n\t// compute sort order based on offsets\n\tfor (size_t i = 0; i < collapse_count; ++i)\n\t{\n\t\t// skip sign bit since error is non-negative\n\t\tunsigned int error = collapses[i].errorui;\n\t\tunsigned int key = (error << 1) >> (32 - sort_bits);\n\t\tkey = key < sort_bins ? key : sort_bins - 1;\n\n\t\tsort_order[histogram[key]++] = unsigned(i);\n\t}\n}\n\nstatic size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char* collapse_locked, const Collapse* collapses, size_t collapse_count, const unsigned int* collapse_order, const unsigned int* remap, const unsigned int* wedge, const unsigned char* vertex_kind, const unsigned int* loop, const unsigned int* loopback, const Vector3* vertex_positions, const EdgeAdjacency& adjacency, size_t triangle_collapse_goal, float error_limit, float& result_error)\n{\n\tsize_t edge_collapses = 0;\n\tsize_t triangle_collapses = 0;\n\n\t// most collapses remove 2 triangles; use this to establish a bound on the pass in terms of error limit\n\t// note that edge_collapse_goal is an estimate; triangle_collapse_goal will be used to actually limit collapses\n\tsize_t edge_collapse_goal = triangle_collapse_goal / 2;\n\n#if TRACE\n\tsize_t stats[7] = {};\n#endif\n\n\tfor (size_t i = 0; i < collapse_count; ++i)\n\t{\n\t\tconst Collapse& c = collapses[collapse_order[i]];\n\n\t\tTRACESTATS(0);\n\n\t\tif (c.error > error_limit)\n\t\t{\n\t\t\tTRACESTATS(4);\n\t\t\tbreak;\n\t\t}\n\n\t\tif (triangle_collapses >= triangle_collapse_goal)\n\t\t{\n\t\t\tTRACESTATS(5);\n\t\t\tbreak;\n\t\t}\n\n\t\t// we limit the error in each pass based on the error of optimal last collapse; since many collapses will be locked\n\t\t// as they will share vertices with other successfull collapses, we need to increase the acceptable error by some factor\n\t\tfloat error_goal = edge_collapse_goal < collapse_count ? 1.5f * collapses[collapse_order[edge_collapse_goal]].error : FLT_MAX;\n\n\t\t// on average, each collapse is expected to lock 6 other collapses; to avoid degenerate passes on meshes with odd\n\t\t// topology, we only abort if we got over 1/6 collapses accordingly.\n\t\tif (c.error > error_goal && c.error > result_error && triangle_collapses > triangle_collapse_goal / 6)\n\t\t{\n\t\t\tTRACESTATS(6);\n\t\t\tbreak;\n\t\t}\n\n\t\tunsigned int i0 = c.v0;\n\t\tunsigned int i1 = c.v1;\n\n\t\tunsigned int r0 = remap[i0];\n\t\tunsigned int r1 = remap[i1];\n\n\t\tunsigned char kind = vertex_kind[i0];\n\n\t\t// we don't collapse vertices that had source or target vertex involved in a collapse\n\t\t// it's important to not move the vertices twice since it complicates the tracking/remapping logic\n\t\t// it's important to not move other vertices towards a moved vertex to preserve error since we don't re-rank collapses mid-pass\n\t\tif (collapse_locked[r0] | collapse_locked[r1])\n\t\t{\n\t\t\tTRACESTATS(1);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (hasTriangleFlips(adjacency, vertex_positions, collapse_remap, r0, r1))\n\t\t{\n\t\t\t// adjust collapse goal since this collapse is invalid and shouldn't factor into error goal\n\t\t\tedge_collapse_goal++;\n\n\t\t\tTRACESTATS(2);\n\t\t\tcontinue;\n\t\t}\n\n#if TRACE >= 2\n\t\tprintf(\"edge commit %d -> %d: kind %d->%d, error %f\\n\", i0, i1, vertex_kind[i0], vertex_kind[i1], sqrtf(c.error));\n#endif\n\n\t\tassert(collapse_remap[r0] == r0);\n\t\tassert(collapse_remap[r1] == r1);\n\n\t\tif (kind == Kind_Complex)\n\t\t{\n\t\t\t// remap all vertices in the complex to the target vertex\n\t\t\tunsigned int v = i0;\n\n\t\t\tdo\n\t\t\t{\n\t\t\t\tunsigned int t = getComplexTarget(v, i1, remap, loop, loopback);\n\n\t\t\t\tcollapse_remap[v] = t;\n\t\t\t\tv = wedge[v];\n\t\t\t} while (v != i0);\n\t\t}\n\t\telse if (kind == Kind_Seam)\n\t\t{\n\t\t\t// for seam collapses we need to move the seam pair together; this is a bit tricky since we need to rely on edge loops as target vertex may be locked (and thus have more than two wedges)\n\t\t\tunsigned int s0 = wedge[i0];\n\t\t\tunsigned int s1 = loop[i0] == i1 ? loopback[s0] : loop[s0];\n\t\t\tassert(wedge[s0] == i0); // s0 may be equal to i0 for half-seams\n\t\t\tassert(s1 != ~0u && remap[s1] == r1);\n\n\t\t\t// additional asserts to verify that the seam pair is consistent\n\t\t\tassert(kind != vertex_kind[i1] || s1 == wedge[i1]);\n\t\t\tassert(loop[i0] == i1 || loopback[i0] == i1);\n\t\t\tassert(loop[s0] == s1 || loopback[s0] == s1);\n\n\t\t\t// note: this should never happen due to the assertion above, but when disabled if we ever hit this case we'll get a memory safety issue; for now play it safe\n\t\t\ts1 = (s1 != ~0u) ? s1 : wedge[i1];\n\n\t\t\tcollapse_remap[i0] = i1;\n\t\t\tcollapse_remap[s0] = s1;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tassert(wedge[i0] == i0);\n\n\t\t\tcollapse_remap[i0] = i1;\n\t\t}\n\n\t\t// note: we technically don't need to lock r1 if it's a locked vertex, as it can't move and its quadric won't be used\n\t\t// however, this results in slightly worse error on some meshes because the locked collapses get an unfair advantage wrt scheduling\n\t\tcollapse_locked[r0] = 1;\n\t\tcollapse_locked[r1] = 1;\n\n\t\t// border edges collapse 1 triangle, other edges collapse 2 or more\n\t\ttriangle_collapses += (kind == Kind_Border) ? 1 : 2;\n\t\tedge_collapses++;\n\n\t\tresult_error = result_error < c.error ? c.error : result_error;\n\t}\n\n#if TRACE\n\tfloat error_goal_last = edge_collapse_goal < collapse_count ? 1.5f * collapses[collapse_order[edge_collapse_goal]].error : FLT_MAX;\n\tfloat error_goal_limit = error_goal_last < error_limit ? error_goal_last : error_limit;\n\n\tprintf(\"removed %d triangles, error %e (goal %e); evaluated %d/%d collapses (done %d, skipped %d, invalid %d); %s\\n\",\n\t    int(triangle_collapses), sqrtf(result_error), sqrtf(error_goal_limit),\n\t    int(stats[0]), int(collapse_count), int(edge_collapses), int(stats[1]), int(stats[2]),\n\t    stats[4] ? \"error limit\" : (stats[5] ? \"count limit\" : (stats[6] ? \"error goal\" : \"out of collapses\")));\n#endif\n\n\treturn edge_collapses;\n}\n\nstatic void updateQuadrics(const unsigned int* collapse_remap, size_t vertex_count, Quadric* vertex_quadrics, QuadricGrad* volume_gradients, Quadric* attribute_quadrics, QuadricGrad* attribute_gradients, size_t attribute_count, const Vector3* vertex_positions, const unsigned int* remap, float& vertex_error)\n{\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tif (collapse_remap[i] == i)\n\t\t\tcontinue;\n\n\t\tunsigned int i0 = unsigned(i);\n\t\tunsigned int i1 = collapse_remap[i];\n\n\t\tunsigned int r0 = remap[i0];\n\t\tunsigned int r1 = remap[i1];\n\n\t\t// ensure we only update vertex_quadrics once: primary vertex must be moved if any wedge is moved\n\t\tif (i0 == r0)\n\t\t{\n\t\t\tquadricAdd(vertex_quadrics[r1], vertex_quadrics[r0]);\n\n\t\t\tif (volume_gradients)\n\t\t\t\tquadricAdd(volume_gradients[r1], volume_gradients[r0]);\n\t\t}\n\n\t\tif (attribute_count)\n\t\t{\n\t\t\tquadricAdd(attribute_quadrics[i1], attribute_quadrics[i0]);\n\t\t\tquadricAdd(&attribute_gradients[i1 * attribute_count], &attribute_gradients[i0 * attribute_count], attribute_count);\n\n\t\t\tif (i0 == r0)\n\t\t\t{\n\t\t\t\t// when attributes are used, distance error needs to be recomputed as collapses don't track it; it is safe to do this after the quadric adjustment\n\t\t\t\tfloat derr = quadricError(vertex_quadrics[r0], vertex_positions[r1]);\n\t\t\t\tvertex_error = vertex_error < derr ? derr : vertex_error;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void solvePositions(Vector3* vertex_positions, size_t vertex_count, const Quadric* vertex_quadrics, const QuadricGrad* volume_gradients, const Quadric* attribute_quadrics, const QuadricGrad* attribute_gradients, size_t attribute_count, const unsigned int* remap, const unsigned int* wedge, const EdgeAdjacency& adjacency, const unsigned char* vertex_kind, const unsigned char* vertex_update)\n{\n#if TRACE\n\tsize_t stats[6] = {};\n#endif\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tif (!vertex_update[i])\n\t\t\tcontinue;\n\n\t\t// moving vertices on an attribute discontinuity may result in extrapolating UV outside of the chart bounds\n\t\t// moving vertices on a border requires a stronger edge quadric to preserve the border geometry\n\t\tif (vertex_kind[i] == Kind_Locked || vertex_kind[i] == Kind_Seam || vertex_kind[i] == Kind_Border)\n\t\t\tcontinue;\n\n\t\tif (remap[i] != i)\n\t\t{\n\t\t\tvertex_positions[i] = vertex_positions[remap[i]];\n\t\t\tcontinue;\n\t\t}\n\n\t\tTRACESTATS(0);\n\n\t\tconst Vector3& vp = vertex_positions[i];\n\n\t\tQuadric Q = vertex_quadrics[i];\n\t\tQuadricGrad GV = {};\n\n\t\t// add a point quadric for regularization to stabilize the solution\n\t\tQuadric R;\n\t\tquadricFromPoint(R, vp.x, vp.y, vp.z, Q.w * 1e-4f);\n\t\tquadricAdd(Q, R);\n\n\t\tif (attribute_count)\n\t\t{\n\t\t\t// optimal point simultaneously minimizes attribute quadrics for all wedges\n\t\t\tunsigned int v = unsigned(i);\n\t\t\tdo\n\t\t\t{\n\t\t\t\tquadricReduceAttributes(Q, attribute_quadrics[v], &attribute_gradients[v * attribute_count], attribute_count);\n\t\t\t\tv = wedge[v];\n\t\t\t} while (v != i);\n\n\t\t\t// minimizing attribute quadrics results in volume loss so we incorporate volume gradient as a constraint\n\t\t\tif (volume_gradients)\n\t\t\t\tGV = volume_gradients[i];\n\t\t}\n\n\t\tVector3 p;\n\t\tif (!quadricSolve(p, Q, GV))\n\t\t{\n\t\t\tTRACESTATS(2);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// reject updates that move the vertex too far from its neighborhood\n\t\t// this detects and fixes most cases when the quadric is not well-defined\n\t\tfloat nr = getNeighborhoodRadius(adjacency, vertex_positions, unsigned(i));\n\t\tfloat dp = (p.x - vp.x) * (p.x - vp.x) + (p.y - vp.y) * (p.y - vp.y) + (p.z - vp.z) * (p.z - vp.z);\n\n\t\tif (dp > nr * nr)\n\t\t{\n\t\t\tTRACESTATS(3);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// reject updates that would flip a neighboring triangle, as we do for edge collapse\n\t\tif (hasTriangleFlips(adjacency, vertex_positions, unsigned(i), p))\n\t\t{\n\t\t\tTRACESTATS(4);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// reject updates that increase positional error too much; allow some tolerance to improve attribute quality\n\t\tif (quadricError(vertex_quadrics[i], p) > quadricError(vertex_quadrics[i], vp) * 1.5f + 1e-6f)\n\t\t{\n\t\t\tTRACESTATS(5);\n\t\t\tcontinue;\n\t\t}\n\n\t\tTRACESTATS(1);\n\t\tvertex_positions[i] = p;\n\t}\n\n#if TRACE\n\tprintf(\"updated %d/%d positions; failed solve %d bounds %d flip %d error %d\\n\", int(stats[1]), int(stats[0]), int(stats[2]), int(stats[3]), int(stats[4]), int(stats[5]));\n#endif\n}\n\nstatic void solveAttributes(Vector3* vertex_positions, float* vertex_attributes, size_t vertex_count, const Quadric* attribute_quadrics, const QuadricGrad* attribute_gradients, size_t attribute_count, const unsigned int* remap, const unsigned int* wedge, const unsigned char* vertex_kind, const unsigned char* vertex_update)\n{\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tif (!vertex_update[i])\n\t\t\tcontinue;\n\n\t\tif (remap[i] != i)\n\t\t\tcontinue;\n\n\t\tfor (size_t k = 0; k < attribute_count; ++k)\n\t\t{\n\t\t\tunsigned int shared = ~0u;\n\n\t\t\t// for complex vertices, preserve attribute continuity and use highest weight wedge if values were shared\n\t\t\tif (vertex_kind[i] == Kind_Complex)\n\t\t\t{\n\t\t\t\tshared = unsigned(i);\n\n\t\t\t\tfor (unsigned int v = wedge[i]; v != i; v = wedge[v])\n\t\t\t\t\tif (vertex_attributes[v * attribute_count + k] != vertex_attributes[i * attribute_count + k])\n\t\t\t\t\t\tshared = ~0u;\n\t\t\t\t\telse if (shared != ~0u && attribute_quadrics[v].w > attribute_quadrics[shared].w)\n\t\t\t\t\t\tshared = v;\n\t\t\t}\n\n\t\t\t// update attributes for all wedges\n\t\t\tunsigned int v = unsigned(i);\n\t\t\tdo\n\t\t\t{\n\t\t\t\tunsigned int r = (shared == ~0u) ? v : shared;\n\n\t\t\t\tconst Vector3& p = vertex_positions[i]; // same for all wedges\n\t\t\t\tconst Quadric& A = attribute_quadrics[r];\n\t\t\t\tconst QuadricGrad& G = attribute_gradients[r * attribute_count + k];\n\n\t\t\t\tfloat iw = A.w == 0 ? 0.f : 1.f / A.w;\n\t\t\t\tfloat av = (G.gx * p.x + G.gy * p.y + G.gz * p.z + G.gw) * iw;\n\n\t\t\t\tvertex_attributes[v * attribute_count + k] = av;\n\t\t\t\tv = wedge[v];\n\t\t\t} while (v != i);\n\t\t}\n\t}\n}\n\nstatic size_t remapIndexBuffer(unsigned int* indices, size_t index_count, const unsigned int* collapse_remap, const unsigned int* remap)\n{\n\tsize_t write = 0;\n\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tunsigned int v0 = collapse_remap[indices[i + 0]];\n\t\tunsigned int v1 = collapse_remap[indices[i + 1]];\n\t\tunsigned int v2 = collapse_remap[indices[i + 2]];\n\n\t\t// we never move the vertex twice during a single pass\n\t\tassert(collapse_remap[v0] == v0);\n\t\tassert(collapse_remap[v1] == v1);\n\t\tassert(collapse_remap[v2] == v2);\n\n\t\t// collapse zero area triangles even if they are not topologically degenerate\n\t\t// this is required to cleanup manifold->seam collapses when a vertex is collapsed onto a seam pair\n\t\t// as well as complex collapses and some other cases where cross wedge collapses are performed\n\t\tunsigned int r0 = remap[v0];\n\t\tunsigned int r1 = remap[v1];\n\t\tunsigned int r2 = remap[v2];\n\n\t\tif (r0 != r1 && r0 != r2 && r1 != r2)\n\t\t{\n\t\t\tindices[write + 0] = v0;\n\t\t\tindices[write + 1] = v1;\n\t\t\tindices[write + 2] = v2;\n\t\t\twrite += 3;\n\t\t}\n\t}\n\n\treturn write;\n}\n\nstatic void remapEdgeLoops(unsigned int* loop, size_t vertex_count, const unsigned int* collapse_remap)\n{\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\t// note: this is a no-op for vertices that were remapped\n\t\t// ideally we would clear the loop entries for those for consistency, even though they aren't going to be used\n\t\t// however, the remapping process needs loop information for remapped vertices, so this would require a separate pass\n\t\tif (loop[i] != ~0u)\n\t\t{\n\t\t\tunsigned int l = loop[i];\n\t\t\tunsigned int r = collapse_remap[l];\n\n\t\t\t// i == r is a special case when the seam edge is collapsed in a direction opposite to where loop goes\n\t\t\tif (i == r)\n\t\t\t\tloop[i] = (loop[l] != ~0u) ? collapse_remap[loop[l]] : ~0u;\n\t\t\telse\n\t\t\t\tloop[i] = r;\n\t\t}\n\t}\n}\n\nstatic unsigned int follow(unsigned int* parents, unsigned int index)\n{\n\twhile (index != parents[index])\n\t{\n\t\tunsigned int parent = parents[index];\n\t\tparents[index] = parents[parent];\n\t\tindex = parent;\n\t}\n\n\treturn index;\n}\n\nstatic size_t buildComponents(unsigned int* components, size_t vertex_count, const unsigned int* indices, size_t index_count, const unsigned int* remap)\n{\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tcomponents[i] = unsigned(i);\n\n\t// compute a unique (but not sequential!) index for each component via union-find\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tstatic const int next[4] = {1, 2, 0, 1};\n\n\t\tfor (int e = 0; e < 3; ++e)\n\t\t{\n\t\t\tunsigned int i0 = indices[i + e];\n\t\t\tunsigned int i1 = indices[i + next[e]];\n\n\t\t\tunsigned int r0 = remap[i0];\n\t\t\tunsigned int r1 = remap[i1];\n\n\t\t\tr0 = follow(components, r0);\n\t\t\tr1 = follow(components, r1);\n\n\t\t\t// merge components with larger indices into components with smaller indices\n\t\t\t// this guarantees that the root of the component is always the one with the smallest index\n\t\t\tif (r0 != r1)\n\t\t\t\tcomponents[r0 < r1 ? r1 : r0] = r0 < r1 ? r0 : r1;\n\t\t}\n\t}\n\n\t// make sure each element points to the component root *before* we renumber the components\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tif (remap[i] == i)\n\t\t\tcomponents[i] = follow(components, unsigned(i));\n\n\tunsigned int next_component = 0;\n\n\t// renumber components using sequential indices\n\t// a sequential pass is sufficient because component root always has the smallest index\n\t// note: it is unsafe to use follow() in this pass because we're replacing component links with sequential indices inplace\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tif (remap[i] == i)\n\t\t{\n\t\t\tunsigned int root = components[i];\n\t\t\tassert(root <= i); // make sure we already computed the component for non-roots\n\t\t\tcomponents[i] = (root == i) ? next_component++ : components[root];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tassert(remap[i] < i); // make sure we already computed the component\n\t\t\tcomponents[i] = components[remap[i]];\n\t\t}\n\t}\n\n\treturn next_component;\n}\n\nstatic void measureComponents(float* component_errors, size_t component_count, const unsigned int* components, const Vector3* vertex_positions, size_t vertex_count)\n{\n\tmemset(component_errors, 0, component_count * 4 * sizeof(float));\n\n\t// compute approximate sphere center for each component as an average\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tunsigned int c = components[i];\n\t\tassert(components[i] < component_count);\n\n\t\tVector3 v = vertex_positions[i]; // copy avoids aliasing issues\n\n\t\tcomponent_errors[c * 4 + 0] += v.x;\n\t\tcomponent_errors[c * 4 + 1] += v.y;\n\t\tcomponent_errors[c * 4 + 2] += v.z;\n\t\tcomponent_errors[c * 4 + 3] += 1; // weight\n\t}\n\n\t// complete the center computation, and reinitialize [3] as a radius\n\tfor (size_t i = 0; i < component_count; ++i)\n\t{\n\t\tfloat w = component_errors[i * 4 + 3];\n\t\tfloat iw = w == 0.f ? 0.f : 1.f / w;\n\n\t\tcomponent_errors[i * 4 + 0] *= iw;\n\t\tcomponent_errors[i * 4 + 1] *= iw;\n\t\tcomponent_errors[i * 4 + 2] *= iw;\n\t\tcomponent_errors[i * 4 + 3] = 0; // radius\n\t}\n\n\t// compute squared radius for each component\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tunsigned int c = components[i];\n\n\t\tfloat dx = vertex_positions[i].x - component_errors[c * 4 + 0];\n\t\tfloat dy = vertex_positions[i].y - component_errors[c * 4 + 1];\n\t\tfloat dz = vertex_positions[i].z - component_errors[c * 4 + 2];\n\t\tfloat r = dx * dx + dy * dy + dz * dz;\n\n\t\tcomponent_errors[c * 4 + 3] = component_errors[c * 4 + 3] < r ? r : component_errors[c * 4 + 3];\n\t}\n\n\t// we've used the output buffer as scratch space, so we need to move the results to proper indices\n\tfor (size_t i = 0; i < component_count; ++i)\n\t{\n#if TRACE >= 2\n\t\tprintf(\"component %d: center %f %f %f, error %e\\n\", int(i),\n\t\t    component_errors[i * 4 + 0], component_errors[i * 4 + 1], component_errors[i * 4 + 2], sqrtf(component_errors[i * 4 + 3]));\n#endif\n\t\t// note: we keep the squared error to make it match quadric error metric\n\t\tcomponent_errors[i] = component_errors[i * 4 + 3];\n\t}\n}\n\nstatic size_t pruneComponents(unsigned int* indices, size_t index_count, const unsigned int* components, const float* component_errors, size_t component_count, float error_cutoff, float& nexterror)\n{\n\t(void)component_count;\n\n\tsize_t write = 0;\n\tfloat min_error = FLT_MAX;\n\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tunsigned int v0 = indices[i + 0], v1 = indices[i + 1], v2 = indices[i + 2];\n\t\tunsigned int c = components[v0];\n\t\tassert(c == components[v1] && c == components[v2]);\n\n\t\tif (component_errors[c] > error_cutoff)\n\t\t{\n\t\t\tmin_error = min_error > component_errors[c] ? component_errors[c] : min_error;\n\n\t\t\tindices[write + 0] = v0;\n\t\t\tindices[write + 1] = v1;\n\t\t\tindices[write + 2] = v2;\n\t\t\twrite += 3;\n\t\t}\n\t}\n\n#if TRACE\n\tsize_t pruned_components = 0;\n\tfor (size_t i = 0; i < component_count; ++i)\n\t\tpruned_components += (component_errors[i] >= nexterror && component_errors[i] <= error_cutoff);\n\n\tprintf(\"pruned %d triangles in %d components (goal %e); next %e\\n\", int((index_count - write) / 3), int(pruned_components), sqrtf(error_cutoff), min_error < FLT_MAX ? sqrtf(min_error) : min_error * 2);\n#endif\n\n\t// update next error with the smallest error of the remaining components\n\tnexterror = min_error;\n\treturn write;\n}\n\nstruct CellHasher\n{\n\tconst unsigned int* vertex_ids;\n\n\tsize_t hash(unsigned int i) const\n\t{\n\t\tunsigned int h = vertex_ids[i];\n\n\t\t// MurmurHash2 finalizer\n\t\th ^= h >> 13;\n\t\th *= 0x5bd1e995;\n\t\th ^= h >> 15;\n\t\treturn h;\n\t}\n\n\tbool equal(unsigned int lhs, unsigned int rhs) const\n\t{\n\t\treturn vertex_ids[lhs] == vertex_ids[rhs];\n\t}\n};\n\nstruct IdHasher\n{\n\tsize_t hash(unsigned int id) const\n\t{\n\t\tunsigned int h = id;\n\n\t\t// MurmurHash2 finalizer\n\t\th ^= h >> 13;\n\t\th *= 0x5bd1e995;\n\t\th ^= h >> 15;\n\t\treturn h;\n\t}\n\n\tbool equal(unsigned int lhs, unsigned int rhs) const\n\t{\n\t\treturn lhs == rhs;\n\t}\n};\n\nstruct TriangleHasher\n{\n\tconst unsigned int* indices;\n\n\tsize_t hash(unsigned int i) const\n\t{\n\t\tconst unsigned int* tri = indices + i * 3;\n\n\t\t// Optimized Spatial Hashing for Collision Detection of Deformable Objects\n\t\treturn (tri[0] * 73856093) ^ (tri[1] * 19349663) ^ (tri[2] * 83492791);\n\t}\n\n\tbool equal(unsigned int lhs, unsigned int rhs) const\n\t{\n\t\tconst unsigned int* lt = indices + lhs * 3;\n\t\tconst unsigned int* rt = indices + rhs * 3;\n\n\t\treturn lt[0] == rt[0] && lt[1] == rt[1] && lt[2] == rt[2];\n\t}\n};\n\nstatic void computeVertexIds(unsigned int* vertex_ids, const Vector3* vertex_positions, const unsigned char* vertex_lock, size_t vertex_count, int grid_size)\n{\n\tassert(grid_size >= 1 && grid_size <= 1024);\n\tfloat cell_scale = float(grid_size - 1);\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tconst Vector3& v = vertex_positions[i];\n\n\t\tint xi = int(v.x * cell_scale + 0.5f);\n\t\tint yi = int(v.y * cell_scale + 0.5f);\n\t\tint zi = int(v.z * cell_scale + 0.5f);\n\n\t\tif (vertex_lock && (vertex_lock[i] & meshopt_SimplifyVertex_Lock))\n\t\t\tvertex_ids[i] = (1 << 30) | unsigned(i);\n\t\telse\n\t\t\tvertex_ids[i] = (xi << 20) | (yi << 10) | zi;\n\t}\n}\n\nstatic size_t countTriangles(const unsigned int* vertex_ids, const unsigned int* indices, size_t index_count)\n{\n\tsize_t result = 0;\n\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tunsigned int id0 = vertex_ids[indices[i + 0]];\n\t\tunsigned int id1 = vertex_ids[indices[i + 1]];\n\t\tunsigned int id2 = vertex_ids[indices[i + 2]];\n\n\t\tresult += (id0 != id1) & (id0 != id2) & (id1 != id2);\n\t}\n\n\treturn result;\n}\n\nstatic size_t fillVertexCells(unsigned int* table, size_t table_size, unsigned int* vertex_cells, const unsigned int* vertex_ids, size_t vertex_count)\n{\n\tCellHasher hasher = {vertex_ids};\n\n\tmemset(table, -1, table_size * sizeof(unsigned int));\n\n\tsize_t result = 0;\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tunsigned int* entry = hashLookup2(table, table_size, hasher, unsigned(i), ~0u);\n\n\t\tif (*entry == ~0u)\n\t\t{\n\t\t\t*entry = unsigned(i);\n\t\t\tvertex_cells[i] = unsigned(result++);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvertex_cells[i] = vertex_cells[*entry];\n\t\t}\n\t}\n\n\treturn result;\n}\n\nstatic size_t countVertexCells(unsigned int* table, size_t table_size, const unsigned int* vertex_ids, size_t vertex_count)\n{\n\tIdHasher hasher;\n\n\tmemset(table, -1, table_size * sizeof(unsigned int));\n\n\tsize_t result = 0;\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tunsigned int id = vertex_ids[i];\n\t\tunsigned int* entry = hashLookup2(table, table_size, hasher, id, ~0u);\n\n\t\tresult += (*entry == ~0u);\n\t\t*entry = id;\n\t}\n\n\treturn result;\n}\n\nstatic void fillCellQuadrics(Quadric* cell_quadrics, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const unsigned int* vertex_cells)\n{\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tunsigned int i0 = indices[i + 0];\n\t\tunsigned int i1 = indices[i + 1];\n\t\tunsigned int i2 = indices[i + 2];\n\n\t\tunsigned int c0 = vertex_cells[i0];\n\t\tunsigned int c1 = vertex_cells[i1];\n\t\tunsigned int c2 = vertex_cells[i2];\n\n\t\tint single_cell = (c0 == c1) & (c0 == c2);\n\n\t\tQuadric Q;\n\t\tquadricFromTriangle(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], single_cell ? 3.f : 1.f);\n\n\t\tif (single_cell)\n\t\t{\n\t\t\tquadricAdd(cell_quadrics[c0], Q);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tquadricAdd(cell_quadrics[c0], Q);\n\t\t\tquadricAdd(cell_quadrics[c1], Q);\n\t\t\tquadricAdd(cell_quadrics[c2], Q);\n\t\t}\n\t}\n}\n\nstatic void fillCellReservoirs(Reservoir* cell_reservoirs, size_t cell_count, const Vector3* vertex_positions, const float* vertex_colors, size_t vertex_colors_stride, size_t vertex_count, const unsigned int* vertex_cells)\n{\n\tstatic const float dummy_color[] = {0.f, 0.f, 0.f};\n\n\tsize_t vertex_colors_stride_float = vertex_colors_stride / sizeof(float);\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tunsigned int cell = vertex_cells[i];\n\t\tconst Vector3& v = vertex_positions[i];\n\t\tReservoir& r = cell_reservoirs[cell];\n\n\t\tconst float* color = vertex_colors ? &vertex_colors[i * vertex_colors_stride_float] : dummy_color;\n\n\t\tr.x += v.x;\n\t\tr.y += v.y;\n\t\tr.z += v.z;\n\t\tr.r += color[0];\n\t\tr.g += color[1];\n\t\tr.b += color[2];\n\t\tr.w += 1.f;\n\t}\n\n\tfor (size_t i = 0; i < cell_count; ++i)\n\t{\n\t\tReservoir& r = cell_reservoirs[i];\n\n\t\tfloat iw = r.w == 0.f ? 0.f : 1.f / r.w;\n\n\t\tr.x *= iw;\n\t\tr.y *= iw;\n\t\tr.z *= iw;\n\t\tr.r *= iw;\n\t\tr.g *= iw;\n\t\tr.b *= iw;\n\t}\n}\n\nstatic void fillCellRemap(unsigned int* cell_remap, float* cell_errors, size_t cell_count, const unsigned int* vertex_cells, const Quadric* cell_quadrics, const Vector3* vertex_positions, size_t vertex_count)\n{\n\tmemset(cell_remap, -1, cell_count * sizeof(unsigned int));\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tunsigned int cell = vertex_cells[i];\n\t\tfloat error = quadricError(cell_quadrics[cell], vertex_positions[i]);\n\n\t\tif (cell_remap[cell] == ~0u || cell_errors[cell] > error)\n\t\t{\n\t\t\tcell_remap[cell] = unsigned(i);\n\t\t\tcell_errors[cell] = error;\n\t\t}\n\t}\n}\n\nstatic void fillCellRemap(unsigned int* cell_remap, float* cell_errors, size_t cell_count, const unsigned int* vertex_cells, const Reservoir* cell_reservoirs, const Vector3* vertex_positions, const float* vertex_colors, size_t vertex_colors_stride, float color_weight, size_t vertex_count)\n{\n\tstatic const float dummy_color[] = {0.f, 0.f, 0.f};\n\n\tsize_t vertex_colors_stride_float = vertex_colors_stride / sizeof(float);\n\n\tmemset(cell_remap, -1, cell_count * sizeof(unsigned int));\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tunsigned int cell = vertex_cells[i];\n\t\tconst Vector3& v = vertex_positions[i];\n\t\tconst Reservoir& r = cell_reservoirs[cell];\n\n\t\tconst float* color = vertex_colors ? &vertex_colors[i * vertex_colors_stride_float] : dummy_color;\n\n\t\tfloat pos_error = (v.x - r.x) * (v.x - r.x) + (v.y - r.y) * (v.y - r.y) + (v.z - r.z) * (v.z - r.z);\n\t\tfloat col_error = (color[0] - r.r) * (color[0] - r.r) + (color[1] - r.g) * (color[1] - r.g) + (color[2] - r.b) * (color[2] - r.b);\n\t\tfloat error = pos_error + color_weight * col_error;\n\n\t\tif (cell_remap[cell] == ~0u || cell_errors[cell] > error)\n\t\t{\n\t\t\tcell_remap[cell] = unsigned(i);\n\t\t\tcell_errors[cell] = error;\n\t\t}\n\t}\n}\n\nstatic size_t filterTriangles(unsigned int* destination, unsigned int* tritable, size_t tritable_size, const unsigned int* indices, size_t index_count, const unsigned int* vertex_cells, const unsigned int* cell_remap)\n{\n\tTriangleHasher hasher = {destination};\n\n\tmemset(tritable, -1, tritable_size * sizeof(unsigned int));\n\n\tsize_t result = 0;\n\n\tfor (size_t i = 0; i < index_count; i += 3)\n\t{\n\t\tunsigned int c0 = vertex_cells[indices[i + 0]];\n\t\tunsigned int c1 = vertex_cells[indices[i + 1]];\n\t\tunsigned int c2 = vertex_cells[indices[i + 2]];\n\n\t\tif (c0 != c1 && c0 != c2 && c1 != c2)\n\t\t{\n\t\t\tunsigned int a = cell_remap[c0];\n\t\t\tunsigned int b = cell_remap[c1];\n\t\t\tunsigned int c = cell_remap[c2];\n\n\t\t\tif (b < a && b < c)\n\t\t\t{\n\t\t\t\tunsigned int t = a;\n\t\t\t\ta = b, b = c, c = t;\n\t\t\t}\n\t\t\telse if (c < a && c < b)\n\t\t\t{\n\t\t\t\tunsigned int t = c;\n\t\t\t\tc = b, b = a, a = t;\n\t\t\t}\n\n\t\t\tdestination[result * 3 + 0] = a;\n\t\t\tdestination[result * 3 + 1] = b;\n\t\t\tdestination[result * 3 + 2] = c;\n\n\t\t\tunsigned int* entry = hashLookup2(tritable, tritable_size, hasher, unsigned(result), ~0u);\n\n\t\t\tif (*entry == ~0u)\n\t\t\t\t*entry = unsigned(result++);\n\t\t}\n\t}\n\n\treturn result * 3;\n}\n\nstatic float interpolate(float y, float x0, float y0, float x1, float y1, float x2, float y2)\n{\n\t// three point interpolation from \"revenge of interpolation search\" paper\n\tfloat num = (y1 - y) * (x1 - x2) * (x1 - x0) * (y2 - y0);\n\tfloat den = (y2 - y) * (x1 - x2) * (y0 - y1) + (y0 - y) * (x1 - x0) * (y1 - y2);\n\treturn x1 + (den == 0.f ? 0.f : num / den);\n}\n\n} // namespace meshopt\n\n// Note: this is only exposed for development purposes; do *not* use\nenum\n{\n\tmeshopt_SimplifyInternalSolve = 1 << 29,\n\tmeshopt_SimplifyInternalDebug = 1 << 30\n};\n\nsize_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes_data, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* out_result_error)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\tassert(target_index_count <= index_count);\n\tassert(target_error >= 0);\n\tassert((options & ~(meshopt_SimplifyLockBorder | meshopt_SimplifySparse | meshopt_SimplifyErrorAbsolute | meshopt_SimplifyPrune | meshopt_SimplifyRegularize | meshopt_SimplifyPermissive | meshopt_SimplifyInternalSolve | meshopt_SimplifyInternalDebug)) == 0);\n\tassert(vertex_attributes_stride >= attribute_count * sizeof(float) && vertex_attributes_stride <= 256);\n\tassert(vertex_attributes_stride % sizeof(float) == 0);\n\tassert(attribute_count <= kMaxAttributes);\n\tfor (size_t i = 0; i < attribute_count; ++i)\n\t\tassert(attribute_weights[i] >= 0);\n\n\tmeshopt_Allocator allocator;\n\n\tunsigned int* result = destination;\n\tif (result != indices)\n\t\tmemcpy(result, indices, index_count * sizeof(unsigned int));\n\n\t// build an index remap and update indices/vertex_count to minimize the subsequent work\n\t// note: as a consequence, errors will be computed relative to the subset extent\n\tunsigned int* sparse_remap = NULL;\n\tif (options & meshopt_SimplifySparse)\n\t\tsparse_remap = buildSparseRemap(result, index_count, vertex_count, &vertex_count, allocator);\n\n\t// build adjacency information\n\tEdgeAdjacency adjacency = {};\n\tprepareEdgeAdjacency(adjacency, index_count, vertex_count, allocator);\n\tupdateEdgeAdjacency(adjacency, result, index_count, vertex_count, NULL);\n\n\t// build position remap that maps each vertex to the one with identical position\n\t// wedge table stores next vertex with identical position for each vertex\n\tunsigned int* remap = allocator.allocate<unsigned int>(vertex_count);\n\tunsigned int* wedge = allocator.allocate<unsigned int>(vertex_count);\n\tbuildPositionRemap(remap, wedge, vertex_positions_data, vertex_count, vertex_positions_stride, sparse_remap, allocator);\n\n\t// classify vertices; vertex kind determines collapse rules, see kCanCollapse\n\tunsigned char* vertex_kind = allocator.allocate<unsigned char>(vertex_count);\n\tunsigned int* loop = allocator.allocate<unsigned int>(vertex_count);\n\tunsigned int* loopback = allocator.allocate<unsigned int>(vertex_count);\n\tclassifyVertices(vertex_kind, loop, loopback, vertex_count, adjacency, remap, wedge, vertex_lock, sparse_remap, options);\n\n#if TRACE\n\tsize_t unique_positions = 0;\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tunique_positions += remap[i] == i;\n\n\tprintf(\"position remap: %d vertices => %d positions\\n\", int(vertex_count), int(unique_positions));\n\n\tsize_t kinds[Kind_Count] = {};\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tkinds[vertex_kind[i]] += remap[i] == i;\n\n\tprintf(\"kinds: manifold %d, border %d, seam %d, complex %d, locked %d\\n\",\n\t    int(kinds[Kind_Manifold]), int(kinds[Kind_Border]), int(kinds[Kind_Seam]), int(kinds[Kind_Complex]), int(kinds[Kind_Locked]));\n#endif\n\n\tVector3* vertex_positions = allocator.allocate<Vector3>(vertex_count);\n\tfloat vertex_offset[3] = {};\n\tfloat vertex_scale = rescalePositions(vertex_positions, vertex_positions_data, vertex_count, vertex_positions_stride, sparse_remap, vertex_offset);\n\n\tfloat* vertex_attributes = NULL;\n\tunsigned int attribute_remap[kMaxAttributes];\n\n\tif (attribute_count)\n\t{\n\t\t// remap attributes to only include ones with weight > 0 to minimize memory/compute overhead for quadrics\n\t\tsize_t attributes_used = 0;\n\t\tfor (size_t i = 0; i < attribute_count; ++i)\n\t\t\tif (attribute_weights[i] > 0)\n\t\t\t\tattribute_remap[attributes_used++] = unsigned(i);\n\n\t\tattribute_count = attributes_used;\n\t\tvertex_attributes = allocator.allocate<float>(vertex_count * attribute_count);\n\t\trescaleAttributes(vertex_attributes, vertex_attributes_data, vertex_count, vertex_attributes_stride, attribute_weights, attribute_count, attribute_remap, sparse_remap);\n\t}\n\n\tQuadric* vertex_quadrics = allocator.allocate<Quadric>(vertex_count);\n\tmemset(vertex_quadrics, 0, vertex_count * sizeof(Quadric));\n\n\tQuadric* attribute_quadrics = NULL;\n\tQuadricGrad* attribute_gradients = NULL;\n\tQuadricGrad* volume_gradients = NULL;\n\n\tif (attribute_count)\n\t{\n\t\tattribute_quadrics = allocator.allocate<Quadric>(vertex_count);\n\t\tmemset(attribute_quadrics, 0, vertex_count * sizeof(Quadric));\n\n\t\tattribute_gradients = allocator.allocate<QuadricGrad>(vertex_count * attribute_count);\n\t\tmemset(attribute_gradients, 0, vertex_count * attribute_count * sizeof(QuadricGrad));\n\n\t\tif (options & meshopt_SimplifyInternalSolve)\n\t\t{\n\t\t\tvolume_gradients = allocator.allocate<QuadricGrad>(vertex_count);\n\t\t\tmemset(volume_gradients, 0, vertex_count * sizeof(QuadricGrad));\n\t\t}\n\t}\n\n\tfillFaceQuadrics(vertex_quadrics, volume_gradients, result, index_count, vertex_positions, remap);\n\tfillVertexQuadrics(vertex_quadrics, vertex_positions, vertex_count, remap, options);\n\tfillEdgeQuadrics(vertex_quadrics, result, index_count, vertex_positions, remap, vertex_kind, loop, loopback);\n\n\tif (attribute_count)\n\t\tfillAttributeQuadrics(attribute_quadrics, attribute_gradients, result, index_count, vertex_positions, vertex_attributes, attribute_count);\n\n\tunsigned int* components = NULL;\n\tfloat* component_errors = NULL;\n\tsize_t component_count = 0;\n\tfloat component_nexterror = 0;\n\n\tif (options & meshopt_SimplifyPrune)\n\t{\n\t\tcomponents = allocator.allocate<unsigned int>(vertex_count);\n\t\tcomponent_count = buildComponents(components, vertex_count, result, index_count, remap);\n\n\t\tcomponent_errors = allocator.allocate<float>(component_count * 4); // overallocate for temporary use inside measureComponents\n\t\tmeasureComponents(component_errors, component_count, components, vertex_positions, vertex_count);\n\n\t\tcomponent_nexterror = FLT_MAX;\n\t\tfor (size_t i = 0; i < component_count; ++i)\n\t\t\tcomponent_nexterror = component_nexterror > component_errors[i] ? component_errors[i] : component_nexterror;\n\n#if TRACE\n\t\tprintf(\"components: %d (min error %e)\\n\", int(component_count), sqrtf(component_nexterror));\n#endif\n\t}\n\n#if TRACE\n\tsize_t pass_count = 0;\n#endif\n\n\tsize_t collapse_capacity = boundEdgeCollapses(adjacency, vertex_count, index_count, vertex_kind);\n\n\tCollapse* edge_collapses = allocator.allocate<Collapse>(collapse_capacity);\n\tunsigned int* collapse_order = allocator.allocate<unsigned int>(collapse_capacity);\n\tunsigned int* collapse_remap = allocator.allocate<unsigned int>(vertex_count);\n\tunsigned char* collapse_locked = allocator.allocate<unsigned char>(vertex_count);\n\n\tsize_t result_count = index_count;\n\tfloat result_error = 0;\n\tfloat vertex_error = 0;\n\n\t// target_error input is linear; we need to adjust it to match quadricError units\n\tfloat error_scale = (options & meshopt_SimplifyErrorAbsolute) ? vertex_scale : 1.f;\n\tfloat error_limit = (target_error * target_error) / (error_scale * error_scale);\n\n\twhile (result_count > target_index_count)\n\t{\n\t\t// note: throughout the simplification process adjacency structure reflects welded topology for result-in-progress\n\t\tupdateEdgeAdjacency(adjacency, result, result_count, vertex_count, remap);\n\n\t\tsize_t edge_collapse_count = pickEdgeCollapses(edge_collapses, collapse_capacity, result, result_count, remap, vertex_kind, loop, loopback);\n\t\tassert(edge_collapse_count <= collapse_capacity);\n\n\t\t// no edges can be collapsed any more due to topology restrictions\n\t\tif (edge_collapse_count == 0)\n\t\t\tbreak;\n\n#if TRACE\n\t\tprintf(\"pass %d:%c\", int(pass_count++), TRACE >= 2 ? '\\n' : ' ');\n#endif\n\n\t\trankEdgeCollapses(edge_collapses, edge_collapse_count, vertex_positions, vertex_attributes, vertex_quadrics, attribute_quadrics, attribute_gradients, attribute_count, remap, wedge, vertex_kind, loop, loopback);\n\n\t\tsortEdgeCollapses(collapse_order, edge_collapses, edge_collapse_count);\n\n\t\tsize_t triangle_collapse_goal = (result_count - target_index_count) / 3;\n\n\t\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\t\tcollapse_remap[i] = unsigned(i);\n\n\t\tmemset(collapse_locked, 0, vertex_count);\n\n\t\tsize_t collapses = performEdgeCollapses(collapse_remap, collapse_locked, edge_collapses, edge_collapse_count, collapse_order, remap, wedge, vertex_kind, loop, loopback, vertex_positions, adjacency, triangle_collapse_goal, error_limit, result_error);\n\n\t\t// no edges can be collapsed any more due to hitting the error limit or triangle collapse limit\n\t\tif (collapses == 0)\n\t\t\tbreak;\n\n\t\tupdateQuadrics(collapse_remap, vertex_count, vertex_quadrics, volume_gradients, attribute_quadrics, attribute_gradients, attribute_count, vertex_positions, remap, vertex_error);\n\n\t\t// updateQuadrics will update vertex error if we use attributes, but if we don't then result_error and vertex_error are equivalent\n\t\tvertex_error = attribute_count == 0 ? result_error : vertex_error;\n\n\t\t// note: we update loops following edge collapses, but after this we might still have stale loop data\n\t\t// this can happen when a triangle with a loop edge gets collapsed along a non-loop edge\n\t\t// that works since a loop that points to a vertex that is no longer connected is not affecting collapse logic\n\t\tremapEdgeLoops(loop, vertex_count, collapse_remap);\n\t\tremapEdgeLoops(loopback, vertex_count, collapse_remap);\n\n\t\tresult_count = remapIndexBuffer(result, result_count, collapse_remap, remap);\n\n\t\tif ((options & meshopt_SimplifyPrune) && result_count > target_index_count && component_nexterror <= vertex_error)\n\t\t\tresult_count = pruneComponents(result, result_count, components, component_errors, component_count, vertex_error, component_nexterror);\n\t}\n\n\t// at this point, component_nexterror might be stale: component it references may have been removed through a series of edge collapses\n\tbool component_nextstale = true;\n\n\t// we're done with the regular simplification but we're still short of the target; try pruning more aggressively towards error_limit\n\twhile ((options & meshopt_SimplifyPrune) && result_count > target_index_count && component_nexterror <= error_limit)\n\t{\n#if TRACE\n\t\tprintf(\"pass %d: cleanup; \", int(pass_count++));\n#endif\n\n\t\tfloat component_cutoff = component_nexterror * 1.5f < error_limit ? component_nexterror * 1.5f : error_limit;\n\n\t\t// track maximum error in eligible components as we are increasing resulting error\n\t\tfloat component_maxerror = 0;\n\t\tfor (size_t i = 0; i < component_count; ++i)\n\t\t\tif (component_errors[i] > component_maxerror && component_errors[i] <= component_cutoff)\n\t\t\t\tcomponent_maxerror = component_errors[i];\n\n\t\tsize_t new_count = pruneComponents(result, result_count, components, component_errors, component_count, component_cutoff, component_nexterror);\n\t\tif (new_count == result_count && !component_nextstale)\n\t\t\tbreak;\n\n\t\tcomponent_nextstale = false; // pruneComponents guarantees next error is up to date\n\t\tresult_count = new_count;\n\t\tresult_error = result_error < component_maxerror ? component_maxerror : result_error;\n\t\tvertex_error = vertex_error < component_maxerror ? component_maxerror : vertex_error;\n\t}\n\n#if TRACE\n\tprintf(\"result: %d triangles, error: %e (pos %.3e); total %d passes\\n\", int(result_count / 3), sqrtf(result_error), sqrtf(vertex_error), int(pass_count));\n#endif\n\n\t// if solve is requested, update input buffers destructively from internal data\n\tif (options & meshopt_SimplifyInternalSolve)\n\t{\n\t\tunsigned char* vertex_update = collapse_locked; // reuse as scratch space\n\t\tmemset(vertex_update, 0, vertex_count);\n\n\t\t// limit quadric solve to vertices that are still used in the result\n\t\tfor (size_t i = 0; i < result_count; ++i)\n\t\t{\n\t\t\tunsigned int v = result[i];\n\n\t\t\t// mark the vertex for finalizeVertices and root vertex for solve*\n\t\t\tvertex_update[remap[v]] = vertex_update[v] = 1;\n\t\t}\n\n\t\t// edge adjacency may be stale as we haven't updated it after last series of edge collapses\n\t\tupdateEdgeAdjacency(adjacency, result, result_count, vertex_count, remap);\n\n\t\tsolvePositions(vertex_positions, vertex_count, vertex_quadrics, volume_gradients, attribute_quadrics, attribute_gradients, attribute_count, remap, wedge, adjacency, vertex_kind, vertex_update);\n\n\t\tif (attribute_count)\n\t\t\tsolveAttributes(vertex_positions, vertex_attributes, vertex_count, attribute_quadrics, attribute_gradients, attribute_count, remap, wedge, vertex_kind, vertex_update);\n\n\t\tfinalizeVertices(const_cast<float*>(vertex_positions_data), vertex_positions_stride, const_cast<float*>(vertex_attributes_data), vertex_attributes_stride, attribute_weights, attribute_count, vertex_count, vertex_positions, vertex_attributes, sparse_remap, attribute_remap, vertex_scale, vertex_offset, vertex_kind, vertex_update, vertex_lock);\n\t}\n\n\t// if debug visualization data is requested, fill it instead of index data; for simplicity, this doesn't work with sparsity\n\tif ((options & meshopt_SimplifyInternalDebug) && !sparse_remap)\n\t{\n\t\tassert(Kind_Count <= 8 && vertex_count < (1 << 28)); // 3 bit kind, 1 bit loop\n\n\t\tfor (size_t i = 0; i < result_count; i += 3)\n\t\t{\n\t\t\tunsigned int a = result[i + 0], b = result[i + 1], c = result[i + 2];\n\n\t\t\tresult[i + 0] |= (vertex_kind[a] << 28) | (unsigned(loop[a] == b || loopback[b] == a) << 31);\n\t\t\tresult[i + 1] |= (vertex_kind[b] << 28) | (unsigned(loop[b] == c || loopback[c] == b) << 31);\n\t\t\tresult[i + 2] |= (vertex_kind[c] << 28) | (unsigned(loop[c] == a || loopback[a] == c) << 31);\n\t\t}\n\t}\n\n\t// convert resulting indices back into the dense space of the larger mesh\n\tif (sparse_remap)\n\t\tfor (size_t i = 0; i < result_count; ++i)\n\t\t\tresult[i] = sparse_remap[result[i]];\n\n\t// result_error is quadratic; we need to remap it back to linear\n\tif (out_result_error)\n\t\t*out_result_error = sqrtf(result_error) * error_scale;\n\n\treturn result_count;\n}\n\nsize_t meshopt_simplify(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options, float* out_result_error)\n{\n\tassert((options & meshopt_SimplifyInternalSolve) == 0); // use meshopt_simplifyWithUpdate instead\n\n\treturn meshopt_simplifyEdge(destination, indices, index_count, vertex_positions_data, vertex_count, vertex_positions_stride, NULL, 0, NULL, 0, NULL, target_index_count, target_error, options, out_result_error);\n}\n\nsize_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes_data, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* out_result_error)\n{\n\tassert((options & meshopt_SimplifyInternalSolve) == 0); // use meshopt_simplifyWithUpdate instead\n\n\treturn meshopt_simplifyEdge(destination, indices, index_count, vertex_positions_data, vertex_count, vertex_positions_stride, vertex_attributes_data, vertex_attributes_stride, attribute_weights, attribute_count, vertex_lock, target_index_count, target_error, options, out_result_error);\n}\n\nsize_t meshopt_simplifyWithUpdate(unsigned int* indices, size_t index_count, float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, float* vertex_attributes_data, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* out_result_error)\n{\n\treturn meshopt_simplifyEdge(indices, indices, index_count, vertex_positions_data, vertex_count, vertex_positions_stride, vertex_attributes_data, vertex_attributes_stride, attribute_weights, attribute_count, vertex_lock, target_index_count, target_error, options | meshopt_SimplifyInternalSolve, out_result_error);\n}\n\nsize_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, const unsigned char* vertex_lock, size_t target_index_count, float target_error, float* out_result_error)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\tassert(target_index_count <= index_count);\n\n\t// we expect to get ~2 triangles/vertex in the output\n\tsize_t target_cell_count = target_index_count / 6;\n\n\tmeshopt_Allocator allocator;\n\n\tVector3* vertex_positions = allocator.allocate<Vector3>(vertex_count);\n\trescalePositions(vertex_positions, vertex_positions_data, vertex_count, vertex_positions_stride);\n\n\t// find the optimal grid size using guided binary search\n#if TRACE\n\tprintf(\"source: %d vertices, %d triangles\\n\", int(vertex_count), int(index_count / 3));\n\tprintf(\"target: %d cells, %d triangles\\n\", int(target_cell_count), int(target_index_count / 3));\n#endif\n\n\tunsigned int* vertex_ids = allocator.allocate<unsigned int>(vertex_count);\n\n\tconst int kInterpolationPasses = 5;\n\n\t// invariant: # of triangles in min_grid <= target_count\n\tint min_grid = int(1.f / (target_error < 1e-3f ? 1e-3f : (target_error < 1.f ? target_error : 1.f)));\n\tint max_grid = 1025;\n\tsize_t min_triangles = 0;\n\tsize_t max_triangles = index_count / 3;\n\n\t// when we're error-limited, we compute the triangle count for the min. size; this accelerates convergence and provides the correct answer when we can't use a larger grid\n\tif (min_grid > 1 || vertex_lock)\n\t{\n\t\tcomputeVertexIds(vertex_ids, vertex_positions, vertex_lock, vertex_count, min_grid);\n\t\tmin_triangles = countTriangles(vertex_ids, indices, index_count);\n\t}\n\n\t// instead of starting in the middle, let's guess as to what the answer might be! triangle count usually grows as a square of grid size...\n\tint next_grid_size = int(sqrtf(float(target_cell_count)) + 0.5f);\n\n\tfor (int pass = 0; pass < 10 + kInterpolationPasses; ++pass)\n\t{\n\t\tif (min_triangles >= target_index_count / 3 || max_grid - min_grid <= 1)\n\t\t\tbreak;\n\n\t\t// we clamp the prediction of the grid size to make sure that the search converges\n\t\tint grid_size = next_grid_size;\n\t\tgrid_size = (grid_size <= min_grid) ? min_grid + 1 : (grid_size >= max_grid ? max_grid - 1 : grid_size);\n\n\t\tcomputeVertexIds(vertex_ids, vertex_positions, vertex_lock, vertex_count, grid_size);\n\t\tsize_t triangles = countTriangles(vertex_ids, indices, index_count);\n\n#if TRACE\n\t\tprintf(\"pass %d (%s): grid size %d, triangles %d, %s\\n\",\n\t\t    pass, (pass == 0) ? \"guess\" : (pass <= kInterpolationPasses ? \"lerp\" : \"binary\"),\n\t\t    grid_size, int(triangles),\n\t\t    (triangles <= target_index_count / 3) ? \"under\" : \"over\");\n#endif\n\n\t\tfloat tip = interpolate(float(size_t(target_index_count / 3)), float(min_grid), float(min_triangles), float(grid_size), float(triangles), float(max_grid), float(max_triangles));\n\n\t\tif (triangles <= target_index_count / 3)\n\t\t{\n\t\t\tmin_grid = grid_size;\n\t\t\tmin_triangles = triangles;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmax_grid = grid_size;\n\t\t\tmax_triangles = triangles;\n\t\t}\n\n\t\t// we start by using interpolation search - it usually converges faster\n\t\t// however, interpolation search has a worst case of O(N) so we switch to binary search after a few iterations which converges in O(logN)\n\t\tnext_grid_size = (pass < kInterpolationPasses) ? int(tip + 0.5f) : (min_grid + max_grid) / 2;\n\t}\n\n\tif (min_triangles == 0)\n\t{\n\t\tif (out_result_error)\n\t\t\t*out_result_error = 1.f;\n\n\t\treturn 0;\n\t}\n\n\t// build vertex->cell association by mapping all vertices with the same quantized position to the same cell\n\tsize_t table_size = hashBuckets2(vertex_count);\n\tunsigned int* table = allocator.allocate<unsigned int>(table_size);\n\n\tunsigned int* vertex_cells = allocator.allocate<unsigned int>(vertex_count);\n\n\tcomputeVertexIds(vertex_ids, vertex_positions, vertex_lock, vertex_count, min_grid);\n\tsize_t cell_count = fillVertexCells(table, table_size, vertex_cells, vertex_ids, vertex_count);\n\n\t// build a quadric for each target cell\n\tQuadric* cell_quadrics = allocator.allocate<Quadric>(cell_count);\n\tmemset(cell_quadrics, 0, cell_count * sizeof(Quadric));\n\n\tfillCellQuadrics(cell_quadrics, indices, index_count, vertex_positions, vertex_cells);\n\n\t// for each target cell, find the vertex with the minimal error\n\tunsigned int* cell_remap = allocator.allocate<unsigned int>(cell_count);\n\tfloat* cell_errors = allocator.allocate<float>(cell_count);\n\n\tfillCellRemap(cell_remap, cell_errors, cell_count, vertex_cells, cell_quadrics, vertex_positions, vertex_count);\n\n\t// compute error\n\tfloat result_error = 0.f;\n\n\tfor (size_t i = 0; i < cell_count; ++i)\n\t\tresult_error = result_error < cell_errors[i] ? cell_errors[i] : result_error;\n\n\t// vertex collapses often result in duplicate triangles; we need a table to filter them out\n\tsize_t tritable_size = hashBuckets2(min_triangles);\n\tunsigned int* tritable = allocator.allocate<unsigned int>(tritable_size);\n\n\t// note: this is the first and last write to destination, which allows aliasing destination with indices\n\tsize_t write = filterTriangles(destination, tritable, tritable_size, indices, index_count, vertex_cells, cell_remap);\n\n#if TRACE\n\tprintf(\"result: grid size %d, %d cells, %d triangles (%d unfiltered), error %e\\n\", min_grid, int(cell_count), int(write / 3), int(min_triangles), sqrtf(result_error));\n#endif\n\n\tif (out_result_error)\n\t\t*out_result_error = sqrtf(result_error);\n\n\treturn write;\n}\n\nsize_t meshopt_simplifyPrune(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, float target_error)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\tassert(target_error >= 0);\n\n\tmeshopt_Allocator allocator;\n\n\tunsigned int* result = destination;\n\tif (result != indices)\n\t\tmemcpy(result, indices, index_count * sizeof(unsigned int));\n\n\t// build position remap that maps each vertex to the one with identical position\n\tunsigned int* remap = allocator.allocate<unsigned int>(vertex_count);\n\tbuildPositionRemap(remap, NULL, vertex_positions_data, vertex_count, vertex_positions_stride, NULL, allocator);\n\n\tVector3* vertex_positions = allocator.allocate<Vector3>(vertex_count);\n\trescalePositions(vertex_positions, vertex_positions_data, vertex_count, vertex_positions_stride, NULL);\n\n\tunsigned int* components = allocator.allocate<unsigned int>(vertex_count);\n\tsize_t component_count = buildComponents(components, vertex_count, indices, index_count, remap);\n\n\tfloat* component_errors = allocator.allocate<float>(component_count * 4); // overallocate for temporary use inside measureComponents\n\tmeasureComponents(component_errors, component_count, components, vertex_positions, vertex_count);\n\n\tfloat component_nexterror = 0;\n\tsize_t result_count = pruneComponents(result, index_count, components, component_errors, component_count, target_error * target_error, component_nexterror);\n\n\treturn result_count;\n}\n\nsize_t meshopt_simplifyPoints(unsigned int* destination, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_colors, size_t vertex_colors_stride, float color_weight, size_t target_vertex_count)\n{\n\tusing namespace meshopt;\n\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\tassert(vertex_colors_stride == 0 || (vertex_colors_stride >= 12 && vertex_colors_stride <= 256));\n\tassert(vertex_colors_stride % sizeof(float) == 0);\n\tassert(vertex_colors == NULL || vertex_colors_stride != 0);\n\tassert(target_vertex_count <= vertex_count);\n\n\tsize_t target_cell_count = target_vertex_count;\n\n\tif (target_cell_count == 0)\n\t\treturn 0;\n\n\tmeshopt_Allocator allocator;\n\n\tVector3* vertex_positions = allocator.allocate<Vector3>(vertex_count);\n\trescalePositions(vertex_positions, vertex_positions_data, vertex_count, vertex_positions_stride);\n\n\t// find the optimal grid size using guided binary search\n#if TRACE\n\tprintf(\"source: %d vertices\\n\", int(vertex_count));\n\tprintf(\"target: %d cells\\n\", int(target_cell_count));\n#endif\n\n\tunsigned int* vertex_ids = allocator.allocate<unsigned int>(vertex_count);\n\n\tsize_t table_size = hashBuckets2(vertex_count);\n\tunsigned int* table = allocator.allocate<unsigned int>(table_size);\n\n\tconst int kInterpolationPasses = 5;\n\n\t// invariant: # of vertices in min_grid <= target_count\n\tint min_grid = 0;\n\tint max_grid = 1025;\n\tsize_t min_vertices = 0;\n\tsize_t max_vertices = vertex_count;\n\n\t// instead of starting in the middle, let's guess as to what the answer might be! triangle count usually grows as a square of grid size...\n\tint next_grid_size = int(sqrtf(float(target_cell_count)) + 0.5f);\n\n\tfor (int pass = 0; pass < 10 + kInterpolationPasses; ++pass)\n\t{\n\t\tassert(min_vertices < target_vertex_count);\n\t\tassert(max_grid - min_grid > 1);\n\n\t\t// we clamp the prediction of the grid size to make sure that the search converges\n\t\tint grid_size = next_grid_size;\n\t\tgrid_size = (grid_size <= min_grid) ? min_grid + 1 : (grid_size >= max_grid ? max_grid - 1 : grid_size);\n\n\t\tcomputeVertexIds(vertex_ids, vertex_positions, NULL, vertex_count, grid_size);\n\t\tsize_t vertices = countVertexCells(table, table_size, vertex_ids, vertex_count);\n\n#if TRACE\n\t\tprintf(\"pass %d (%s): grid size %d, vertices %d, %s\\n\",\n\t\t    pass, (pass == 0) ? \"guess\" : (pass <= kInterpolationPasses ? \"lerp\" : \"binary\"),\n\t\t    grid_size, int(vertices),\n\t\t    (vertices <= target_vertex_count) ? \"under\" : \"over\");\n#endif\n\n\t\tfloat tip = interpolate(float(target_vertex_count), float(min_grid), float(min_vertices), float(grid_size), float(vertices), float(max_grid), float(max_vertices));\n\n\t\tif (vertices <= target_vertex_count)\n\t\t{\n\t\t\tmin_grid = grid_size;\n\t\t\tmin_vertices = vertices;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmax_grid = grid_size;\n\t\t\tmax_vertices = vertices;\n\t\t}\n\n\t\tif (vertices == target_vertex_count || max_grid - min_grid <= 1)\n\t\t\tbreak;\n\n\t\t// we start by using interpolation search - it usually converges faster\n\t\t// however, interpolation search has a worst case of O(N) so we switch to binary search after a few iterations which converges in O(logN)\n\t\tnext_grid_size = (pass < kInterpolationPasses) ? int(tip + 0.5f) : (min_grid + max_grid) / 2;\n\t}\n\n\tif (min_vertices == 0)\n\t\treturn 0;\n\n\t// build vertex->cell association by mapping all vertices with the same quantized position to the same cell\n\tunsigned int* vertex_cells = allocator.allocate<unsigned int>(vertex_count);\n\n\tcomputeVertexIds(vertex_ids, vertex_positions, NULL, vertex_count, min_grid);\n\tsize_t cell_count = fillVertexCells(table, table_size, vertex_cells, vertex_ids, vertex_count);\n\n\t// accumulate points into a reservoir for each target cell\n\tReservoir* cell_reservoirs = allocator.allocate<Reservoir>(cell_count);\n\tmemset(cell_reservoirs, 0, cell_count * sizeof(Reservoir));\n\n\tfillCellReservoirs(cell_reservoirs, cell_count, vertex_positions, vertex_colors, vertex_colors_stride, vertex_count, vertex_cells);\n\n\t// for each target cell, find the vertex with the minimal error\n\tunsigned int* cell_remap = allocator.allocate<unsigned int>(cell_count);\n\tfloat* cell_errors = allocator.allocate<float>(cell_count);\n\n\t// we scale the color weight to bring it to the same scale as position so that error addition makes sense\n\tfloat color_weight_scaled = color_weight * (min_grid == 1 ? 1.f : 1.f / (min_grid - 1));\n\n\tfillCellRemap(cell_remap, cell_errors, cell_count, vertex_cells, cell_reservoirs, vertex_positions, vertex_colors, vertex_colors_stride, color_weight_scaled * color_weight_scaled, vertex_count);\n\n\t// copy results to the output\n\tassert(cell_count <= target_vertex_count);\n\tmemcpy(destination, cell_remap, sizeof(unsigned int) * cell_count);\n\n#if TRACE\n\t// compute error\n\tfloat result_error = 0.f;\n\n\tfor (size_t i = 0; i < cell_count; ++i)\n\t\tresult_error = result_error < cell_errors[i] ? cell_errors[i] : result_error;\n\n\tprintf(\"result: %d cells, %e error\\n\", int(cell_count), sqrtf(result_error));\n#endif\n\n\treturn cell_count;\n}\n\nfloat meshopt_simplifyScale(const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tusing namespace meshopt;\n\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\n\tfloat extent = rescalePositions(NULL, vertex_positions, vertex_count, vertex_positions_stride);\n\n\treturn extent;\n}\n"
  },
  {
    "path": "src/spatialorder.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <float.h>\n#include <string.h>\n\n// This work is based on:\n// Fabian Giesen. Decoding Morton codes. 2009\nnamespace meshopt\n{\n\n// \"Insert\" two 0 bits after each of the 20 low bits of x\ninline unsigned long long part1By2(unsigned long long x)\n{\n\tx &= 0x000fffffull;                          // x = ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- jihg fedc ba98 7654 3210\n\tx = (x ^ (x << 32)) & 0x000f00000000ffffull; // x = ---- ---- ---- jihg ---- ---- ---- ---- ---- ---- ---- ---- fedc ba98 7654 3210\n\tx = (x ^ (x << 16)) & 0x000f0000ff0000ffull; // x = ---- ---- ---- jihg ---- ---- ---- ---- fedc ba98 ---- ---- ---- ---- 7654 3210\n\tx = (x ^ (x << 8)) & 0x000f00f00f00f00full;  // x = ---- ---- ---- jihg ---- ---- fedc ---- ---- ba98 ---- ---- 7654 ---- ---- 3210\n\tx = (x ^ (x << 4)) & 0x00c30c30c30c30c3ull;  // x = ---- ---- ji-- --hg ---- fe-- --dc ---- ba-- --98 ---- 76-- --54 ---- 32-- --10\n\tx = (x ^ (x << 2)) & 0x0249249249249249ull;  // x = ---- --j- -i-- h--g --f- -e-- d--c --b- -a-- 9--8 --7- -6-- 5--4 --3- -2-- 1--0\n\treturn x;\n}\n\nstatic void computeOrder(unsigned long long* result, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, bool morton)\n{\n\tsize_t vertex_stride_float = vertex_positions_stride / sizeof(float);\n\n\tfloat minv[3] = {FLT_MAX, FLT_MAX, FLT_MAX};\n\tfloat maxv[3] = {-FLT_MAX, -FLT_MAX, -FLT_MAX};\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tconst float* v = vertex_positions_data + i * vertex_stride_float;\n\n\t\tfor (int j = 0; j < 3; ++j)\n\t\t{\n\t\t\tfloat vj = v[j];\n\n\t\t\tminv[j] = minv[j] > vj ? vj : minv[j];\n\t\t\tmaxv[j] = maxv[j] < vj ? vj : maxv[j];\n\t\t}\n\t}\n\n\tfloat extent = 0.f;\n\n\textent = (maxv[0] - minv[0]) < extent ? extent : (maxv[0] - minv[0]);\n\textent = (maxv[1] - minv[1]) < extent ? extent : (maxv[1] - minv[1]);\n\textent = (maxv[2] - minv[2]) < extent ? extent : (maxv[2] - minv[2]);\n\n\t// rescale each axis to 16 bits to get 48-bit Morton codes\n\tfloat scale = extent == 0 ? 0.f : 65535.f / extent;\n\n\t// generate Morton order based on the position inside a unit cube\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tconst float* v = vertex_positions_data + i * vertex_stride_float;\n\n\t\tint x = int((v[0] - minv[0]) * scale + 0.5f);\n\t\tint y = int((v[1] - minv[1]) * scale + 0.5f);\n\t\tint z = int((v[2] - minv[2]) * scale + 0.5f);\n\n\t\tif (morton)\n\t\t\tresult[i] = part1By2(x) | (part1By2(y) << 1) | (part1By2(z) << 2);\n\t\telse\n\t\t\tresult[i] = ((unsigned long long)x << 0) | ((unsigned long long)y << 20) | ((unsigned long long)z << 40);\n\t}\n}\n\nstatic void radixSort10(unsigned int* destination, const unsigned int* source, const unsigned short* keys, size_t count)\n{\n\tunsigned int hist[1024];\n\tmemset(hist, 0, sizeof(hist));\n\n\t// compute histogram (assume keys are 10-bit)\n\tfor (size_t i = 0; i < count; ++i)\n\t\thist[keys[i]]++;\n\n\tunsigned int sum = 0;\n\n\t// replace histogram data with prefix histogram sums in-place\n\tfor (int i = 0; i < 1024; ++i)\n\t{\n\t\tunsigned int h = hist[i];\n\t\thist[i] = sum;\n\t\tsum += h;\n\t}\n\n\tassert(sum == count);\n\n\t// reorder values\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned int id = keys[source[i]];\n\n\t\tdestination[hist[id]++] = source[i];\n\t}\n}\n\nstatic void computeHistogram(unsigned int (&hist)[256][2], const unsigned short* data, size_t count)\n{\n\tmemset(hist, 0, sizeof(hist));\n\n\t// compute 2 8-bit histograms in parallel\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned long long id = data[i];\n\n\t\thist[(id >> 0) & 255][0]++;\n\t\thist[(id >> 8) & 255][1]++;\n\t}\n\n\tunsigned int sum0 = 0, sum1 = 0;\n\n\t// replace histogram data with prefix histogram sums in-place\n\tfor (int i = 0; i < 256; ++i)\n\t{\n\t\tunsigned int h0 = hist[i][0], h1 = hist[i][1];\n\n\t\thist[i][0] = sum0;\n\t\thist[i][1] = sum1;\n\n\t\tsum0 += h0;\n\t\tsum1 += h1;\n\t}\n\n\tassert(sum0 == count && sum1 == count);\n}\n\nstatic void radixPass(unsigned int* destination, const unsigned int* source, const unsigned short* keys, size_t count, unsigned int (&hist)[256][2], int pass)\n{\n\tint bitoff = pass * 8;\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned int id = unsigned(keys[source[i]] >> bitoff) & 255;\n\n\t\tdestination[hist[id][pass]++] = source[i];\n\t}\n}\n\nstatic void partitionPoints(unsigned int* target, const unsigned int* order, const unsigned char* sides, size_t split, size_t count)\n{\n\tsize_t l = 0, r = split;\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned char side = sides[order[i]];\n\t\ttarget[side ? r : l] = order[i];\n\t\tl += 1;\n\t\tl -= side;\n\t\tr += side;\n\t}\n\n\tassert(l == split && r == count);\n}\n\nstatic void splitPoints(unsigned int* destination, unsigned int* orderx, unsigned int* ordery, unsigned int* orderz, const unsigned long long* keys, size_t count, void* scratch, size_t cluster_size)\n{\n\tif (count <= cluster_size)\n\t{\n\t\tmemcpy(destination, orderx, count * sizeof(unsigned int));\n\t\treturn;\n\t}\n\n\tunsigned int* axes[3] = {orderx, ordery, orderz};\n\n\tint bestk = -1;\n\tunsigned int bestdim = 0;\n\n\tfor (int k = 0; k < 3; ++k)\n\t{\n\t\tconst unsigned int mask = (1 << 20) - 1;\n\t\tunsigned int dim = (unsigned(keys[axes[k][count - 1]] >> (k * 20)) & mask) - (unsigned(keys[axes[k][0]] >> (k * 20)) & mask);\n\n\t\tif (dim >= bestdim)\n\t\t{\n\t\t\tbestk = k;\n\t\t\tbestdim = dim;\n\t\t}\n\t}\n\n\tassert(bestk >= 0);\n\n\t// split roughly in half, with the left split always being aligned to cluster size\n\tsize_t split = ((count / 2) + cluster_size - 1) / cluster_size * cluster_size;\n\tassert(split > 0 && split < count);\n\n\t// mark sides of split for partitioning\n\tunsigned char* sides = static_cast<unsigned char*>(scratch) + count * sizeof(unsigned int);\n\n\tfor (size_t i = 0; i < split; ++i)\n\t\tsides[axes[bestk][i]] = 0;\n\n\tfor (size_t i = split; i < count; ++i)\n\t\tsides[axes[bestk][i]] = 1;\n\n\t// partition all axes into two sides, maintaining order\n\tunsigned int* temp = static_cast<unsigned int*>(scratch);\n\n\tfor (int k = 0; k < 3; ++k)\n\t{\n\t\tif (k == bestk)\n\t\t\tcontinue;\n\n\t\tunsigned int* axis = axes[k];\n\t\tmemcpy(temp, axis, sizeof(unsigned int) * count);\n\t\tpartitionPoints(axis, temp, sides, split, count);\n\t}\n\n\t// recursion depth is logarithmic and bounded as we always split in approximately half\n\tsplitPoints(destination, orderx, ordery, orderz, keys, split, scratch, cluster_size);\n\tsplitPoints(destination + split, orderx + split, ordery + split, orderz + split, keys, count - split, scratch, cluster_size);\n}\n\n} // namespace meshopt\n\nvoid meshopt_spatialSortRemap(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tusing namespace meshopt;\n\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\n\tmeshopt_Allocator allocator;\n\n\tunsigned long long* keys = allocator.allocate<unsigned long long>(vertex_count);\n\tcomputeOrder(keys, vertex_positions, vertex_count, vertex_positions_stride, /* morton= */ true);\n\n\tunsigned int* scratch = allocator.allocate<unsigned int>(vertex_count * 2); // 4b for order + 2b for keys\n\tunsigned short* keyk = (unsigned short*)(scratch + vertex_count);\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tdestination[i] = unsigned(i);\n\n\tunsigned int* order[] = {scratch, destination};\n\n\t// 5-pass radix sort computes the resulting order into scratch\n\tfor (int k = 0; k < 5; ++k)\n\t{\n\t\t// copy 10-bit key segments into keyk to reduce cache pressure during radix pass\n\t\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\t\tkeyk[i] = (unsigned short)((keys[i] >> (k * 10)) & 1023);\n\n\t\tradixSort10(order[k % 2], order[(k + 1) % 2], keyk, vertex_count);\n\t}\n\n\t// since our remap table is mapping old=>new, we need to reverse it\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tdestination[scratch[i]] = unsigned(i);\n}\n\nvoid meshopt_spatialSortTriangles(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\n\t(void)vertex_count;\n\n\tsize_t face_count = index_count / 3;\n\tsize_t vertex_stride_float = vertex_positions_stride / sizeof(float);\n\n\tmeshopt_Allocator allocator;\n\n\tfloat* centroids = allocator.allocate<float>(face_count * 3);\n\n\tfor (size_t i = 0; i < face_count; ++i)\n\t{\n\t\tunsigned int a = indices[i * 3 + 0], b = indices[i * 3 + 1], c = indices[i * 3 + 2];\n\t\tassert(a < vertex_count && b < vertex_count && c < vertex_count);\n\n\t\tconst float* va = vertex_positions + a * vertex_stride_float;\n\t\tconst float* vb = vertex_positions + b * vertex_stride_float;\n\t\tconst float* vc = vertex_positions + c * vertex_stride_float;\n\n\t\tcentroids[i * 3 + 0] = (va[0] + vb[0] + vc[0]) / 3.f;\n\t\tcentroids[i * 3 + 1] = (va[1] + vb[1] + vc[1]) / 3.f;\n\t\tcentroids[i * 3 + 2] = (va[2] + vb[2] + vc[2]) / 3.f;\n\t}\n\n\tunsigned int* remap = allocator.allocate<unsigned int>(face_count);\n\n\tmeshopt_spatialSortRemap(remap, centroids, face_count, sizeof(float) * 3);\n\n\t// support in-order remap\n\tif (destination == indices)\n\t{\n\t\tunsigned int* indices_copy = allocator.allocate<unsigned int>(index_count);\n\t\tmemcpy(indices_copy, indices, index_count * sizeof(unsigned int));\n\t\tindices = indices_copy;\n\t}\n\n\tfor (size_t i = 0; i < face_count; ++i)\n\t{\n\t\tunsigned int a = indices[i * 3 + 0], b = indices[i * 3 + 1], c = indices[i * 3 + 2];\n\t\tunsigned int r = remap[i];\n\n\t\tdestination[r * 3 + 0] = a;\n\t\tdestination[r * 3 + 1] = b;\n\t\tdestination[r * 3 + 2] = c;\n\t}\n}\n\nvoid meshopt_spatialClusterPoints(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t cluster_size)\n{\n\tusing namespace meshopt;\n\n\tassert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);\n\tassert(vertex_positions_stride % sizeof(float) == 0);\n\tassert(cluster_size > 0);\n\n\tmeshopt_Allocator allocator;\n\n\tunsigned long long* keys = allocator.allocate<unsigned long long>(vertex_count);\n\tcomputeOrder(keys, vertex_positions, vertex_count, vertex_positions_stride, /* morton= */ false);\n\n\tunsigned int* order = allocator.allocate<unsigned int>(vertex_count * 3);\n\tunsigned int* scratch = allocator.allocate<unsigned int>(vertex_count * 2); // 4b for order + 1b for side or 2b for keys\n\tunsigned short* keyk = reinterpret_cast<unsigned short*>(scratch + vertex_count);\n\n\tfor (int k = 0; k < 3; ++k)\n\t{\n\t\t// copy 16-bit key segments into keyk to reduce cache pressure during radix pass\n\t\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\t\tkeyk[i] = (unsigned short)(keys[i] >> (k * 20));\n\n\t\tunsigned int hist[256][2];\n\t\tcomputeHistogram(hist, keyk, vertex_count);\n\n\t\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\t\torder[k * vertex_count + i] = unsigned(i);\n\n\t\tradixPass(scratch, order + k * vertex_count, keyk, vertex_count, hist, 0);\n\t\tradixPass(order + k * vertex_count, scratch, keyk, vertex_count, hist, 1);\n\t}\n\n\tsplitPoints(destination, order, order + vertex_count, order + 2 * vertex_count, keys, vertex_count, scratch, cluster_size);\n}\n"
  },
  {
    "path": "src/stripifier.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <limits.h>\n#include <string.h>\n\n// This work is based on:\n// Francine Evans, Steven Skiena and Amitabh Varshney. Optimizing Triangle Strips for Fast Rendering. 1996\nnamespace meshopt\n{\n\nstatic unsigned int findStripFirst(const unsigned int buffer[][3], unsigned int buffer_size, const unsigned char* valence)\n{\n\tunsigned int index = 0;\n\tunsigned int iv = ~0u;\n\n\tfor (size_t i = 0; i < buffer_size; ++i)\n\t{\n\t\tunsigned char va = valence[buffer[i][0]], vb = valence[buffer[i][1]], vc = valence[buffer[i][2]];\n\t\tunsigned int v = (va < vb && va < vc) ? va : (vb < vc ? vb : vc);\n\n\t\tif (v < iv)\n\t\t{\n\t\t\tindex = unsigned(i);\n\t\t\tiv = v;\n\t\t}\n\t}\n\n\treturn index;\n}\n\nstatic int findStripNext(const unsigned int buffer[][3], unsigned int buffer_size, unsigned int e0, unsigned int e1)\n{\n\tfor (size_t i = 0; i < buffer_size; ++i)\n\t{\n\t\tunsigned int a = buffer[i][0], b = buffer[i][1], c = buffer[i][2];\n\n\t\tif (e0 == a && e1 == b)\n\t\t\treturn (int(i) << 2) | 2;\n\t\telse if (e0 == b && e1 == c)\n\t\t\treturn (int(i) << 2) | 0;\n\t\telse if (e0 == c && e1 == a)\n\t\t\treturn (int(i) << 2) | 1;\n\t}\n\n\treturn -1;\n}\n\n} // namespace meshopt\n\nsize_t meshopt_stripify(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int restart_index)\n{\n\tassert(destination != indices);\n\tassert(index_count % 3 == 0);\n\n\tusing namespace meshopt;\n\n\tmeshopt_Allocator allocator;\n\n\tconst size_t buffer_capacity = 8;\n\n\tunsigned int buffer[buffer_capacity][3] = {};\n\tunsigned int buffer_size = 0;\n\n\tsize_t index_offset = 0;\n\n\tunsigned int strip[2] = {};\n\tunsigned int parity = 0;\n\n\tsize_t strip_size = 0;\n\n\t// compute vertex valence; this is used to prioritize starting triangle for strips\n\t// note: we use 8-bit counters for performance; for outlier vertices the valence is incorrect but that just affects the heuristic\n\tunsigned char* valence = allocator.allocate<unsigned char>(vertex_count);\n\tmemset(valence, 0, vertex_count);\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int index = indices[i];\n\t\tassert(index < vertex_count);\n\n\t\tvalence[index]++;\n\t}\n\n\tint next = -1;\n\n\twhile (buffer_size > 0 || index_offset < index_count)\n\t{\n\t\tassert(next < 0 || (size_t(next >> 2) < buffer_size && (next & 3) < 3));\n\n\t\t// fill triangle buffer\n\t\twhile (buffer_size < buffer_capacity && index_offset < index_count)\n\t\t{\n\t\t\tbuffer[buffer_size][0] = indices[index_offset + 0];\n\t\t\tbuffer[buffer_size][1] = indices[index_offset + 1];\n\t\t\tbuffer[buffer_size][2] = indices[index_offset + 2];\n\n\t\t\tbuffer_size++;\n\t\t\tindex_offset += 3;\n\t\t}\n\n\t\tassert(buffer_size > 0);\n\n\t\tif (next >= 0)\n\t\t{\n\t\t\tunsigned int i = next >> 2;\n\t\t\tunsigned int a = buffer[i][0], b = buffer[i][1], c = buffer[i][2];\n\t\t\tunsigned int v = buffer[i][next & 3];\n\n\t\t\t// ordered removal from the buffer\n\t\t\tmemmove(buffer[i], buffer[i + 1], (buffer_size - i - 1) * sizeof(buffer[0]));\n\t\t\tbuffer_size--;\n\n\t\t\t// update vertex valences for strip start heuristic\n\t\t\tvalence[a]--;\n\t\t\tvalence[b]--;\n\t\t\tvalence[c]--;\n\n\t\t\t// find next triangle (note that edge order flips on every iteration)\n\t\t\t// in some cases we need to perform a swap to pick a different outgoing triangle edge\n\t\t\t// for [a b c], the default strip edge is [b c], but we might want to use [a c]\n\t\t\tint cont = findStripNext(buffer, buffer_size, parity ? strip[1] : v, parity ? v : strip[1]);\n\t\t\tint swap = cont < 0 ? findStripNext(buffer, buffer_size, parity ? v : strip[0], parity ? strip[0] : v) : -1;\n\n\t\t\tif (cont < 0 && swap >= 0)\n\t\t\t{\n\t\t\t\t// [a b c] => [a b a c]\n\t\t\t\tdestination[strip_size++] = strip[0];\n\t\t\t\tdestination[strip_size++] = v;\n\n\t\t\t\t// next strip has same winding\n\t\t\t\t// ? a b => b a v\n\t\t\t\tstrip[1] = v;\n\n\t\t\t\tnext = swap;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// emit the next vertex in the strip\n\t\t\t\tdestination[strip_size++] = v;\n\n\t\t\t\t// next strip has flipped winding\n\t\t\t\tstrip[0] = strip[1];\n\t\t\t\tstrip[1] = v;\n\t\t\t\tparity ^= 1;\n\n\t\t\t\tnext = cont;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// if we didn't find anything, we need to find the next new triangle\n\t\t\t// we use a heuristic to maximize the strip length\n\t\t\tunsigned int i = findStripFirst(buffer, buffer_size, valence);\n\t\t\tunsigned int a = buffer[i][0], b = buffer[i][1], c = buffer[i][2];\n\n\t\t\t// ordered removal from the buffer\n\t\t\tmemmove(buffer[i], buffer[i + 1], (buffer_size - i - 1) * sizeof(buffer[0]));\n\t\t\tbuffer_size--;\n\n\t\t\t// update vertex valences for strip start heuristic\n\t\t\tvalence[a]--;\n\t\t\tvalence[b]--;\n\t\t\tvalence[c]--;\n\n\t\t\t// we need to pre-rotate the triangle so that we will find a match in the existing buffer on the next iteration\n\t\t\tint ea = findStripNext(buffer, buffer_size, c, b);\n\t\t\tint eb = findStripNext(buffer, buffer_size, a, c);\n\t\t\tint ec = findStripNext(buffer, buffer_size, b, a);\n\n\t\t\t// in some cases we can have several matching edges; since we can pick any edge, we pick the one with the smallest\n\t\t\t// triangle index in the buffer. this reduces the effect of stripification on ACMR and additionally - for unclear\n\t\t\t// reasons - slightly improves the stripification efficiency\n\t\t\tint mine = INT_MAX;\n\t\t\tmine = (ea >= 0 && mine > ea) ? ea : mine;\n\t\t\tmine = (eb >= 0 && mine > eb) ? eb : mine;\n\t\t\tmine = (ec >= 0 && mine > ec) ? ec : mine;\n\n\t\t\tif (ea == mine)\n\t\t\t{\n\t\t\t\t// keep abc\n\t\t\t\tnext = ea;\n\t\t\t}\n\t\t\telse if (eb == mine)\n\t\t\t{\n\t\t\t\t// abc -> bca\n\t\t\t\tunsigned int t = a;\n\t\t\t\ta = b, b = c, c = t;\n\n\t\t\t\tnext = eb;\n\t\t\t}\n\t\t\telse if (ec == mine)\n\t\t\t{\n\t\t\t\t// abc -> cab\n\t\t\t\tunsigned int t = c;\n\t\t\t\tc = b, b = a, a = t;\n\n\t\t\t\tnext = ec;\n\t\t\t}\n\n\t\t\tif (restart_index)\n\t\t\t{\n\t\t\t\tif (strip_size)\n\t\t\t\t\tdestination[strip_size++] = restart_index;\n\n\t\t\t\tdestination[strip_size++] = a;\n\t\t\t\tdestination[strip_size++] = b;\n\t\t\t\tdestination[strip_size++] = c;\n\n\t\t\t\t// new strip always starts with the same edge winding\n\t\t\t\tstrip[0] = b;\n\t\t\t\tstrip[1] = c;\n\t\t\t\tparity = 1;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (strip_size)\n\t\t\t\t{\n\t\t\t\t\t// connect last strip using degenerate triangles\n\t\t\t\t\tdestination[strip_size++] = strip[1];\n\t\t\t\t\tdestination[strip_size++] = a;\n\t\t\t\t}\n\n\t\t\t\t// note that we may need to flip the emitted triangle based on parity\n\t\t\t\t// we always end up with outgoing edge \"cb\" in the end\n\t\t\t\tunsigned int e0 = parity ? c : b;\n\t\t\t\tunsigned int e1 = parity ? b : c;\n\n\t\t\t\tdestination[strip_size++] = a;\n\t\t\t\tdestination[strip_size++] = e0;\n\t\t\t\tdestination[strip_size++] = e1;\n\n\t\t\t\tstrip[0] = e0;\n\t\t\t\tstrip[1] = e1;\n\t\t\t\tparity ^= 1;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn strip_size;\n}\n\nsize_t meshopt_stripifyBound(size_t index_count)\n{\n\tassert(index_count % 3 == 0);\n\n\t// worst case without restarts is 2 degenerate indices and 3 indices per triangle\n\t// worst case with restarts is 1 restart index and 3 indices per triangle\n\treturn (index_count / 3) * 5;\n}\n\nsize_t meshopt_unstripify(unsigned int* destination, const unsigned int* indices, size_t index_count, unsigned int restart_index)\n{\n\tassert(destination != indices);\n\n\tsize_t offset = 0;\n\tsize_t start = 0;\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tif (restart_index && indices[i] == restart_index)\n\t\t{\n\t\t\tstart = i + 1;\n\t\t}\n\t\telse if (i - start >= 2)\n\t\t{\n\t\t\tunsigned int a = indices[i - 2], b = indices[i - 1], c = indices[i];\n\n\t\t\t// flip winding for odd triangles\n\t\t\tif ((i - start) & 1)\n\t\t\t{\n\t\t\t\tunsigned int t = a;\n\t\t\t\ta = b, b = t;\n\t\t\t}\n\n\t\t\t// although we use restart indices, strip swaps still produce degenerate triangles, so skip them\n\t\t\tif (a != b && a != c && b != c)\n\t\t\t{\n\t\t\t\tdestination[offset + 0] = a;\n\t\t\t\tdestination[offset + 1] = b;\n\t\t\t\tdestination[offset + 2] = c;\n\t\t\t\toffset += 3;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn offset;\n}\n\nsize_t meshopt_unstripifyBound(size_t index_count)\n{\n\tassert(index_count == 0 || index_count >= 3);\n\n\treturn (index_count == 0) ? 0 : (index_count - 2) * 3;\n}\n"
  },
  {
    "path": "src/vcacheoptimizer.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <string.h>\n\n// This work is based on:\n// Tom Forsyth. Linear-Speed Vertex Cache Optimisation. 2006\n// Pedro Sander, Diego Nehab and Joshua Barczak. Fast Triangle Reordering for Vertex Locality and Reduced Overdraw. 2007\nnamespace meshopt\n{\n\nconst size_t kCacheSizeMax = 16;\nconst size_t kValenceMax = 8;\n\nstruct VertexScoreTable\n{\n\tfloat cache[1 + kCacheSizeMax];\n\tfloat live[1 + kValenceMax];\n};\n\n// Tuned to minimize the ACMR of a GPU that has a cache profile similar to NVidia and AMD\nstatic const VertexScoreTable kVertexScoreTable = {\n    {0.f, 0.779f, 0.791f, 0.789f, 0.981f, 0.843f, 0.726f, 0.847f, 0.882f, 0.867f, 0.799f, 0.642f, 0.613f, 0.600f, 0.568f, 0.372f, 0.234f},\n    {0.f, 0.995f, 0.713f, 0.450f, 0.404f, 0.059f, 0.005f, 0.147f, 0.006f},\n};\n\n// Tuned to minimize the encoded index buffer size\nstatic const VertexScoreTable kVertexScoreTableStrip = {\n    {0.f, 1.000f, 1.000f, 1.000f, 0.453f, 0.561f, 0.490f, 0.459f, 0.179f, 0.526f, 0.000f, 0.227f, 0.184f, 0.490f, 0.112f, 0.050f, 0.131f},\n    {0.f, 0.956f, 0.786f, 0.577f, 0.558f, 0.618f, 0.549f, 0.499f, 0.489f},\n};\n\nstruct TriangleAdjacency\n{\n\tunsigned int* counts;\n\tunsigned int* offsets;\n\tunsigned int* data;\n};\n\nstatic void buildTriangleAdjacency(TriangleAdjacency& adjacency, const unsigned int* indices, size_t index_count, size_t vertex_count, meshopt_Allocator& allocator)\n{\n\tsize_t face_count = index_count / 3;\n\n\t// allocate arrays\n\tadjacency.counts = allocator.allocate<unsigned int>(vertex_count);\n\tadjacency.offsets = allocator.allocate<unsigned int>(vertex_count);\n\tadjacency.data = allocator.allocate<unsigned int>(index_count);\n\n\t// fill triangle counts\n\tmemset(adjacency.counts, 0, vertex_count * sizeof(unsigned int));\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tassert(indices[i] < vertex_count);\n\n\t\tadjacency.counts[indices[i]]++;\n\t}\n\n\t// fill offset table\n\tunsigned int offset = 0;\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tadjacency.offsets[i] = offset;\n\t\toffset += adjacency.counts[i];\n\t}\n\n\tassert(offset == index_count);\n\n\t// fill triangle data\n\tfor (size_t i = 0; i < face_count; ++i)\n\t{\n\t\tunsigned int a = indices[i * 3 + 0], b = indices[i * 3 + 1], c = indices[i * 3 + 2];\n\n\t\tadjacency.data[adjacency.offsets[a]++] = unsigned(i);\n\t\tadjacency.data[adjacency.offsets[b]++] = unsigned(i);\n\t\tadjacency.data[adjacency.offsets[c]++] = unsigned(i);\n\t}\n\n\t// fix offsets that have been disturbed by the previous pass\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tassert(adjacency.offsets[i] >= adjacency.counts[i]);\n\n\t\tadjacency.offsets[i] -= adjacency.counts[i];\n\t}\n}\n\nstatic unsigned int getNextVertexDeadEnd(const unsigned int* dead_end, unsigned int& dead_end_top, unsigned int& input_cursor, const unsigned int* live_triangles, size_t vertex_count)\n{\n\t// check dead-end stack\n\twhile (dead_end_top)\n\t{\n\t\tunsigned int vertex = dead_end[--dead_end_top];\n\n\t\tif (live_triangles[vertex] > 0)\n\t\t\treturn vertex;\n\t}\n\n\t// input order\n\twhile (input_cursor < vertex_count)\n\t{\n\t\tif (live_triangles[input_cursor] > 0)\n\t\t\treturn input_cursor;\n\n\t\t++input_cursor;\n\t}\n\n\treturn ~0u;\n}\n\nstatic unsigned int getNextVertexNeighbor(const unsigned int* next_candidates_begin, const unsigned int* next_candidates_end, const unsigned int* live_triangles, const unsigned int* cache_timestamps, unsigned int timestamp, unsigned int cache_size)\n{\n\tunsigned int best_candidate = ~0u;\n\tint best_priority = -1;\n\n\tfor (const unsigned int* next_candidate = next_candidates_begin; next_candidate != next_candidates_end; ++next_candidate)\n\t{\n\t\tunsigned int vertex = *next_candidate;\n\n\t\t// otherwise we don't need to process it\n\t\tif (live_triangles[vertex] > 0)\n\t\t{\n\t\t\tint priority = 0;\n\n\t\t\t// will it be in cache after fanning?\n\t\t\tif (2 * live_triangles[vertex] + timestamp - cache_timestamps[vertex] <= cache_size)\n\t\t\t{\n\t\t\t\tpriority = timestamp - cache_timestamps[vertex]; // position in cache\n\t\t\t}\n\n\t\t\tif (priority > best_priority)\n\t\t\t{\n\t\t\t\tbest_candidate = vertex;\n\t\t\t\tbest_priority = priority;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn best_candidate;\n}\n\nstatic float vertexScore(const VertexScoreTable* table, int cache_position, unsigned int live_triangles)\n{\n\tassert(cache_position >= -1 && cache_position < int(kCacheSizeMax));\n\n\tunsigned int live_triangles_clamped = live_triangles < kValenceMax ? live_triangles : kValenceMax;\n\n\treturn table->cache[1 + cache_position] + table->live[live_triangles_clamped];\n}\n\nstatic unsigned int getNextTriangleDeadEnd(unsigned int& input_cursor, const unsigned char* emitted_flags, size_t face_count)\n{\n\t// input order\n\twhile (input_cursor < face_count)\n\t{\n\t\tif (!emitted_flags[input_cursor])\n\t\t\treturn input_cursor;\n\n\t\t++input_cursor;\n\t}\n\n\treturn ~0u;\n}\n\n} // namespace meshopt\n\nvoid meshopt_optimizeVertexCacheTable(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const meshopt::VertexScoreTable* table)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\n\tmeshopt_Allocator allocator;\n\n\t// guard for empty meshes\n\tif (index_count == 0 || vertex_count == 0)\n\t\treturn;\n\n\t// support in-place optimization\n\tif (destination == indices)\n\t{\n\t\tunsigned int* indices_copy = allocator.allocate<unsigned int>(index_count);\n\t\tmemcpy(indices_copy, indices, index_count * sizeof(unsigned int));\n\t\tindices = indices_copy;\n\t}\n\n\tunsigned int cache_size = 16;\n\tassert(cache_size <= kCacheSizeMax);\n\n\tsize_t face_count = index_count / 3;\n\n\t// build adjacency information\n\tTriangleAdjacency adjacency = {};\n\tbuildTriangleAdjacency(adjacency, indices, index_count, vertex_count, allocator);\n\n\t// live triangle counts; note, we alias adjacency.counts as we remove triangles after emitting them so the counts always match\n\tunsigned int* live_triangles = adjacency.counts;\n\n\t// emitted flags\n\tunsigned char* emitted_flags = allocator.allocate<unsigned char>(face_count);\n\tmemset(emitted_flags, 0, face_count);\n\n\t// compute initial vertex scores\n\tfloat* vertex_scores = allocator.allocate<float>(vertex_count);\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tvertex_scores[i] = vertexScore(table, -1, live_triangles[i]);\n\n\t// compute triangle scores\n\tfloat* triangle_scores = allocator.allocate<float>(face_count);\n\n\tfor (size_t i = 0; i < face_count; ++i)\n\t{\n\t\tunsigned int a = indices[i * 3 + 0];\n\t\tunsigned int b = indices[i * 3 + 1];\n\t\tunsigned int c = indices[i * 3 + 2];\n\n\t\ttriangle_scores[i] = vertex_scores[a] + vertex_scores[b] + vertex_scores[c];\n\t}\n\n\tunsigned int cache_holder[2 * (kCacheSizeMax + 4)];\n\tunsigned int* cache = cache_holder;\n\tunsigned int* cache_new = cache_holder + kCacheSizeMax + 4;\n\tsize_t cache_count = 0;\n\n\tunsigned int current_triangle = 0;\n\tunsigned int input_cursor = 1;\n\n\tunsigned int output_triangle = 0;\n\n\twhile (current_triangle != ~0u)\n\t{\n\t\tassert(output_triangle < face_count);\n\n\t\tunsigned int a = indices[current_triangle * 3 + 0];\n\t\tunsigned int b = indices[current_triangle * 3 + 1];\n\t\tunsigned int c = indices[current_triangle * 3 + 2];\n\n\t\t// output indices\n\t\tdestination[output_triangle * 3 + 0] = a;\n\t\tdestination[output_triangle * 3 + 1] = b;\n\t\tdestination[output_triangle * 3 + 2] = c;\n\t\toutput_triangle++;\n\n\t\t// update emitted flags\n\t\temitted_flags[current_triangle] = true;\n\t\ttriangle_scores[current_triangle] = 0;\n\n\t\t// new triangle\n\t\tsize_t cache_write = 0;\n\t\tcache_new[cache_write++] = a;\n\t\tcache_new[cache_write++] = b;\n\t\tcache_new[cache_write++] = c;\n\n\t\t// old triangles\n\t\tfor (size_t i = 0; i < cache_count; ++i)\n\t\t{\n\t\t\tunsigned int index = cache[i];\n\n\t\t\tcache_new[cache_write] = index;\n\t\t\tcache_write += (index != a) & (index != b) & (index != c);\n\t\t}\n\n\t\tunsigned int* cache_temp = cache;\n\t\tcache = cache_new, cache_new = cache_temp;\n\t\tcache_count = cache_write > cache_size ? cache_size : cache_write;\n\n\t\t// remove emitted triangle from adjacency data\n\t\t// this makes sure that we spend less time traversing these lists on subsequent iterations\n\t\t// live triangle counts are updated as a byproduct of these adjustments\n\t\tfor (size_t k = 0; k < 3; ++k)\n\t\t{\n\t\t\tunsigned int index = indices[current_triangle * 3 + k];\n\n\t\t\tunsigned int* neighbors = &adjacency.data[0] + adjacency.offsets[index];\n\t\t\tsize_t neighbors_size = adjacency.counts[index];\n\n\t\t\tfor (size_t i = 0; i < neighbors_size; ++i)\n\t\t\t{\n\t\t\t\tunsigned int tri = neighbors[i];\n\n\t\t\t\tif (tri == current_triangle)\n\t\t\t\t{\n\t\t\t\t\tneighbors[i] = neighbors[neighbors_size - 1];\n\t\t\t\t\tadjacency.counts[index]--;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tunsigned int best_triangle = ~0u;\n\t\tfloat best_score = 0;\n\n\t\t// update cache positions, vertex scores and triangle scores, and find next best triangle\n\t\tfor (size_t i = 0; i < cache_write; ++i)\n\t\t{\n\t\t\tunsigned int index = cache[i];\n\n\t\t\t// no need to update scores if we are never going to use this vertex\n\t\t\tif (adjacency.counts[index] == 0)\n\t\t\t\tcontinue;\n\n\t\t\tint cache_position = i >= cache_size ? -1 : int(i);\n\n\t\t\t// update vertex score\n\t\t\tfloat score = vertexScore(table, cache_position, live_triangles[index]);\n\t\t\tfloat score_diff = score - vertex_scores[index];\n\n\t\t\tvertex_scores[index] = score;\n\n\t\t\t// update scores of vertex triangles\n\t\t\tconst unsigned int* neighbors_begin = &adjacency.data[0] + adjacency.offsets[index];\n\t\t\tconst unsigned int* neighbors_end = neighbors_begin + adjacency.counts[index];\n\n\t\t\tfor (const unsigned int* it = neighbors_begin; it != neighbors_end; ++it)\n\t\t\t{\n\t\t\t\tunsigned int tri = *it;\n\t\t\t\tassert(!emitted_flags[tri]);\n\n\t\t\t\tfloat tri_score = triangle_scores[tri] + score_diff;\n\t\t\t\tassert(tri_score > 0);\n\n\t\t\t\tbest_triangle = best_score < tri_score ? tri : best_triangle;\n\t\t\t\tbest_score = best_score < tri_score ? tri_score : best_score;\n\n\t\t\t\ttriangle_scores[tri] = tri_score;\n\t\t\t}\n\t\t}\n\n\t\t// step through input triangles in order if we hit a dead-end\n\t\tcurrent_triangle = best_triangle;\n\n\t\tif (current_triangle == ~0u)\n\t\t{\n\t\t\tcurrent_triangle = getNextTriangleDeadEnd(input_cursor, &emitted_flags[0], face_count);\n\t\t}\n\t}\n\n\tassert(input_cursor == face_count);\n\tassert(output_triangle == face_count);\n}\n\nvoid meshopt_optimizeVertexCache(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count)\n{\n\tmeshopt_optimizeVertexCacheTable(destination, indices, index_count, vertex_count, &meshopt::kVertexScoreTable);\n}\n\nvoid meshopt_optimizeVertexCacheStrip(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count)\n{\n\tmeshopt_optimizeVertexCacheTable(destination, indices, index_count, vertex_count, &meshopt::kVertexScoreTableStrip);\n}\n\nvoid meshopt_optimizeVertexCacheFifo(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int cache_size)\n{\n\tusing namespace meshopt;\n\n\tassert(index_count % 3 == 0);\n\tassert(cache_size >= 3);\n\n\tmeshopt_Allocator allocator;\n\n\t// guard for empty meshes\n\tif (index_count == 0 || vertex_count == 0)\n\t\treturn;\n\n\t// support in-place optimization\n\tif (destination == indices)\n\t{\n\t\tunsigned int* indices_copy = allocator.allocate<unsigned int>(index_count);\n\t\tmemcpy(indices_copy, indices, index_count * sizeof(unsigned int));\n\t\tindices = indices_copy;\n\t}\n\n\tsize_t face_count = index_count / 3;\n\n\t// build adjacency information\n\tTriangleAdjacency adjacency = {};\n\tbuildTriangleAdjacency(adjacency, indices, index_count, vertex_count, allocator);\n\n\t// live triangle counts\n\tunsigned int* live_triangles = allocator.allocate<unsigned int>(vertex_count);\n\tmemcpy(live_triangles, adjacency.counts, vertex_count * sizeof(unsigned int));\n\n\t// cache time stamps\n\tunsigned int* cache_timestamps = allocator.allocate<unsigned int>(vertex_count);\n\tmemset(cache_timestamps, 0, vertex_count * sizeof(unsigned int));\n\n\t// dead-end stack\n\tunsigned int* dead_end = allocator.allocate<unsigned int>(index_count);\n\tunsigned int dead_end_top = 0;\n\n\t// emitted flags\n\tunsigned char* emitted_flags = allocator.allocate<unsigned char>(face_count);\n\tmemset(emitted_flags, 0, face_count);\n\n\tunsigned int current_vertex = 0;\n\n\tunsigned int timestamp = cache_size + 1;\n\tunsigned int input_cursor = 1; // vertex to restart from in case of dead-end\n\n\tunsigned int output_triangle = 0;\n\n\twhile (current_vertex != ~0u)\n\t{\n\t\tconst unsigned int* next_candidates_begin = &dead_end[0] + dead_end_top;\n\n\t\t// emit all vertex neighbors\n\t\tconst unsigned int* neighbors_begin = &adjacency.data[0] + adjacency.offsets[current_vertex];\n\t\tconst unsigned int* neighbors_end = neighbors_begin + adjacency.counts[current_vertex];\n\n\t\tfor (const unsigned int* it = neighbors_begin; it != neighbors_end; ++it)\n\t\t{\n\t\t\tunsigned int triangle = *it;\n\n\t\t\tif (!emitted_flags[triangle])\n\t\t\t{\n\t\t\t\tunsigned int a = indices[triangle * 3 + 0], b = indices[triangle * 3 + 1], c = indices[triangle * 3 + 2];\n\n\t\t\t\t// output indices\n\t\t\t\tdestination[output_triangle * 3 + 0] = a;\n\t\t\t\tdestination[output_triangle * 3 + 1] = b;\n\t\t\t\tdestination[output_triangle * 3 + 2] = c;\n\t\t\t\toutput_triangle++;\n\n\t\t\t\t// update dead-end stack\n\t\t\t\tdead_end[dead_end_top + 0] = a;\n\t\t\t\tdead_end[dead_end_top + 1] = b;\n\t\t\t\tdead_end[dead_end_top + 2] = c;\n\t\t\t\tdead_end_top += 3;\n\n\t\t\t\t// update live triangle counts\n\t\t\t\tlive_triangles[a]--;\n\t\t\t\tlive_triangles[b]--;\n\t\t\t\tlive_triangles[c]--;\n\n\t\t\t\t// update cache info\n\t\t\t\t// if vertex is not in cache, put it in cache\n\t\t\t\tif (timestamp - cache_timestamps[a] > cache_size)\n\t\t\t\t\tcache_timestamps[a] = timestamp++;\n\n\t\t\t\tif (timestamp - cache_timestamps[b] > cache_size)\n\t\t\t\t\tcache_timestamps[b] = timestamp++;\n\n\t\t\t\tif (timestamp - cache_timestamps[c] > cache_size)\n\t\t\t\t\tcache_timestamps[c] = timestamp++;\n\n\t\t\t\t// update emitted flags\n\t\t\t\temitted_flags[triangle] = true;\n\t\t\t}\n\t\t}\n\n\t\t// next candidates are the ones we pushed to dead-end stack just now\n\t\tconst unsigned int* next_candidates_end = &dead_end[0] + dead_end_top;\n\n\t\t// get next vertex\n\t\tcurrent_vertex = getNextVertexNeighbor(next_candidates_begin, next_candidates_end, &live_triangles[0], &cache_timestamps[0], timestamp, cache_size);\n\n\t\tif (current_vertex == ~0u)\n\t\t{\n\t\t\tcurrent_vertex = getNextVertexDeadEnd(&dead_end[0], dead_end_top, input_cursor, &live_triangles[0], vertex_count);\n\t\t}\n\t}\n\n\tassert(output_triangle == face_count);\n}\n"
  },
  {
    "path": "src/vertexcodec.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <string.h>\n\n// The block below auto-detects SIMD ISA that can be used on the target platform\n#ifndef MESHOPTIMIZER_NO_SIMD\n\n// The SIMD implementation requires SSSE3, which can be enabled unconditionally through compiler settings\n#if defined(__AVX__) || defined(__SSSE3__)\n#define SIMD_SSE\n#endif\n\n// An experimental implementation using AVX512 instructions; it's only enabled when AVX512 is enabled through compiler settings\n#if defined(__AVX512VBMI2__) && defined(__AVX512VBMI__) && defined(__AVX512VL__) && defined(__POPCNT__)\n#undef SIMD_SSE\n#define SIMD_AVX\n#endif\n\n// MSVC supports compiling SSSE3 code regardless of compile options; we use a cpuid-based scalar fallback\n#if !defined(SIMD_SSE) && !defined(SIMD_AVX) && defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC)))\n#define SIMD_SSE\n#define SIMD_FALLBACK\n#endif\n\n// GCC 4.9+ and clang 3.8+ support targeting SIMD ISA from individual functions; we use a cpuid-based scalar fallback\n#if !defined(SIMD_SSE) && !defined(SIMD_AVX) && ((defined(__clang__) && __clang_major__ * 100 + __clang_minor__ >= 308) || (defined(__GNUC__) && __GNUC__ * 100 + __GNUC_MINOR__ >= 409)) && (defined(__i386__) || defined(__x86_64__))\n#define SIMD_SSE\n#define SIMD_FALLBACK\n#define SIMD_TARGET __attribute__((target(\"ssse3\")))\n#endif\n\n// GCC/clang define these when NEON support is available\n#if defined(__ARM_NEON__) || defined(__ARM_NEON)\n#define SIMD_NEON\n#endif\n\n// On MSVC, we assume that ARM builds always target NEON-capable devices\n#if !defined(SIMD_NEON) && defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC))\n#define SIMD_NEON\n#endif\n\n// When targeting Wasm SIMD we can't use runtime cpuid checks so we unconditionally enable SIMD\n#if defined(__wasm_simd128__)\n#define SIMD_WASM\n// Prevent compiling other variant when wasm simd compilation is active\n#undef SIMD_NEON\n#undef SIMD_SSE\n#undef SIMD_AVX\n#endif\n\n#ifndef SIMD_TARGET\n#define SIMD_TARGET\n#endif\n\n// When targeting AArch64/x64, optimize for latency to allow decoding of individual 16-byte groups to overlap\n// We don't do this for 32-bit systems because we need 64-bit math for this and this will hurt in-order CPUs\n#if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)\n#define SIMD_LATENCYOPT\n#endif\n\n// In switch dispatch, marking default case as unreachable allows to remove redundant bounds checks\n#if defined(__GNUC__)\n#define SIMD_UNREACHABLE() __builtin_unreachable()\n#elif defined(_MSC_VER)\n#define SIMD_UNREACHABLE() __assume(false)\n#else\n#define SIMD_UNREACHABLE() assert(!\"Unreachable\")\n#endif\n\n#endif // !MESHOPTIMIZER_NO_SIMD\n\n#ifdef SIMD_SSE\n#include <tmmintrin.h>\n#endif\n\n#if defined(SIMD_SSE) && defined(SIMD_FALLBACK)\n#ifdef _MSC_VER\n#include <intrin.h> // __cpuid\n#else\n#include <cpuid.h> // __cpuid\n#endif\n#endif\n\n#ifdef SIMD_AVX\n#include <immintrin.h>\n#endif\n\n#ifdef SIMD_NEON\n#if defined(_MSC_VER) && defined(_M_ARM64)\n#include <arm64_neon.h>\n#else\n#include <arm_neon.h>\n#endif\n#endif\n\n#ifdef SIMD_WASM\n#include <wasm_simd128.h>\n#endif\n\n#ifndef TRACE\n#define TRACE 0\n#endif\n\n#if TRACE\n#include <stdio.h>\n#endif\n\n#ifdef SIMD_WASM\n#define wasmx_splat_v32x4(v, i) wasm_i32x4_shuffle(v, v, i, i, i, i)\n#define wasmx_unpacklo_v8x16(a, b) wasm_i8x16_shuffle(a, b, 0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23)\n#define wasmx_unpackhi_v8x16(a, b) wasm_i8x16_shuffle(a, b, 8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31)\n#define wasmx_unpacklo_v16x8(a, b) wasm_i16x8_shuffle(a, b, 0, 8, 1, 9, 2, 10, 3, 11)\n#define wasmx_unpackhi_v16x8(a, b) wasm_i16x8_shuffle(a, b, 4, 12, 5, 13, 6, 14, 7, 15)\n#define wasmx_unpacklo_v64x2(a, b) wasm_i64x2_shuffle(a, b, 0, 2)\n#define wasmx_unpackhi_v64x2(a, b) wasm_i64x2_shuffle(a, b, 1, 3)\n#endif\n\nnamespace meshopt\n{\n\nconst unsigned char kVertexHeader = 0xa0;\n\nstatic int gEncodeVertexVersion = 1;\nconst int kDecodeVertexVersion = 1;\n\nconst size_t kVertexBlockSizeBytes = 8192;\nconst size_t kVertexBlockMaxSize = 256;\nconst size_t kByteGroupSize = 16;\nconst size_t kByteGroupDecodeLimit = 24;\nconst size_t kTailMinSizeV0 = 32;\nconst size_t kTailMinSizeV1 = 24;\n\nstatic const int kBitsV0[4] = {0, 2, 4, 8};\nstatic const int kBitsV1[5] = {0, 1, 2, 4, 8};\n\nconst int kEncodeDefaultLevel = 2;\n\nstatic size_t getVertexBlockSize(size_t vertex_size)\n{\n\t// make sure the entire block fits into the scratch buffer and is aligned to byte group size\n\t// note: the block size is implicitly part of the format, so we can't change it without breaking compatibility\n\tsize_t result = (kVertexBlockSizeBytes / vertex_size) & ~(kByteGroupSize - 1);\n\n\treturn (result < kVertexBlockMaxSize) ? result : kVertexBlockMaxSize;\n}\n\ninline unsigned int rotate(unsigned int v, int r)\n{\n\treturn (v << r) | (v >> ((32 - r) & 31));\n}\n\ntemplate <typename T>\ninline T zigzag(T v)\n{\n\treturn (0 - (v >> (sizeof(T) * 8 - 1))) ^ (v << 1);\n}\n\ntemplate <typename T>\ninline T unzigzag(T v)\n{\n\treturn (0 - (v & 1)) ^ (v >> 1);\n}\n\n#if TRACE\nstruct Stats\n{\n\tsize_t size;\n\tsize_t header;  // bytes for header\n\tsize_t bitg[9]; // bytes for bit groups\n\tsize_t bitc[8]; // bit consistency: how many bits are shared between all bytes in a group\n\tsize_t ctrl[4]; // number of control groups\n};\n\nstatic Stats* bytestats = NULL;\nstatic Stats vertexstats[256];\n#endif\n\nstatic bool encodeBytesGroupZero(const unsigned char* buffer)\n{\n\tassert(kByteGroupSize == sizeof(unsigned long long) * 2);\n\n\tunsigned long long v[2];\n\tmemcpy(v, buffer, sizeof(v));\n\n\treturn (v[0] | v[1]) == 0;\n}\n\nstatic size_t encodeBytesGroupMeasure(const unsigned char* buffer, int bits)\n{\n\tassert(bits >= 0 && bits <= 8);\n\n\tif (bits == 0)\n\t\treturn encodeBytesGroupZero(buffer) ? 0 : size_t(-1);\n\n\tif (bits == 8)\n\t\treturn kByteGroupSize;\n\n\tsize_t result = kByteGroupSize * bits / 8;\n\n\tunsigned char sentinel = (1 << bits) - 1;\n\n\tfor (size_t i = 0; i < kByteGroupSize; ++i)\n\t\tresult += buffer[i] >= sentinel;\n\n\treturn result;\n}\n\nstatic unsigned char* encodeBytesGroup(unsigned char* data, const unsigned char* buffer, int bits)\n{\n\tassert(bits >= 0 && bits <= 8);\n\tassert(kByteGroupSize % 8 == 0);\n\n\tif (bits == 0)\n\t\treturn data;\n\n\tif (bits == 8)\n\t{\n\t\tmemcpy(data, buffer, kByteGroupSize);\n\t\treturn data + kByteGroupSize;\n\t}\n\n\tsize_t byte_size = 8 / bits;\n\tassert(kByteGroupSize % byte_size == 0);\n\n\t// fixed portion: bits bits for each value\n\t// variable portion: full byte for each out-of-range value (using 1...1 as sentinel)\n\tunsigned char sentinel = (1 << bits) - 1;\n\n\tfor (size_t i = 0; i < kByteGroupSize; i += byte_size)\n\t{\n\t\tunsigned char byte = 0;\n\n\t\tfor (size_t k = 0; k < byte_size; ++k)\n\t\t{\n\t\t\tunsigned char enc = (buffer[i + k] >= sentinel) ? sentinel : buffer[i + k];\n\n\t\t\tbyte <<= bits;\n\t\t\tbyte |= enc;\n\t\t}\n\n\t\t// encode 1-bit groups in reverse bit order\n\t\t// this makes them faster to decode alongside other groups\n\t\tif (bits == 1)\n\t\t\tbyte = (unsigned char)(((byte * 0x80200802ull) & 0x0884422110ull) * 0x0101010101ull >> 32);\n\n\t\t*data++ = byte;\n\t}\n\n\tfor (size_t i = 0; i < kByteGroupSize; ++i)\n\t{\n\t\tunsigned char v = buffer[i];\n\n\t\t// branchless append of out-of-range values\n\t\t*data = v;\n\t\tdata += v >= sentinel;\n\t}\n\n\treturn data;\n}\n\nstatic unsigned char* encodeBytes(unsigned char* data, unsigned char* data_end, const unsigned char* buffer, size_t buffer_size, const int bits[4])\n{\n\tassert(buffer_size % kByteGroupSize == 0);\n\n\tunsigned char* header = data;\n\n\t// round number of groups to 4 to get number of header bytes\n\tsize_t header_size = (buffer_size / kByteGroupSize + 3) / 4;\n\n\tif (size_t(data_end - data) < header_size)\n\t\treturn NULL;\n\n\tdata += header_size;\n\n\tmemset(header, 0, header_size);\n\n\tint last_bits = -1;\n\n\tfor (size_t i = 0; i < buffer_size; i += kByteGroupSize)\n\t{\n\t\tif (size_t(data_end - data) < kByteGroupDecodeLimit)\n\t\t\treturn NULL;\n\n\t\tint best_bitk = 3;\n\t\tsize_t best_size = encodeBytesGroupMeasure(buffer + i, bits[best_bitk]);\n\n\t\tfor (int bitk = 0; bitk < 3; ++bitk)\n\t\t{\n\t\t\tsize_t size = encodeBytesGroupMeasure(buffer + i, bits[bitk]);\n\n\t\t\t// favor consistent bit selection across groups, but never replace literals\n\t\t\tif (size < best_size || (size == best_size && bits[bitk] == last_bits && bits[best_bitk] != 8))\n\t\t\t{\n\t\t\t\tbest_bitk = bitk;\n\t\t\t\tbest_size = size;\n\t\t\t}\n\t\t}\n\n\t\tsize_t header_offset = i / kByteGroupSize;\n\t\theader[header_offset / 4] |= best_bitk << ((header_offset % 4) * 2);\n\n\t\tint best_bits = bits[best_bitk];\n\t\tunsigned char* next = encodeBytesGroup(data, buffer + i, best_bits);\n\n\t\tassert(data + best_size == next);\n\t\tdata = next;\n\t\tlast_bits = best_bits;\n\n#if TRACE\n\t\tbytestats->bitg[best_bits] += best_size;\n#endif\n\t}\n\n#if TRACE\n\tbytestats->header += header_size;\n#endif\n\n\treturn data;\n}\n\ntemplate <typename T, bool Xor>\nstatic void encodeDeltas1(unsigned char* buffer, const unsigned char* vertex_data, size_t vertex_count, size_t vertex_size, const unsigned char last_vertex[256], size_t k, int rot)\n{\n\tsize_t k0 = k & ~(sizeof(T) - 1);\n\tint ks = (k & (sizeof(T) - 1)) * 8;\n\n\tT p = last_vertex[k0];\n\tfor (size_t j = 1; j < sizeof(T); ++j)\n\t\tp |= T(last_vertex[k0 + j]) << (j * 8);\n\n\tconst unsigned char* vertex = vertex_data + k0;\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t{\n\t\tT v = vertex[0];\n\t\tfor (size_t j = 1; j < sizeof(T); ++j)\n\t\t\tv |= vertex[j] << (j * 8);\n\n\t\tT d = Xor ? T(rotate(v ^ p, rot)) : zigzag(T(v - p));\n\n\t\tbuffer[i] = (unsigned char)(d >> ks);\n\t\tp = v;\n\t\tvertex += vertex_size;\n\t}\n}\n\nstatic void encodeDeltas(unsigned char* buffer, const unsigned char* vertex_data, size_t vertex_count, size_t vertex_size, const unsigned char last_vertex[256], size_t k, int channel)\n{\n\tswitch (channel & 3)\n\t{\n\tcase 0:\n\t\treturn encodeDeltas1<unsigned char, false>(buffer, vertex_data, vertex_count, vertex_size, last_vertex, k, 0);\n\tcase 1:\n\t\treturn encodeDeltas1<unsigned short, false>(buffer, vertex_data, vertex_count, vertex_size, last_vertex, k, 0);\n\tcase 2:\n\t\treturn encodeDeltas1<unsigned int, true>(buffer, vertex_data, vertex_count, vertex_size, last_vertex, k, channel >> 4);\n\tdefault:\n\t\tassert(!\"Unsupported channel encoding\"); // unreachable\n\t}\n}\n\nstatic int estimateBits(unsigned char v)\n{\n\treturn v <= 15 ? (v <= 3 ? (v == 0 ? 0 : 2) : 4) : 8;\n}\n\nstatic int estimateRotate(const unsigned char* vertex_data, size_t vertex_count, size_t vertex_size, size_t k, size_t group_size)\n{\n\tsize_t sizes[8] = {};\n\n\tconst unsigned char* vertex = vertex_data + k;\n\tunsigned int last = vertex[0] | (vertex[1] << 8) | (vertex[2] << 16) | (vertex[3] << 24);\n\n\tfor (size_t i = 0; i < vertex_count; i += group_size)\n\t{\n\t\tunsigned int bitg = 0;\n\n\t\t// calculate bit consistency mask for the group\n\t\tfor (size_t j = 0; j < group_size && i + j < vertex_count; ++j)\n\t\t{\n\t\t\tunsigned int v = vertex[0] | (vertex[1] << 8) | (vertex[2] << 16) | (vertex[3] << 24);\n\t\t\tunsigned int d = v ^ last;\n\n\t\t\tbitg |= d;\n\t\t\tlast = v;\n\t\t\tvertex += vertex_size;\n\t\t}\n\n#if TRACE\n\t\tfor (int j = 0; j < 32; ++j)\n\t\t\tvertexstats[k + (j / 8)].bitc[j % 8] += (i + group_size < vertex_count ? group_size : vertex_count - i) * (1 - ((bitg >> j) & 1));\n#endif\n\n\t\tfor (int j = 0; j < 8; ++j)\n\t\t{\n\t\t\tunsigned int bitr = rotate(bitg, j);\n\n\t\t\tsizes[j] += estimateBits((unsigned char)(bitr >> 0)) + estimateBits((unsigned char)(bitr >> 8));\n\t\t\tsizes[j] += estimateBits((unsigned char)(bitr >> 16)) + estimateBits((unsigned char)(bitr >> 24));\n\t\t}\n\t}\n\n\tint best_rot = 0;\n\tfor (int rot = 1; rot < 8; ++rot)\n\t\tbest_rot = (sizes[rot] < sizes[best_rot]) ? rot : best_rot;\n\n\treturn best_rot;\n}\n\nstatic int estimateChannel(const unsigned char* vertex_data, size_t vertex_count, size_t vertex_size, size_t k, size_t vertex_block_size, size_t block_skip, int max_channel, int xor_rot)\n{\n\tunsigned char block[kVertexBlockMaxSize];\n\tassert(vertex_block_size <= kVertexBlockMaxSize);\n\n\tunsigned char last_vertex[256] = {};\n\n\tsize_t sizes[3] = {};\n\tassert(max_channel <= 3);\n\n\tfor (size_t i = 0; i < vertex_count; i += vertex_block_size * block_skip)\n\t{\n\t\tsize_t block_size = i + vertex_block_size < vertex_count ? vertex_block_size : vertex_count - i;\n\t\tsize_t block_size_aligned = (block_size + kByteGroupSize - 1) & ~(kByteGroupSize - 1);\n\n\t\tmemcpy(last_vertex, vertex_data + (i == 0 ? 0 : i - 1) * vertex_size, vertex_size);\n\n\t\t// we sometimes encode elements we didn't fill when rounding to kByteGroupSize\n\t\tif (block_size < block_size_aligned)\n\t\t\tmemset(block + block_size, 0, block_size_aligned - block_size);\n\n\t\tfor (int channel = 0; channel < max_channel; ++channel)\n\t\t\tfor (size_t j = 0; j < 4; ++j)\n\t\t\t{\n\t\t\t\tencodeDeltas(block, vertex_data + i * vertex_size, block_size, vertex_size, last_vertex, k + j, channel | (xor_rot << 4));\n\n\t\t\t\tfor (size_t ig = 0; ig < block_size; ig += kByteGroupSize)\n\t\t\t\t{\n\t\t\t\t\t// to maximize encoding performance we only evaluate 1/2/4/8 bit groups\n\t\t\t\t\tsize_t size1 = encodeBytesGroupMeasure(block + ig, 1);\n\t\t\t\t\tsize_t size2 = encodeBytesGroupMeasure(block + ig, 2);\n\t\t\t\t\tsize_t size4 = encodeBytesGroupMeasure(block + ig, 4);\n\t\t\t\t\tsize_t size8 = encodeBytesGroupMeasure(block + ig, 8);\n\n\t\t\t\t\tsize_t best_size = size1 < size2 ? size1 : size2;\n\t\t\t\t\tbest_size = best_size < size4 ? best_size : size4;\n\t\t\t\t\tbest_size = best_size < size8 ? best_size : size8;\n\n\t\t\t\t\tsizes[channel] += best_size;\n\t\t\t\t}\n\t\t\t}\n\t}\n\n\tint best_channel = 0;\n\tfor (int channel = 1; channel < max_channel; ++channel)\n\t\tbest_channel = (sizes[channel] < sizes[best_channel]) ? channel : best_channel;\n\n\treturn best_channel == 2 ? best_channel | (xor_rot << 4) : best_channel;\n}\n\nstatic bool estimateControlZero(const unsigned char* buffer, size_t vertex_count_aligned)\n{\n\tfor (size_t i = 0; i < vertex_count_aligned; i += kByteGroupSize)\n\t\tif (!encodeBytesGroupZero(buffer + i))\n\t\t\treturn false;\n\n\treturn true;\n}\n\nstatic int estimateControl(const unsigned char* buffer, size_t vertex_count, size_t vertex_count_aligned, int level)\n{\n\tif (estimateControlZero(buffer, vertex_count_aligned))\n\t\treturn 2; // zero encoding\n\n\tif (level == 0)\n\t\treturn 1; // 1248 encoding in level 0 for encoding speed\n\n\t// round number of groups to 4 to get number of header bytes\n\tsize_t header_size = (vertex_count_aligned / kByteGroupSize + 3) / 4;\n\n\tsize_t est_bytes0 = header_size, est_bytes1 = header_size;\n\n\tfor (size_t i = 0; i < vertex_count_aligned; i += kByteGroupSize)\n\t{\n\t\t// assumes kBitsV1[] = {0, 1, 2, 4, 8} for performance\n\t\tsize_t size0 = encodeBytesGroupMeasure(buffer + i, 0);\n\t\tsize_t size1 = encodeBytesGroupMeasure(buffer + i, 1);\n\t\tsize_t size2 = encodeBytesGroupMeasure(buffer + i, 2);\n\t\tsize_t size4 = encodeBytesGroupMeasure(buffer + i, 4);\n\t\tsize_t size8 = encodeBytesGroupMeasure(buffer + i, 8);\n\n\t\t// both control modes have access to 1/2/4 bit encoding\n\t\tsize_t size12 = size1 < size2 ? size1 : size2;\n\t\tsize_t size124 = size12 < size4 ? size12 : size4;\n\n\t\t// each control mode has access to 0/8 bit encoding respectively\n\t\test_bytes0 += size124 < size0 ? size124 : size0;\n\t\test_bytes1 += size124 < size8 ? size124 : size8;\n\t}\n\n\t// pick shortest control entry but prefer literal encoding\n\tif (est_bytes0 < vertex_count || est_bytes1 < vertex_count)\n\t\treturn est_bytes0 < est_bytes1 ? 0 : 1;\n\telse\n\t\treturn 3; // literal encoding\n}\n\nstatic unsigned char* encodeVertexBlock(unsigned char* data, unsigned char* data_end, const unsigned char* vertex_data, size_t vertex_count, size_t vertex_size, unsigned char last_vertex[256], const unsigned char* channels, int version, int level)\n{\n\tassert(vertex_count > 0 && vertex_count <= kVertexBlockMaxSize);\n\tassert(vertex_size % 4 == 0);\n\n\tunsigned char buffer[kVertexBlockMaxSize];\n\tassert(sizeof(buffer) % kByteGroupSize == 0);\n\n\tsize_t vertex_count_aligned = (vertex_count + kByteGroupSize - 1) & ~(kByteGroupSize - 1);\n\n\t// we sometimes encode elements we didn't fill when rounding to kByteGroupSize\n\tmemset(buffer, 0, sizeof(buffer));\n\n\tsize_t control_size = version == 0 ? 0 : vertex_size / 4;\n\tif (size_t(data_end - data) < control_size)\n\t\treturn NULL;\n\n\tunsigned char* control = data;\n\tdata += control_size;\n\n\tmemset(control, 0, control_size);\n\n\tfor (size_t k = 0; k < vertex_size; ++k)\n\t{\n\t\tencodeDeltas(buffer, vertex_data, vertex_count, vertex_size, last_vertex, k, version == 0 ? 0 : channels[k / 4]);\n\n#if TRACE\n\t\tconst unsigned char* olddata = data;\n\t\tbytestats = &vertexstats[k];\n#endif\n\n\t\tint ctrl = 0;\n\n\t\tif (version != 0)\n\t\t{\n\t\t\tctrl = estimateControl(buffer, vertex_count, vertex_count_aligned, level);\n\n\t\t\tassert(unsigned(ctrl) < 4);\n\t\t\tcontrol[k / 4] |= ctrl << ((k % 4) * 2);\n\n#if TRACE\n\t\t\tvertexstats[k].ctrl[ctrl]++;\n#endif\n\t\t}\n\n\t\tif (ctrl == 3)\n\t\t{\n\t\t\t// literal encoding\n\t\t\tif (size_t(data_end - data) < vertex_count)\n\t\t\t\treturn NULL;\n\n\t\t\tmemcpy(data, buffer, vertex_count);\n\t\t\tdata += vertex_count;\n\t\t}\n\t\telse if (ctrl != 2) // non-zero encoding\n\t\t{\n\t\t\tdata = encodeBytes(data, data_end, buffer, vertex_count_aligned, version == 0 ? kBitsV0 : kBitsV1 + ctrl);\n\t\t\tif (!data)\n\t\t\t\treturn NULL;\n\t\t}\n\n#if TRACE\n\t\tbytestats = NULL;\n\t\tvertexstats[k].size += data - olddata;\n#endif\n\t}\n\n\tmemcpy(last_vertex, &vertex_data[vertex_size * (vertex_count - 1)], vertex_size);\n\n\treturn data;\n}\n\n#if defined(SIMD_FALLBACK) || (!defined(SIMD_SSE) && !defined(SIMD_NEON) && !defined(SIMD_AVX) && !defined(SIMD_WASM))\nstatic const unsigned char* decodeBytesGroup(const unsigned char* data, unsigned char* buffer, int bits)\n{\n#define READ() byte = *data++\n#define NEXT(bits) enc = byte >> (8 - bits), byte <<= bits, encv = *data_var, *buffer++ = (enc == (1 << bits) - 1) ? encv : enc, data_var += (enc == (1 << bits) - 1)\n\n\tunsigned char byte, enc, encv;\n\tconst unsigned char* data_var;\n\n\tswitch (bits)\n\t{\n\tcase 0:\n\t\tmemset(buffer, 0, kByteGroupSize);\n\t\treturn data;\n\tcase 1:\n\t\tdata_var = data + 2;\n\n\t\t// 2 groups with 8 1-bit values in each byte (reversed from the order in other groups)\n\t\tREAD();\n\t\tbyte = (unsigned char)(((byte * 0x80200802ull) & 0x0884422110ull) * 0x0101010101ull >> 32);\n\t\tNEXT(1), NEXT(1), NEXT(1), NEXT(1), NEXT(1), NEXT(1), NEXT(1), NEXT(1);\n\t\tREAD();\n\t\tbyte = (unsigned char)(((byte * 0x80200802ull) & 0x0884422110ull) * 0x0101010101ull >> 32);\n\t\tNEXT(1), NEXT(1), NEXT(1), NEXT(1), NEXT(1), NEXT(1), NEXT(1), NEXT(1);\n\n\t\treturn data_var;\n\tcase 2:\n\t\tdata_var = data + 4;\n\n\t\t// 4 groups with 4 2-bit values in each byte\n\t\tREAD(), NEXT(2), NEXT(2), NEXT(2), NEXT(2);\n\t\tREAD(), NEXT(2), NEXT(2), NEXT(2), NEXT(2);\n\t\tREAD(), NEXT(2), NEXT(2), NEXT(2), NEXT(2);\n\t\tREAD(), NEXT(2), NEXT(2), NEXT(2), NEXT(2);\n\n\t\treturn data_var;\n\tcase 4:\n\t\tdata_var = data + 8;\n\n\t\t// 8 groups with 2 4-bit values in each byte\n\t\tREAD(), NEXT(4), NEXT(4);\n\t\tREAD(), NEXT(4), NEXT(4);\n\t\tREAD(), NEXT(4), NEXT(4);\n\t\tREAD(), NEXT(4), NEXT(4);\n\t\tREAD(), NEXT(4), NEXT(4);\n\t\tREAD(), NEXT(4), NEXT(4);\n\t\tREAD(), NEXT(4), NEXT(4);\n\t\tREAD(), NEXT(4), NEXT(4);\n\n\t\treturn data_var;\n\tcase 8:\n\t\tmemcpy(buffer, data, kByteGroupSize);\n\t\treturn data + kByteGroupSize;\n\tdefault:\n\t\tassert(!\"Unexpected bit length\"); // unreachable\n\t\treturn data;\n\t}\n\n#undef READ\n#undef NEXT\n}\n\nstatic const unsigned char* decodeBytes(const unsigned char* data, const unsigned char* data_end, unsigned char* buffer, size_t buffer_size, const int* bits)\n{\n\tassert(buffer_size % kByteGroupSize == 0);\n\n\t// round number of groups to 4 to get number of header bytes\n\tsize_t header_size = (buffer_size / kByteGroupSize + 3) / 4;\n\tif (size_t(data_end - data) < header_size)\n\t\treturn NULL;\n\n\tconst unsigned char* header = data;\n\tdata += header_size;\n\n\tfor (size_t i = 0; i < buffer_size; i += kByteGroupSize)\n\t{\n\t\tif (size_t(data_end - data) < kByteGroupDecodeLimit)\n\t\t\treturn NULL;\n\n\t\tsize_t header_offset = i / kByteGroupSize;\n\t\tint bitsk = (header[header_offset / 4] >> ((header_offset % 4) * 2)) & 3;\n\n\t\tdata = decodeBytesGroup(data, buffer + i, bits[bitsk]);\n\t}\n\n\treturn data;\n}\n\ntemplate <typename T, bool Xor>\nstatic void decodeDeltas1(const unsigned char* buffer, unsigned char* transposed, size_t vertex_count, size_t vertex_size, const unsigned char* last_vertex, int rot)\n{\n\tfor (size_t k = 0; k < 4; k += sizeof(T))\n\t{\n\t\tsize_t vertex_offset = k;\n\n\t\tT p = last_vertex[0];\n\t\tfor (size_t j = 1; j < sizeof(T); ++j)\n\t\t\tp |= last_vertex[j] << (8 * j);\n\n\t\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\t{\n\t\t\tT v = buffer[i];\n\t\t\tfor (size_t j = 1; j < sizeof(T); ++j)\n\t\t\t\tv |= buffer[i + vertex_count * j] << (8 * j);\n\n\t\t\tv = Xor ? T(rotate(v, rot)) ^ p : unzigzag(v) + p;\n\n\t\t\tfor (size_t j = 0; j < sizeof(T); ++j)\n\t\t\t\ttransposed[vertex_offset + j] = (unsigned char)(v >> (j * 8));\n\n\t\t\tp = v;\n\n\t\t\tvertex_offset += vertex_size;\n\t\t}\n\n\t\tbuffer += vertex_count * sizeof(T);\n\t\tlast_vertex += sizeof(T);\n\t}\n}\n\nstatic const unsigned char* decodeVertexBlock(const unsigned char* data, const unsigned char* data_end, unsigned char* vertex_data, size_t vertex_count, size_t vertex_size, unsigned char last_vertex[256], const unsigned char* channels, int version)\n{\n\tassert(vertex_count > 0 && vertex_count <= kVertexBlockMaxSize);\n\n\tunsigned char buffer[kVertexBlockMaxSize * 4];\n\tunsigned char transposed[kVertexBlockSizeBytes];\n\n\tsize_t vertex_count_aligned = (vertex_count + kByteGroupSize - 1) & ~(kByteGroupSize - 1);\n\tassert(vertex_count <= vertex_count_aligned);\n\n\tsize_t control_size = version == 0 ? 0 : vertex_size / 4;\n\tif (size_t(data_end - data) < control_size)\n\t\treturn NULL;\n\n\tconst unsigned char* control = data;\n\tdata += control_size;\n\n\tfor (size_t k = 0; k < vertex_size; k += 4)\n\t{\n\t\tunsigned char ctrl_byte = version == 0 ? 0 : control[k / 4];\n\n\t\tfor (size_t j = 0; j < 4; ++j)\n\t\t{\n\t\t\tint ctrl = (ctrl_byte >> (j * 2)) & 3;\n\n\t\t\tif (ctrl == 3)\n\t\t\t{\n\t\t\t\t// literal encoding\n\t\t\t\tif (size_t(data_end - data) < vertex_count)\n\t\t\t\t\treturn NULL;\n\n\t\t\t\tmemcpy(buffer + j * vertex_count, data, vertex_count);\n\t\t\t\tdata += vertex_count;\n\t\t\t}\n\t\t\telse if (ctrl == 2)\n\t\t\t{\n\t\t\t\t// zero encoding\n\t\t\t\tmemset(buffer + j * vertex_count, 0, vertex_count);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tdata = decodeBytes(data, data_end, buffer + j * vertex_count, vertex_count_aligned, version == 0 ? kBitsV0 : kBitsV1 + ctrl);\n\t\t\t\tif (!data)\n\t\t\t\t\treturn NULL;\n\t\t\t}\n\t\t}\n\n\t\tint channel = version == 0 ? 0 : channels[k / 4];\n\n\t\tswitch (channel & 3)\n\t\t{\n\t\tcase 0:\n\t\t\tdecodeDeltas1<unsigned char, false>(buffer, transposed + k, vertex_count, vertex_size, last_vertex + k, 0);\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tdecodeDeltas1<unsigned short, false>(buffer, transposed + k, vertex_count, vertex_size, last_vertex + k, 0);\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tdecodeDeltas1<unsigned int, true>(buffer, transposed + k, vertex_count, vertex_size, last_vertex + k, (32 - (channel >> 4)) & 31);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn NULL; // invalid channel type\n\t\t}\n\t}\n\n\tmemcpy(vertex_data, transposed, vertex_count * vertex_size);\n\n\tmemcpy(last_vertex, &transposed[vertex_size * (vertex_count - 1)], vertex_size);\n\n\treturn data;\n}\n#endif\n\n#if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM)\nstatic unsigned char kDecodeBytesGroupShuffle[256][8];\nstatic unsigned char kDecodeBytesGroupCount[256];\n\n#ifdef __wasm__\n__attribute__((cold)) // this saves 500 bytes in the output binary - we don't need to vectorize this loop!\n#endif\nstatic bool\ndecodeBytesGroupBuildTables()\n{\n\tfor (int mask = 0; mask < 256; ++mask)\n\t{\n\t\tunsigned char shuffle[8];\n\t\tunsigned char count = 0;\n\n\t\tfor (int i = 0; i < 8; ++i)\n\t\t{\n\t\t\tint maski = (mask >> i) & 1;\n\t\t\tshuffle[i] = maski ? count : 0x80;\n\t\t\tcount += (unsigned char)(maski);\n\t\t}\n\n\t\tmemcpy(kDecodeBytesGroupShuffle[mask], shuffle, 8);\n\t\tkDecodeBytesGroupCount[mask] = count;\n\t}\n\n\treturn true;\n}\n\nstatic bool gDecodeBytesGroupInitialized = decodeBytesGroupBuildTables();\n#endif\n\n#ifdef SIMD_SSE\nSIMD_TARGET\ninline __m128i decodeShuffleMask(unsigned char mask0, unsigned char mask1)\n{\n\t__m128i sm0 = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(&kDecodeBytesGroupShuffle[mask0]));\n\t__m128i sm1 = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(&kDecodeBytesGroupShuffle[mask1]));\n\t__m128i sm1off = _mm_set1_epi8(kDecodeBytesGroupCount[mask0]);\n\n\t__m128i sm1r = _mm_add_epi8(sm1, sm1off);\n\n\treturn _mm_unpacklo_epi64(sm0, sm1r);\n}\n\n#ifdef __GNUC__\ntypedef int __attribute__((aligned(1))) unaligned_int;\n#else\ntypedef int unaligned_int;\n#endif\n\nSIMD_TARGET\ninline const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsigned char* buffer, int hbits)\n{\n\tswitch (hbits)\n\t{\n\tcase 0:\n\tcase 4:\n\t{\n\t\t__m128i result = _mm_setzero_si128();\n\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(buffer), result);\n\n\t\treturn data;\n\t}\n\n\tcase 1:\n\tcase 6:\n\t{\n#ifdef SIMD_LATENCYOPT\n\t\tunsigned int data32;\n\t\tmemcpy(&data32, data, 4);\n\t\tdata32 &= data32 >> 1;\n\n\t\t// arrange bits such that low bits of nibbles of data64 contain all 2-bit elements of data32\n\t\tunsigned long long data64 = ((unsigned long long)data32 << 30) | (data32 & 0x3fffffff);\n\n\t\t// adds all 1-bit nibbles together; the sum fits in 4 bits because datacnt=16 would have used mode 3\n\t\tint datacnt = int(((data64 & 0x1111111111111111ull) * 0x1111111111111111ull) >> 60);\n#endif\n\n\t\t__m128i sel2 = _mm_cvtsi32_si128(*reinterpret_cast<const unaligned_int*>(data));\n\t\t__m128i rest = _mm_loadu_si128(reinterpret_cast<const __m128i*>(data + 4));\n\n\t\t__m128i sel22 = _mm_unpacklo_epi8(_mm_srli_epi16(sel2, 4), sel2);\n\t\t__m128i sel2222 = _mm_unpacklo_epi8(_mm_srli_epi16(sel22, 2), sel22);\n\t\t__m128i sel = _mm_and_si128(sel2222, _mm_set1_epi8(3));\n\n\t\t__m128i mask = _mm_cmpeq_epi8(sel, _mm_set1_epi8(3));\n\t\tint mask16 = _mm_movemask_epi8(mask);\n\t\tunsigned char mask0 = (unsigned char)(mask16 & 255);\n\t\tunsigned char mask1 = (unsigned char)(mask16 >> 8);\n\n\t\t__m128i shuf = decodeShuffleMask(mask0, mask1);\n\t\t__m128i result = _mm_or_si128(_mm_shuffle_epi8(rest, shuf), _mm_andnot_si128(mask, sel));\n\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(buffer), result);\n\n#ifdef SIMD_LATENCYOPT\n\t\treturn data + 4 + datacnt;\n#else\n\t\treturn data + 4 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];\n#endif\n\t}\n\n\tcase 2:\n\tcase 7:\n\t{\n#ifdef SIMD_LATENCYOPT\n\t\tunsigned long long data64;\n\t\tmemcpy(&data64, data, 8);\n\t\tdata64 &= data64 >> 1;\n\t\tdata64 &= data64 >> 2;\n\n\t\t// adds all 1-bit nibbles together; the sum fits in 4 bits because datacnt=16 would have used mode 3\n\t\tint datacnt = int(((data64 & 0x1111111111111111ull) * 0x1111111111111111ull) >> 60);\n#endif\n\n\t\t__m128i sel4 = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(data));\n\t\t__m128i rest = _mm_loadu_si128(reinterpret_cast<const __m128i*>(data + 8));\n\n\t\t__m128i sel44 = _mm_unpacklo_epi8(_mm_srli_epi16(sel4, 4), sel4);\n\t\t__m128i sel = _mm_and_si128(sel44, _mm_set1_epi8(15));\n\n\t\t__m128i mask = _mm_cmpeq_epi8(sel, _mm_set1_epi8(15));\n\t\tint mask16 = _mm_movemask_epi8(mask);\n\t\tunsigned char mask0 = (unsigned char)(mask16 & 255);\n\t\tunsigned char mask1 = (unsigned char)(mask16 >> 8);\n\n\t\t__m128i shuf = decodeShuffleMask(mask0, mask1);\n\t\t__m128i result = _mm_or_si128(_mm_shuffle_epi8(rest, shuf), _mm_andnot_si128(mask, sel));\n\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(buffer), result);\n\n#ifdef SIMD_LATENCYOPT\n\t\treturn data + 8 + datacnt;\n#else\n\t\treturn data + 8 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];\n#endif\n\t}\n\n\tcase 3:\n\tcase 8:\n\t{\n\t\t__m128i result = _mm_loadu_si128(reinterpret_cast<const __m128i*>(data));\n\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(buffer), result);\n\n\t\treturn data + 16;\n\t}\n\n\tcase 5:\n\t{\n\t\t__m128i rest = _mm_loadu_si128(reinterpret_cast<const __m128i*>(data + 2));\n\n\t\tunsigned char mask0 = data[0];\n\t\tunsigned char mask1 = data[1];\n\n\t\t__m128i shuf = decodeShuffleMask(mask0, mask1);\n\t\t__m128i result = _mm_shuffle_epi8(rest, shuf);\n\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(buffer), result);\n\n\t\treturn data + 2 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];\n\t}\n\n\tdefault:\n\t\tSIMD_UNREACHABLE(); // unreachable\n\t}\n}\n#endif\n\n#ifdef SIMD_AVX\nstatic const __m128i kDecodeBytesGroupConfig[8][2] = {\n    {_mm_setzero_si128(), _mm_setzero_si128()},\n    {_mm_set1_epi8(3), _mm_setr_epi8(6, 4, 2, 0, 14, 12, 10, 8, 22, 20, 18, 16, 30, 28, 26, 24)},\n    {_mm_set1_epi8(15), _mm_setr_epi8(4, 0, 12, 8, 20, 16, 28, 24, 36, 32, 44, 40, 52, 48, 60, 56)},\n    {_mm_setzero_si128(), _mm_setzero_si128()},\n    {_mm_setzero_si128(), _mm_setzero_si128()},\n    {_mm_set1_epi8(1), _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)},\n    {_mm_set1_epi8(3), _mm_setr_epi8(6, 4, 2, 0, 14, 12, 10, 8, 22, 20, 18, 16, 30, 28, 26, 24)},\n    {_mm_set1_epi8(15), _mm_setr_epi8(4, 0, 12, 8, 20, 16, 28, 24, 36, 32, 44, 40, 52, 48, 60, 56)},\n};\n\nSIMD_TARGET\ninline const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsigned char* buffer, int hbits)\n{\n\tswitch (hbits)\n\t{\n\tcase 0:\n\tcase 4:\n\t{\n\t\t__m128i result = _mm_setzero_si128();\n\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(buffer), result);\n\n\t\treturn data;\n\t}\n\n\tcase 5: // 1-bit\n\tcase 1: // 2-bit\n\tcase 6:\n\tcase 2: // 4-bit\n\tcase 7:\n\t{\n\t\tconst unsigned char* skip = data + (2 << (hbits < 3 ? hbits : hbits - 5));\n\n\t\t__m128i selb = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(data));\n\t\t__m128i rest = _mm_loadu_si128(reinterpret_cast<const __m128i*>(skip));\n\n\t\t__m128i sent = kDecodeBytesGroupConfig[hbits][0];\n\t\t__m128i ctrl = kDecodeBytesGroupConfig[hbits][1];\n\n\t\t__m128i selw = _mm_shuffle_epi32(selb, 0x44);\n\t\t__m128i sel = _mm_and_si128(sent, _mm_multishift_epi64_epi8(ctrl, selw));\n\t\t__mmask16 mask16 = _mm_cmp_epi8_mask(sel, sent, _MM_CMPINT_EQ);\n\n\t\t__m128i result = _mm_mask_expand_epi8(sel, mask16, rest);\n\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(buffer), result);\n\n\t\treturn skip + _mm_popcnt_u32(mask16);\n\t}\n\n\tcase 3:\n\tcase 8:\n\t{\n\t\t__m128i result = _mm_loadu_si128(reinterpret_cast<const __m128i*>(data));\n\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(buffer), result);\n\n\t\treturn data + 16;\n\t}\n\n\tdefault:\n\t\tSIMD_UNREACHABLE(); // unreachable\n\t}\n}\n#endif\n\n#ifdef SIMD_NEON\nSIMD_TARGET\ninline uint8x16_t shuffleBytes(unsigned char mask0, unsigned char mask1, uint8x8_t rest0, uint8x8_t rest1)\n{\n\tuint8x8_t sm0 = vld1_u8(kDecodeBytesGroupShuffle[mask0]);\n\tuint8x8_t sm1 = vld1_u8(kDecodeBytesGroupShuffle[mask1]);\n\n\tuint8x8_t r0 = vtbl1_u8(rest0, sm0);\n\tuint8x8_t r1 = vtbl1_u8(rest1, sm1);\n\n\treturn vcombine_u8(r0, r1);\n}\n\nSIMD_TARGET\ninline void neonMoveMask(uint8x16_t mask, unsigned char& mask0, unsigned char& mask1)\n{\n\t// magic constant found using z3 SMT assuming mask has 8 groups of 0xff or 0x00\n\tconst uint64_t magic = 0x000103070f1f3f80ull;\n\n\tuint64x2_t mask2 = vreinterpretq_u64_u8(mask);\n\n\tmask0 = uint8_t((vgetq_lane_u64(mask2, 0) * magic) >> 56);\n\tmask1 = uint8_t((vgetq_lane_u64(mask2, 1) * magic) >> 56);\n}\n\nSIMD_TARGET\ninline const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsigned char* buffer, int hbits)\n{\n\tswitch (hbits)\n\t{\n\tcase 0:\n\tcase 4:\n\t{\n\t\tuint8x16_t result = vdupq_n_u8(0);\n\n\t\tvst1q_u8(buffer, result);\n\n\t\treturn data;\n\t}\n\n\tcase 1:\n\tcase 6:\n\t{\n#ifdef SIMD_LATENCYOPT\n\t\tunsigned int data32;\n\t\tmemcpy(&data32, data, 4);\n\t\tdata32 &= data32 >> 1;\n\n\t\t// arrange bits such that low bits of nibbles of data64 contain all 2-bit elements of data32\n\t\tunsigned long long data64 = ((unsigned long long)data32 << 30) | (data32 & 0x3fffffff);\n\n\t\t// adds all 1-bit nibbles together; the sum fits in 4 bits because datacnt=16 would have used mode 3\n\t\tint datacnt = int(((data64 & 0x1111111111111111ull) * 0x1111111111111111ull) >> 60);\n#endif\n\n\t\tuint8x8_t sel2 = vld1_u8(data);\n\t\tuint8x8_t sel22 = vzip_u8(vshr_n_u8(sel2, 4), sel2).val[0];\n\t\tuint8x8x2_t sel2222 = vzip_u8(vshr_n_u8(sel22, 2), sel22);\n\t\tuint8x16_t sel = vandq_u8(vcombine_u8(sel2222.val[0], sel2222.val[1]), vdupq_n_u8(3));\n\n\t\tuint8x16_t mask = vceqq_u8(sel, vdupq_n_u8(3));\n\t\tunsigned char mask0, mask1;\n\t\tneonMoveMask(mask, mask0, mask1);\n\n\t\tuint8x8_t rest0 = vld1_u8(data + 4);\n\t\tuint8x8_t rest1 = vld1_u8(data + 4 + kDecodeBytesGroupCount[mask0]);\n\n\t\tuint8x16_t result = vbslq_u8(mask, shuffleBytes(mask0, mask1, rest0, rest1), sel);\n\n\t\tvst1q_u8(buffer, result);\n\n#ifdef SIMD_LATENCYOPT\n\t\treturn data + 4 + datacnt;\n#else\n\t\treturn data + 4 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];\n#endif\n\t}\n\n\tcase 2:\n\tcase 7:\n\t{\n#ifdef SIMD_LATENCYOPT\n\t\tunsigned long long data64;\n\t\tmemcpy(&data64, data, 8);\n\t\tdata64 &= data64 >> 1;\n\t\tdata64 &= data64 >> 2;\n\n\t\t// adds all 1-bit nibbles together; the sum fits in 4 bits because datacnt=16 would have used mode 3\n\t\tint datacnt = int(((data64 & 0x1111111111111111ull) * 0x1111111111111111ull) >> 60);\n#endif\n\n\t\tuint8x8_t sel4 = vld1_u8(data);\n\t\tuint8x8x2_t sel44 = vzip_u8(vshr_n_u8(sel4, 4), vand_u8(sel4, vdup_n_u8(15)));\n\t\tuint8x16_t sel = vcombine_u8(sel44.val[0], sel44.val[1]);\n\n\t\tuint8x16_t mask = vceqq_u8(sel, vdupq_n_u8(15));\n\t\tunsigned char mask0, mask1;\n\t\tneonMoveMask(mask, mask0, mask1);\n\n\t\tuint8x8_t rest0 = vld1_u8(data + 8);\n\t\tuint8x8_t rest1 = vld1_u8(data + 8 + kDecodeBytesGroupCount[mask0]);\n\n\t\tuint8x16_t result = vbslq_u8(mask, shuffleBytes(mask0, mask1, rest0, rest1), sel);\n\n\t\tvst1q_u8(buffer, result);\n\n#ifdef SIMD_LATENCYOPT\n\t\treturn data + 8 + datacnt;\n#else\n\t\treturn data + 8 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];\n#endif\n\t}\n\n\tcase 3:\n\tcase 8:\n\t{\n\t\tuint8x16_t result = vld1q_u8(data);\n\n\t\tvst1q_u8(buffer, result);\n\n\t\treturn data + 16;\n\t}\n\n\tcase 5:\n\t{\n\t\tunsigned char mask0 = data[0];\n\t\tunsigned char mask1 = data[1];\n\n\t\tuint8x8_t rest0 = vld1_u8(data + 2);\n\t\tuint8x8_t rest1 = vld1_u8(data + 2 + kDecodeBytesGroupCount[mask0]);\n\n\t\tuint8x16_t result = shuffleBytes(mask0, mask1, rest0, rest1);\n\n\t\tvst1q_u8(buffer, result);\n\n\t\treturn data + 2 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];\n\t}\n\n\tdefault:\n\t\tSIMD_UNREACHABLE(); // unreachable\n\t}\n}\n#endif\n\n#ifdef SIMD_WASM\nSIMD_TARGET\ninline v128_t decodeShuffleMask(unsigned char mask0, unsigned char mask1)\n{\n\tv128_t sm0 = wasm_v128_load(&kDecodeBytesGroupShuffle[mask0]);\n\tv128_t sm1 = wasm_v128_load(&kDecodeBytesGroupShuffle[mask1]);\n\n\tv128_t sm1off = wasm_v128_load8_splat(&kDecodeBytesGroupCount[mask0]);\n\tv128_t sm1r = wasm_i8x16_add(sm1, sm1off);\n\n\treturn wasmx_unpacklo_v64x2(sm0, sm1r);\n}\n\nSIMD_TARGET\ninline void wasmMoveMask(v128_t mask, unsigned char& mask0, unsigned char& mask1)\n{\n\t// magic constant found using z3 SMT assuming mask has 8 groups of 0xff or 0x00\n\tconst uint64_t magic = 0x000103070f1f3f80ull;\n\n\tmask0 = uint8_t((wasm_i64x2_extract_lane(mask, 0) * magic) >> 56);\n\tmask1 = uint8_t((wasm_i64x2_extract_lane(mask, 1) * magic) >> 56);\n}\n\nSIMD_TARGET\ninline const unsigned char* decodeBytesGroupSimd(const unsigned char* data, unsigned char* buffer, int hbits)\n{\n\tswitch (hbits)\n\t{\n\tcase 0:\n\tcase 4:\n\t{\n\t\tv128_t result = wasm_i8x16_splat(0);\n\n\t\twasm_v128_store(buffer, result);\n\n\t\treturn data;\n\t}\n\n\tcase 1:\n\tcase 6:\n\t{\n\t\tv128_t sel2 = wasm_v128_load(data);\n\t\tv128_t rest = wasm_v128_load(data + 4);\n\n\t\tv128_t sel22 = wasmx_unpacklo_v8x16(wasm_i16x8_shr(sel2, 4), sel2);\n\t\tv128_t sel2222 = wasmx_unpacklo_v8x16(wasm_i16x8_shr(sel22, 2), sel22);\n\t\tv128_t sel = wasm_v128_and(sel2222, wasm_i8x16_splat(3));\n\n\t\tv128_t mask = wasm_i8x16_eq(sel, wasm_i8x16_splat(3));\n\n\t\tunsigned char mask0, mask1;\n\t\twasmMoveMask(mask, mask0, mask1);\n\n\t\tv128_t shuf = decodeShuffleMask(mask0, mask1);\n\t\tv128_t result = wasm_v128_bitselect(wasm_i8x16_swizzle(rest, shuf), sel, mask);\n\n\t\twasm_v128_store(buffer, result);\n\n\t\treturn data + 4 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];\n\t}\n\n\tcase 2:\n\tcase 7:\n\t{\n\t\tv128_t sel4 = wasm_v128_load(data);\n\t\tv128_t rest = wasm_v128_load(data + 8);\n\n\t\tv128_t sel44 = wasmx_unpacklo_v8x16(wasm_i16x8_shr(sel4, 4), sel4);\n\t\tv128_t sel = wasm_v128_and(sel44, wasm_i8x16_splat(15));\n\n\t\tv128_t mask = wasm_i8x16_eq(sel, wasm_i8x16_splat(15));\n\n\t\tunsigned char mask0, mask1;\n\t\twasmMoveMask(mask, mask0, mask1);\n\n\t\tv128_t shuf = decodeShuffleMask(mask0, mask1);\n\t\tv128_t result = wasm_v128_bitselect(wasm_i8x16_swizzle(rest, shuf), sel, mask);\n\n\t\twasm_v128_store(buffer, result);\n\n\t\treturn data + 8 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];\n\t}\n\n\tcase 3:\n\tcase 8:\n\t{\n\t\tv128_t result = wasm_v128_load(data);\n\n\t\twasm_v128_store(buffer, result);\n\n\t\treturn data + 16;\n\t}\n\n\tcase 5:\n\t{\n\t\tv128_t rest = wasm_v128_load(data + 2);\n\n\t\tunsigned char mask0 = data[0];\n\t\tunsigned char mask1 = data[1];\n\n\t\tv128_t shuf = decodeShuffleMask(mask0, mask1);\n\t\tv128_t result = wasm_i8x16_swizzle(rest, shuf);\n\n\t\twasm_v128_store(buffer, result);\n\n\t\treturn data + 2 + kDecodeBytesGroupCount[mask0] + kDecodeBytesGroupCount[mask1];\n\t}\n\n\tdefault:\n\t\tSIMD_UNREACHABLE(); // unreachable\n\t}\n}\n#endif\n\n#if defined(SIMD_SSE) || defined(SIMD_AVX)\nSIMD_TARGET\ninline void transpose8(__m128i& x0, __m128i& x1, __m128i& x2, __m128i& x3)\n{\n\t__m128i t0 = _mm_unpacklo_epi8(x0, x1);\n\t__m128i t1 = _mm_unpackhi_epi8(x0, x1);\n\t__m128i t2 = _mm_unpacklo_epi8(x2, x3);\n\t__m128i t3 = _mm_unpackhi_epi8(x2, x3);\n\n\tx0 = _mm_unpacklo_epi16(t0, t2);\n\tx1 = _mm_unpackhi_epi16(t0, t2);\n\tx2 = _mm_unpacklo_epi16(t1, t3);\n\tx3 = _mm_unpackhi_epi16(t1, t3);\n}\n\nSIMD_TARGET\ninline __m128i unzigzag8(__m128i v)\n{\n\t__m128i xl = _mm_sub_epi8(_mm_setzero_si128(), _mm_and_si128(v, _mm_set1_epi8(1)));\n\t__m128i xr = _mm_and_si128(_mm_srli_epi16(v, 1), _mm_set1_epi8(127));\n\n\treturn _mm_xor_si128(xl, xr);\n}\n\nSIMD_TARGET\ninline __m128i unzigzag16(__m128i v)\n{\n\t__m128i xl = _mm_sub_epi16(_mm_setzero_si128(), _mm_and_si128(v, _mm_set1_epi16(1)));\n\t__m128i xr = _mm_srli_epi16(v, 1);\n\n\treturn _mm_xor_si128(xl, xr);\n}\n\nSIMD_TARGET\ninline __m128i rotate32(__m128i v, int r)\n{\n\treturn _mm_or_si128(_mm_slli_epi32(v, r), _mm_srli_epi32(v, 32 - r));\n}\n#endif\n\n#ifdef SIMD_NEON\nSIMD_TARGET\ninline void transpose8(uint8x16_t& x0, uint8x16_t& x1, uint8x16_t& x2, uint8x16_t& x3)\n{\n\tuint8x16x2_t t01 = vzipq_u8(x0, x1);\n\tuint8x16x2_t t23 = vzipq_u8(x2, x3);\n\n\tuint16x8x2_t x01 = vzipq_u16(vreinterpretq_u16_u8(t01.val[0]), vreinterpretq_u16_u8(t23.val[0]));\n\tuint16x8x2_t x23 = vzipq_u16(vreinterpretq_u16_u8(t01.val[1]), vreinterpretq_u16_u8(t23.val[1]));\n\n\tx0 = vreinterpretq_u8_u16(x01.val[0]);\n\tx1 = vreinterpretq_u8_u16(x01.val[1]);\n\tx2 = vreinterpretq_u8_u16(x23.val[0]);\n\tx3 = vreinterpretq_u8_u16(x23.val[1]);\n}\n\nSIMD_TARGET\ninline uint8x16_t unzigzag8(uint8x16_t v)\n{\n\tuint8x16_t xl = vreinterpretq_u8_s8(vnegq_s8(vreinterpretq_s8_u8(vandq_u8(v, vdupq_n_u8(1)))));\n\tuint8x16_t xr = vshrq_n_u8(v, 1);\n\n\treturn veorq_u8(xl, xr);\n}\n\nSIMD_TARGET\ninline uint8x16_t unzigzag16(uint8x16_t v)\n{\n\tuint16x8_t vv = vreinterpretq_u16_u8(v);\n\tuint8x16_t xl = vreinterpretq_u8_s16(vnegq_s16(vreinterpretq_s16_u16(vandq_u16(vv, vdupq_n_u16(1)))));\n\tuint8x16_t xr = vreinterpretq_u8_u16(vshrq_n_u16(vv, 1));\n\n\treturn veorq_u8(xl, xr);\n}\n\nSIMD_TARGET\ninline uint8x16_t rotate32(uint8x16_t v, int r)\n{\n\tuint32x4_t v32 = vreinterpretq_u32_u8(v);\n\treturn vreinterpretq_u8_u32(vorrq_u32(vshlq_u32(v32, vdupq_n_s32(r)), vshlq_u32(v32, vdupq_n_s32(r - 32))));\n}\n\ntemplate <int Channel>\nSIMD_TARGET inline uint8x8_t rebase(uint8x8_t npi, uint8x16_t r0, uint8x16_t r1, uint8x16_t r2, uint8x16_t r3)\n{\n\tswitch (Channel)\n\t{\n\tcase 0:\n\t{\n\t\tuint8x16_t rsum = vaddq_u8(vaddq_u8(r0, r1), vaddq_u8(r2, r3));\n\t\tuint8x8_t rsumx = vadd_u8(vget_low_u8(rsum), vget_high_u8(rsum));\n\t\treturn vadd_u8(vadd_u8(npi, rsumx), vext_u8(rsumx, rsumx, 4));\n\t}\n\tcase 1:\n\t{\n\t\tuint16x8_t rsum = vaddq_u16(vaddq_u16(vreinterpretq_u16_u8(r0), vreinterpretq_u16_u8(r1)), vaddq_u16(vreinterpretq_u16_u8(r2), vreinterpretq_u16_u8(r3)));\n\t\tuint16x4_t rsumx = vadd_u16(vget_low_u16(rsum), vget_high_u16(rsum));\n\t\treturn vreinterpret_u8_u16(vadd_u16(vadd_u16(vreinterpret_u16_u8(npi), rsumx), vext_u16(rsumx, rsumx, 2)));\n\t}\n\tcase 2:\n\t{\n\t\tuint8x16_t rsum = veorq_u8(veorq_u8(r0, r1), veorq_u8(r2, r3));\n\t\tuint8x8_t rsumx = veor_u8(vget_low_u8(rsum), vget_high_u8(rsum));\n\t\treturn veor_u8(veor_u8(npi, rsumx), vext_u8(rsumx, rsumx, 4));\n\t}\n\tdefault:\n\t\treturn npi;\n\t}\n}\n#endif\n\n#ifdef SIMD_WASM\nSIMD_TARGET\ninline void transpose8(v128_t& x0, v128_t& x1, v128_t& x2, v128_t& x3)\n{\n\tv128_t t0 = wasmx_unpacklo_v8x16(x0, x1);\n\tv128_t t1 = wasmx_unpackhi_v8x16(x0, x1);\n\tv128_t t2 = wasmx_unpacklo_v8x16(x2, x3);\n\tv128_t t3 = wasmx_unpackhi_v8x16(x2, x3);\n\n\tx0 = wasmx_unpacklo_v16x8(t0, t2);\n\tx1 = wasmx_unpackhi_v16x8(t0, t2);\n\tx2 = wasmx_unpacklo_v16x8(t1, t3);\n\tx3 = wasmx_unpackhi_v16x8(t1, t3);\n}\n\nSIMD_TARGET\ninline v128_t unzigzag8(v128_t v)\n{\n\tv128_t xl = wasm_i8x16_neg(wasm_v128_and(v, wasm_i8x16_splat(1)));\n\tv128_t xr = wasm_u8x16_shr(v, 1);\n\n\treturn wasm_v128_xor(xl, xr);\n}\n\nSIMD_TARGET\ninline v128_t unzigzag16(v128_t v)\n{\n\tv128_t xl = wasm_i16x8_neg(wasm_v128_and(v, wasm_i16x8_splat(1)));\n\tv128_t xr = wasm_u16x8_shr(v, 1);\n\n\treturn wasm_v128_xor(xl, xr);\n}\n\nSIMD_TARGET\ninline v128_t rotate32(v128_t v, int r)\n{\n\treturn wasm_v128_or(wasm_i32x4_shl(v, r), wasm_i32x4_shr(v, 32 - r));\n}\n#endif\n\n#if defined(SIMD_SSE) || defined(SIMD_AVX) || defined(SIMD_NEON) || defined(SIMD_WASM)\nSIMD_TARGET\nstatic const unsigned char* decodeBytesSimd(const unsigned char* data, const unsigned char* data_end, unsigned char* buffer, size_t buffer_size, int hshift)\n{\n\tassert(buffer_size % kByteGroupSize == 0);\n\tassert(kByteGroupSize == 16);\n\n\t// round number of groups to 4 to get number of header bytes\n\tsize_t header_size = (buffer_size / kByteGroupSize + 3) / 4;\n\tif (size_t(data_end - data) < header_size)\n\t\treturn NULL;\n\n\tconst unsigned char* header = data;\n\tdata += header_size;\n\n\tsize_t i = 0;\n\n\t// fast-path: process 4 groups at a time, do a shared bounds check\n\tfor (; i + kByteGroupSize * 4 <= buffer_size && size_t(data_end - data) >= kByteGroupDecodeLimit * 4; i += kByteGroupSize * 4)\n\t{\n\t\tsize_t header_offset = i / kByteGroupSize;\n\t\tunsigned char header_byte = header[header_offset / 4];\n\n\t\tdata = decodeBytesGroupSimd(data, buffer + i + kByteGroupSize * 0, hshift + ((header_byte >> 0) & 3));\n\t\tdata = decodeBytesGroupSimd(data, buffer + i + kByteGroupSize * 1, hshift + ((header_byte >> 2) & 3));\n\t\tdata = decodeBytesGroupSimd(data, buffer + i + kByteGroupSize * 2, hshift + ((header_byte >> 4) & 3));\n\t\tdata = decodeBytesGroupSimd(data, buffer + i + kByteGroupSize * 3, hshift + ((header_byte >> 6) & 3));\n\t}\n\n\t// slow-path: process remaining groups\n\tfor (; i < buffer_size; i += kByteGroupSize)\n\t{\n\t\tif (size_t(data_end - data) < kByteGroupDecodeLimit)\n\t\t\treturn NULL;\n\n\t\tsize_t header_offset = i / kByteGroupSize;\n\t\tunsigned char header_byte = header[header_offset / 4];\n\n\t\tdata = decodeBytesGroupSimd(data, buffer + i, hshift + ((header_byte >> ((header_offset % 4) * 2)) & 3));\n\t}\n\n\treturn data;\n}\n\ntemplate <int Channel>\nSIMD_TARGET static void\ndecodeDeltas4Simd(const unsigned char* buffer, unsigned char* transposed, size_t vertex_count_aligned, size_t vertex_size, unsigned char last_vertex[4], int rot)\n{\n#if defined(SIMD_SSE) || defined(SIMD_AVX)\n#define TEMP __m128i\n#define PREP() __m128i pi = _mm_cvtsi32_si128(*reinterpret_cast<const int*>(last_vertex))\n#define LOAD(i) __m128i r##i = _mm_loadu_si128(reinterpret_cast<const __m128i*>(buffer + j + i * vertex_count_aligned))\n#define GRP4(i) t0 = r##i, t1 = _mm_shuffle_epi32(r##i, 1), t2 = _mm_shuffle_epi32(r##i, 2), t3 = _mm_shuffle_epi32(r##i, 3)\n#define FIXD(i) t##i = pi = Channel == 0 ? _mm_add_epi8(pi, t##i) : (Channel == 1 ? _mm_add_epi16(pi, t##i) : _mm_xor_si128(pi, t##i))\n#define SAVE(i) *reinterpret_cast<int*>(savep) = _mm_cvtsi128_si32(t##i), savep += vertex_size\n#endif\n\n#ifdef SIMD_NEON\n#define TEMP uint8x8_t\n#define PREP() uint8x8_t pi = vreinterpret_u8_u32(vld1_lane_u32(reinterpret_cast<uint32_t*>(last_vertex), vdup_n_u32(0), 0))\n#define LOAD(i) uint8x16_t r##i = vld1q_u8(buffer + j + i * vertex_count_aligned)\n#define GRP4(i) t0 = vget_low_u8(r##i), t1 = vreinterpret_u8_u32(vdup_lane_u32(vreinterpret_u32_u8(t0), 1)), t2 = vget_high_u8(r##i), t3 = vreinterpret_u8_u32(vdup_lane_u32(vreinterpret_u32_u8(t2), 1))\n#define FIXD(i) t##i = pi = Channel == 0 ? vadd_u8(pi, t##i) : (Channel == 1 ? vreinterpret_u8_u16(vadd_u16(vreinterpret_u16_u8(pi), vreinterpret_u16_u8(t##i))) : veor_u8(pi, t##i))\n#define SAVE(i) vst1_lane_u32(reinterpret_cast<uint32_t*>(savep), vreinterpret_u32_u8(t##i), 0), savep += vertex_size\n#endif\n\n#ifdef SIMD_WASM\n#define TEMP v128_t\n#define PREP() v128_t pi = wasm_v128_load(last_vertex)\n#define LOAD(i) v128_t r##i = wasm_v128_load(buffer + j + i * vertex_count_aligned)\n#define GRP4(i) t0 = r##i, t1 = wasmx_splat_v32x4(r##i, 1), t2 = wasmx_splat_v32x4(r##i, 2), t3 = wasmx_splat_v32x4(r##i, 3)\n#define FIXD(i) t##i = pi = Channel == 0 ? wasm_i8x16_add(pi, t##i) : (Channel == 1 ? wasm_i16x8_add(pi, t##i) : wasm_v128_xor(pi, t##i))\n#define SAVE(i) wasm_v128_store32_lane(savep, t##i, 0), savep += vertex_size\n#endif\n\n#define UNZR(i) r##i = Channel == 0 ? unzigzag8(r##i) : (Channel == 1 ? unzigzag16(r##i) : rotate32(r##i, rot))\n\n\tPREP();\n\n\tunsigned char* savep = transposed;\n\n\tfor (size_t j = 0; j < vertex_count_aligned; j += 16)\n\t{\n\t\tLOAD(0);\n\t\tLOAD(1);\n\t\tLOAD(2);\n\t\tLOAD(3);\n\n\t\ttranspose8(r0, r1, r2, r3);\n\n\t\tTEMP t0, t1, t2, t3;\n\t\tTEMP npi = pi;\n\n\t\tUNZR(0);\n\t\tGRP4(0);\n\t\tFIXD(0), FIXD(1), FIXD(2), FIXD(3);\n\t\tSAVE(0), SAVE(1), SAVE(2), SAVE(3);\n\n\t\tUNZR(1);\n\t\tGRP4(1);\n\t\tFIXD(0), FIXD(1), FIXD(2), FIXD(3);\n\t\tSAVE(0), SAVE(1), SAVE(2), SAVE(3);\n\n\t\tUNZR(2);\n\t\tGRP4(2);\n\t\tFIXD(0), FIXD(1), FIXD(2), FIXD(3);\n\t\tSAVE(0), SAVE(1), SAVE(2), SAVE(3);\n\n\t\tUNZR(3);\n\t\tGRP4(3);\n\t\tFIXD(0), FIXD(1), FIXD(2), FIXD(3);\n\t\tSAVE(0), SAVE(1), SAVE(2), SAVE(3);\n\n#if defined(SIMD_LATENCYOPT) && defined(SIMD_NEON) && (defined(__APPLE__) || defined(_WIN32))\n\t\t// instead of relying on accumulated pi, recompute it from scratch from r0..r3; this shortens dependency between loop iterations\n\t\tpi = rebase<Channel>(npi, r0, r1, r2, r3);\n#else\n\t\t(void)npi;\n#endif\n\n#undef UNZR\n#undef TEMP\n#undef PREP\n#undef LOAD\n#undef GRP4\n#undef FIXD\n#undef SAVE\n\t}\n}\n\nSIMD_TARGET\nstatic const unsigned char* decodeVertexBlockSimd(const unsigned char* data, const unsigned char* data_end, unsigned char* vertex_data, size_t vertex_count, size_t vertex_size, unsigned char last_vertex[256], const unsigned char* channels, int version)\n{\n\tassert(vertex_count > 0 && vertex_count <= kVertexBlockMaxSize);\n\n\tunsigned char buffer[kVertexBlockMaxSize * 4];\n\tunsigned char transposed[kVertexBlockSizeBytes];\n\n\tsize_t vertex_count_aligned = (vertex_count + kByteGroupSize - 1) & ~(kByteGroupSize - 1);\n\n\tsize_t control_size = version == 0 ? 0 : vertex_size / 4;\n\tif (size_t(data_end - data) < control_size)\n\t\treturn NULL;\n\n\tconst unsigned char* control = data;\n\tdata += control_size;\n\n\tfor (size_t k = 0; k < vertex_size; k += 4)\n\t{\n\t\tunsigned char ctrl_byte = version == 0 ? 0 : control[k / 4];\n\n\t\tfor (size_t j = 0; j < 4; ++j)\n\t\t{\n\t\t\tint ctrl = (ctrl_byte >> (j * 2)) & 3;\n\n\t\t\tif (ctrl == 3)\n\t\t\t{\n\t\t\t\t// literal encoding; safe to over-copy due to tail\n\t\t\t\tif (size_t(data_end - data) < vertex_count_aligned)\n\t\t\t\t\treturn NULL;\n\n\t\t\t\tmemcpy(buffer + j * vertex_count_aligned, data, vertex_count_aligned);\n\t\t\t\tdata += vertex_count;\n\t\t\t}\n\t\t\telse if (ctrl == 2)\n\t\t\t{\n\t\t\t\t// zero encoding\n\t\t\t\tmemset(buffer + j * vertex_count_aligned, 0, vertex_count_aligned);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// for v0, headers are mapped to 0..3; for v1, headers are mapped to 4..8\n\t\t\t\tint hshift = version == 0 ? 0 : 4 + ctrl;\n\n\t\t\t\tdata = decodeBytesSimd(data, data_end, buffer + j * vertex_count_aligned, vertex_count_aligned, hshift);\n\t\t\t\tif (!data)\n\t\t\t\t\treturn NULL;\n\t\t\t}\n\t\t}\n\n\t\tint channel = version == 0 ? 0 : channels[k / 4];\n\n\t\tswitch (channel & 3)\n\t\t{\n\t\tcase 0:\n\t\t\tdecodeDeltas4Simd<0>(buffer, transposed + k, vertex_count_aligned, vertex_size, last_vertex + k, 0);\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tdecodeDeltas4Simd<1>(buffer, transposed + k, vertex_count_aligned, vertex_size, last_vertex + k, 0);\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tdecodeDeltas4Simd<2>(buffer, transposed + k, vertex_count_aligned, vertex_size, last_vertex + k, (32 - (channel >> 4)) & 31);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn NULL; // invalid channel type\n\t\t}\n\t}\n\n\tmemcpy(vertex_data, transposed, vertex_count * vertex_size);\n\n\tmemcpy(last_vertex, &transposed[vertex_size * (vertex_count - 1)], vertex_size);\n\n\treturn data;\n}\n#endif\n\n#if defined(SIMD_SSE) && defined(SIMD_FALLBACK)\nstatic unsigned int getCpuFeatures()\n{\n\tint cpuinfo[4] = {};\n#ifdef _MSC_VER\n\t__cpuid(cpuinfo, 1);\n#else\n\t__cpuid(1, cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]);\n#endif\n\treturn cpuinfo[2];\n}\n\nstatic unsigned int cpuid = getCpuFeatures();\n#endif\n\n} // namespace meshopt\n\nsize_t meshopt_encodeVertexBufferLevel(unsigned char* buffer, size_t buffer_size, const void* vertices, size_t vertex_count, size_t vertex_size, int level, int version)\n{\n\tusing namespace meshopt;\n\n\tassert(vertex_size > 0 && vertex_size <= 256);\n\tassert(vertex_size % 4 == 0);\n\tassert(level >= 0 && level <= 9); // only a subset of this range is used right now\n\tassert(version < 0 || unsigned(version) <= kDecodeVertexVersion);\n\n\tversion = version < 0 ? gEncodeVertexVersion : version;\n\n#if TRACE\n\tmemset(vertexstats, 0, sizeof(vertexstats));\n#endif\n\n\tconst unsigned char* vertex_data = static_cast<const unsigned char*>(vertices);\n\n\tunsigned char* data = buffer;\n\tunsigned char* data_end = buffer + buffer_size;\n\n\tif (size_t(data_end - data) < 1)\n\t\treturn 0;\n\n\t*data++ = (unsigned char)(kVertexHeader | version);\n\n\tunsigned char first_vertex[256] = {};\n\tif (vertex_count > 0)\n\t\tmemcpy(first_vertex, vertex_data, vertex_size);\n\n\tunsigned char last_vertex[256] = {};\n\tmemcpy(last_vertex, first_vertex, vertex_size);\n\n\tsize_t vertex_block_size = getVertexBlockSize(vertex_size);\n\n\tunsigned char channels[64] = {};\n\tif (version != 0 && level > 1 && vertex_count > 1)\n\t\tfor (size_t k = 0; k < vertex_size; k += 4)\n\t\t{\n\t\t\tint rot = level >= 3 ? estimateRotate(vertex_data, vertex_count, vertex_size, k, /* group_size= */ 16) : 0;\n\t\t\tint channel = estimateChannel(vertex_data, vertex_count, vertex_size, k, vertex_block_size, /* block_skip= */ 3, /* max_channels= */ level >= 3 ? 3 : 2, rot);\n\n\t\t\tassert(unsigned(channel) < 2 || ((channel & 3) == 2 && unsigned(channel >> 4) < 8));\n\t\t\tchannels[k / 4] = (unsigned char)channel;\n\t\t}\n\n\tsize_t vertex_offset = 0;\n\n\twhile (vertex_offset < vertex_count)\n\t{\n\t\tsize_t block_size = (vertex_offset + vertex_block_size < vertex_count) ? vertex_block_size : vertex_count - vertex_offset;\n\n\t\tdata = encodeVertexBlock(data, data_end, vertex_data + vertex_offset * vertex_size, block_size, vertex_size, last_vertex, channels, version, level);\n\t\tif (!data)\n\t\t\treturn 0;\n\n\t\tvertex_offset += block_size;\n\t}\n\n\tsize_t tail_size = vertex_size + (version == 0 ? 0 : vertex_size / 4);\n\tsize_t tail_size_min = version == 0 ? kTailMinSizeV0 : kTailMinSizeV1;\n\tsize_t tail_size_pad = tail_size < tail_size_min ? tail_size_min : tail_size;\n\n\tif (size_t(data_end - data) < tail_size_pad)\n\t\treturn 0;\n\n\tif (tail_size < tail_size_pad)\n\t{\n\t\tmemset(data, 0, tail_size_pad - tail_size);\n\t\tdata += tail_size_pad - tail_size;\n\t}\n\n\tmemcpy(data, first_vertex, vertex_size);\n\tdata += vertex_size;\n\n\tif (version != 0)\n\t{\n\t\tmemcpy(data, channels, vertex_size / 4);\n\t\tdata += vertex_size / 4;\n\t}\n\n\tassert(data >= buffer + tail_size);\n\tassert(data <= buffer + buffer_size);\n\n#if TRACE\n\tsize_t total_size = data - buffer;\n\n\tfor (size_t k = 0; k < vertex_size; ++k)\n\t{\n\t\tconst Stats& vsk = vertexstats[k];\n\n\t\tprintf(\"%2d: %7d bytes [%4.1f%%] %.1f bpv\", int(k), int(vsk.size), double(vsk.size) / double(total_size) * 100, double(vsk.size) / double(vertex_count) * 8);\n\n\t\tsize_t total_k = vsk.header + vsk.bitg[1] + vsk.bitg[2] + vsk.bitg[4] + vsk.bitg[8];\n\t\tdouble total_kr = total_k ? 1.0 / double(total_k) : 0;\n\n\t\tif (version != 0)\n\t\t{\n\t\t\tint channel = channels[k / 4];\n\n\t\t\tif ((channel & 3) == 2 && k % 4 == 0)\n\t\t\t\tprintf(\" | ^%d\", channel >> 4);\n\t\t\telse\n\t\t\t\tprintf(\" | %2s\", channel == 0 ? \"1\" : (channel == 1 && k % 2 == 0 ? \"2\" : \".\"));\n\t\t}\n\n\t\tprintf(\" | hdr [%5.1f%%] bitg [1 %4.1f%% 2 %4.1f%% 4 %4.1f%% 8 %4.1f%%]\",\n\t\t    double(vsk.header) * total_kr * 100,\n\t\t    double(vsk.bitg[1]) * total_kr * 100, double(vsk.bitg[2]) * total_kr * 100,\n\t\t    double(vsk.bitg[4]) * total_kr * 100, double(vsk.bitg[8]) * total_kr * 100);\n\n\t\tsize_t total_ctrl = vsk.ctrl[0] + vsk.ctrl[1] + vsk.ctrl[2] + vsk.ctrl[3];\n\n\t\tif (total_ctrl)\n\t\t{\n\t\t\tprintf(\" | ctrl %3.0f%% %3.0f%% %3.0f%% %3.0f%%\",\n\t\t\t    double(vsk.ctrl[0]) / double(total_ctrl) * 100, double(vsk.ctrl[1]) / double(total_ctrl) * 100,\n\t\t\t    double(vsk.ctrl[2]) / double(total_ctrl) * 100, double(vsk.ctrl[3]) / double(total_ctrl) * 100);\n\t\t}\n\n\t\tif (level >= 3)\n\t\t\tprintf(\" | bitc [%3.0f%% %3.0f%% %3.0f%% %3.0f%% %3.0f%% %3.0f%% %3.0f%% %3.0f%%]\",\n\t\t\t    double(vsk.bitc[0]) / double(vertex_count) * 100, double(vsk.bitc[1]) / double(vertex_count) * 100,\n\t\t\t    double(vsk.bitc[2]) / double(vertex_count) * 100, double(vsk.bitc[3]) / double(vertex_count) * 100,\n\t\t\t    double(vsk.bitc[4]) / double(vertex_count) * 100, double(vsk.bitc[5]) / double(vertex_count) * 100,\n\t\t\t    double(vsk.bitc[6]) / double(vertex_count) * 100, double(vsk.bitc[7]) / double(vertex_count) * 100);\n\n\t\tprintf(\"\\n\");\n\t}\n#endif\n\n\treturn data - buffer;\n}\n\nsize_t meshopt_encodeVertexBuffer(unsigned char* buffer, size_t buffer_size, const void* vertices, size_t vertex_count, size_t vertex_size)\n{\n\treturn meshopt_encodeVertexBufferLevel(buffer, buffer_size, vertices, vertex_count, vertex_size, meshopt::kEncodeDefaultLevel, meshopt::gEncodeVertexVersion);\n}\n\nsize_t meshopt_encodeVertexBufferBound(size_t vertex_count, size_t vertex_size)\n{\n\tusing namespace meshopt;\n\n\tassert(vertex_size > 0 && vertex_size <= 256);\n\tassert(vertex_size % 4 == 0);\n\n\tsize_t vertex_block_size = getVertexBlockSize(vertex_size);\n\tsize_t vertex_block_count = (vertex_count + vertex_block_size - 1) / vertex_block_size;\n\n\tsize_t vertex_block_control_size = vertex_size / 4;\n\tsize_t vertex_block_header_size = (vertex_block_size / kByteGroupSize + 3) / 4;\n\tsize_t vertex_block_data_size = vertex_block_size;\n\n\tsize_t tail_size = vertex_size + (vertex_size / 4);\n\tsize_t tail_size_min = kTailMinSizeV0 > kTailMinSizeV1 ? kTailMinSizeV0 : kTailMinSizeV1;\n\tsize_t tail_size_pad = tail_size < tail_size_min ? tail_size_min : tail_size;\n\tassert(tail_size_pad >= kByteGroupDecodeLimit);\n\n\treturn 1 + vertex_block_count * vertex_size * (vertex_block_control_size + vertex_block_header_size + vertex_block_data_size) + tail_size_pad;\n}\n\nvoid meshopt_encodeVertexVersion(int version)\n{\n\tassert(unsigned(version) <= unsigned(meshopt::kDecodeVertexVersion));\n\n\tmeshopt::gEncodeVertexVersion = version;\n}\n\nint meshopt_decodeVertexVersion(const unsigned char* buffer, size_t buffer_size)\n{\n\tif (buffer_size < 1)\n\t\treturn -1;\n\n\tunsigned char header = buffer[0];\n\n\tif ((header & 0xf0) != meshopt::kVertexHeader)\n\t\treturn -1;\n\n\tint version = header & 0x0f;\n\tif (version > meshopt::kDecodeVertexVersion)\n\t\treturn -1;\n\n\treturn version;\n}\n\nint meshopt_decodeVertexBuffer(void* destination, size_t vertex_count, size_t vertex_size, const unsigned char* buffer, size_t buffer_size)\n{\n\tusing namespace meshopt;\n\n\tassert(vertex_size > 0 && vertex_size <= 256);\n\tassert(vertex_size % 4 == 0);\n\n\tconst unsigned char* (*decode)(const unsigned char*, const unsigned char*, unsigned char*, size_t, size_t, unsigned char[256], const unsigned char*, int) = NULL;\n\n#if defined(SIMD_SSE) && defined(SIMD_FALLBACK)\n\tdecode = (cpuid & (1 << 9)) ? decodeVertexBlockSimd : decodeVertexBlock;\n#elif defined(SIMD_SSE) || defined(SIMD_AVX) || defined(SIMD_NEON) || defined(SIMD_WASM)\n\tdecode = decodeVertexBlockSimd;\n#else\n\tdecode = decodeVertexBlock;\n#endif\n\n#if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM)\n\tassert(gDecodeBytesGroupInitialized);\n\t(void)gDecodeBytesGroupInitialized;\n#endif\n\n\tunsigned char* vertex_data = static_cast<unsigned char*>(destination);\n\n\tconst unsigned char* data = buffer;\n\tconst unsigned char* data_end = buffer + buffer_size;\n\n\tif (size_t(data_end - data) < 1)\n\t\treturn -2;\n\n\tunsigned char data_header = *data++;\n\n\tif ((data_header & 0xf0) != kVertexHeader)\n\t\treturn -1;\n\n\tint version = data_header & 0x0f;\n\tif (version > kDecodeVertexVersion)\n\t\treturn -1;\n\n\tsize_t tail_size = vertex_size + (version == 0 ? 0 : vertex_size / 4);\n\tsize_t tail_size_min = version == 0 ? kTailMinSizeV0 : kTailMinSizeV1;\n\tsize_t tail_size_pad = tail_size < tail_size_min ? tail_size_min : tail_size;\n\n\tif (size_t(data_end - data) < tail_size_pad)\n\t\treturn -2;\n\n\tconst unsigned char* tail = data_end - tail_size;\n\n\tunsigned char last_vertex[256];\n\tmemcpy(last_vertex, tail, vertex_size);\n\n\tconst unsigned char* channels = version == 0 ? NULL : tail + vertex_size;\n\n\tsize_t vertex_block_size = getVertexBlockSize(vertex_size);\n\n\tsize_t vertex_offset = 0;\n\n\twhile (vertex_offset < vertex_count)\n\t{\n\t\tsize_t block_size = (vertex_offset + vertex_block_size < vertex_count) ? vertex_block_size : vertex_count - vertex_offset;\n\n\t\tdata = decode(data, data_end, vertex_data + vertex_offset * vertex_size, block_size, vertex_size, last_vertex, channels, version);\n\t\tif (!data)\n\t\t\treturn -2;\n\n\t\tvertex_offset += block_size;\n\t}\n\n\tif (size_t(data_end - data) != tail_size_pad)\n\t\treturn -3;\n\n\treturn 0;\n}\n\n#undef SIMD_NEON\n#undef SIMD_SSE\n#undef SIMD_AVX\n#undef SIMD_WASM\n#undef SIMD_FALLBACK\n#undef SIMD_TARGET\n#undef SIMD_LATENCYOPT\n"
  },
  {
    "path": "src/vertexfilter.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <math.h>\n#include <string.h>\n\n// The block below auto-detects SIMD ISA that can be used on the target platform\n#ifndef MESHOPTIMIZER_NO_SIMD\n\n// The SIMD implementation requires SSE2, which can be enabled unconditionally through compiler settings\n#if defined(__SSE2__)\n#define SIMD_SSE\n#endif\n\n// MSVC supports compiling SSE2 code regardless of compile options; we assume all 32-bit CPUs support SSE2\n#if !defined(SIMD_SSE) && defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC)))\n#define SIMD_SSE\n#endif\n\n// GCC/clang define these when NEON support is available\n#if defined(__ARM_NEON__) || defined(__ARM_NEON)\n#define SIMD_NEON\n#endif\n\n// On MSVC, we assume that ARM builds always target NEON-capable devices\n#if !defined(SIMD_NEON) && defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC))\n#define SIMD_NEON\n#endif\n\n// When targeting Wasm SIMD we can't use runtime cpuid checks so we unconditionally enable SIMD\n#if defined(__wasm_simd128__)\n#define SIMD_WASM\n// Prevent compiling other variant when wasm simd compilation is active\n#undef SIMD_NEON\n#undef SIMD_SSE\n#endif\n\n#endif // !MESHOPTIMIZER_NO_SIMD\n\n#ifdef SIMD_SSE\n#include <emmintrin.h>\n#include <stdint.h>\n#endif\n\n#ifdef _MSC_VER\n#include <intrin.h>\n#endif\n\n#ifdef SIMD_NEON\n#if defined(_MSC_VER) && defined(_M_ARM64)\n#include <arm64_neon.h>\n#else\n#include <arm_neon.h>\n#endif\n#endif\n\n#ifdef SIMD_WASM\n#undef __DEPRECATED\n#include <wasm_simd128.h>\n#endif\n\n#ifdef SIMD_WASM\n#define wasmx_unpacklo_v16x8(a, b) wasm_v16x8_shuffle(a, b, 0, 8, 1, 9, 2, 10, 3, 11)\n#define wasmx_unpackhi_v16x8(a, b) wasm_v16x8_shuffle(a, b, 4, 12, 5, 13, 6, 14, 7, 15)\n#define wasmx_unziplo_v32x4(a, b) wasm_v32x4_shuffle(a, b, 0, 2, 4, 6)\n#define wasmx_unziphi_v32x4(a, b) wasm_v32x4_shuffle(a, b, 1, 3, 5, 7)\n#endif\n\n#ifndef __has_builtin\n#define __has_builtin(x) 0\n#endif\n\nnamespace meshopt\n{\n\n#if !defined(SIMD_SSE) && !defined(SIMD_NEON) && !defined(SIMD_WASM)\ntemplate <typename T>\nstatic void decodeFilterOct(T* data, size_t count)\n{\n\tconst float max = float((1 << (sizeof(T) * 8 - 1)) - 1);\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\t// convert x and y to floats and reconstruct z; this assumes zf encodes 1.f at the same bit count\n\t\tfloat x = float(data[i * 4 + 0]);\n\t\tfloat y = float(data[i * 4 + 1]);\n\t\tfloat z = float(data[i * 4 + 2]) - fabsf(x) - fabsf(y);\n\n\t\t// fixup octahedral coordinates for z<0\n\t\tfloat t = (z >= 0.f) ? 0.f : z;\n\n\t\tx += (x >= 0.f) ? t : -t;\n\t\ty += (y >= 0.f) ? t : -t;\n\n\t\t// compute normal length & scale\n\t\tfloat l = sqrtf(x * x + y * y + z * z);\n\t\tfloat s = max / l;\n\n\t\t// rounded signed float->int\n\t\tint xf = int(x * s + (x >= 0.f ? 0.5f : -0.5f));\n\t\tint yf = int(y * s + (y >= 0.f ? 0.5f : -0.5f));\n\t\tint zf = int(z * s + (z >= 0.f ? 0.5f : -0.5f));\n\n\t\tdata[i * 4 + 0] = T(xf);\n\t\tdata[i * 4 + 1] = T(yf);\n\t\tdata[i * 4 + 2] = T(zf);\n\t}\n}\n\nstatic void decodeFilterQuat(short* data, size_t count)\n{\n\tconst float scale = 32767.f / sqrtf(2.f);\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\t// recover scale from the high byte of the component\n\t\tint sf = data[i * 4 + 3] | 3;\n\t\tfloat s = float(sf);\n\n\t\t// convert x/y/z to floating point (unscaled! implied scale of 1/sqrt(2.f) * 1/sf)\n\t\tfloat x = float(data[i * 4 + 0]);\n\t\tfloat y = float(data[i * 4 + 1]);\n\t\tfloat z = float(data[i * 4 + 2]);\n\n\t\t// reconstruct w as a square root (unscaled); we clamp to 0.f to avoid NaN due to precision errors\n\t\tfloat ws = s * s;\n\t\tfloat ww = ws * 2.f - x * x - y * y - z * z;\n\t\tfloat w = sqrtf(ww >= 0.f ? ww : 0.f);\n\n\t\t// compute final scale; note that all computations above are unscaled\n\t\t// we need to divide by sf to get out of fixed point, divide by sqrt(2) to renormalize and multiply by 32767 to get to int16 range\n\t\tfloat ss = scale / s;\n\n\t\t// rounded signed float->int\n\t\tint xf = int(x * ss + (x >= 0.f ? 0.5f : -0.5f));\n\t\tint yf = int(y * ss + (y >= 0.f ? 0.5f : -0.5f));\n\t\tint zf = int(z * ss + (z >= 0.f ? 0.5f : -0.5f));\n\t\tint wf = int(w * ss + 0.5f);\n\n\t\tint qc = data[i * 4 + 3] & 3;\n\n\t\t// output order is dictated by input index\n\t\tdata[i * 4 + ((qc + 1) & 3)] = short(xf);\n\t\tdata[i * 4 + ((qc + 2) & 3)] = short(yf);\n\t\tdata[i * 4 + ((qc + 3) & 3)] = short(zf);\n\t\tdata[i * 4 + ((qc + 0) & 3)] = short(wf);\n\t}\n}\n\nstatic void decodeFilterExp(unsigned int* data, size_t count)\n{\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tunsigned int v = data[i];\n\n\t\t// decode mantissa and exponent\n\t\tint m = int(v << 8) >> 8;\n\t\tint e = int(v) >> 24;\n\n\t\tunion\n\t\t{\n\t\t\tfloat f;\n\t\t\tunsigned int ui;\n\t\t} u;\n\n\t\t// optimized version of ldexp(float(m), e)\n\t\tu.ui = unsigned(e + 127) << 23;\n\t\tu.f = u.f * float(m);\n\n\t\tdata[i] = u.ui;\n\t}\n}\n\ntemplate <typename ST, typename T>\nstatic void decodeFilterColor(T* data, size_t count)\n{\n\tconst float max = float((1 << (sizeof(T) * 8)) - 1);\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\t// recover scale from alpha high bit\n\t\tint as = data[i * 4 + 3];\n\t\tas |= as >> 1;\n\t\tas |= as >> 2;\n\t\tas |= as >> 4;\n\t\tas |= as >> 8; // noop for 8-bit\n\n\t\t// convert to RGB in fixed point (co/cg are sign extended)\n\t\tint y = data[i * 4 + 0], co = ST(data[i * 4 + 1]), cg = ST(data[i * 4 + 2]);\n\n\t\tint r = y + co - cg;\n\t\tint g = y + cg;\n\t\tint b = y - co - cg;\n\n\t\t// expand alpha by one bit to match other components\n\t\tint a = data[i * 4 + 3];\n\t\ta = ((a << 1) & as) | (a & 1);\n\n\t\t// compute scaling factor\n\t\tfloat ss = max / float(as);\n\n\t\t// rounded float->int\n\t\tint rf = int(float(r) * ss + 0.5f);\n\t\tint gf = int(float(g) * ss + 0.5f);\n\t\tint bf = int(float(b) * ss + 0.5f);\n\t\tint af = int(float(a) * ss + 0.5f);\n\n\t\tdata[i * 4 + 0] = T(rf);\n\t\tdata[i * 4 + 1] = T(gf);\n\t\tdata[i * 4 + 2] = T(bf);\n\t\tdata[i * 4 + 3] = T(af);\n\t}\n}\n#endif\n\n#if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM)\ntemplate <typename T>\nstatic void dispatchSimd(void (*process)(T*, size_t), T* data, size_t count, size_t stride)\n{\n\tassert(stride <= 4);\n\n\tsize_t count4 = count & ~size_t(3);\n\tprocess(data, count4);\n\n\tif (count4 < count)\n\t{\n\t\tT tail[4 * 4] = {}; // max stride 4, max count 4\n\t\tsize_t tail_size = (count - count4) * stride * sizeof(T);\n\t\tassert(tail_size <= sizeof(tail));\n\n\t\tmemcpy(tail, data + count4 * stride, tail_size);\n\t\tprocess(tail, count - count4);\n\t\tmemcpy(data + count4 * stride, tail, tail_size);\n\t}\n}\n\ninline uint64_t rotateleft64(uint64_t v, int x)\n{\n#if defined(_MSC_VER) && !defined(__clang__)\n\treturn _rotl64(v, x);\n#elif defined(__clang__) && __has_builtin(__builtin_rotateleft64)\n\treturn __builtin_rotateleft64(v, x);\n#else\n\treturn (v << (x & 63)) | (v >> ((64 - x) & 63));\n#endif\n}\n#endif\n\n#ifdef SIMD_SSE\nstatic void decodeFilterOctSimd8(signed char* data, size_t count)\n{\n\tconst __m128 sign = _mm_set1_ps(-0.f);\n\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\t__m128i n4 = _mm_loadu_si128(reinterpret_cast<__m128i*>(&data[i * 4]));\n\n\t\t// sign-extends each of x,y in [x y ? ?] with arithmetic shifts\n\t\t__m128i xf = _mm_srai_epi32(_mm_slli_epi32(n4, 24), 24);\n\t\t__m128i yf = _mm_srai_epi32(_mm_slli_epi32(n4, 16), 24);\n\n\t\t// unpack z; note that z is unsigned so we technically don't need to sign extend it\n\t\t__m128i zf = _mm_srai_epi32(_mm_slli_epi32(n4, 8), 24);\n\n\t\t// convert x and y to floats and reconstruct z; this assumes zf encodes 1.f at the same bit count\n\t\t__m128 x = _mm_cvtepi32_ps(xf);\n\t\t__m128 y = _mm_cvtepi32_ps(yf);\n\t\t__m128 z = _mm_sub_ps(_mm_cvtepi32_ps(zf), _mm_add_ps(_mm_andnot_ps(sign, x), _mm_andnot_ps(sign, y)));\n\n\t\t// fixup octahedral coordinates for z<0\n\t\t__m128 t = _mm_min_ps(z, _mm_setzero_ps());\n\n\t\tx = _mm_add_ps(x, _mm_xor_ps(t, _mm_and_ps(x, sign)));\n\t\ty = _mm_add_ps(y, _mm_xor_ps(t, _mm_and_ps(y, sign)));\n\n\t\t// compute normal length & scale\n\t\t__m128 ll = _mm_add_ps(_mm_mul_ps(x, x), _mm_add_ps(_mm_mul_ps(y, y), _mm_mul_ps(z, z)));\n\t\t__m128 s = _mm_mul_ps(_mm_set1_ps(127.f), _mm_rsqrt_ps(ll));\n\n\t\t// rounded signed float->int\n\t\t__m128i xr = _mm_cvtps_epi32(_mm_mul_ps(x, s));\n\t\t__m128i yr = _mm_cvtps_epi32(_mm_mul_ps(y, s));\n\t\t__m128i zr = _mm_cvtps_epi32(_mm_mul_ps(z, s));\n\n\t\t// combine xr/yr/zr into final value\n\t\t__m128i res = _mm_and_si128(n4, _mm_set1_epi32(0xff000000));\n\t\tres = _mm_or_si128(res, _mm_and_si128(xr, _mm_set1_epi32(0xff)));\n\t\tres = _mm_or_si128(res, _mm_slli_epi32(_mm_and_si128(yr, _mm_set1_epi32(0xff)), 8));\n\t\tres = _mm_or_si128(res, _mm_slli_epi32(_mm_and_si128(zr, _mm_set1_epi32(0xff)), 16));\n\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(&data[i * 4]), res);\n\t}\n}\n\nstatic void decodeFilterOctSimd16(short* data, size_t count)\n{\n\tconst __m128 sign = _mm_set1_ps(-0.f);\n\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\t__m128 n4_0 = _mm_loadu_ps(reinterpret_cast<float*>(&data[(i + 0) * 4]));\n\t\t__m128 n4_1 = _mm_loadu_ps(reinterpret_cast<float*>(&data[(i + 2) * 4]));\n\n\t\t// gather both x/y 16-bit pairs in each 32-bit lane\n\t\t__m128i n4 = _mm_castps_si128(_mm_shuffle_ps(n4_0, n4_1, _MM_SHUFFLE(2, 0, 2, 0)));\n\n\t\t// sign-extends each of x,y in [x y] with arithmetic shifts\n\t\t__m128i xf = _mm_srai_epi32(_mm_slli_epi32(n4, 16), 16);\n\t\t__m128i yf = _mm_srai_epi32(n4, 16);\n\n\t\t// unpack z; note that z is unsigned so we don't need to sign extend it\n\t\t__m128i z4 = _mm_castps_si128(_mm_shuffle_ps(n4_0, n4_1, _MM_SHUFFLE(3, 1, 3, 1)));\n\t\t__m128i zf = _mm_and_si128(z4, _mm_set1_epi32(0x7fff));\n\n\t\t// convert x and y to floats and reconstruct z; this assumes zf encodes 1.f at the same bit count\n\t\t__m128 x = _mm_cvtepi32_ps(xf);\n\t\t__m128 y = _mm_cvtepi32_ps(yf);\n\t\t__m128 z = _mm_sub_ps(_mm_cvtepi32_ps(zf), _mm_add_ps(_mm_andnot_ps(sign, x), _mm_andnot_ps(sign, y)));\n\n\t\t// fixup octahedral coordinates for z<0\n\t\t__m128 t = _mm_min_ps(z, _mm_setzero_ps());\n\n\t\tx = _mm_add_ps(x, _mm_xor_ps(t, _mm_and_ps(x, sign)));\n\t\ty = _mm_add_ps(y, _mm_xor_ps(t, _mm_and_ps(y, sign)));\n\n\t\t// compute normal length & scale\n\t\t__m128 ll = _mm_add_ps(_mm_mul_ps(x, x), _mm_add_ps(_mm_mul_ps(y, y), _mm_mul_ps(z, z)));\n\t\t__m128 s = _mm_div_ps(_mm_set1_ps(32767.f), _mm_sqrt_ps(ll));\n\n\t\t// rounded signed float->int\n\t\t__m128i xr = _mm_cvtps_epi32(_mm_mul_ps(x, s));\n\t\t__m128i yr = _mm_cvtps_epi32(_mm_mul_ps(y, s));\n\t\t__m128i zr = _mm_cvtps_epi32(_mm_mul_ps(z, s));\n\n\t\t// mix x/z and y/0 to make 16-bit unpack easier\n\t\t__m128i xzr = _mm_or_si128(_mm_and_si128(xr, _mm_set1_epi32(0xffff)), _mm_slli_epi32(zr, 16));\n\t\t__m128i y0r = _mm_and_si128(yr, _mm_set1_epi32(0xffff));\n\n\t\t// pack x/y/z using 16-bit unpacks; note that this has 0 where we should have .w\n\t\t__m128i res_0 = _mm_unpacklo_epi16(xzr, y0r);\n\t\t__m128i res_1 = _mm_unpackhi_epi16(xzr, y0r);\n\n\t\t// patch in .w\n\t\t__m128i maskw = _mm_set_epi32(0xffff0000, 0, 0xffff0000, 0);\n\t\tres_0 = _mm_or_si128(res_0, _mm_and_si128(_mm_castps_si128(n4_0), maskw));\n\t\tres_1 = _mm_or_si128(res_1, _mm_and_si128(_mm_castps_si128(n4_1), maskw));\n\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(&data[(i + 0) * 4]), res_0);\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(&data[(i + 2) * 4]), res_1);\n\t}\n}\n\nstatic void decodeFilterQuatSimd(short* data, size_t count)\n{\n\tconst float scale = 32767.f / sqrtf(2.f);\n\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\t__m128 q4_0 = _mm_loadu_ps(reinterpret_cast<float*>(&data[(i + 0) * 4]));\n\t\t__m128 q4_1 = _mm_loadu_ps(reinterpret_cast<float*>(&data[(i + 2) * 4]));\n\n\t\t// gather both x/y 16-bit pairs in each 32-bit lane\n\t\t__m128i q4_xy = _mm_castps_si128(_mm_shuffle_ps(q4_0, q4_1, _MM_SHUFFLE(2, 0, 2, 0)));\n\t\t__m128i q4_zc = _mm_castps_si128(_mm_shuffle_ps(q4_0, q4_1, _MM_SHUFFLE(3, 1, 3, 1)));\n\n\t\t// sign-extends each of x,y in [x y] with arithmetic shifts\n\t\t__m128i xf = _mm_srai_epi32(_mm_slli_epi32(q4_xy, 16), 16);\n\t\t__m128i yf = _mm_srai_epi32(q4_xy, 16);\n\t\t__m128i zf = _mm_srai_epi32(_mm_slli_epi32(q4_zc, 16), 16);\n\t\t__m128i cf = _mm_srai_epi32(q4_zc, 16);\n\n\t\t// get a floating-point scaler using zc with bottom 2 bits set to 1 (which represents 1.f)\n\t\t__m128i sf = _mm_or_si128(cf, _mm_set1_epi32(3));\n\t\t__m128 s = _mm_cvtepi32_ps(sf);\n\n\t\t// convert x/y/z to floating point (unscaled! implied scale of 1/sqrt(2.f) * 1/sf)\n\t\t__m128 x = _mm_cvtepi32_ps(xf);\n\t\t__m128 y = _mm_cvtepi32_ps(yf);\n\t\t__m128 z = _mm_cvtepi32_ps(zf);\n\n\t\t// reconstruct w as a square root (unscaled); we clamp to 0.f to avoid NaN due to precision errors\n\t\t__m128 ws = _mm_mul_ps(s, _mm_add_ps(s, s)); // s*2s instead of 2*(s*s) to work around clang bug with integer multiplication\n\t\t__m128 ww = _mm_sub_ps(ws, _mm_add_ps(_mm_mul_ps(x, x), _mm_add_ps(_mm_mul_ps(y, y), _mm_mul_ps(z, z))));\n\t\t__m128 w = _mm_sqrt_ps(_mm_max_ps(ww, _mm_setzero_ps()));\n\n\t\t// compute final scale; note that all computations above are unscaled\n\t\t// we need to divide by sf to get out of fixed point, divide by sqrt(2) to renormalize and multiply by 32767 to get to int16 range\n\t\t__m128 ss = _mm_div_ps(_mm_set1_ps(scale), s);\n\n\t\t// rounded signed float->int\n\t\t__m128i xr = _mm_cvtps_epi32(_mm_mul_ps(x, ss));\n\t\t__m128i yr = _mm_cvtps_epi32(_mm_mul_ps(y, ss));\n\t\t__m128i zr = _mm_cvtps_epi32(_mm_mul_ps(z, ss));\n\t\t__m128i wr = _mm_cvtps_epi32(_mm_mul_ps(w, ss));\n\n\t\t// mix x/z and w/y to make 16-bit unpack easier\n\t\t__m128i xzr = _mm_or_si128(_mm_and_si128(xr, _mm_set1_epi32(0xffff)), _mm_slli_epi32(zr, 16));\n\t\t__m128i wyr = _mm_or_si128(_mm_and_si128(wr, _mm_set1_epi32(0xffff)), _mm_slli_epi32(yr, 16));\n\n\t\t// pack x/y/z/w using 16-bit unpacks; we pack wxyz by default (for qc=0)\n\t\t__m128i res_0 = _mm_unpacklo_epi16(wyr, xzr);\n\t\t__m128i res_1 = _mm_unpackhi_epi16(wyr, xzr);\n\n\t\t// store results to stack so that we can rotate using scalar instructions\n\t\tuint64_t res[4];\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(&res[0]), res_0);\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(&res[2]), res_1);\n\n\t\t// rotate and store\n\t\tuint64_t* out = reinterpret_cast<uint64_t*>(&data[i * 4]);\n\n\t\tout[0] = rotateleft64(res[0], data[(i + 0) * 4 + 3] << 4);\n\t\tout[1] = rotateleft64(res[1], data[(i + 1) * 4 + 3] << 4);\n\t\tout[2] = rotateleft64(res[2], data[(i + 2) * 4 + 3] << 4);\n\t\tout[3] = rotateleft64(res[3], data[(i + 3) * 4 + 3] << 4);\n\t}\n}\n\nstatic void decodeFilterExpSimd(unsigned int* data, size_t count)\n{\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\t__m128i v = _mm_loadu_si128(reinterpret_cast<__m128i*>(&data[i]));\n\n\t\t// decode exponent into 2^x directly\n\t\t__m128i ef = _mm_srai_epi32(v, 24);\n\t\t__m128i es = _mm_slli_epi32(_mm_add_epi32(ef, _mm_set1_epi32(127)), 23);\n\n\t\t// decode 24-bit mantissa into floating-point value\n\t\t__m128i mf = _mm_srai_epi32(_mm_slli_epi32(v, 8), 8);\n\t\t__m128 m = _mm_cvtepi32_ps(mf);\n\n\t\t__m128 r = _mm_mul_ps(_mm_castsi128_ps(es), m);\n\n\t\t_mm_storeu_ps(reinterpret_cast<float*>(&data[i]), r);\n\t}\n}\n\nstatic void decodeFilterColorSimd8(unsigned char* data, size_t count)\n{\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\t__m128i c4 = _mm_loadu_si128(reinterpret_cast<__m128i*>(&data[i * 4]));\n\n\t\t// unpack y/co/cg/a (co/cg are sign extended with arithmetic shifts)\n\t\t__m128i yf = _mm_and_si128(c4, _mm_set1_epi32(0xff));\n\t\t__m128i cof = _mm_srai_epi32(_mm_slli_epi32(c4, 16), 24);\n\t\t__m128i cgf = _mm_srai_epi32(_mm_slli_epi32(c4, 8), 24);\n\t\t__m128i af = _mm_srli_epi32(c4, 24);\n\n\t\t// recover scale from alpha high bit\n\t\t__m128i as = af;\n\t\tas = _mm_or_si128(as, _mm_srli_epi32(as, 1));\n\t\tas = _mm_or_si128(as, _mm_srli_epi32(as, 2));\n\t\tas = _mm_or_si128(as, _mm_srli_epi32(as, 4));\n\n\t\t// expand alpha by one bit to match other components\n\t\taf = _mm_or_si128(_mm_and_si128(_mm_slli_epi32(af, 1), as), _mm_and_si128(af, _mm_set1_epi32(1)));\n\n\t\t// compute scaling factor\n\t\t__m128 ss = _mm_mul_ps(_mm_set1_ps(255.f), _mm_rcp_ps(_mm_cvtepi32_ps(as)));\n\n\t\t// convert to RGB in fixed point\n\t\t__m128i rf = _mm_add_epi32(yf, _mm_sub_epi32(cof, cgf));\n\t\t__m128i gf = _mm_add_epi32(yf, cgf);\n\t\t__m128i bf = _mm_sub_epi32(yf, _mm_add_epi32(cof, cgf));\n\n\t\t// rounded signed float->int\n\t\t__m128i rr = _mm_cvtps_epi32(_mm_mul_ps(_mm_cvtepi32_ps(rf), ss));\n\t\t__m128i gr = _mm_cvtps_epi32(_mm_mul_ps(_mm_cvtepi32_ps(gf), ss));\n\t\t__m128i br = _mm_cvtps_epi32(_mm_mul_ps(_mm_cvtepi32_ps(bf), ss));\n\t\t__m128i ar = _mm_cvtps_epi32(_mm_mul_ps(_mm_cvtepi32_ps(af), ss));\n\n\t\t// repack rgba into final value\n\t\t__m128i res = rr;\n\t\tres = _mm_or_si128(res, _mm_slli_epi32(gr, 8));\n\t\tres = _mm_or_si128(res, _mm_slli_epi32(br, 16));\n\t\tres = _mm_or_si128(res, _mm_slli_epi32(ar, 24));\n\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(&data[i * 4]), res);\n\t}\n}\n\nstatic void decodeFilterColorSimd16(unsigned short* data, size_t count)\n{\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\t__m128i c4_0 = _mm_loadu_si128(reinterpret_cast<__m128i*>(&data[(i + 0) * 4]));\n\t\t__m128i c4_1 = _mm_loadu_si128(reinterpret_cast<__m128i*>(&data[(i + 2) * 4]));\n\n\t\t// gather both y/co 16-bit pairs in each 32-bit lane\n\t\t__m128i c4_yco = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(c4_0), _mm_castsi128_ps(c4_1), _MM_SHUFFLE(2, 0, 2, 0)));\n\t\t__m128i c4_cga = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(c4_0), _mm_castsi128_ps(c4_1), _MM_SHUFFLE(3, 1, 3, 1)));\n\n\t\t// unpack y/co/cg/a components (co/cg are sign extended with arithmetic shifts)\n\t\t__m128i yf = _mm_and_si128(c4_yco, _mm_set1_epi32(0xffff));\n\t\t__m128i cof = _mm_srai_epi32(c4_yco, 16);\n\t\t__m128i cgf = _mm_srai_epi32(_mm_slli_epi32(c4_cga, 16), 16);\n\t\t__m128i af = _mm_srli_epi32(c4_cga, 16);\n\n\t\t// recover scale from alpha high bit\n\t\t__m128i as = af;\n\t\tas = _mm_or_si128(as, _mm_srli_epi32(as, 1));\n\t\tas = _mm_or_si128(as, _mm_srli_epi32(as, 2));\n\t\tas = _mm_or_si128(as, _mm_srli_epi32(as, 4));\n\t\tas = _mm_or_si128(as, _mm_srli_epi32(as, 8));\n\n\t\t// expand alpha by one bit to match other components\n\t\taf = _mm_or_si128(_mm_and_si128(_mm_slli_epi32(af, 1), as), _mm_and_si128(af, _mm_set1_epi32(1)));\n\n\t\t// compute scaling factor\n\t\t__m128 ss = _mm_div_ps(_mm_set1_ps(65535.f), _mm_cvtepi32_ps(as));\n\n\t\t// convert to RGB in fixed point\n\t\t__m128i rf = _mm_add_epi32(yf, _mm_sub_epi32(cof, cgf));\n\t\t__m128i gf = _mm_add_epi32(yf, cgf);\n\t\t__m128i bf = _mm_sub_epi32(yf, _mm_add_epi32(cof, cgf));\n\n\t\t// rounded signed float->int\n\t\t__m128i rr = _mm_cvtps_epi32(_mm_mul_ps(_mm_cvtepi32_ps(rf), ss));\n\t\t__m128i gr = _mm_cvtps_epi32(_mm_mul_ps(_mm_cvtepi32_ps(gf), ss));\n\t\t__m128i br = _mm_cvtps_epi32(_mm_mul_ps(_mm_cvtepi32_ps(bf), ss));\n\t\t__m128i ar = _mm_cvtps_epi32(_mm_mul_ps(_mm_cvtepi32_ps(af), ss));\n\n\t\t// mix r/b and g/a to make 16-bit unpack easier\n\t\t__m128i rbr = _mm_or_si128(_mm_and_si128(rr, _mm_set1_epi32(0xffff)), _mm_slli_epi32(br, 16));\n\t\t__m128i gar = _mm_or_si128(_mm_and_si128(gr, _mm_set1_epi32(0xffff)), _mm_slli_epi32(ar, 16));\n\n\t\t// pack r/g/b/a using 16-bit unpacks\n\t\t__m128i res_0 = _mm_unpacklo_epi16(rbr, gar);\n\t\t__m128i res_1 = _mm_unpackhi_epi16(rbr, gar);\n\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(&data[(i + 0) * 4]), res_0);\n\t\t_mm_storeu_si128(reinterpret_cast<__m128i*>(&data[(i + 2) * 4]), res_1);\n\t}\n}\n#endif\n\n#if defined(SIMD_NEON) && !defined(__aarch64__) && !(defined(_M_ARM64) || defined(_M_ARM64EC))\ninline float32x4_t vsqrtq_f32(float32x4_t x)\n{\n\tfloat32x4_t r = vrsqrteq_f32(x);\n\tr = vmulq_f32(r, vrsqrtsq_f32(vmulq_f32(r, x), r)); // refine rsqrt estimate\n\treturn vmulq_f32(r, x);\n}\n\ninline float32x4_t vdivq_f32(float32x4_t x, float32x4_t y)\n{\n\tfloat32x4_t r = vrecpeq_f32(y);\n\tr = vmulq_f32(r, vrecpsq_f32(y, r)); // refine rcp estimate\n\treturn vmulq_f32(x, r);\n}\n\n#ifndef __ARM_FEATURE_FMA\ninline float32x4_t vfmaq_f32(float32x4_t x, float32x4_t y, float32x4_t z)\n{\n\treturn vaddq_f32(x, vmulq_f32(y, z));\n}\n#endif\n#endif\n\n#ifdef SIMD_NEON\nstatic void decodeFilterOctSimd8(signed char* data, size_t count)\n{\n\tconst int32x4_t sign = vdupq_n_s32(0x80000000);\n\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\tint32x4_t n4 = vld1q_s32(reinterpret_cast<int32_t*>(&data[i * 4]));\n\n\t\t// sign-extends each of x,y in [x y ? ?] with arithmetic shifts\n\t\tint32x4_t xf = vshrq_n_s32(vshlq_n_s32(n4, 24), 24);\n\t\tint32x4_t yf = vshrq_n_s32(vshlq_n_s32(n4, 16), 24);\n\n\t\t// unpack z; note that z is unsigned so we technically don't need to sign extend it\n\t\tint32x4_t zf = vshrq_n_s32(vshlq_n_s32(n4, 8), 24);\n\n\t\t// convert x and y to floats and reconstruct z; this assumes zf encodes 1.f at the same bit count\n\t\tfloat32x4_t x = vcvtq_f32_s32(xf);\n\t\tfloat32x4_t y = vcvtq_f32_s32(yf);\n\t\tfloat32x4_t z = vsubq_f32(vcvtq_f32_s32(zf), vaddq_f32(vabsq_f32(x), vabsq_f32(y)));\n\n\t\t// fixup octahedral coordinates for z<0\n\t\tfloat32x4_t t = vminq_f32(z, vdupq_n_f32(0.f));\n\n\t\tx = vaddq_f32(x, vreinterpretq_f32_s32(veorq_s32(vreinterpretq_s32_f32(t), vandq_s32(vreinterpretq_s32_f32(x), sign))));\n\t\ty = vaddq_f32(y, vreinterpretq_f32_s32(veorq_s32(vreinterpretq_s32_f32(t), vandq_s32(vreinterpretq_s32_f32(y), sign))));\n\n\t\t// compute normal length & scale\n\t\tfloat32x4_t ll = vfmaq_f32(vfmaq_f32(vmulq_f32(x, x), y, y), z, z);\n\t\tfloat32x4_t rl = vrsqrteq_f32(ll);\n\t\tfloat32x4_t s = vmulq_f32(vdupq_n_f32(127.f), rl);\n\n\t\t// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value\n\t\t// note: the result is offset by 0x4B40_0000, but we only need the low 8 bits so we can omit the subtraction\n\t\tconst float32x4_t fsnap = vdupq_n_f32(3 << 22);\n\n\t\tint32x4_t xr = vreinterpretq_s32_f32(vfmaq_f32(fsnap, x, s));\n\t\tint32x4_t yr = vreinterpretq_s32_f32(vfmaq_f32(fsnap, y, s));\n\t\tint32x4_t zr = vreinterpretq_s32_f32(vfmaq_f32(fsnap, z, s));\n\n\t\t// combine xr/yr/zr into final value\n\t\tint32x4_t res = vsliq_n_s32(xr, vsliq_n_s32(yr, zr, 8), 8);\n\t\tres = vbslq_s32(vdupq_n_u32(0xff000000), n4, res);\n\n\t\tvst1q_s32(reinterpret_cast<int32_t*>(&data[i * 4]), res);\n\t}\n}\n\nstatic void decodeFilterOctSimd16(short* data, size_t count)\n{\n\tconst int32x4_t sign = vdupq_n_s32(0x80000000);\n\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\tint32x4_t n4_0 = vld1q_s32(reinterpret_cast<int32_t*>(&data[(i + 0) * 4]));\n\t\tint32x4_t n4_1 = vld1q_s32(reinterpret_cast<int32_t*>(&data[(i + 2) * 4]));\n\n\t\t// gather both x/y 16-bit pairs in each 32-bit lane\n\t\tint32x4_t n4 = vuzpq_s32(n4_0, n4_1).val[0];\n\n\t\t// sign-extends each of x,y in [x y] with arithmetic shifts\n\t\tint32x4_t xf = vshrq_n_s32(vshlq_n_s32(n4, 16), 16);\n\t\tint32x4_t yf = vshrq_n_s32(n4, 16);\n\n\t\t// unpack z; note that z is unsigned so we don't need to sign extend it\n\t\tint32x4_t z4 = vuzpq_s32(n4_0, n4_1).val[1];\n\t\tint32x4_t zf = vandq_s32(z4, vdupq_n_s32(0x7fff));\n\n\t\t// convert x and y to floats and reconstruct z; this assumes zf encodes 1.f at the same bit count\n\t\tfloat32x4_t x = vcvtq_f32_s32(xf);\n\t\tfloat32x4_t y = vcvtq_f32_s32(yf);\n\t\tfloat32x4_t z = vsubq_f32(vcvtq_f32_s32(zf), vaddq_f32(vabsq_f32(x), vabsq_f32(y)));\n\n\t\t// fixup octahedral coordinates for z<0\n\t\tfloat32x4_t t = vminq_f32(z, vdupq_n_f32(0.f));\n\n\t\tx = vaddq_f32(x, vreinterpretq_f32_s32(veorq_s32(vreinterpretq_s32_f32(t), vandq_s32(vreinterpretq_s32_f32(x), sign))));\n\t\ty = vaddq_f32(y, vreinterpretq_f32_s32(veorq_s32(vreinterpretq_s32_f32(t), vandq_s32(vreinterpretq_s32_f32(y), sign))));\n\n\t\t// compute normal length & scale\n\t\tfloat32x4_t ll = vfmaq_f32(vfmaq_f32(vmulq_f32(x, x), y, y), z, z);\n#if !defined(__aarch64__) && !(defined(_M_ARM64) || defined(_M_ARM64EC))\n\t\tfloat32x4_t rl = vrsqrteq_f32(ll);\n\t\trl = vmulq_f32(rl, vrsqrtsq_f32(vmulq_f32(rl, ll), rl)); // refine rsqrt estimate\n\t\tfloat32x4_t s = vmulq_f32(vdupq_n_f32(32767.f), rl);\n#else\n\t\tfloat32x4_t s = vdivq_f32(vdupq_n_f32(32767.f), vsqrtq_f32(ll));\n#endif\n\n\t\t// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value\n\t\t// note: the result is offset by 0x4B40_0000, but we only need the low 16 bits so we can omit the subtraction\n\t\tconst float32x4_t fsnap = vdupq_n_f32(3 << 22);\n\n\t\tint32x4_t xr = vreinterpretq_s32_f32(vfmaq_f32(fsnap, x, s));\n\t\tint32x4_t yr = vreinterpretq_s32_f32(vfmaq_f32(fsnap, y, s));\n\t\tint32x4_t zr = vreinterpretq_s32_f32(vfmaq_f32(fsnap, z, s));\n\n\t\t// mix x/z and y/0 to make 16-bit unpack easier\n\t\tint32x4_t xzr = vsliq_n_s32(xr, zr, 16);\n\t\tint32x4_t y0r = vandq_s32(yr, vdupq_n_s32(0xffff));\n\n\t\t// pack x/y/z using 16-bit unpacks; note that this has 0 where we should have .w\n\t\tint32x4_t res_0 = vreinterpretq_s32_s16(vzipq_s16(vreinterpretq_s16_s32(xzr), vreinterpretq_s16_s32(y0r)).val[0]);\n\t\tint32x4_t res_1 = vreinterpretq_s32_s16(vzipq_s16(vreinterpretq_s16_s32(xzr), vreinterpretq_s16_s32(y0r)).val[1]);\n\n\t\t// patch in .w\n\t\tres_0 = vbslq_s32(vreinterpretq_u32_u64(vdupq_n_u64(0xffff000000000000)), n4_0, res_0);\n\t\tres_1 = vbslq_s32(vreinterpretq_u32_u64(vdupq_n_u64(0xffff000000000000)), n4_1, res_1);\n\n\t\tvst1q_s32(reinterpret_cast<int32_t*>(&data[(i + 0) * 4]), res_0);\n\t\tvst1q_s32(reinterpret_cast<int32_t*>(&data[(i + 2) * 4]), res_1);\n\t}\n}\n\nstatic void decodeFilterQuatSimd(short* data, size_t count)\n{\n\tconst float scale = 32767.f / sqrtf(2.f);\n\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\tint32x4_t q4_0 = vld1q_s32(reinterpret_cast<int32_t*>(&data[(i + 0) * 4]));\n\t\tint32x4_t q4_1 = vld1q_s32(reinterpret_cast<int32_t*>(&data[(i + 2) * 4]));\n\n\t\t// gather both x/y 16-bit pairs in each 32-bit lane\n\t\tint32x4_t q4_xy = vuzpq_s32(q4_0, q4_1).val[0];\n\t\tint32x4_t q4_zc = vuzpq_s32(q4_0, q4_1).val[1];\n\n\t\t// sign-extends each of x,y in [x y] with arithmetic shifts\n\t\tint32x4_t xf = vshrq_n_s32(vshlq_n_s32(q4_xy, 16), 16);\n\t\tint32x4_t yf = vshrq_n_s32(q4_xy, 16);\n\t\tint32x4_t zf = vshrq_n_s32(vshlq_n_s32(q4_zc, 16), 16);\n\t\tint32x4_t cf = vshrq_n_s32(q4_zc, 16);\n\n\t\t// get a floating-point scaler using zc with bottom 2 bits set to 1 (which represents 1.f)\n\t\tint32x4_t sf = vorrq_s32(cf, vdupq_n_s32(3));\n\t\tfloat32x4_t s = vcvtq_f32_s32(sf);\n\n\t\t// convert x/y/z to floating point (unscaled! implied scale of 1/sqrt(2.f) * 1/sf)\n\t\tfloat32x4_t x = vcvtq_f32_s32(xf);\n\t\tfloat32x4_t y = vcvtq_f32_s32(yf);\n\t\tfloat32x4_t z = vcvtq_f32_s32(zf);\n\n\t\t// reconstruct w as a square root (unscaled); we clamp to 0.f to avoid NaN due to precision errors\n\t\tfloat32x4_t ws = vmulq_f32(s, s);\n\t\tfloat32x4_t ww = vsubq_f32(vaddq_f32(ws, ws), vfmaq_f32(vfmaq_f32(vmulq_f32(x, x), y, y), z, z));\n\t\tfloat32x4_t w = vsqrtq_f32(vmaxq_f32(ww, vdupq_n_f32(0.f)));\n\n\t\t// compute final scale; note that all computations above are unscaled\n\t\t// we need to divide by sf to get out of fixed point, divide by sqrt(2) to renormalize and multiply by 32767 to get to int16 range\n\t\tfloat32x4_t ss = vdivq_f32(vdupq_n_f32(scale), s);\n\n\t\t// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value\n\t\t// note: the result is offset by 0x4B40_0000, but we only need the low 16 bits so we can omit the subtraction\n\t\tconst float32x4_t fsnap = vdupq_n_f32(3 << 22);\n\n\t\tint32x4_t xr = vreinterpretq_s32_f32(vfmaq_f32(fsnap, x, ss));\n\t\tint32x4_t yr = vreinterpretq_s32_f32(vfmaq_f32(fsnap, y, ss));\n\t\tint32x4_t zr = vreinterpretq_s32_f32(vfmaq_f32(fsnap, z, ss));\n\t\tint32x4_t wr = vreinterpretq_s32_f32(vfmaq_f32(fsnap, w, ss));\n\n\t\t// mix x/z and w/y to make 16-bit unpack easier\n\t\tint32x4_t xzr = vsliq_n_s32(xr, zr, 16);\n\t\tint32x4_t wyr = vsliq_n_s32(wr, yr, 16);\n\n\t\t// pack x/y/z/w using 16-bit unpacks; we pack wxyz by default (for qc=0)\n\t\tuint64x2_t res_0 = vreinterpretq_u64_s16(vzipq_s16(vreinterpretq_s16_s32(wyr), vreinterpretq_s16_s32(xzr)).val[0]);\n\t\tuint64x2_t res_1 = vreinterpretq_u64_s16(vzipq_s16(vreinterpretq_s16_s32(wyr), vreinterpretq_s16_s32(xzr)).val[1]);\n\n\t\t// store results to stack so that we can rotate using scalar instructions\n\t\t// TODO: volatile works around LLVM mis-optimizing code; https://github.com/llvm/llvm-project/issues/166808\n\t\tvolatile uint64_t res[4];\n\t\tvst1q_u64(const_cast<uint64_t*>(&res[0]), res_0);\n\t\tvst1q_u64(const_cast<uint64_t*>(&res[2]), res_1);\n\n\t\t// rotate and store\n\t\tuint64_t* out = reinterpret_cast<uint64_t*>(&data[i * 4]);\n\n\t\tout[0] = rotateleft64(res[0], data[(i + 0) * 4 + 3] << 4);\n\t\tout[1] = rotateleft64(res[1], data[(i + 1) * 4 + 3] << 4);\n\t\tout[2] = rotateleft64(res[2], data[(i + 2) * 4 + 3] << 4);\n\t\tout[3] = rotateleft64(res[3], data[(i + 3) * 4 + 3] << 4);\n\t}\n}\n\nstatic void decodeFilterExpSimd(unsigned int* data, size_t count)\n{\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\tint32x4_t v = vld1q_s32(reinterpret_cast<int32_t*>(&data[i]));\n\n\t\t// decode exponent into 2^x directly\n\t\tint32x4_t ef = vshrq_n_s32(v, 24);\n\t\tint32x4_t es = vshlq_n_s32(vaddq_s32(ef, vdupq_n_s32(127)), 23);\n\n\t\t// decode 24-bit mantissa into floating-point value\n\t\tint32x4_t mf = vshrq_n_s32(vshlq_n_s32(v, 8), 8);\n\t\tfloat32x4_t m = vcvtq_f32_s32(mf);\n\n\t\tfloat32x4_t r = vmulq_f32(vreinterpretq_f32_s32(es), m);\n\n\t\tvst1q_f32(reinterpret_cast<float*>(&data[i]), r);\n\t}\n}\n\nstatic void decodeFilterColorSimd8(unsigned char* data, size_t count)\n{\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\tint32x4_t c4 = vld1q_s32(reinterpret_cast<int32_t*>(&data[i * 4]));\n\n\t\t// unpack y/co/cg/a (co/cg are sign extended with arithmetic shifts)\n\t\tint32x4_t yf = vandq_s32(c4, vdupq_n_s32(0xff));\n\t\tint32x4_t cof = vshrq_n_s32(vshlq_n_s32(c4, 16), 24);\n\t\tint32x4_t cgf = vshrq_n_s32(vshlq_n_s32(c4, 8), 24);\n\t\tint32x4_t af = vreinterpretq_s32_u32(vshrq_n_u32(vreinterpretq_u32_s32(c4), 24));\n\n\t\t// recover scale from alpha high bit\n\t\tint32x4_t as = af;\n\t\tas = vorrq_s32(as, vshrq_n_s32(as, 1));\n\t\tas = vorrq_s32(as, vshrq_n_s32(as, 2));\n\t\tas = vorrq_s32(as, vshrq_n_s32(as, 4));\n\n\t\t// expand alpha by one bit to match other components\n\t\taf = vorrq_s32(vandq_s32(vshlq_n_s32(af, 1), as), vandq_s32(af, vdupq_n_s32(1)));\n\n\t\t// compute scaling factor\n\t\tfloat32x4_t ss = vmulq_f32(vdupq_n_f32(255.f), vrecpeq_f32(vcvtq_f32_s32(as)));\n\n\t\t// convert to RGB in fixed point\n\t\tint32x4_t rf = vaddq_s32(yf, vsubq_s32(cof, cgf));\n\t\tint32x4_t gf = vaddq_s32(yf, cgf);\n\t\tint32x4_t bf = vsubq_s32(yf, vaddq_s32(cof, cgf));\n\n\t\t// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value\n\t\t// note: the result is offset by 0x4B40_0000, but we only need the low 8 bits so we can omit the subtraction\n\t\tconst float32x4_t fsnap = vdupq_n_f32(3 << 22);\n\n\t\tint32x4_t rr = vreinterpretq_s32_f32(vfmaq_f32(fsnap, vcvtq_f32_s32(rf), ss));\n\t\tint32x4_t gr = vreinterpretq_s32_f32(vfmaq_f32(fsnap, vcvtq_f32_s32(gf), ss));\n\t\tint32x4_t br = vreinterpretq_s32_f32(vfmaq_f32(fsnap, vcvtq_f32_s32(bf), ss));\n\t\tint32x4_t ar = vreinterpretq_s32_f32(vfmaq_f32(fsnap, vcvtq_f32_s32(af), ss));\n\n\t\t// repack rgba into final value\n\t\tint32x4_t res = vsliq_n_s32(rr, vsliq_n_s32(gr, vsliq_n_s32(br, ar, 8), 8), 8);\n\n\t\tvst1q_s32(reinterpret_cast<int32_t*>(&data[i * 4]), res);\n\t}\n}\n\nstatic void decodeFilterColorSimd16(unsigned short* data, size_t count)\n{\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\tint32x4_t c4_0 = vld1q_s32(reinterpret_cast<int32_t*>(&data[(i + 0) * 4]));\n\t\tint32x4_t c4_1 = vld1q_s32(reinterpret_cast<int32_t*>(&data[(i + 2) * 4]));\n\n\t\t// gather both y/co 16-bit pairs in each 32-bit lane\n\t\tint32x4_t c4_yco = vuzpq_s32(c4_0, c4_1).val[0];\n\t\tint32x4_t c4_cga = vuzpq_s32(c4_0, c4_1).val[1];\n\n\t\t// unpack y/co/cg/a components (co/cg are sign extended with arithmetic shifts)\n\t\tint32x4_t yf = vandq_s32(c4_yco, vdupq_n_s32(0xffff));\n\t\tint32x4_t cof = vshrq_n_s32(c4_yco, 16);\n\t\tint32x4_t cgf = vshrq_n_s32(vshlq_n_s32(c4_cga, 16), 16);\n\t\tint32x4_t af = vreinterpretq_s32_u32(vshrq_n_u32(vreinterpretq_u32_s32(c4_cga), 16));\n\n\t\t// recover scale from alpha high bit\n\t\tint32x4_t as = af;\n\t\tas = vorrq_s32(as, vshrq_n_s32(as, 1));\n\t\tas = vorrq_s32(as, vshrq_n_s32(as, 2));\n\t\tas = vorrq_s32(as, vshrq_n_s32(as, 4));\n\t\tas = vorrq_s32(as, vshrq_n_s32(as, 8));\n\n\t\t// expand alpha by one bit to match other components\n\t\taf = vorrq_s32(vandq_s32(vshlq_n_s32(af, 1), as), vandq_s32(af, vdupq_n_s32(1)));\n\n\t\t// compute scaling factor\n\t\tfloat32x4_t ss = vdivq_f32(vdupq_n_f32(65535.f), vcvtq_f32_s32(as));\n\n\t\t// convert to RGB in fixed point\n\t\tint32x4_t rf = vaddq_s32(yf, vsubq_s32(cof, cgf));\n\t\tint32x4_t gf = vaddq_s32(yf, cgf);\n\t\tint32x4_t bf = vsubq_s32(yf, vaddq_s32(cof, cgf));\n\n\t\t// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value\n\t\t// note: the result is offset by 0x4B40_0000, but we only need the low 16 bits so we can omit the subtraction\n\t\tconst float32x4_t fsnap = vdupq_n_f32(3 << 22);\n\n\t\tint32x4_t rr = vreinterpretq_s32_f32(vfmaq_f32(fsnap, vcvtq_f32_s32(rf), ss));\n\t\tint32x4_t gr = vreinterpretq_s32_f32(vfmaq_f32(fsnap, vcvtq_f32_s32(gf), ss));\n\t\tint32x4_t br = vreinterpretq_s32_f32(vfmaq_f32(fsnap, vcvtq_f32_s32(bf), ss));\n\t\tint32x4_t ar = vreinterpretq_s32_f32(vfmaq_f32(fsnap, vcvtq_f32_s32(af), ss));\n\n\t\t// mix r/b and g/a to make 16-bit unpack easier\n\t\tint32x4_t rbr = vsliq_n_s32(rr, br, 16);\n\t\tint32x4_t gar = vsliq_n_s32(gr, ar, 16);\n\n\t\t// pack r/g/b/a using 16-bit unpacks\n\t\tint32x4_t res_0 = vreinterpretq_s32_s16(vzipq_s16(vreinterpretq_s16_s32(rbr), vreinterpretq_s16_s32(gar)).val[0]);\n\t\tint32x4_t res_1 = vreinterpretq_s32_s16(vzipq_s16(vreinterpretq_s16_s32(rbr), vreinterpretq_s16_s32(gar)).val[1]);\n\n\t\tvst1q_s32(reinterpret_cast<int32_t*>(&data[(i + 0) * 4]), res_0);\n\t\tvst1q_s32(reinterpret_cast<int32_t*>(&data[(i + 2) * 4]), res_1);\n\t}\n}\n#endif\n\n#ifdef SIMD_WASM\nstatic void decodeFilterOctSimd8(signed char* data, size_t count)\n{\n\tconst v128_t sign = wasm_f32x4_splat(-0.f);\n\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\tv128_t n4 = wasm_v128_load(&data[i * 4]);\n\n\t\t// sign-extends each of x,y in [x y ? ?] with arithmetic shifts\n\t\tv128_t xf = wasm_i32x4_shr(wasm_i32x4_shl(n4, 24), 24);\n\t\tv128_t yf = wasm_i32x4_shr(wasm_i32x4_shl(n4, 16), 24);\n\n\t\t// unpack z; note that z is unsigned so we technically don't need to sign extend it\n\t\tv128_t zf = wasm_i32x4_shr(wasm_i32x4_shl(n4, 8), 24);\n\n\t\t// convert x and y to floats and reconstruct z; this assumes zf encodes 1.f at the same bit count\n\t\tv128_t x = wasm_f32x4_convert_i32x4(xf);\n\t\tv128_t y = wasm_f32x4_convert_i32x4(yf);\n\t\tv128_t z = wasm_f32x4_sub(wasm_f32x4_convert_i32x4(zf), wasm_f32x4_add(wasm_f32x4_abs(x), wasm_f32x4_abs(y)));\n\n\t\t// fixup octahedral coordinates for z<0\n\t\t// note: i32x4_min with 0 is equvalent to f32x4_min\n\t\tv128_t t = wasm_i32x4_min(z, wasm_i32x4_splat(0));\n\n\t\tx = wasm_f32x4_add(x, wasm_v128_xor(t, wasm_v128_and(x, sign)));\n\t\ty = wasm_f32x4_add(y, wasm_v128_xor(t, wasm_v128_and(y, sign)));\n\n\t\t// compute normal length & scale\n\t\tv128_t ll = wasm_f32x4_add(wasm_f32x4_mul(x, x), wasm_f32x4_add(wasm_f32x4_mul(y, y), wasm_f32x4_mul(z, z)));\n\t\tv128_t s = wasm_f32x4_div(wasm_f32x4_splat(127.f), wasm_f32x4_sqrt(ll));\n\n\t\t// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value\n\t\t// note: the result is offset by 0x4B40_0000, but we only need the low 8 bits so we can omit the subtraction\n\t\tconst v128_t fsnap = wasm_f32x4_splat(3 << 22);\n\n\t\tv128_t xr = wasm_f32x4_add(wasm_f32x4_mul(x, s), fsnap);\n\t\tv128_t yr = wasm_f32x4_add(wasm_f32x4_mul(y, s), fsnap);\n\t\tv128_t zr = wasm_f32x4_add(wasm_f32x4_mul(z, s), fsnap);\n\n\t\t// combine xr/yr/zr into final value\n\t\tv128_t res = wasm_v128_and(n4, wasm_i32x4_splat(0xff000000));\n\t\tres = wasm_v128_or(res, wasm_v128_and(xr, wasm_i32x4_splat(0xff)));\n\t\tres = wasm_v128_or(res, wasm_i32x4_shl(wasm_v128_and(yr, wasm_i32x4_splat(0xff)), 8));\n\t\tres = wasm_v128_or(res, wasm_i32x4_shl(wasm_v128_and(zr, wasm_i32x4_splat(0xff)), 16));\n\n\t\twasm_v128_store(&data[i * 4], res);\n\t}\n}\n\nstatic void decodeFilterOctSimd16(short* data, size_t count)\n{\n\tconst v128_t sign = wasm_f32x4_splat(-0.f);\n\t// TODO: volatile here works around LLVM mis-optimizing code; https://github.com/llvm/llvm-project/issues/149457\n\tvolatile v128_t zmask = wasm_i32x4_splat(0x7fff);\n\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\tv128_t n4_0 = wasm_v128_load(&data[(i + 0) * 4]);\n\t\tv128_t n4_1 = wasm_v128_load(&data[(i + 2) * 4]);\n\n\t\t// gather both x/y 16-bit pairs in each 32-bit lane\n\t\tv128_t n4 = wasmx_unziplo_v32x4(n4_0, n4_1);\n\n\t\t// sign-extends each of x,y in [x y] with arithmetic shifts\n\t\tv128_t xf = wasm_i32x4_shr(wasm_i32x4_shl(n4, 16), 16);\n\t\tv128_t yf = wasm_i32x4_shr(n4, 16);\n\n\t\t// unpack z; note that z is unsigned so we don't need to sign extend it\n\t\tv128_t z4 = wasmx_unziphi_v32x4(n4_0, n4_1);\n\t\tv128_t zf = wasm_v128_and(z4, zmask);\n\n\t\t// convert x and y to floats and reconstruct z; this assumes zf encodes 1.f at the same bit count\n\t\tv128_t x = wasm_f32x4_convert_i32x4(xf);\n\t\tv128_t y = wasm_f32x4_convert_i32x4(yf);\n\t\tv128_t z = wasm_f32x4_sub(wasm_f32x4_convert_i32x4(zf), wasm_f32x4_add(wasm_f32x4_abs(x), wasm_f32x4_abs(y)));\n\n\t\t// fixup octahedral coordinates for z<0\n\t\t// note: i32x4_min with 0 is equvalent to f32x4_min\n\t\tv128_t t = wasm_i32x4_min(z, wasm_i32x4_splat(0));\n\n\t\tx = wasm_f32x4_add(x, wasm_v128_xor(t, wasm_v128_and(x, sign)));\n\t\ty = wasm_f32x4_add(y, wasm_v128_xor(t, wasm_v128_and(y, sign)));\n\n\t\t// compute normal length & scale\n\t\tv128_t ll = wasm_f32x4_add(wasm_f32x4_mul(x, x), wasm_f32x4_add(wasm_f32x4_mul(y, y), wasm_f32x4_mul(z, z)));\n\t\tv128_t s = wasm_f32x4_div(wasm_f32x4_splat(32767.f), wasm_f32x4_sqrt(ll));\n\n\t\t// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value\n\t\t// note: the result is offset by 0x4B40_0000, but we only need the low 16 bits so we can omit the subtraction\n\t\tconst v128_t fsnap = wasm_f32x4_splat(3 << 22);\n\n\t\tv128_t xr = wasm_f32x4_add(wasm_f32x4_mul(x, s), fsnap);\n\t\tv128_t yr = wasm_f32x4_add(wasm_f32x4_mul(y, s), fsnap);\n\t\tv128_t zr = wasm_f32x4_add(wasm_f32x4_mul(z, s), fsnap);\n\n\t\t// mix x/z and y/0 to make 16-bit unpack easier\n\t\tv128_t xzr = wasm_v128_or(wasm_v128_and(xr, wasm_i32x4_splat(0xffff)), wasm_i32x4_shl(zr, 16));\n\t\tv128_t y0r = wasm_v128_and(yr, wasm_i32x4_splat(0xffff));\n\n\t\t// pack x/y/z using 16-bit unpacks; note that this has 0 where we should have .w\n\t\tv128_t res_0 = wasmx_unpacklo_v16x8(xzr, y0r);\n\t\tv128_t res_1 = wasmx_unpackhi_v16x8(xzr, y0r);\n\n\t\t// patch in .w\n\t\tres_0 = wasm_v128_or(res_0, wasm_v128_and(n4_0, wasm_i64x2_splat(0xffff000000000000)));\n\t\tres_1 = wasm_v128_or(res_1, wasm_v128_and(n4_1, wasm_i64x2_splat(0xffff000000000000)));\n\n\t\twasm_v128_store(&data[(i + 0) * 4], res_0);\n\t\twasm_v128_store(&data[(i + 2) * 4], res_1);\n\t}\n}\n\nstatic void decodeFilterQuatSimd(short* data, size_t count)\n{\n\tconst float scale = 32767.f / sqrtf(2.f);\n\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\tv128_t q4_0 = wasm_v128_load(&data[(i + 0) * 4]);\n\t\tv128_t q4_1 = wasm_v128_load(&data[(i + 2) * 4]);\n\n\t\t// gather both x/y 16-bit pairs in each 32-bit lane\n\t\tv128_t q4_xy = wasmx_unziplo_v32x4(q4_0, q4_1);\n\t\tv128_t q4_zc = wasmx_unziphi_v32x4(q4_0, q4_1);\n\n\t\t// sign-extends each of x,y in [x y] with arithmetic shifts\n\t\tv128_t xf = wasm_i32x4_shr(wasm_i32x4_shl(q4_xy, 16), 16);\n\t\tv128_t yf = wasm_i32x4_shr(q4_xy, 16);\n\t\tv128_t zf = wasm_i32x4_shr(wasm_i32x4_shl(q4_zc, 16), 16);\n\t\tv128_t cf = wasm_i32x4_shr(q4_zc, 16);\n\n\t\t// get a floating-point scaler using zc with bottom 2 bits set to 1 (which represents 1.f)\n\t\tv128_t sf = wasm_v128_or(cf, wasm_i32x4_splat(3));\n\t\tv128_t s = wasm_f32x4_convert_i32x4(sf);\n\n\t\t// convert x/y/z to floating point (unscaled! implied scale of 1/sqrt(2.f) * 1/sf)\n\t\tv128_t x = wasm_f32x4_convert_i32x4(xf);\n\t\tv128_t y = wasm_f32x4_convert_i32x4(yf);\n\t\tv128_t z = wasm_f32x4_convert_i32x4(zf);\n\n\t\t// reconstruct w as a square root (unscaled); we clamp to 0.f to avoid NaN due to precision errors\n\t\t// note: i32x4_max with 0 is equivalent to f32x4_max\n\t\tv128_t ws = wasm_f32x4_mul(s, s);\n\t\tv128_t ww = wasm_f32x4_sub(wasm_f32x4_add(ws, ws), wasm_f32x4_add(wasm_f32x4_mul(x, x), wasm_f32x4_add(wasm_f32x4_mul(y, y), wasm_f32x4_mul(z, z))));\n\t\tv128_t w = wasm_f32x4_sqrt(wasm_i32x4_max(ww, wasm_i32x4_splat(0)));\n\n\t\t// compute final scale; note that all computations above are unscaled\n\t\t// we need to divide by sf to get out of fixed point, divide by sqrt(2) to renormalize and multiply by 32767 to get to int16 range\n\t\tv128_t ss = wasm_f32x4_div(wasm_f32x4_splat(scale), s);\n\n\t\t// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value\n\t\t// note: the result is offset by 0x4B40_0000, but we only need the low 16 bits so we can omit the subtraction\n\t\tconst v128_t fsnap = wasm_f32x4_splat(3 << 22);\n\n\t\tv128_t xr = wasm_f32x4_add(wasm_f32x4_mul(x, ss), fsnap);\n\t\tv128_t yr = wasm_f32x4_add(wasm_f32x4_mul(y, ss), fsnap);\n\t\tv128_t zr = wasm_f32x4_add(wasm_f32x4_mul(z, ss), fsnap);\n\t\tv128_t wr = wasm_f32x4_add(wasm_f32x4_mul(w, ss), fsnap);\n\n\t\t// mix x/z and w/y to make 16-bit unpack easier\n\t\tv128_t xzr = wasm_v128_or(wasm_v128_and(xr, wasm_i32x4_splat(0xffff)), wasm_i32x4_shl(zr, 16));\n\t\tv128_t wyr = wasm_v128_or(wasm_v128_and(wr, wasm_i32x4_splat(0xffff)), wasm_i32x4_shl(yr, 16));\n\n\t\t// pack x/y/z/w using 16-bit unpacks; we pack wxyz by default (for qc=0)\n\t\tv128_t res_0 = wasmx_unpacklo_v16x8(wyr, xzr);\n\t\tv128_t res_1 = wasmx_unpackhi_v16x8(wyr, xzr);\n\n\t\t// compute component index shifted left by 4 (and moved into i32x4 slot)\n\t\tv128_t cm = wasm_i32x4_shl(cf, 4);\n\n\t\t// rotate and store\n\t\tuint64_t* out = reinterpret_cast<uint64_t*>(&data[i * 4]);\n\n\t\tout[0] = rotateleft64(wasm_i64x2_extract_lane(res_0, 0), wasm_i32x4_extract_lane(cm, 0));\n\t\tout[1] = rotateleft64(wasm_i64x2_extract_lane(res_0, 1), wasm_i32x4_extract_lane(cm, 1));\n\t\tout[2] = rotateleft64(wasm_i64x2_extract_lane(res_1, 0), wasm_i32x4_extract_lane(cm, 2));\n\t\tout[3] = rotateleft64(wasm_i64x2_extract_lane(res_1, 1), wasm_i32x4_extract_lane(cm, 3));\n\t}\n}\n\nstatic void decodeFilterExpSimd(unsigned int* data, size_t count)\n{\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\tv128_t v = wasm_v128_load(&data[i]);\n\n\t\t// decode exponent into 2^x directly\n\t\tv128_t ef = wasm_i32x4_shr(v, 24);\n\t\tv128_t es = wasm_i32x4_shl(wasm_i32x4_add(ef, wasm_i32x4_splat(127)), 23);\n\n\t\t// decode 24-bit mantissa into floating-point value\n\t\tv128_t mf = wasm_i32x4_shr(wasm_i32x4_shl(v, 8), 8);\n\t\tv128_t m = wasm_f32x4_convert_i32x4(mf);\n\n\t\tv128_t r = wasm_f32x4_mul(es, m);\n\n\t\twasm_v128_store(&data[i], r);\n\t}\n}\n\nstatic void decodeFilterColorSimd8(unsigned char* data, size_t count)\n{\n\t// TODO: volatile here works around LLVM mis-optimizing code; https://github.com/llvm/llvm-project/issues/149457\n\tvolatile v128_t zero = wasm_i32x4_splat(0);\n\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\tv128_t c4 = wasm_v128_load(&data[i * 4]);\n\n\t\t// unpack y/co/cg/a (co/cg are sign extended with arithmetic shifts)\n\t\tv128_t yf = wasm_v128_and(c4, wasm_i32x4_splat(0xff));\n\t\tv128_t cof = wasm_i32x4_shr(wasm_i32x4_shl(c4, 16), 24);\n\t\tv128_t cgf = wasm_i32x4_shr(wasm_i32x4_shl(c4, 8), 24);\n\t\tv128_t af = wasm_v128_or(zero, wasm_u32x4_shr(c4, 24));\n\n\t\t// recover scale from alpha high bit\n\t\tv128_t as = af;\n\t\tas = wasm_v128_or(as, wasm_i32x4_shr(as, 1));\n\t\tas = wasm_v128_or(as, wasm_i32x4_shr(as, 2));\n\t\tas = wasm_v128_or(as, wasm_i32x4_shr(as, 4));\n\n\t\t// expand alpha by one bit to match other components\n\t\taf = wasm_v128_or(wasm_v128_and(wasm_i32x4_shl(af, 1), as), wasm_v128_and(af, wasm_i32x4_splat(1)));\n\n\t\t// compute scaling factor\n\t\tv128_t ss = wasm_f32x4_div(wasm_f32x4_splat(255.f), wasm_f32x4_convert_i32x4(as));\n\n\t\t// convert to RGB in fixed point\n\t\tv128_t rf = wasm_i32x4_add(yf, wasm_i32x4_sub(cof, cgf));\n\t\tv128_t gf = wasm_i32x4_add(yf, cgf);\n\t\tv128_t bf = wasm_i32x4_sub(yf, wasm_i32x4_add(cof, cgf));\n\n\t\t// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value\n\t\t// note: the result is offset by 0x4B40_0000, but we only need the low 8 bits so we can omit the subtraction\n\t\tconst v128_t fsnap = wasm_f32x4_splat(3 << 22);\n\n\t\tv128_t rr = wasm_f32x4_add(wasm_f32x4_mul(wasm_f32x4_convert_i32x4(rf), ss), fsnap);\n\t\tv128_t gr = wasm_f32x4_add(wasm_f32x4_mul(wasm_f32x4_convert_i32x4(gf), ss), fsnap);\n\t\tv128_t br = wasm_f32x4_add(wasm_f32x4_mul(wasm_f32x4_convert_i32x4(bf), ss), fsnap);\n\t\tv128_t ar = wasm_f32x4_add(wasm_f32x4_mul(wasm_f32x4_convert_i32x4(af), ss), fsnap);\n\n\t\t// repack rgba into final value\n\t\tv128_t res = wasm_v128_and(rr, wasm_i32x4_splat(0xff));\n\t\tres = wasm_v128_or(res, wasm_i32x4_shl(wasm_v128_and(gr, wasm_i32x4_splat(0xff)), 8));\n\t\tres = wasm_v128_or(res, wasm_i32x4_shl(wasm_v128_and(br, wasm_i32x4_splat(0xff)), 16));\n\t\tres = wasm_v128_or(res, wasm_i32x4_shl(ar, 24));\n\n\t\twasm_v128_store(&data[i * 4], res);\n\t}\n}\n\nstatic void decodeFilterColorSimd16(unsigned short* data, size_t count)\n{\n\t// TODO: volatile here works around LLVM mis-optimizing code; https://github.com/llvm/llvm-project/issues/149457\n\tvolatile v128_t zero = wasm_i32x4_splat(0);\n\n\tfor (size_t i = 0; i < count; i += 4)\n\t{\n\t\tv128_t c4_0 = wasm_v128_load(&data[(i + 0) * 4]);\n\t\tv128_t c4_1 = wasm_v128_load(&data[(i + 2) * 4]);\n\n\t\t// gather both y/co 16-bit pairs in each 32-bit lane\n\t\tv128_t c4_yco = wasmx_unziplo_v32x4(c4_0, c4_1);\n\t\tv128_t c4_cga = wasmx_unziphi_v32x4(c4_0, c4_1);\n\n\t\t// unpack y/co/cg/a components (co/cg are sign extended with arithmetic shifts)\n\t\tv128_t yf = wasm_v128_and(c4_yco, wasm_i32x4_splat(0xffff));\n\t\tv128_t cof = wasm_i32x4_shr(c4_yco, 16);\n\t\tv128_t cgf = wasm_i32x4_shr(wasm_i32x4_shl(c4_cga, 16), 16);\n\t\tv128_t af = wasm_v128_or(zero, wasm_u32x4_shr(c4_cga, 16));\n\n\t\t// recover scale from alpha high bit\n\t\tv128_t as = af;\n\t\tas = wasm_v128_or(as, wasm_i32x4_shr(as, 1));\n\t\tas = wasm_v128_or(as, wasm_i32x4_shr(as, 2));\n\t\tas = wasm_v128_or(as, wasm_i32x4_shr(as, 4));\n\t\tas = wasm_v128_or(as, wasm_i32x4_shr(as, 8));\n\n\t\t// expand alpha by one bit to match other components\n\t\taf = wasm_v128_or(wasm_v128_and(wasm_i32x4_shl(af, 1), as), wasm_v128_and(af, wasm_i32x4_splat(1)));\n\n\t\t// compute scaling factor\n\t\tv128_t ss = wasm_f32x4_div(wasm_f32x4_splat(65535.f), wasm_f32x4_convert_i32x4(as));\n\n\t\t// convert to RGB in fixed point\n\t\tv128_t rf = wasm_i32x4_add(yf, wasm_i32x4_sub(cof, cgf));\n\t\tv128_t gf = wasm_i32x4_add(yf, cgf);\n\t\tv128_t bf = wasm_i32x4_sub(yf, wasm_i32x4_add(cof, cgf));\n\n\t\t// fast rounded signed float->int: addition triggers renormalization after which mantissa stores the integer value\n\t\t// note: the result is offset by 0x4B40_0000, but we only need the low 16 bits so we can omit the subtraction\n\t\tconst v128_t fsnap = wasm_f32x4_splat(3 << 22);\n\n\t\tv128_t rr = wasm_f32x4_add(wasm_f32x4_mul(wasm_f32x4_convert_i32x4(rf), ss), fsnap);\n\t\tv128_t gr = wasm_f32x4_add(wasm_f32x4_mul(wasm_f32x4_convert_i32x4(gf), ss), fsnap);\n\t\tv128_t br = wasm_f32x4_add(wasm_f32x4_mul(wasm_f32x4_convert_i32x4(bf), ss), fsnap);\n\t\tv128_t ar = wasm_f32x4_add(wasm_f32x4_mul(wasm_f32x4_convert_i32x4(af), ss), fsnap);\n\n\t\t// mix r/b and g/a to make 16-bit unpack easier\n\t\tv128_t rbr = wasm_v128_or(wasm_v128_and(rr, wasm_i32x4_splat(0xffff)), wasm_i32x4_shl(br, 16));\n\t\tv128_t gar = wasm_v128_or(wasm_v128_and(gr, wasm_i32x4_splat(0xffff)), wasm_i32x4_shl(ar, 16));\n\n\t\t// pack r/g/b/a using 16-bit unpacks\n\t\tv128_t res_0 = wasmx_unpacklo_v16x8(rbr, gar);\n\t\tv128_t res_1 = wasmx_unpackhi_v16x8(rbr, gar);\n\n\t\twasm_v128_store(&data[(i + 0) * 4], res_0);\n\t\twasm_v128_store(&data[(i + 2) * 4], res_1);\n\t}\n}\n#endif\n\n// optimized variant of frexp\ninline int optlog2(float v)\n{\n\tunion\n\t{\n\t\tfloat f;\n\t\tunsigned int ui;\n\t} u;\n\n\tu.f = v;\n\t// +1 accounts for implicit 1. in mantissa; denormalized numbers will end up clamped to min_exp by calling code\n\treturn v == 0 ? 0 : int((u.ui >> 23) & 0xff) - 127 + 1;\n}\n\n// optimized variant of ldexp\ninline float optexp2(int e)\n{\n\tunion\n\t{\n\t\tfloat f;\n\t\tunsigned int ui;\n\t} u;\n\n\tu.ui = unsigned(e + 127) << 23;\n\treturn u.f;\n}\n\n} // namespace meshopt\n\nvoid meshopt_decodeFilterOct(void* buffer, size_t count, size_t stride)\n{\n\tusing namespace meshopt;\n\n\tassert(stride == 4 || stride == 8);\n\n#if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM)\n\tif (stride == 4)\n\t\tdispatchSimd(decodeFilterOctSimd8, static_cast<signed char*>(buffer), count, 4);\n\telse\n\t\tdispatchSimd(decodeFilterOctSimd16, static_cast<short*>(buffer), count, 4);\n#else\n\tif (stride == 4)\n\t\tdecodeFilterOct(static_cast<signed char*>(buffer), count);\n\telse\n\t\tdecodeFilterOct(static_cast<short*>(buffer), count);\n#endif\n}\n\nvoid meshopt_decodeFilterQuat(void* buffer, size_t count, size_t stride)\n{\n\tusing namespace meshopt;\n\n\tassert(stride == 8);\n\t(void)stride;\n\n#if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM)\n\tdispatchSimd(decodeFilterQuatSimd, static_cast<short*>(buffer), count, 4);\n#else\n\tdecodeFilterQuat(static_cast<short*>(buffer), count);\n#endif\n}\n\nvoid meshopt_decodeFilterExp(void* buffer, size_t count, size_t stride)\n{\n\tusing namespace meshopt;\n\n\tassert(stride > 0 && stride % 4 == 0);\n\n#if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM)\n\tdispatchSimd(decodeFilterExpSimd, static_cast<unsigned int*>(buffer), count * (stride / 4), 1);\n#else\n\tdecodeFilterExp(static_cast<unsigned int*>(buffer), count * (stride / 4));\n#endif\n}\n\nvoid meshopt_decodeFilterColor(void* buffer, size_t count, size_t stride)\n{\n\tusing namespace meshopt;\n\n\tassert(stride == 4 || stride == 8);\n\n#if defined(SIMD_SSE) || defined(SIMD_NEON) || defined(SIMD_WASM)\n\tif (stride == 4)\n\t\tdispatchSimd(decodeFilterColorSimd8, static_cast<unsigned char*>(buffer), count, 4);\n\telse\n\t\tdispatchSimd(decodeFilterColorSimd16, static_cast<unsigned short*>(buffer), count, 4);\n#else\n\tif (stride == 4)\n\t\tdecodeFilterColor<signed char>(static_cast<unsigned char*>(buffer), count);\n\telse\n\t\tdecodeFilterColor<short>(static_cast<unsigned short*>(buffer), count);\n#endif\n}\n\nvoid meshopt_encodeFilterOct(void* destination, size_t count, size_t stride, int bits, const float* data)\n{\n\tassert(stride == 4 || stride == 8);\n\tassert(bits >= 2 && bits <= 16);\n\n\tsigned char* d8 = static_cast<signed char*>(destination);\n\tshort* d16 = static_cast<short*>(destination);\n\n\tint bytebits = int(stride * 2);\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tconst float* n = &data[i * 4];\n\n\t\t// octahedral encoding of a unit vector\n\t\tfloat nx = n[0], ny = n[1], nz = n[2], nw = n[3];\n\t\tfloat nl = fabsf(nx) + fabsf(ny) + fabsf(nz);\n\t\tfloat ns = nl == 0.f ? 0.f : 1.f / nl;\n\n\t\tnx *= ns;\n\t\tny *= ns;\n\n\t\tfloat u = (nz >= 0.f) ? nx : (1 - fabsf(ny)) * (nx >= 0.f ? 1.f : -1.f);\n\t\tfloat v = (nz >= 0.f) ? ny : (1 - fabsf(nx)) * (ny >= 0.f ? 1.f : -1.f);\n\n\t\tint fu = meshopt_quantizeSnorm(u, bits);\n\t\tint fv = meshopt_quantizeSnorm(v, bits);\n\t\tint fo = meshopt_quantizeSnorm(1.f, bits);\n\t\tint fw = meshopt_quantizeSnorm(nw, bytebits);\n\n\t\tif (stride == 4)\n\t\t{\n\t\t\td8[i * 4 + 0] = (signed char)(fu);\n\t\t\td8[i * 4 + 1] = (signed char)(fv);\n\t\t\td8[i * 4 + 2] = (signed char)(fo);\n\t\t\td8[i * 4 + 3] = (signed char)(fw);\n\t\t}\n\t\telse\n\t\t{\n\t\t\td16[i * 4 + 0] = short(fu);\n\t\t\td16[i * 4 + 1] = short(fv);\n\t\t\td16[i * 4 + 2] = short(fo);\n\t\t\td16[i * 4 + 3] = short(fw);\n\t\t}\n\t}\n}\n\nvoid meshopt_encodeFilterQuat(void* destination_, size_t count, size_t stride, int bits, const float* data)\n{\n\tassert(stride == 8);\n\tassert(bits >= 4 && bits <= 16);\n\t(void)stride;\n\n\tshort* destination = static_cast<short*>(destination_);\n\n\tconst float scaler = sqrtf(2.f);\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tconst float* q = &data[i * 4];\n\t\tshort* d = &destination[i * 4];\n\n\t\t// establish maximum quaternion component\n\t\tint qc = 0;\n\t\tqc = fabsf(q[1]) > fabsf(q[qc]) ? 1 : qc;\n\t\tqc = fabsf(q[2]) > fabsf(q[qc]) ? 2 : qc;\n\t\tqc = fabsf(q[3]) > fabsf(q[qc]) ? 3 : qc;\n\n\t\t// we use double-cover properties to discard the sign\n\t\tfloat sign = q[qc] < 0.f ? -1.f : 1.f;\n\n\t\t// note: we always encode a cyclical swizzle to be able to recover the order via rotation\n\t\td[0] = short(meshopt_quantizeSnorm(q[(qc + 1) & 3] * scaler * sign, bits));\n\t\td[1] = short(meshopt_quantizeSnorm(q[(qc + 2) & 3] * scaler * sign, bits));\n\t\td[2] = short(meshopt_quantizeSnorm(q[(qc + 3) & 3] * scaler * sign, bits));\n\t\td[3] = short((meshopt_quantizeSnorm(1.f, bits) & ~3) | qc);\n\t}\n}\n\nvoid meshopt_encodeFilterExp(void* destination_, size_t count, size_t stride, int bits, const float* data, enum meshopt_EncodeExpMode mode)\n{\n\tusing namespace meshopt;\n\n\tassert(stride > 0 && stride % 4 == 0 && stride <= 256);\n\tassert(bits >= 1 && bits <= 24);\n\n\tunsigned int* destination = static_cast<unsigned int*>(destination_);\n\tsize_t stride_float = stride / sizeof(float);\n\n\tint component_exp[64];\n\tassert(stride_float <= sizeof(component_exp) / sizeof(int));\n\n\tconst int min_exp = -100;\n\n\tif (mode == meshopt_EncodeExpSharedComponent)\n\t{\n\t\tfor (size_t j = 0; j < stride_float; ++j)\n\t\t\tcomponent_exp[j] = min_exp;\n\n\t\tfor (size_t i = 0; i < count; ++i)\n\t\t{\n\t\t\tconst float* v = &data[i * stride_float];\n\n\t\t\t// use maximum exponent to encode values; this guarantees that mantissa is [-1, 1]\n\t\t\tfor (size_t j = 0; j < stride_float; ++j)\n\t\t\t{\n\t\t\t\tint e = optlog2(v[j]);\n\n\t\t\t\tcomponent_exp[j] = (component_exp[j] < e) ? e : component_exp[j];\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tconst float* v = &data[i * stride_float];\n\t\tunsigned int* d = &destination[i * stride_float];\n\n\t\tint vector_exp = min_exp;\n\n\t\tif (mode == meshopt_EncodeExpSharedVector)\n\t\t{\n\t\t\t// use maximum exponent to encode values; this guarantees that mantissa is [-1, 1]\n\t\t\tfor (size_t j = 0; j < stride_float; ++j)\n\t\t\t{\n\t\t\t\tint e = optlog2(v[j]);\n\n\t\t\t\tvector_exp = (vector_exp < e) ? e : vector_exp;\n\t\t\t}\n\t\t}\n\t\telse if (mode == meshopt_EncodeExpSeparate)\n\t\t{\n\t\t\tfor (size_t j = 0; j < stride_float; ++j)\n\t\t\t{\n\t\t\t\tint e = optlog2(v[j]);\n\n\t\t\t\tcomponent_exp[j] = (min_exp < e) ? e : min_exp;\n\t\t\t}\n\t\t}\n\t\telse if (mode == meshopt_EncodeExpClamped)\n\t\t{\n\t\t\tfor (size_t j = 0; j < stride_float; ++j)\n\t\t\t{\n\t\t\t\tint e = optlog2(v[j]);\n\n\t\t\t\tcomponent_exp[j] = (0 < e) ? e : 0;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// the code below assumes component_exp is initialized outside of the loop\n\t\t\tassert(mode == meshopt_EncodeExpSharedComponent);\n\t\t}\n\n\t\tfor (size_t j = 0; j < stride_float; ++j)\n\t\t{\n\t\t\tint exp = (mode == meshopt_EncodeExpSharedVector) ? vector_exp : component_exp[j];\n\n\t\t\t// note that we additionally scale the mantissa to make it a K-bit signed integer (K-1 bits for magnitude)\n\t\t\texp -= (bits - 1);\n\n\t\t\t// compute renormalized rounded mantissa for each component\n\t\t\tint mmask = (1 << 24) - 1;\n\t\t\tint m = int(v[j] * optexp2(-exp) + (v[j] >= 0 ? 0.5f : -0.5f));\n\n\t\t\td[j] = (m & mmask) | (unsigned(exp) << 24);\n\t\t}\n\t}\n}\n\nvoid meshopt_encodeFilterColor(void* destination, size_t count, size_t stride, int bits, const float* data)\n{\n\tassert(stride == 4 || stride == 8);\n\tassert(bits >= 2 && bits <= 16);\n\n\tunsigned char* d8 = static_cast<unsigned char*>(destination);\n\tunsigned short* d16 = static_cast<unsigned short*>(destination);\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tconst float* c = &data[i * 4];\n\n\t\tint fr = meshopt_quantizeUnorm(c[0], bits);\n\t\tint fg = meshopt_quantizeUnorm(c[1], bits);\n\t\tint fb = meshopt_quantizeUnorm(c[2], bits);\n\n\t\t// YCoCg-R encoding with truncated Co/Cg ensures that decoding can be done using integers\n\t\tint fco = (fr - fb) / 2;\n\t\tint tmp = fb + fco;\n\t\tint fcg = (fg - tmp) / 2;\n\t\tint fy = tmp + fcg;\n\n\t\t// validate that R/G/B can be reconstructed with K bit integers\n\t\tassert(unsigned((fy + fco - fcg) | (fy + fcg) | (fy - fco - fcg)) < (1u << bits));\n\n\t\t// alpha: K-1-bit encoding with high bit set to 1\n\t\tint fa = (meshopt_quantizeUnorm(c[3], bits) >> 1) | (1 << (bits - 1));\n\n\t\tif (stride == 4)\n\t\t{\n\t\t\td8[i * 4 + 0] = (unsigned char)(fy);\n\t\t\td8[i * 4 + 1] = (unsigned char)(fco);\n\t\t\td8[i * 4 + 2] = (unsigned char)(fcg);\n\t\t\td8[i * 4 + 3] = (unsigned char)(fa);\n\t\t}\n\t\telse\n\t\t{\n\t\t\td16[i * 4 + 0] = (unsigned short)(fy);\n\t\t\td16[i * 4 + 1] = (unsigned short)(fco);\n\t\t\td16[i * 4 + 2] = (unsigned short)(fcg);\n\t\t\td16[i * 4 + 3] = (unsigned short)(fa);\n\t\t}\n\t}\n}\n\n#undef SIMD_SSE\n#undef SIMD_NEON\n#undef SIMD_WASM\n"
  },
  {
    "path": "src/vfetchoptimizer.cpp",
    "content": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h\"\n\n#include <assert.h>\n#include <string.h>\n\nsize_t meshopt_optimizeVertexFetchRemap(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count)\n{\n\tassert(index_count % 3 == 0);\n\n\tmemset(destination, -1, vertex_count * sizeof(unsigned int));\n\n\tunsigned int next_vertex = 0;\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int index = indices[i];\n\t\tassert(index < vertex_count);\n\n\t\tif (destination[index] == ~0u)\n\t\t{\n\t\t\tdestination[index] = next_vertex++;\n\t\t}\n\t}\n\n\tassert(next_vertex <= vertex_count);\n\n\treturn next_vertex;\n}\n\nsize_t meshopt_optimizeVertexFetch(void* destination, unsigned int* indices, size_t index_count, const void* vertices, size_t vertex_count, size_t vertex_size)\n{\n\tassert(index_count % 3 == 0);\n\tassert(vertex_size > 0 && vertex_size <= 256);\n\n\tmeshopt_Allocator allocator;\n\n\t// support in-place optimization\n\tif (destination == vertices)\n\t{\n\t\tunsigned char* vertices_copy = allocator.allocate<unsigned char>(vertex_count * vertex_size);\n\t\tmemcpy(vertices_copy, vertices, vertex_count * vertex_size);\n\t\tvertices = vertices_copy;\n\t}\n\n\t// build vertex remap table\n\tunsigned int* vertex_remap = allocator.allocate<unsigned int>(vertex_count);\n\tmemset(vertex_remap, -1, vertex_count * sizeof(unsigned int));\n\n\tunsigned int next_vertex = 0;\n\n\tfor (size_t i = 0; i < index_count; ++i)\n\t{\n\t\tunsigned int index = indices[i];\n\t\tassert(index < vertex_count);\n\n\t\tunsigned int& remap = vertex_remap[index];\n\n\t\tif (remap == ~0u) // vertex was not added to destination VB\n\t\t{\n\t\t\t// add vertex\n\t\t\tmemcpy(static_cast<unsigned char*>(destination) + next_vertex * vertex_size, static_cast<const unsigned char*>(vertices) + index * vertex_size, vertex_size);\n\n\t\t\tremap = next_vertex++;\n\t\t}\n\n\t\t// modify indices in place\n\t\tindices[i] = remap;\n\t}\n\n\tassert(next_vertex <= vertex_count);\n\n\treturn next_vertex;\n}\n"
  },
  {
    "path": "tools/bitmask.py",
    "content": "#!/usr/bin/python3\n\nfrom z3 import *\n\ndef same8(v):\n  return Or(v == 0, v == 0xff)\n\nmagic = BitVec('magic', 64)\n\nx = BitVec('x', 64)\ny = x * magic\n\ns = Solver()\nsolve_using(s, ForAll([x],\n  Or(\n    Not(And([same8((x >> (i * 8)) & 0xff) for i in range(8)])), # x has bytes that aren't equal to 0xff or 0x00\n    And([(x >> (i * 8 + 7)) & 1 == (y >> (56 + i)) & 1 for i in range(8)]) # every byte of x has bits that are equal to a corresponding top bit of y\n)))\n\nprint(\"magic =\", hex(s.model().eval(magic).as_long()))\n"
  },
  {
    "path": "tools/clusterfuzz.cpp",
    "content": "#include \"../src/meshoptimizer.h\"\n\n#include <float.h>\n#include <stdint.h>\n#include <stdlib.h>\n\nextern \"C\" int LLVMFuzzerTestOneInput(const uint8_t* buffer, size_t size)\n{\n\tif (size == 0)\n\t\treturn 0;\n\n\tsrand(buffer[0]);\n\n\tfloat vb[100][4];\n\n\tfor (int i = 0; i < 100; ++i)\n\t{\n\t\tvb[i][0] = rand() % 10;\n\t\tvb[i][1] = rand() % 10;\n\t\tvb[i][2] = rand() % 10;\n\t\tvb[i][3] = rand() % 10;\n\t}\n\n\tunsigned int ib[999];\n\tint indices = (size - 1) < 999 ? (size - 1) / 3 * 3 : 999;\n\n\tfor (int i = 0; i < indices; ++i)\n\t\tib[i] = buffer[1 + i] % 100;\n\n\tmeshopt_Meshlet ml[333];\n\tunsigned int mv[333 * 3];\n\tunsigned char mt[333 * 4];\n\n\tmeshopt_buildMeshlets(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 4, /* max_triangles= */ 4, 0.f);\n\n\tmeshopt_buildMeshletsScan(ml, mv, mt, ib, indices, 100, /* max_vertices= */ 4, /* max_triangles= */ 4);\n\tmeshopt_buildMeshletsScan(ml, mv, mt, ib, indices, 100, /* max_vertices= */ 4, /* max_triangles= */ 8);\n\n\tmeshopt_buildMeshletsFlex(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 4, /* min_triangles= */ 4, /* max_triangles= */ 8, 0.f, 2.f);\n\tmeshopt_buildMeshletsFlex(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 8, /* min_triangles= */ 8, /* max_triangles= */ 16, 0.f, 2.f);\n\tmeshopt_buildMeshletsFlex(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 16, /* min_triangles= */ 8, /* max_triangles= */ 32, 0.f, 2.f);\n\tmeshopt_buildMeshletsFlex(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 16, /* min_triangles= */ 16, /* max_triangles= */ 32, 0.f, 2.f);\n\n\tmeshopt_buildMeshletsSpatial(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 4, /* min_triangles= */ 4, /* max_triangles= */ 8, 0.f);\n\tmeshopt_buildMeshletsSpatial(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 8, /* min_triangles= */ 4, /* max_triangles= */ 32, 0.f);\n\tmeshopt_buildMeshletsSpatial(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 8, /* min_triangles= */ 8, /* max_triangles= */ 32, 0.f);\n\tmeshopt_buildMeshletsSpatial(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 8, /* min_triangles= */ 12, /* max_triangles= */ 32, 0.f);\n\tmeshopt_buildMeshletsSpatial(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 16, /* min_triangles= */ 16, /* max_triangles= */ 32, 0.f);\n\tmeshopt_buildMeshletsSpatial(ml, mv, mt, ib, indices, vb[0], 100, sizeof(float) * 4, /* max_vertices= */ 16, /* min_triangles= */ 32, /* max_triangles= */ 32, 0.f);\n\n\tunsigned int part[333];\n\tunsigned int clsize[333];\n\n\t// treat each triangle as a cluster\n\tfor (int i = 0; i < indices; ++i)\n\t\tclsize[i / 3] = 3;\n\n\tmeshopt_partitionClusters(part, ib, indices, clsize, indices / 3, vb[0], 100, sizeof(float) * 4, /* target_group_size= */ 12);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "tools/codecbench.cpp",
    "content": "#include \"../src/meshoptimizer.h\"\n\n#include <algorithm>\n#include <vector>\n\n#include <stdint.h>\n#include <stdio.h>\n#include <string.h>\n#include <time.h>\n\n#ifdef __EMSCRIPTEN__\n#include <emscripten.h>\n\ndouble timestamp()\n{\n\treturn emscripten_get_now() * 1e-3;\n}\n#elif defined(_WIN32)\nstruct LARGE_INTEGER\n{\n\t__int64 QuadPart;\n};\nextern \"C\" __declspec(dllimport) int __stdcall QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount);\nextern \"C\" __declspec(dllimport) int __stdcall QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency);\n\ndouble timestamp()\n{\n\tLARGE_INTEGER freq, counter;\n\tQueryPerformanceFrequency(&freq);\n\tQueryPerformanceCounter(&counter);\n\treturn double(counter.QuadPart) / double(freq.QuadPart);\n}\n#else\ndouble timestamp()\n{\n\ttimespec ts;\n\tclock_gettime(CLOCK_MONOTONIC, &ts);\n\treturn double(ts.tv_sec) + 1e-9 * double(ts.tv_nsec);\n}\n#endif\n\nstruct Vertex\n{\n\tuint16_t data[16];\n};\n\nuint32_t murmur3(uint32_t h)\n{\n\th ^= h >> 16;\n\th *= 0x85ebca6bu;\n\th ^= h >> 13;\n\th *= 0xc2b2ae35u;\n\th ^= h >> 16;\n\n\treturn h;\n}\n\nvoid benchCodecs(const std::vector<Vertex>& vertices, const std::vector<unsigned int>& indices, double& bestvd, double& bestid, bool verbose)\n{\n\tstd::vector<Vertex> vb(vertices.size());\n\tstd::vector<unsigned int> ib(indices.size());\n\n\tstd::vector<unsigned char> vc(meshopt_encodeVertexBufferBound(vertices.size(), sizeof(Vertex)));\n\tstd::vector<unsigned char> ic(meshopt_encodeIndexBufferBound(indices.size(), vertices.size()));\n\n\tif (verbose)\n\t\tprintf(\"source: vertex data %d bytes, index data %d bytes\\n\", int(vertices.size() * sizeof(Vertex)), int(indices.size() * 4));\n\n\tmeshopt_optimizeVertexCache(&ib[0], &indices[0], indices.size(), vertices.size());\n\n\tmeshopt_optimizeVertexFetch(&vb[0], &ib[0], indices.size(), &vertices[0], vertices.size(), sizeof(Vertex));\n\n\tvc.resize(vc.capacity());\n\tvc.resize(meshopt_encodeVertexBuffer(&vc[0], vc.size(), &vb[0], vertices.size(), sizeof(Vertex)));\n\n\tic.resize(ic.capacity());\n\tic.resize(meshopt_encodeIndexBuffer(&ic[0], ic.size(), &ib[0], indices.size()));\n\n\tif (verbose)\n\t\tprintf(\"encode: vertex data %d bytes, index data %d bytes\\n\", int(vc.size()), int(ic.size()));\n\n\tfor (int attempt = 0; attempt < 50; ++attempt)\n\t{\n\t\tdouble t0 = timestamp();\n\n\t\tint rv = meshopt_decodeVertexBuffer(&vb[0], vertices.size(), sizeof(Vertex), &vc[0], vc.size());\n\t\tassert(rv == 0);\n\t\t(void)rv;\n\n\t\tdouble t1 = timestamp();\n\n\t\tint ri = meshopt_decodeIndexBuffer(&ib[0], indices.size(), 4, &ic[0], ic.size());\n\t\tassert(ri == 0);\n\t\t(void)ri;\n\n\t\tdouble t2 = timestamp();\n\n\t\tif (verbose)\n\t\t\tprintf(\"decode: vertex %.2f ms (%.2f GB/sec), index %.2f ms (%.2f GB/sec)\\n\",\n\t\t\t    (t1 - t0) * 1000, double(vertices.size() * sizeof(Vertex)) / 1e9 / (t1 - t0),\n\t\t\t    (t2 - t1) * 1000, double(indices.size() * 4) / 1e9 / (t2 - t1));\n\n\t\tbestvd = std::max(bestvd, double(vertices.size() * sizeof(Vertex)) / 1e9 / (t1 - t0));\n\t\tbestid = std::max(bestid, double(indices.size() * 4) / 1e9 / (t2 - t1));\n\t}\n}\n\nvoid benchFilters(size_t count, double& besto8, double& besto12, double& bestq12, double& bestc8, double& bestc12, double& bestexp, bool verbose)\n{\n\t// note: the filters are branchless so we just run them on runs of zeroes\n\tsize_t count4 = (count + 3) & ~3;\n\tstd::vector<unsigned char> d4(count4 * 4);\n\tstd::vector<unsigned char> d8(count4 * 8);\n\n\tif (verbose)\n\t\tprintf(\"filters: oct8 data %d bytes, oct12/quat12 data %d bytes\\n\", int(d4.size()), int(d8.size()));\n\n\tfor (int attempt = 0; attempt < 50; ++attempt)\n\t{\n\t\tdouble t0 = timestamp();\n\n\t\tmeshopt_decodeFilterOct(&d4[0], count4, 4);\n\n\t\tdouble t1 = timestamp();\n\n\t\tmeshopt_decodeFilterOct(&d8[0], count4, 8);\n\n\t\tdouble t2 = timestamp();\n\n\t\tmeshopt_decodeFilterQuat(&d8[0], count4, 8);\n\n\t\tdouble t3 = timestamp();\n\n\t\tmeshopt_decodeFilterColor(&d4[0], count4, 4);\n\n\t\tdouble t4 = timestamp();\n\n\t\tmeshopt_decodeFilterColor(&d8[0], count4, 8);\n\n\t\tdouble t5 = timestamp();\n\n\t\tmeshopt_decodeFilterExp(&d8[0], count4, 8);\n\n\t\tdouble t6 = timestamp();\n\n\t\tif (verbose)\n\t\t\tprintf(\"filter: oct8 %.2f ms (%.2f GB/sec), oct12 %.2f ms (%.2f GB/sec), quat12 %.2f ms (%.2f GB/sec), col8 %.2f ms (%.2f GB/sec), col12 %.2f ms (%.2f GB/sec), exp %.2f ms (%.2f GB/sec)\\n\",\n\t\t\t    (t1 - t0) * 1000, double(d4.size()) / 1e9 / (t1 - t0),\n\t\t\t    (t2 - t1) * 1000, double(d8.size()) / 1e9 / (t2 - t1),\n\t\t\t    (t3 - t2) * 1000, double(d8.size()) / 1e9 / (t3 - t2),\n\t\t\t    (t4 - t3) * 1000, double(d8.size()) / 1e9 / (t4 - t3),\n\t\t\t    (t5 - t4) * 1000, double(d4.size()) / 1e9 / (t5 - t4),\n\t\t\t    (t6 - t5) * 1000, double(d8.size()) / 1e9 / (t6 - t5));\n\n\t\tbesto8 = std::max(besto8, double(d4.size()) / 1e9 / (t1 - t0));\n\t\tbesto12 = std::max(besto12, double(d8.size()) / 1e9 / (t2 - t1));\n\t\tbestq12 = std::max(bestq12, double(d8.size()) / 1e9 / (t3 - t2));\n\t\tbestc8 = std::max(bestc8, double(d4.size()) / 1e9 / (t4 - t3));\n\t\tbestc12 = std::max(bestc12, double(d8.size()) / 1e9 / (t5 - t4));\n\t\tbestexp = std::max(bestexp, double(d8.size()) / 1e9 / (t6 - t5));\n\t}\n}\n\nvoid benchMeshlets(const std::vector<float>& positions, const std::vector<unsigned int>& indices, bool encoderefs, double& bestml, bool verbose)\n{\n\tconst size_t max_vertices = 64;\n\tconst size_t max_triangles = 96;\n\n\tsize_t max_meshlets = meshopt_buildMeshletsBound(indices.size(), max_vertices, max_triangles);\n\tstd::vector<meshopt_Meshlet> meshlets(max_meshlets);\n\tstd::vector<unsigned int> meshlet_vertices(indices.size());\n\tstd::vector<unsigned char> meshlet_triangles(indices.size());\n\n\tmeshlets.resize(meshopt_buildMeshlets(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(),\n\t    indices.data(), indices.size(), positions.data(), positions.size() / 3, sizeof(float) * 3, max_vertices, max_triangles, 0.f));\n\n\tconst meshopt_Meshlet& last = meshlets.back();\n\n\tmeshlet_vertices.resize(last.vertex_offset + last.vertex_count);\n\tmeshlet_triangles.resize(last.triangle_offset + last.triangle_count * 3);\n\n\tstd::vector<unsigned char> cbuf(meshopt_encodeMeshletBound(max_vertices, max_triangles));\n\n\t// optimize each meshlet for locality before encoding\n\tfor (size_t i = 0; i < meshlets.size(); ++i)\n\t\tmeshopt_optimizeMeshlet(&meshlet_vertices[meshlets[i].vertex_offset], &meshlet_triangles[meshlets[i].triangle_offset], meshlets[i].triangle_count, meshlets[i].vertex_count);\n\n\t// optimize meshlet_vertices for locality (requires vertex reorder)\n\t// TODO: over-allocate meshlet_vertices to multiple of 3 to make meshopt_optimizeVertexFetch below work without assertions\n\tstd::vector<float> positionscopy(positions.size());\n\tmeshlet_vertices.resize((meshlet_vertices.size() + 2) / 3 * 3);\n\tmeshopt_optimizeVertexFetch(&positionscopy[0], &meshlet_vertices[0], meshlet_vertices.size(), &positions[0], positions.size() / 3, sizeof(float) * 3);\n\n#if 0 // TODO: disabled for now to make meshlet decoder tuning more accurate; might make sense to re-enable in the future for SOL timings\n\t// pack meshlets in decreasing triangle count order to reduce branch mispredictions\n\t// (this matters less on real-world meshes but is more prominent on regular grids?)\n\tstd::sort(meshlets.begin(), meshlets.end(), [](const meshopt_Meshlet& lhs, const meshopt_Meshlet& rhs)\n\t    { return lhs.triangle_count > rhs.triangle_count; });\n#endif\n\n\tstd::vector<unsigned char> packed;\n\n\tsize_t totals = 0;\n\n\tfor (size_t i = 0; i < meshlets.size(); ++i)\n\t{\n\t\tconst meshopt_Meshlet& meshlet = meshlets[i];\n\n\t\tsize_t mvc = encoderefs ? meshlet.vertex_count : 0;\n\n\t\tsize_t mbs = meshopt_encodeMeshlet(&cbuf[0], cbuf.size(), &meshlet_vertices[meshlet.vertex_offset], mvc, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count);\n\t\tassert(mbs > 0);\n\n\t\tpacked.push_back((unsigned char)mvc);\n\t\tpacked.push_back((unsigned char)meshlet.triangle_count);\n\t\tpacked.push_back((unsigned char)(mbs & 0xff));\n\t\tpacked.push_back((unsigned char)((mbs >> 8) & 0xff));\n\t\tpacked.insert(packed.end(), cbuf.begin(), cbuf.begin() + mbs);\n\n\t\ttotals += size_t(meshlet.triangle_count) * 3 + size_t(mvc) * 4;\n\t}\n\n\tif (verbose)\n\t\tprintf(\"meshlets (%d/%d): %d meshlets, packed %d bytes (%.1f bits/triangle)\\n\",\n\t\t    int(max_vertices), int(max_triangles), int(meshlets.size()), int(packed.size()), double(packed.size() * 8) / double(indices.size() / 3));\n\n\tunsigned int rv[256];\n\tunsigned char rt[256 * 3];\n\n\tfor (int attempt = 0; attempt < 50; ++attempt)\n\t{\n\t\tdouble t0 = timestamp();\n\n\t\tconst unsigned char* p = &packed[0];\n\n\t\tfor (size_t i = 0; i < meshlets.size(); ++i)\n\t\t{\n\t\t\tsize_t mbs = p[2] | (p[3] << 8);\n\t\t\tint rc = meshopt_decodeMeshlet(rv, p[0], rt, p[1], p + 4, mbs);\n\t\t\tassert(rc == 0);\n\t\t\t(void)rc;\n\t\t\tp += 4 + mbs;\n\t\t}\n\n\t\tassert(p == &packed[0] + packed.size());\n\n\t\tdouble t1 = timestamp();\n\n\t\tif (verbose)\n\t\t\tprintf(\"meshlet decode: %.2f ms (%.2f GB/sec)\\n\", (t1 - t0) * 1000, double(totals) / 1e9 / (t1 - t0));\n\n\t\tbestml = std::max(bestml, double(totals) / 1e9 / (t1 - t0));\n\t}\n}\n\nstruct File\n{\n\tstd::vector<unsigned char> data;\n\tsize_t stride;\n\tsize_t count;\n};\n\nFile readFile(const char* path)\n{\n\tFILE* file = fopen(path, \"rb\");\n\tassert(file);\n\n\tconst char* name = strrchr(path, '/');\n\tname = name ? name + 1 : path;\n\n\tint vcnt, vsz;\n\tint sr = sscanf(name, \"v%d_s%d_\", &vcnt, &vsz);\n\tassert(sr == 2);\n\t(void)sr;\n\n\tstd::vector<unsigned char> input;\n\tunsigned char buffer[4096];\n\tsize_t bytes_read;\n\n\twhile ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0)\n\t\tinput.insert(input.end(), buffer, buffer + bytes_read);\n\n\tfclose(file);\n\n\tFile result = {};\n\tresult.count = vcnt;\n\tresult.stride = vsz;\n\n\tstd::vector<unsigned char> decoded(result.count * result.stride);\n\tint res = meshopt_decodeVertexBuffer(&decoded[0], result.count, result.stride, &input[0], input.size());\n\tif (res != 0 && input.size() == decoded.size())\n\t{\n\t\t// some files are not encoded\n\t\tmemcpy(decoded.data(), input.data(), decoded.size());\n\t}\n\n\tresult.data.resize(meshopt_encodeVertexBufferBound(result.count, result.stride));\n\tresult.data.resize(meshopt_encodeVertexBuffer(result.data.data(), result.data.size(), decoded.data(), result.count, result.stride));\n\n\treturn result;\n}\n\nint main(int argc, char** argv)\n{\n\tbool verbose = false;\n\tbool loop = false;\n\tbool inputs = false;\n\n\tfor (int i = 1; i < argc; ++i)\n\t{\n\t\tif (strcmp(argv[i], \"-v\") == 0)\n\t\t\tverbose = true;\n\t\tif (strcmp(argv[i], \"-l\") == 0)\n\t\t\tloop = true;\n\t\tif (argv[i][0] != '-')\n\t\t\tinputs = true;\n\t}\n\n\tif (inputs)\n\t{\n\t\tstd::vector<File> files;\n\t\tfor (int i = 1; i < argc; ++i)\n\t\t\tif (argv[i][0] != '-')\n\t\t\t\tfiles.push_back(readFile(argv[i]));\n\n\t\tsize_t max_size = 0;\n\t\tsize_t total_size = 0;\n\t\tsize_t total_comp = 0;\n\t\tfor (size_t i = 0; i < files.size(); ++i)\n\t\t{\n\t\t\tmax_size = std::max(max_size, files[i].count * files[i].stride);\n\t\t\ttotal_size += files[i].count * files[i].stride;\n\t\t\ttotal_comp += files[i].data.size();\n\t\t}\n\n\t\tprintf(\"%d files, ratio %.2f (%.2f MB => %.2f MB)\\n\",\n\t\t    int(files.size()), double(total_comp) / double(total_size), double(total_size) / 1024 / 1024, double(total_comp) / 1024 / 1024);\n\n\t\tstd::vector<unsigned char> buffer(max_size);\n\n\t\tfor (int l = 0; l < (loop ? 100 : 1); ++l)\n\t\t{\n\t\t\tdouble bestvd = 0;\n\n\t\t\tfor (int attempt = 0; attempt < 50; ++attempt)\n\t\t\t{\n\t\t\t\tdouble t0 = timestamp();\n\n\t\t\t\tfor (size_t i = 0; i < files.size(); ++i)\n\t\t\t\t{\n\t\t\t\t\tint rv = meshopt_decodeVertexBuffer(&buffer[0], files[i].count, files[i].stride, &files[i].data[0], files[i].data.size());\n\t\t\t\t\tassert(rv == 0);\n\t\t\t\t\t(void)rv;\n\t\t\t\t}\n\n\t\t\t\tdouble t1 = timestamp();\n\n\t\t\t\tbestvd = std::max(bestvd, double(total_size) / 1e9 / (t1 - t0));\n\t\t\t}\n\n\t\t\tprintf(\"Score (GB/s):\\t%.2f\\n\", bestvd);\n\t\t}\n\t\treturn 0;\n\t}\n\n\tconst int N = 1000;\n\n\tstd::vector<Vertex> vertices;\n\tvertices.reserve((N + 1) * (N + 1));\n\n\tstd::vector<float> positions((N + 1) * (N + 1) * 3);\n\n\tfor (int x = 0; x <= N; ++x)\n\t{\n\t\tfor (int y = 0; y <= N; ++y)\n\t\t{\n\t\t\tVertex v;\n\n\t\t\tfor (int k = 0; k < 16; ++k)\n\t\t\t{\n\t\t\t\tuint32_t h = murmur3((x * (N + 1) + y) * 16 + k);\n\n\t\t\t\t// use random k-bit sequence for each word to test all encoding types\n\t\t\t\t// note: this doesn't stress the sentinel logic too much but it's all branchless so it's probably fine?\n\t\t\t\tv.data[k] = h & ((1 << (k + 1)) - 1);\n\t\t\t}\n\n\t\t\tsize_t index = vertices.size();\n\n\t\t\tpositions[index * 3 + 0] = float(x);\n\t\t\tpositions[index * 3 + 1] = float(y);\n\t\t\tpositions[index * 3 + 2] = 0.f;\n\n\t\t\tvertices.push_back(v);\n\t\t}\n\t}\n\n\tstd::vector<unsigned int> indices;\n\tindices.reserve(N * N * 6);\n\n\tfor (int x = 0; x < N; ++x)\n\t{\n\t\tfor (int y = 0; y < N; ++y)\n\t\t{\n\t\t\tindices.push_back((x + 0) * N + (y + 0));\n\t\t\tindices.push_back((x + 1) * N + (y + 0));\n\t\t\tindices.push_back((x + 0) * N + (y + 1));\n\n\t\t\tindices.push_back((x + 0) * N + (y + 1));\n\t\t\tindices.push_back((x + 1) * N + (y + 0));\n\t\t\tindices.push_back((x + 1) * N + (y + 1));\n\t\t}\n\t}\n\n\tprintf(\"Codec:\\tvtx\\tidx\\tmlet\\tmletΔ\\toct8\\toct12\\tquat12\\tcol8\\tcol12\\texp\\n\");\n\n\tfor (int l = 0; l < (loop ? 100 : 1); ++l)\n\t{\n\t\tdouble bestvd = 0, bestid = 0;\n\t\tbenchCodecs(vertices, indices, bestvd, bestid, verbose);\n\n\t\tdouble bestml = 0, bestmt = 0;\n\t\tbenchMeshlets(positions, indices, /* encoderefs= */ true, bestml, verbose);\n\t\tbenchMeshlets(positions, indices, /* encoderefs= */ false, bestmt, verbose);\n\n\t\tdouble besto8 = 0, besto12 = 0, bestq12 = 0, bestc8 = 0, bestc12 = 0, bestexp = 0;\n\t\tbenchFilters(8 * N * N, besto8, besto12, bestq12, bestc8, bestc12, bestexp, verbose);\n\n\t\tprintf(\"GB/s :\\t%.2f\\t%.2f\\t%.2f\\t%.2f\\t%.2f\\t%.2f\\t%.2f\\t%.2f\\t%.2f\\t%.2f\\n\",\n\t\t    bestvd, bestid, bestml, bestmt, besto8, besto12, bestq12, bestc8, bestc12, bestexp);\n\t}\n}\n"
  },
  {
    "path": "tools/codecfuzz.cpp",
    "content": "#include \"../src/meshoptimizer.h\"\n\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n\nvoid fuzzDecoder(const uint8_t* data, size_t size, size_t stride, int (*decode)(void*, size_t, size_t, const unsigned char*, size_t))\n{\n\tsize_t count = 66; // must be divisible by 3 for decodeIndexBuffer; should be >=64 to cover large vertex blocks\n\n\tvoid* destination = malloc(count * stride);\n\tassert(destination);\n\n\tint rc = decode(destination, count, stride, reinterpret_cast<const unsigned char*>(data), size);\n\t(void)rc;\n\n\tfree(destination);\n}\n\nvoid fuzzRoundtrip(const uint8_t* data, size_t size, size_t stride, int level)\n{\n\tsize_t count = size / stride;\n\n\tsize_t bound = meshopt_encodeVertexBufferBound(count, stride);\n\tvoid* encoded = malloc(bound);\n\tvoid* decoded = malloc(count * stride);\n\tassert(encoded && decoded);\n\n\tsize_t res = meshopt_encodeVertexBufferLevel(static_cast<unsigned char*>(encoded), bound, data, count, stride, level, -1);\n\tassert(res > 0 && res <= bound);\n\n\t// encode again at the boundary to check for memory safety\n\t// this should produce the same output because encoder is deterministic\n\tsize_t rese = meshopt_encodeVertexBufferLevel(static_cast<unsigned char*>(encoded) + bound - res, res, data, count, stride, level, -1);\n\tassert(rese == res);\n\n\tint rc = meshopt_decodeVertexBuffer(decoded, count, stride, static_cast<unsigned char*>(encoded) + bound - res, res);\n\tassert(rc == 0);\n\n\tassert(memcmp(data, decoded, count * stride) == 0);\n\n\tfree(decoded);\n\tfree(encoded);\n}\n\nsize_t align(size_t value, size_t alignment)\n{\n\treturn (value + alignment - 1) & ~(alignment - 1);\n}\n\nvoid fuzzDecodeMeshlet(size_t vertex_count, size_t triangle_count, const unsigned char* data, size_t size)\n{\n\t// raw decoding: allowed to write align(count, 4) elements\n\tunsigned int rt[256];\n\tunsigned int rv[256];\n\tmeshopt_decodeMeshletRaw(rv + 256 - align(vertex_count, 4), vertex_count, rt + 256 - align(triangle_count, 4), triangle_count, data, size);\n\n\t// regular decoding: allowed to write align(count * size, 4) bytes\n\t// with variations for 3-byte triangles and 2-byte vertex references\n\tunsigned short rsv[256];\n\tunsigned char rbt[256 * 3];\n\n\tmeshopt_decodeMeshlet(rv + 256 - vertex_count, vertex_count, 4, rt + 256 - triangle_count, triangle_count, 4, data, size);\n\tmeshopt_decodeMeshlet(rsv + 256 - align(vertex_count, 2), vertex_count, 2, rt + 256 - triangle_count, triangle_count, 4, data, size);\n\tmeshopt_decodeMeshlet(rv + 256 - vertex_count, vertex_count, 4, rbt + 256 * 3 - align(triangle_count * 3, 4), triangle_count, 3, data, size);\n\tmeshopt_decodeMeshlet(rsv + 256 - align(vertex_count, 2), vertex_count, 2, rbt + 256 * 3 - align(triangle_count * 3, 4), triangle_count, 3, data, size);\n}\n\nvoid fuzzRoundtripMeshlet(const uint8_t* data, size_t size)\n{\n\tsize_t triangle_count = size / 3;\n\tif (triangle_count > 256)\n\t\ttriangle_count = 256;\n\n\tunsigned char buf[4096];\n\tsize_t enc = meshopt_encodeMeshlet(buf, sizeof(buf), NULL, 0, reinterpret_cast<const unsigned char*>(data), triangle_count);\n\tassert(enc > 0);\n\tassert(enc <= meshopt_encodeMeshletBound(0, triangle_count));\n\n\tunsigned int rt4[256];\n\tint rc4 = meshopt_decodeMeshlet(static_cast<unsigned int*>(NULL), 0, rt4, triangle_count, buf, enc);\n\tassert(rc4 == 0);\n\n\tfor (size_t i = 0; i < triangle_count; ++i)\n\t{\n\t\tunsigned char a = data[i * 3 + 0], b = data[i * 3 + 1], c = data[i * 3 + 2];\n\n\t\tunsigned int abc = (a << 0) | (b << 8) | (c << 16);\n\t\tunsigned int bca = (b << 0) | (c << 8) | (a << 16);\n\t\tunsigned int cba = (c << 0) | (a << 8) | (b << 16);\n\n\t\tunsigned int tri = rt4[i];\n\n\t\tassert(tri == abc || tri == bca || tri == cba);\n\t}\n\n\tunsigned char rt3[256 * 3];\n\tint rc3 = meshopt_decodeMeshlet(static_cast<unsigned int*>(NULL), 0, rt3, triangle_count, buf, enc);\n\tassert(rc3 == 0);\n\n\tfor (size_t i = 0; i < triangle_count; ++i)\n\t{\n\t\tunsigned char a = data[i * 3 + 0], b = data[i * 3 + 1], c = data[i * 3 + 2];\n\n\t\tunsigned int abc = (a << 0) | (b << 8) | (c << 16);\n\t\tunsigned int bca = (b << 0) | (c << 8) | (a << 16);\n\t\tunsigned int cba = (c << 0) | (a << 8) | (b << 16);\n\n\t\tunsigned int tri = rt3[i * 3 + 0] | (rt3[i * 3 + 1] << 8) | (rt3[i * 3 + 2] << 16);\n\n\t\tassert(tri == abc || tri == bca || tri == cba);\n\t}\n}\n\nvoid fuzzRoundtripMeshletV(const uint8_t* data, size_t size)\n{\n\tsize_t vertex_count = size / 4;\n\tif (vertex_count > 256)\n\t\tvertex_count = 256;\n\n\tunsigned char tri[4] = {0, 1, 2};\n\n\tunsigned char buf[4096];\n\tsize_t enc = meshopt_encodeMeshlet(buf, sizeof(buf), reinterpret_cast<const uint32_t*>(data), vertex_count, tri, 1);\n\tassert(enc > 0);\n\tassert(enc <= meshopt_encodeMeshletBound(vertex_count, 1));\n\n\tunsigned int rv4[256];\n\tint rc4 = meshopt_decodeMeshlet(rv4, vertex_count, tri, 1, buf, enc);\n\tassert(rc4 == 0);\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tassert(rv4[i] == reinterpret_cast<const uint32_t*>(data)[i]);\n\n\tunsigned short rv2[256];\n\tint rc2 = meshopt_decodeMeshlet(rv2, vertex_count, tri, 1, buf, enc);\n\tassert(rc2 == 0);\n\n\tfor (size_t i = 0; i < vertex_count; ++i)\n\t\tassert(rv2[i] == uint16_t(reinterpret_cast<const uint32_t*>(data)[i]));\n}\n\nextern \"C\" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)\n{\n\t// decodeIndexBuffer supports 2 and 4-byte indices\n\tfuzzDecoder(data, size, 2, meshopt_decodeIndexBuffer);\n\tfuzzDecoder(data, size, 4, meshopt_decodeIndexBuffer);\n\n\t// decodeIndexSequence supports 2 and 4-byte indices\n\tfuzzDecoder(data, size, 2, meshopt_decodeIndexSequence);\n\tfuzzDecoder(data, size, 4, meshopt_decodeIndexSequence);\n\n\t// decodeVertexBuffer supports any strides divisible by 4 in 4-256 interval\n\t// it's a waste of time to check all of them, so we'll just check a few with different alignment mod 16\n\tfuzzDecoder(data, size, 4, meshopt_decodeVertexBuffer);\n\tfuzzDecoder(data, size, 16, meshopt_decodeVertexBuffer);\n\tfuzzDecoder(data, size, 24, meshopt_decodeVertexBuffer);\n\tfuzzDecoder(data, size, 32, meshopt_decodeVertexBuffer);\n\n\t// encodeVertexBuffer/decodeVertexBuffer should roundtrip for any stride, check a few with different alignment mod 16\n\t// this also checks memory safety properties of the encoder\n\t// to conserve time, we only check one version/level combination, biased towards version 1\n\tuint8_t data0 = size > 0 ? data[0] : 0;\n\tint level = data0 % 5;\n\n\tmeshopt_encodeVertexVersion(level < 4 ? 1 : 0);\n\n\tfuzzRoundtrip(data, size, 4, level);\n\tfuzzRoundtrip(data, size, 16, level);\n\tfuzzRoundtrip(data, size, 24, level);\n\tfuzzRoundtrip(data, size, 32, level);\n\n\t// validate that decodeMeshlet works on untrusted data and is memory safe within documented limits\n\tif (size > 2)\n\t\tfuzzDecodeMeshlet(data[0] + 1, data[1] + 1, reinterpret_cast<const unsigned char*>(data + 2), size - 2);\n\n\t// validate that index data roundtrips in meshlet encoding modulo rotation\n\tfuzzRoundtripMeshlet(data, size);\n\n\t// validate that vertex data roundtrips in meshlet encoding\n\tfuzzRoundtripMeshletV(data, size);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "tools/codectest.cpp",
    "content": "#include \"../src/meshoptimizer.h\"\n\n#include <vector>\n\n#include <math.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#ifdef _WIN32\n#include <fcntl.h>\n#include <io.h>\n#endif\n\nsize_t measure(const char* cmd, const std::vector<unsigned char>& data)\n{\n\tFILE* file = fopen(\"/tmp/codectest.in\", \"wb\");\n\tif (!file)\n\t\treturn 0;\n\tfwrite(data.data(), data.size(), 1, file);\n\tfclose(file);\n\n\tchar actualcmd[1024];\n\tsnprintf(actualcmd, sizeof(actualcmd), cmd, \"/tmp/codectest.in\", \"/tmp/codectest.out\");\n\n\tint rc = system(actualcmd);\n\tif (rc)\n\t\treturn 0;\n\n\tfile = fopen(\"/tmp/codectest.out\", \"rb\");\n\tif (!file)\n\t\treturn 0;\n\tfseek(file, 0, SEEK_END);\n\tlong result = ftell(file);\n\tfclose(file);\n\n\treturn result;\n}\n\nsize_t measure_lz4(const std::vector<unsigned char>& data)\n{\n\treturn measure(\"lz4 -f -q %s %s\", data);\n}\n\nsize_t measure_zstd(const std::vector<unsigned char>& data)\n{\n\treturn measure(\"zstd -f -q %s -o %s\", data);\n}\n\nstruct Stats\n{\n\tbool testz;\n\tdouble count;\n\tdouble size;\n\n\tdouble ratio;\n\tdouble ratio_lz4;\n\tdouble ratio_zstd;\n\n\tdouble total;\n\tdouble total_lz4;\n\tdouble total_zstd;\n};\n\nvoid testFile(FILE* file, size_t count, size_t stride, int level, Stats* stats = 0)\n{\n\tstd::vector<unsigned char> input;\n\tunsigned char buffer[4096];\n\tsize_t bytes_read;\n\n\twhile ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0)\n\t\tinput.insert(input.end(), buffer, buffer + bytes_read);\n\n\tstd::vector<unsigned char> decoded(count * stride);\n\tint res = meshopt_decodeVertexBuffer(&decoded[0], count, stride, &input[0], input.size());\n\tif (res != 0 && input.size() == decoded.size())\n\t{\n\t\t// some files are not encoded\n\t\tmemcpy(decoded.data(), input.data(), decoded.size());\n\t}\n\telse if (res != 0)\n\t{\n\t\tprintf(\" error decoding input: %d\", res);\n\t\treturn;\n\t}\n\n\tstd::vector<unsigned char> output(meshopt_encodeVertexBufferBound(count, stride));\n\toutput.resize(meshopt_encodeVertexBufferLevel(output.data(), output.size(), decoded.data(), count, stride, level, -1));\n\n\tprintf(\" raw %zu KB\\t\", decoded.size() / 1024);\n\tprintf(\" vtx %.3f\", double(output.size()) / double(decoded.size()));\n\n\tif (stats)\n\t{\n\t\tstats->count++;\n\t\tstats->size += double(decoded.size());\n\n\t\tstats->total += double(output.size());\n\t\tstats->ratio += log(double(output.size()) / double(decoded.size()));\n\t}\n\n\tif (stats && stats->testz)\n\t{\n\t\tsize_t decoded_lz4 = measure_lz4(decoded);\n\t\tsize_t output_lz4 = measure_lz4(output);\n\t\tsize_t decoded_zstd = measure_zstd(decoded);\n\t\tsize_t output_zstd = measure_zstd(output);\n\n\t\tstats->total_lz4 += output_lz4;\n\t\tstats->total_zstd += output_zstd;\n\n\t\tstats->ratio_lz4 += log(double(output_lz4) / double(decoded.size()));\n\t\tstats->ratio_zstd += log(double(output_zstd) / double(decoded.size()));\n\n\t\tprintf(\"\\tlz4 %.3f\", double(decoded_lz4) / double(decoded.size()));\n\t\tprintf(\" vtx+lz4 %.3f\", double(output_lz4) / double(decoded.size()));\n\n\t\tprintf(\"\\tzstd %.3f\", double(decoded_zstd) / double(decoded.size()));\n\t\tprintf(\" vtx+zstd %.3f\", double(output_zstd) / double(decoded.size()));\n\t}\n}\n\nvoid testFile(const char* path, int level, Stats* stats = 0)\n{\n\tFILE* file = fopen(path, \"rb\");\n\tif (!file)\n\t\treturn;\n\n\tconst char* name = strrchr(path, '/');\n\tname = name ? name + 1 : path;\n\n\tint vcnt, vsz;\n\tint sr = sscanf(name, \"v%d_s%d_\", &vcnt, &vsz);\n\tassert(sr == 2);\n\t(void)sr;\n\n\tconst char* name0 = strchr(strchr(name, '_') + 1, '_') + 1;\n\tconst char* name1 = strstr(name0, \"_R\");\n\tsize_t namel = name1 ? name1 - name0 : strlen(name0);\n\tnamel = namel > 25 ? 25 : namel;\n\n#if TRACE\n\tprintf(\"%.*s: %s\\n\", int(namel), name0, path);\n#else\n\tprintf(\"%25.*s:\", int(namel), name0);\n#endif\n\n\ttestFile(file, vcnt, vsz, level, stats);\n\tprintf(\"\\n\");\n\n\tfclose(file);\n}\n\nint main(int argc, char** argv)\n{\n#ifdef _WIN32\n\t_setmode(_fileno(stdin), _O_BINARY);\n\t_setmode(_fileno(stdout), _O_BINARY);\n#endif\n\n\tif (argc > 1 && (strcmp(argv[1], \"-test\") == 0 || strcmp(argv[1], \"-testz\") == 0))\n\t{\n\t\tStats stats = {};\n\t\tstats.testz = strcmp(argv[1], \"-testz\") == 0;\n\t\tint level = -1;\n\t\tif (argc > 2 && argv[2][0] == '-' && argv[2][1] >= '0' && argv[2][1] <= '9')\n\t\t\tlevel = atoi(argv[2] + 1);\n\t\tfor (int i = level < 0 ? 2 : 3; i < argc; ++i)\n\t\t\ttestFile(argv[i], level < 0 ? 2 : level, &stats);\n\n\t\tprintf(\"---\\n\");\n\t\tprintf(\"%d files: vtx %.3f\", int(stats.count), exp(stats.ratio / stats.count));\n\t\tif (stats.testz)\n\t\t\tprintf(\", vtx+lz4 %.3f, vtx+zstd %.3f\\n\", exp(stats.ratio_lz4 / stats.count), exp(stats.ratio_zstd / stats.count));\n\t\telse\n\t\t\tprintf(\"\\n\");\n\n\t\tprintf(\"total: %d files, raw %.2f MB, vtx %.2f MB\",\n\t\t    int(stats.count),\n\t\t    stats.size / 1024 / 1024, stats.total / 1024 / 1024);\n\t\tif (stats.testz)\n\t\t\tprintf(\", vtx+lz4 %.2f MB, vtx+zstd %.2f MB\\n\", stats.total_lz4 / 1024 / 1024, stats.total_zstd / 1024 / 1024);\n\t\telse\n\t\t\tprintf(\"\\n\");\n\n\t\treturn 0;\n\t}\n\n\tif (argc < 2 || argc > 3 || atoi(argv[1]) <= 0)\n\t{\n\t\tfprintf(stderr, \"Usage: %s <stride> [<count>]\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tsize_t stride = atoi(argv[1]);\n\n\tstd::vector<unsigned char> input;\n\tunsigned char buffer[4096];\n\tsize_t bytes_read;\n\n\twhile ((bytes_read = fread(buffer, 1, sizeof(buffer), stdin)) > 0)\n\t\tinput.insert(input.end(), buffer, buffer + bytes_read);\n\n\tif (argc == 3)\n\t{\n\t\t// if count is specified, we assume input is meshopt-encoded and decode it first\n\t\tsize_t count = atoi(argv[2]);\n\n\t\tstd::vector<unsigned char> decoded(count * stride);\n\t\tint res = meshopt_decodeVertexBuffer(&decoded[0], count, stride, &input[0], input.size());\n\t\tif (res != 0)\n\t\t{\n\t\t\tfprintf(stderr, \"Error decoding input: %d\\n\", res);\n\t\t\treturn 2;\n\t\t}\n\n\t\tfwrite(decoded.data(), 1, decoded.size(), stdout);\n\t\treturn 0;\n\t}\n\telse\n\t{\n\t\tsize_t vertex_count = input.size() / stride;\n\t\tstd::vector<unsigned char> output(meshopt_encodeVertexBufferBound(vertex_count, stride));\n\t\tsize_t output_size = meshopt_encodeVertexBuffer(output.data(), output.size(), input.data(), vertex_count, stride);\n\n#if TRACE\n\t\tprintf(\"%d => %d\\n\", int(input.size()), int(output_size));\n#else\n\t\tfwrite(output.data(), 1, output_size, stdout);\n#endif\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "tools/gltfbasis.py",
    "content": "#!/usr/bin/python3\n\nimport argparse\nimport concurrent.futures\nimport matplotlib.pyplot as plt\nimport os\nimport os.path\nimport re\nimport subprocess\n\nargp = argparse.ArgumentParser()\nargp.add_argument('--basisu', type=str, required=True)\nargp.add_argument('--graph', type=str, default=\"basisu.png\")\nargp.add_argument('--etcbase', action='store_true')\nargp.add_argument('files', type=str, nargs='+')\nargs = argp.parse_args()\n\ndef compress(path, flags):\n    temp_path = \"/dev/null\" if os.name == \"posix\" else \"NUL\"\n    output = subprocess.check_output([args.basisu, \"-file\", path, \"-output_file\", temp_path, \"-stats\", \"-ktx2\"] + flags)\n    for line in output.splitlines():\n        if m := re.match(r\".*source image.*?(\\d+)x(\\d+)\", line.decode()):\n            pixels = int(m.group(1)) * int(m.group(2))\n        elif m := re.match(r\".*Compression succeeded.*size (\\d+) bytes\", line.decode()):\n            bytes = int(m.group(1))\n        elif m := re.match(r\"\\.basis RGB Avg:.*RMS: (\\d+\\.\\d+) PSNR: (\\d+\\.\\d+)\", line.decode()):\n            rms = float(m.group(1))\n            psnr = float(m.group(2))\n\n    return {'path': path, 'bpp': bytes * 8 / pixels, 'rms': rms, 'psnr': psnr}\n\ndef stats(path):\n    with concurrent.futures.ThreadPoolExecutor(16) as executor:\n        futures = []\n        for i in range(0, 26):\n            rdo_l = i / 5\n            flags = [\"-uastc\", \"-uastc_level\", \"1\", \"-uastc_rdo_l\", str(rdo_l), \"-uastc_rdo_d\", \"1024\"]\n            futures.append((executor.submit(compress, path, flags), rdo_l))\n        concurrent.futures.wait([f for (f, r) in futures])\n        results = []\n        bppbase = 0\n        for future, rdo_l in futures:\n            res = future.result()\n            if rdo_l == 0:\n                bppbase = res['bpp']\n            res['rdo_l'] = rdo_l\n            res['ratio'] = res['bpp'] / bppbase\n            results.append(res)\n        return results\n\nfields = ['bpp', 'rms', 'psnr', 'ratio']\nfig, axs = plt.subplots(1, len(fields) + 1, layout='constrained')\nfig.set_figwidth(5 * (len(fields) + 1))\nlines = []\n\nfor path in args.files:\n    print('Processing', path)\n    results = stats(path)\n    etcbase = compress(path, [\"-q\", \"192\"]) if args.etcbase else None\n\n    for idx, field in enumerate(fields):\n        line, = axs[idx].plot([r['rdo_l'] for r in results], [r[field] for r in results])\n        if etcbase and field in etcbase:\n            axs[idx].axhline(etcbase[field], color=line.get_color(), linestyle='dotted')\n        if idx == 0:\n            lines.append(line)\n\n    axs[len(fields)].scatter([r['ratio'] for r in results], [r['psnr'] for r in results], color=line.get_color())\n\nfor idx, field in enumerate(fields):\n    axs[idx].set_title(field)\n\naxs[len(fields)].set_title('psnr vs ratio')\n\nfig.legend(lines, [os.path.basename(path) for path in args.files], loc='outside right upper')\n\nplt.savefig(args.graph)\n"
  },
  {
    "path": "tools/objloader.cpp",
    "content": "#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#define FAST_OBJ_IMPLEMENTATION\n#include \"../extern/fast_obj.h\"\n"
  },
  {
    "path": "tools/simplifyfuzz.cpp",
    "content": "#include \"../src/meshoptimizer.h\"\n\n#include <float.h>\n#include <stdint.h>\n#include <stdlib.h>\n\nextern \"C\" int LLVMFuzzerTestOneInput(const uint8_t* buffer, size_t size)\n{\n\tif (size == 0)\n\t\treturn 0;\n\n\tsrand(buffer[0]);\n\n\tfloat vb[100][4];\n\n\tfor (int i = 0; i < 100; ++i)\n\t{\n\t\tvb[i][0] = rand() % 10;\n\t\tvb[i][1] = rand() % 10;\n\t\tvb[i][2] = rand() % 10;\n\t\tvb[i][3] = rand() % 10;\n\t}\n\n\tunsigned int ib[999];\n\tint indices = (size - 1) < 999 ? (size - 1) / 3 * 3 : 999;\n\n\tfor (int i = 0; i < indices; ++i)\n\t\tib[i] = buffer[1 + i] % 100;\n\n\tunsigned int res[999];\n\n\tmeshopt_simplify(res, ib, indices, vb[0], 100, sizeof(float) * 4, 0, 1e-1f, 0, NULL);\n\tmeshopt_simplify(res, ib, indices, vb[0], 100, sizeof(float) * 4, 0, FLT_MAX, 0, NULL);\n\tmeshopt_simplify(res, ib, indices, vb[0], 100, sizeof(float) * 4, 0, FLT_MAX, meshopt_SimplifyLockBorder, NULL);\n\tmeshopt_simplify(res, ib, indices, vb[0], 100, sizeof(float) * 4, 0, FLT_MAX, meshopt_SimplifySparse, NULL);\n\tmeshopt_simplify(res, ib, indices, vb[0], 100, sizeof(float) * 4, 0, FLT_MAX, meshopt_SimplifyPrune, NULL);\n\tmeshopt_simplify(res, ib, indices, vb[0], 100, sizeof(float) * 4, 0, FLT_MAX, meshopt_SimplifyPermissive, NULL);\n\n\tfloat aw = 1;\n\tmeshopt_simplifyWithAttributes(res, ib, indices, vb[0], 100, sizeof(float) * 4, vb[0] + 3, sizeof(float) * 4, &aw, 1, NULL, 0, FLT_MAX, 0, NULL);\n\n\t// must be last, as it modifies all inputs in place\n\tmeshopt_simplifyWithUpdate(ib, indices, vb[0], 100, sizeof(float) * 4, vb[0] + 3, sizeof(float) * 4, &aw, 1, NULL, 0, FLT_MAX, 0, NULL);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "tools/vcachetester.cpp",
    "content": "#ifdef _WIN32\n#include <assert.h>\n#include <d3d11.h>\n#include <d3dcompiler.h>\n#include <stdio.h>\n\n#include <cassert>\n#include <cmath>\n\n#include <algorithm>\n#include <vector>\n\n#include \"../extern/fast_obj.h\"\n#include \"../src/meshoptimizer.h\"\n\n#pragma comment(lib, \"d3d11.lib\")\n#pragma comment(lib, \"d3dcompiler.lib\")\n#pragma comment(lib, \"dxgi.lib\")\n\nvoid stripGen(std::vector<unsigned int>& indices, int x0, int x1, int y0, int y1, int width, bool prefetch)\n{\n\tif (prefetch)\n\t{\n\t\tfor (int x = x0; x < x1; x++)\n\t\t{\n\t\t\tindices.push_back(x + 0);\n\t\t\tindices.push_back(x + 0);\n\t\t\tindices.push_back(x + 1);\n\t\t}\n\t}\n\n\tfor (int y = y0; y < y1; y++)\n\t{\n\t\tfor (int x = x0; x < x1; x++)\n\t\t{\n\t\t\tindices.push_back((width + 1) * (y + 0) + (x + 0));\n\t\t\tindices.push_back((width + 1) * (y + 1) + (x + 0));\n\t\t\tindices.push_back((width + 1) * (y + 0) + (x + 1));\n\n\t\t\tindices.push_back((width + 1) * (y + 0) + (x + 1));\n\t\t\tindices.push_back((width + 1) * (y + 1) + (x + 0));\n\t\t\tindices.push_back((width + 1) * (y + 1) + (x + 1));\n\t\t}\n\t}\n}\n\nvoid gridGen(std::vector<unsigned int>& indices, int x0, int x1, int y0, int y1, int width, int cacheSize, bool prefetch)\n{\n\tif (x1 - x0 + 1 < cacheSize)\n\t{\n\t\tbool prefetchStrip = 2 * (x1 - x0) + 1 > cacheSize && prefetch;\n\n\t\tstripGen(indices, x0, x1, y0, y1, width, prefetchStrip);\n\t}\n\telse\n\t{\n\t\tint xm = x0 + cacheSize - 2;\n\t\tgridGen(indices, x0, xm, y0, y1, width, cacheSize, prefetch);\n\t\tgridGen(indices, xm, x1, y0, y1, width, cacheSize, prefetch);\n\t}\n}\n\nunsigned int queryVSInvocations(ID3D11Device* device, ID3D11DeviceContext* context, const unsigned int* indices, size_t index_count)\n{\n\tif (index_count == 0)\n\t\treturn 0;\n\n\tID3D11Buffer* ib = 0;\n\n\t{\n\t\tD3D11_BUFFER_DESC bd = {};\n\n\t\tbd.Usage = D3D11_USAGE_DYNAMIC;\n\t\tbd.ByteWidth = index_count * 4;\n\t\tbd.BindFlags = D3D11_BIND_INDEX_BUFFER;\n\t\tbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;\n\n\t\tdevice->CreateBuffer(&bd, 0, &ib);\n\n\t\tD3D11_MAPPED_SUBRESOURCE ms;\n\t\tcontext->Map(ib, 0, D3D11_MAP_WRITE_DISCARD, 0, &ms);\n\t\tmemcpy(ms.pData, indices, index_count * 4);\n\t\tcontext->Unmap(ib, 0);\n\t}\n\n\tcontext->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);\n\tcontext->IASetIndexBuffer(ib, DXGI_FORMAT_R32_UINT, 0);\n\n\tD3D11_QUERY_DESC qdesc = {D3D11_QUERY_PIPELINE_STATISTICS};\n\tID3D11Query* query = 0;\n\tdevice->CreateQuery(&qdesc, &query);\n\n\tcontext->Begin(query);\n\tcontext->DrawIndexed(index_count, 0, 0);\n\tcontext->End(query);\n\n\tD3D11_QUERY_DATA_PIPELINE_STATISTICS stats = {};\n\twhile (S_FALSE == context->GetData(query, &stats, sizeof(stats), 0))\n\t\t;\n\n\tquery->Release();\n\tib->Release();\n\n\tassert(stats.IAVertices == index_count);\n\n\treturn stats.VSInvocations;\n}\n\nvoid setupShaders(ID3D11Device* device, ID3D11DeviceContext* context)\n{\n\t// load and compile the two shaders\n\tconst char* shaders =\n\t    \"#define ATTRIBUTES 5\\n\"\n\t    \"struct Foo { float4 v[ATTRIBUTES]; };\"\n\t    \"float4 VS(uint index: SV_VertexId, out Foo foo: FOO): SV_Position { uint i = index % 3; [unroll] for (int j = 0; j < ATTRIBUTES; j++) foo.v[j] = j; return float4(i != 0, i != 2, 0, 1); }\"\n\t    \"float4 PS(Foo foo: FOO): SV_Target { float4 result = 0; [unroll] for (int j = 0; j < ATTRIBUTES; j++) result += foo.v[j]; return result; }\";\n\n\tID3DBlob* vsblob = 0;\n\tID3DBlob* psblob = 0;\n\tD3DCompile(shaders, strlen(shaders), 0, 0, 0, \"VS\", \"vs_5_0\", 0, 0, &vsblob, 0);\n\tD3DCompile(shaders, strlen(shaders), 0, 0, 0, \"PS\", \"ps_5_0\", 0, 0, &psblob, 0);\n\n\tID3D11VertexShader* vs = 0;\n\tID3D11PixelShader* ps = 0;\n\tdevice->CreateVertexShader(vsblob->GetBufferPointer(), vsblob->GetBufferSize(), 0, &vs);\n\tdevice->CreatePixelShader(psblob->GetBufferPointer(), psblob->GetBufferSize(), 0, &ps);\n\n\tcontext->VSSetShader(vs, 0, 0);\n\tcontext->PSSetShader(ps, 0, 0);\n}\n\ntemplate <typename Cache>\nvoid inspectCache(Cache cache)\n{\n\tunsigned int max_cache_size = 200;\n\tunsigned int grid_size = 100;\n\n\tfor (unsigned int cache_size = 3; cache_size <= max_cache_size; cache_size += 1)\n\t{\n\t\tstd::vector<unsigned int> grid1;\n\t\tgridGen(grid1, 0, grid_size, 0, grid_size, grid_size, cache_size, true);\n\n\t\tstd::vector<unsigned int> grid2;\n\t\tgridGen(grid2, 0, grid_size, 0, grid_size, grid_size, cache_size, false);\n\n\t\tstd::vector<unsigned int> grid3;\n\t\tgridGen(grid3, 0, grid_size, 0, grid_size, grid_size, grid_size * 4, false); // this generates a simple indexed grid without striping/degenerate triangles\n\t\tmeshopt_optimizeVertexCacheFifo(&grid3[0], &grid3[0], grid3.size(), (grid_size + 1) * (grid_size + 1), cache_size);\n\n\t\tstd::vector<unsigned int> grid4;\n\t\tgridGen(grid4, 0, grid_size, 0, grid_size, grid_size, grid_size * 4, false); // this generates a simple indexed grid without striping/degenerate triangles\n\t\tmeshopt_optimizeVertexCache(&grid4[0], &grid4[0], grid4.size(), (grid_size + 1) * (grid_size + 1));\n\n\t\tunsigned int invocations1 = cache(&grid1[0], grid1.size());\n\t\tunsigned int invocations2 = cache(&grid2[0], grid2.size());\n\t\tunsigned int invocations3 = cache(&grid3[0], grid3.size());\n\t\tunsigned int invocations4 = cache(&grid4[0], grid4.size());\n\n\t\tunsigned int ideal_invocations = (grid_size + 1) * (grid_size + 1);\n\n\t\tprintf(\"%d, %f, %f, %f, %f\\n\", cache_size,\n\t\t    double(invocations1) / double(ideal_invocations),\n\t\t    double(invocations2) / double(ideal_invocations),\n\t\t    double(invocations3) / double(ideal_invocations),\n\t\t    double(invocations4) / double(ideal_invocations));\n\t}\n}\n\nvoid testCache(IDXGIAdapter* adapter)\n{\n\tID3D11Device* device = 0;\n\tID3D11DeviceContext* context = 0;\n\tD3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, 0, 0, 0, 0, D3D11_SDK_VERSION, &device, 0, &context);\n\n\tsetupShaders(device, context);\n\n\tinspectCache([&](const unsigned int* indices, size_t index_count)\n\t    { return queryVSInvocations(device, context, indices, index_count); });\n}\n\nvoid testCacheSequence(IDXGIAdapter* adapter, int argc, char** argv)\n{\n\tID3D11Device* device = 0;\n\tID3D11DeviceContext* context = 0;\n\tD3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, 0, 0, 0, 0, D3D11_SDK_VERSION, &device, 0, &context);\n\n\tsetupShaders(device, context);\n\n\tstd::vector<unsigned int> ib;\n\n\tfor (int i = 2; i < argc; ++i)\n\t{\n\t\tchar* end;\n\t\tint i0 = strtol(argv[i], &end, 10);\n\n\t\tif (end[0] == '-')\n\t\t{\n\t\t\tint i1 = strtol(end + 1, &end, 10);\n\n\t\t\tif (end[0] != 0)\n\t\t\t{\n\t\t\t\tprintf(\"Unrecognized index range: %s\\n\", argv[i]);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (i0 < i1)\n\t\t\t{\n\t\t\t\tfor (int ii = i0; ii <= i1; ++ii)\n\t\t\t\t\tib.push_back(ii);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfor (int ii = i0; ii >= i1; --ii)\n\t\t\t\t\tib.push_back(ii);\n\t\t\t}\n\t\t}\n\t\telse if (end[0] == '*')\n\t\t{\n\t\t\tint i1 = strtol(end + 1, &end, 10);\n\n\t\t\tif (end[0] != 0 || i1 == 0)\n\t\t\t{\n\t\t\t\tprintf(\"Unrecognized index range: %s\\n\", argv[i]);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfor (int ii = 0; ii < i1; ++ii)\n\t\t\t\tib.push_back(i0);\n\t\t}\n\t\telse if (end[0] == 'x')\n\t\t{\n\t\t\tint i1 = strtol(end + 1, &end, 10);\n\n\t\t\tif (end[0] != 0)\n\t\t\t{\n\t\t\t\tprintf(\"Unrecognized index range: %s\\n\", argv[i]);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tstripGen(ib, 0, i0, 0, i1, i0, true);\n\t\t}\n\t\telse if (end[0] == 0)\n\t\t{\n\t\t\tib.push_back(i0);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tprintf(\"Unrecognized index range: %s\\n\", argv[i]);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (ib.size() % 3)\n\t\tib.resize(ib.size() - ib.size() % 3);\n\n\tstd::vector<bool> xformed(ib.size());\n\n\tfor (size_t i = 0; i < ib.size(); i += 3)\n\t{\n\t\tunsigned int inv0 = i == 0 ? 0 : queryVSInvocations(device, context, ib.data(), i);\n\t\tunsigned int inv1 = queryVSInvocations(device, context, ib.data(), i + 3);\n\n\t\tassert(inv0 <= inv1);\n\t\tassert(inv0 + 3 >= inv1);\n\n\t\tswitch (inv1 - inv0)\n\t\t{\n\t\tcase 0:\n\t\t\txformed[i + 0] = xformed[i + 1] = xformed[i + 2] = false;\n\t\t\tbreak;\n\n\t\tcase 3:\n\t\t\txformed[i + 0] = xformed[i + 1] = xformed[i + 2] = true;\n\t\t\tbreak;\n\n\t\tcase 1:\n\t\tcase 2:\n\t\t{\n\t\t\tunsigned int a = ib[i + 0];\n\t\t\tunsigned int b = ib[i + 1];\n\t\t\tunsigned int c = ib[i + 2];\n\n\t\t\tib[i + 0] = ib[i + 1] = ib[i + 2] = a;\n\t\t\tunsigned int inva = queryVSInvocations(device, context, ib.data(), i + 3);\n\n\t\t\tib[i + 1] = ib[i + 2] = b;\n\t\t\tunsigned int invb = queryVSInvocations(device, context, ib.data(), i + 3);\n\n\t\t\tib[i + 2] = c;\n\t\t\tunsigned int invc = queryVSInvocations(device, context, ib.data(), i + 3);\n\n\t\t\tassert(inv0 <= inva && inva <= inv1);\n\t\t\tassert(inv0 <= invb && invb <= inv1);\n\t\t\tassert(inv0 <= invc && invc <= inv1);\n\n\t\t\tif (inv1 - inv0 == 1 && a == c && inva == inv1 && invb == inv0 && invc == inv1)\n\t\t\t{\n\t\t\t\txformed[i + 0] = false;\n\t\t\t\txformed[i + 1] = false;\n\t\t\t\txformed[i + 2] = true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tassert(inva <= invb);\n\t\t\t\tassert(invb <= invc);\n\n\t\t\t\txformed[i + 0] = inva == inv0 + 1;\n\t\t\t\txformed[i + 1] = invb == inva + 1;\n\t\t\t\txformed[i + 2] = invc == invb + 1;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\t}\n\t}\n\n\tunsigned int xformed_total = 0;\n\n\tfor (size_t i = 0; i < ib.size(); ++i)\n\t\txformed_total += xformed[i];\n\n\tprintf(\"// Sequence: %d indices\", int(ib.size()));\n\n\tfor (size_t i = 0; i < ib.size(); ++i)\n\t{\n\t\tif (i % 12 == 0)\n\t\t{\n\t\t\tprintf(\"\\n// %3d*3:\", int(i / 3));\n\t\t}\n\n\t\tif (xformed[i])\n\t\t\tprintf(\" %3d*\", ib[i]);\n\t\telse\n\t\t\tprintf(\" %3d \", ib[i]);\n\t}\n\n\tprintf(\"\\n\");\n\n\tstd::vector<unsigned int> cached;\n\n\tfor (size_t i = 0; i < ib.size(); ++i)\n\t{\n\t\tunsigned int index = ib[i];\n\t\tunsigned int inv0 = queryVSInvocations(device, context, ib.data(), ib.size());\n\n\t\tib.push_back(index);\n\t\tib.push_back(index);\n\t\tib.push_back(index);\n\n\t\tunsigned int inv1 = queryVSInvocations(device, context, ib.data(), ib.size());\n\n\t\tib.resize(ib.size() - 3);\n\n\t\tif (inv1 == inv0)\n\t\t\tcached.push_back(index);\n\t}\n\n\tstd::sort(cached.begin(), cached.end());\n\tcached.erase(std::unique(cached.begin(), cached.end()), cached.end());\n\n\tprintf(\"// Cached  :\");\n\n\tfor (size_t i = 0; i < cached.size(); ++i)\n\t\tprintf(\" %d\", cached[i]);\n\n\tprintf(\" (%d)\\n\", int(cached.size()));\n\n\tunsigned int invocations = queryVSInvocations(device, context, ib.data(), ib.size());\n\n\tprintf(\"// Invocations: %d\\n\", invocations);\n\n\tassert(xformed_total == invocations);\n}\n\nvoid testCacheMeshes(IDXGIAdapter* adapter, int argc, char** argv)\n{\n\tID3D11Device* device = 0;\n\tID3D11DeviceContext* context = 0;\n\tD3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, 0, 0, 0, 0, D3D11_SDK_VERSION, &device, 0, &context);\n\n\tsetupShaders(device, context);\n\n\tbool stat = false;\n\n\tdouble atvr_sum = 0;\n\tdouble atvr_count = 0;\n\n\tunsigned int total_invocations = 0;\n\tunsigned int total_vertices = 0;\n\n\tfor (int i = 1; i < argc; ++i)\n\t{\n\t\tconst char* path = argv[i];\n\n\t\tif (strcmp(path, \"--stat\") == 0)\n\t\t{\n\t\t\tstat = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\tfastObjMesh* obj = fast_obj_read(path);\n\t\tif (!obj)\n\t\t{\n\t\t\tprintf(\"Error loading %s: file not found\\n\", path);\n\t\t\tcontinue;\n\t\t}\n\n\t\tstd::vector<unsigned int> ib1;\n\n\t\tsize_t index_offset = 0;\n\n\t\tfor (unsigned int i = 0; i < obj->face_count; ++i)\n\t\t{\n\t\t\tif (obj->face_vertices[i] <= 2)\n\t\t\t\tcontinue;\n\n\t\t\tfor (unsigned int j = 0; j < obj->face_vertices[i]; ++j)\n\t\t\t{\n\t\t\t\tfastObjIndex gi = obj->indices[index_offset + j];\n\n\t\t\t\t// triangulate polygon on the fly; offset-3 is always the first polygon vertex\n\t\t\t\tif (j >= 3)\n\t\t\t\t{\n\t\t\t\t\tunsigned int i0 = ib1[ib1.size() - 3];\n\t\t\t\t\tunsigned int i1 = ib1[ib1.size() - 1];\n\n\t\t\t\t\tib1.push_back(i0);\n\t\t\t\t\tib1.push_back(i1);\n\t\t\t\t}\n\n\t\t\t\tib1.push_back(gi.p);\n\t\t\t}\n\n\t\t\tindex_offset += obj->face_vertices[i];\n\t\t}\n\n\t\tunsigned int vertex_count = obj->position_count;\n\t\tunsigned int index_count = ib1.size();\n\n\t\tunsigned int invocations1 = queryVSInvocations(device, context, ib1.data(), index_count);\n\n\t\tif (stat)\n\t\t{\n\t\t\tstd::vector<unsigned int> ib2(ib1.size());\n\t\t\tmeshopt_optimizeVertexCache(&ib2[0], &ib1[0], ib1.size(), vertex_count);\n\n\t\t\tunsigned int invocations = queryVSInvocations(device, context, ib2.data(), index_count);\n\n\t\t\tatvr_sum += double(invocations) / double(vertex_count);\n\t\t\tatvr_count += 1;\n\n\t\t\ttotal_invocations += invocations;\n\t\t\ttotal_vertices += vertex_count;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tprintf(\"%s: baseline    %f\\n\", path, double(invocations1) / double(vertex_count));\n\n\t\t\tstd::vector<unsigned int> ib3(ib1.size());\n\t\t\tmeshopt_optimizeVertexCache(&ib3[0], &ib1[0], ib1.size(), vertex_count);\n\n\t\t\tunsigned int invocations3 = queryVSInvocations(device, context, ib3.data(), index_count);\n\n\t\t\tprintf(\"%s: forsyth     %f\\n\", path, double(invocations3) / double(vertex_count));\n\n\t\t\tfor (unsigned int cache_size = 12; cache_size <= 24; ++cache_size)\n\t\t\t{\n\t\t\t\tstd::vector<unsigned int> ib2(ib1.size());\n\t\t\t\tmeshopt_optimizeVertexCacheFifo(&ib2[0], &ib1[0], ib1.size(), vertex_count, cache_size);\n\n\t\t\t\tunsigned int invocations2 = queryVSInvocations(device, context, ib2.data(), index_count);\n\n\t\t\t\tprintf(\"%s: tipsify(%d) %f\\n\", path, cache_size, double(invocations2) / double(vertex_count));\n\t\t\t}\n\t\t}\n\t}\n\n\tif (stat)\n\t{\n\t\tprintf(\"ATVR: average %f cumulative %f; %d vertices\\n\", atvr_sum / atvr_count, double(total_invocations) / double(total_vertices), total_vertices);\n\t}\n}\n\nint main(int argc, char** argv)\n{\n\tIDXGIFactory1* factory = 0;\n\tCreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory);\n\n\tIDXGIAdapter* adapter = NULL;\n\tfor (unsigned int index = 0; SUCCEEDED(factory->EnumAdapters(index, &adapter)); ++index)\n\t{\n\t\tDXGI_ADAPTER_DESC ad = {};\n\t\tadapter->GetDesc(&ad);\n\n\t\tif (ad.VendorId == 0x1414 && ad.DeviceId == 0x8c)\n\t\t\tcontinue; // Skip Microsoft Basic Render Driver\n\n\t\tprintf(\"// GPU %d: %S (Vendor %04x Device %04x)\\n\", index, ad.Description, ad.VendorId, ad.DeviceId);\n\n\t\tif (argc == 1)\n\t\t{\n\t\t\ttestCache(adapter);\n\t\t}\n\t\telse if (argc > 1 && strcmp(argv[1], \"--\") == 0)\n\t\t{\n\t\t\ttestCacheSequence(adapter, argc, argv);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ttestCacheMeshes(adapter, argc, argv);\n\t\t}\n\t}\n}\n#endif\n"
  },
  {
    "path": "tools/vcachetuner.cpp",
    "content": "#include \"../extern/fast_obj.h\"\n#include \"../src/meshoptimizer.h\"\n\n#define SDEFL_IMPLEMENTATION\n#include \"../extern/sdefl.h\"\n\n#include <algorithm>\n#include <functional>\n#include <vector>\n\n#include <cmath>\n#include <cstdint>\n#include <cstdio>\n#include <cstring>\n\nconst int kCacheSizeMax = 16;\nconst int kValenceMax = 8;\n\nnamespace meshopt\n{\nstruct VertexScoreTable\n{\n\tfloat cache[1 + kCacheSizeMax];\n\tfloat live[1 + kValenceMax];\n};\n} // namespace meshopt\n\nvoid meshopt_optimizeVertexCacheTable(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, const meshopt::VertexScoreTable* table);\n\nstruct Profile\n{\n\tfloat weight;\n\tint cache, warp, triangle; // vcache tuning parameters\n\tint compression;\n};\n\nProfile profiles[] = {\n    {1.f, 0, 0, 0, 0}, // Compression\n    {1.f, 0, 0, 0, 1}, // Compression w/deflate\n\n    // {1.f, 14, 64, 128}, // AMD GCN\n    // {1.f, 32, 32, 32},  // NVidia Pascal\n    // {1.f, 16, 32, 32}, // NVidia Kepler, Maxwell\n    // {1.f, 128, 0, 0}, // Intel\n};\n\nconst int Profile_Count = sizeof(profiles) / sizeof(profiles[0]);\n\nstruct pcg32_random_t\n{\n\tuint64_t state;\n\tuint64_t inc;\n};\n\n#define PCG32_INITIALIZER {0x853c49e6748fea9bULL, 0xda3e39cb94b95bdbULL}\n\nuint32_t pcg32_random_r(pcg32_random_t* rng)\n{\n\tuint64_t oldstate = rng->state;\n\t// Advance internal state\n\trng->state = oldstate * 6364136223846793005ULL + (rng->inc | 1);\n\t// Calculate output function (XSH RR), uses old state for max ILP\n\tuint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;\n\tuint32_t rot = oldstate >> 59u;\n\treturn (xorshifted >> rot) | (xorshifted << ((-rot) & 31));\n}\n\npcg32_random_t rngstate = PCG32_INITIALIZER;\n\nfloat rand01()\n{\n\treturn pcg32_random_r(&rngstate) / float(1ull << 32);\n}\n\nuint32_t rand32()\n{\n\treturn pcg32_random_r(&rngstate);\n}\n\nstruct State\n{\n\tfloat cache[kCacheSizeMax];\n\tfloat live[kValenceMax];\n\tfloat fitness;\n};\n\nstruct Mesh\n{\n\tconst char* name;\n\n\tsize_t vertex_count;\n\tstd::vector<unsigned int> indices;\n\n\tfloat metric_base[Profile_Count];\n};\n\nMesh gridmesh(unsigned int N)\n{\n\tMesh result;\n\n\tresult.name = \"grid\";\n\n\tresult.vertex_count = (N + 1) * (N + 1);\n\tresult.indices.reserve(N * N * 6);\n\n\tfor (unsigned int y = 0; y < N; ++y)\n\t\tfor (unsigned int x = 0; x < N; ++x)\n\t\t{\n\t\t\tresult.indices.push_back((y + 0) * (N + 1) + (x + 0));\n\t\t\tresult.indices.push_back((y + 0) * (N + 1) + (x + 1));\n\t\t\tresult.indices.push_back((y + 1) * (N + 1) + (x + 0));\n\n\t\t\tresult.indices.push_back((y + 1) * (N + 1) + (x + 0));\n\t\t\tresult.indices.push_back((y + 0) * (N + 1) + (x + 1));\n\t\t\tresult.indices.push_back((y + 1) * (N + 1) + (x + 1));\n\t\t}\n\n\treturn result;\n}\n\nMesh objmesh(const char* path)\n{\n\tfastObjMesh* obj = fast_obj_read(path);\n\tif (!obj)\n\t{\n\t\tprintf(\"Error loading %s: file not found\\n\", path);\n\t\treturn Mesh();\n\t}\n\n\tsize_t total_indices = 0;\n\n\tfor (unsigned int i = 0; i < obj->face_count; ++i)\n\t\tif (obj->face_vertices[i] > 2)\n\t\t\ttotal_indices += 3 * (obj->face_vertices[i] - 2);\n\n\tstruct Vertex\n\t{\n\t\tfloat px, py, pz;\n\t\tfloat nx, ny, nz;\n\t\tfloat tx, ty;\n\t};\n\n\tstd::vector<Vertex> vertices(total_indices);\n\n\tsize_t vertex_offset = 0;\n\tsize_t index_offset = 0;\n\n\tfor (unsigned int i = 0; i < obj->face_count; ++i)\n\t{\n\t\tif (obj->face_vertices[i] <= 2)\n\t\t\tcontinue;\n\n\t\tfor (unsigned int j = 0; j < obj->face_vertices[i]; ++j)\n\t\t{\n\t\t\tfastObjIndex gi = obj->indices[index_offset + j];\n\n\t\t\tVertex v =\n\t\t\t    {\n\t\t\t        obj->positions[gi.p * 3 + 0],\n\t\t\t        obj->positions[gi.p * 3 + 1],\n\t\t\t        obj->positions[gi.p * 3 + 2],\n\t\t\t        obj->normals[gi.n * 3 + 0],\n\t\t\t        obj->normals[gi.n * 3 + 1],\n\t\t\t        obj->normals[gi.n * 3 + 2],\n\t\t\t        obj->texcoords[gi.t * 2 + 0],\n\t\t\t        obj->texcoords[gi.t * 2 + 1],\n\t\t\t    };\n\n\t\t\t// triangulate polygon on the fly; offset-3 is always the first polygon vertex\n\t\t\tif (j >= 3)\n\t\t\t{\n\t\t\t\tvertices[vertex_offset + 0] = vertices[vertex_offset - 3];\n\t\t\t\tvertices[vertex_offset + 1] = vertices[vertex_offset - 1];\n\t\t\t\tvertex_offset += 2;\n\t\t\t}\n\n\t\t\tvertices[vertex_offset] = v;\n\t\t\tvertex_offset++;\n\t\t}\n\n\t\tindex_offset += obj->face_vertices[i];\n\t}\n\n\tfast_obj_destroy(obj);\n\n\tMesh result;\n\n\tresult.name = path;\n\n\tstd::vector<unsigned int> remap(total_indices);\n\n\tsize_t total_vertices = meshopt_generateVertexRemap(&remap[0], NULL, total_indices, &vertices[0], total_indices, sizeof(Vertex));\n\n\tresult.indices.resize(total_indices);\n\tmeshopt_remapIndexBuffer(&result.indices[0], NULL, total_indices, &remap[0]);\n\n\tresult.vertex_count = total_vertices;\n\n\treturn result;\n}\n\ntemplate <typename T>\nsize_t compress(const std::vector<T>& data, int level = SDEFL_LVL_DEF)\n{\n\tstd::vector<unsigned char> cbuf(sdefl_bound(int(data.size() * sizeof(T))));\n\tsdefl s = {};\n\treturn sdeflate(&s, &cbuf[0], reinterpret_cast<const unsigned char*>(&data[0]), int(data.size() * sizeof(T)), level);\n}\n\nvoid compute_metric(const State* state, const Mesh& mesh, float result[Profile_Count])\n{\n\tstd::vector<unsigned int> indices(mesh.indices.size());\n\n\tif (state)\n\t{\n\t\tmeshopt::VertexScoreTable table = {};\n\t\tmemcpy(table.cache + 1, state->cache, kCacheSizeMax * sizeof(float));\n\t\tmemcpy(table.live + 1, state->live, kValenceMax * sizeof(float));\n\t\tmeshopt_optimizeVertexCacheTable(&indices[0], &mesh.indices[0], mesh.indices.size(), mesh.vertex_count, &table);\n\t}\n\telse\n\t{\n\t\tmeshopt_optimizeVertexCache(&indices[0], &mesh.indices[0], mesh.indices.size(), mesh.vertex_count);\n\t}\n\n\tmeshopt_optimizeVertexFetch(NULL, &indices[0], indices.size(), NULL, mesh.vertex_count, 0);\n\n\tstd::vector<unsigned char> ibuf;\n\n\tfor (int profile = 0; profile < Profile_Count; ++profile)\n\t{\n\t\tif (profiles[profile].cache == 0)\n\t\t{\n\t\t\tibuf.resize(meshopt_encodeIndexBufferBound(indices.size(), mesh.vertex_count));\n\t\t\tibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), &indices[0], indices.size()));\n\t\t}\n\t}\n\n\tfor (int profile = 0; profile < Profile_Count; ++profile)\n\t{\n\t\tif (profiles[profile].cache)\n\t\t{\n\t\t\tmeshopt_VertexCacheStatistics stats = meshopt_analyzeVertexCache(&indices[0], indices.size(), mesh.vertex_count, profiles[profile].cache, profiles[profile].warp, profiles[profile].triangle);\n\t\t\tresult[profile] = stats.atvr;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// take into account both pre-deflate and post-deflate size but focus a bit more on post-deflate\n\t\t\tsize_t csize = profiles[profile].compression ? compress(ibuf) : ibuf.size();\n\n\t\t\tresult[profile] = double(csize) / double(indices.size() / 3);\n\t\t}\n\t}\n}\n\nfloat fitness_score(const State& state, const std::vector<Mesh>& meshes)\n{\n\tfloat result = 0;\n\tfloat count = 0;\n\n\tfor (auto& mesh : meshes)\n\t{\n\t\tfloat metric[Profile_Count];\n\t\tcompute_metric(&state, mesh, metric);\n\n\t\tfor (int profile = 0; profile < Profile_Count; ++profile)\n\t\t{\n\t\t\tresult += mesh.metric_base[profile] / metric[profile] * profiles[profile].weight;\n\t\t\tcount += profiles[profile].weight;\n\t\t}\n\t}\n\n\treturn result / count;\n}\n\nstd::vector<State> gen0(size_t count, const std::vector<Mesh>& meshes)\n{\n\tstd::vector<State> result;\n\n\tfor (size_t i = 0; i < count; ++i)\n\t{\n\t\tState state = {};\n\n\t\tfor (int j = 0; j < kCacheSizeMax; ++j)\n\t\t\tstate.cache[j] = rand01();\n\n\t\tfor (int j = 0; j < kValenceMax; ++j)\n\t\t\tstate.live[j] = rand01();\n\n\t\tstate.fitness = fitness_score(state, meshes);\n\n\t\tresult.push_back(state);\n\t}\n\n\treturn result;\n}\n\n// https://en.wikipedia.org/wiki/Differential_evolution\n// Good Parameters for Differential Evolution. Magnus Erik Hvass Pedersen, 2010\nstd::pair<State, float> genN(std::vector<State>& seed, const std::vector<Mesh>& meshes, float crossover = 0.8803f, float weight = 0.4717f)\n{\n\tstd::vector<State> result(seed.size());\n\n\tfor (size_t i = 0; i < seed.size(); ++i)\n\t{\n\t\tfor (;;)\n\t\t{\n\t\t\tint a = rand32() % seed.size();\n\t\t\tint b = rand32() % seed.size();\n\t\t\tint c = rand32() % seed.size();\n\n\t\t\tif (a == b || a == c || b == c || a == int(i) || b == int(i) || c == int(i))\n\t\t\t\tcontinue;\n\n\t\t\tint rc = rand32() % kCacheSizeMax;\n\t\t\tint rl = rand32() % kValenceMax;\n\n\t\t\tfor (int j = 0; j < kCacheSizeMax; ++j)\n\t\t\t{\n\t\t\t\tfloat r = rand01();\n\n\t\t\t\tif (r < crossover || j == rc)\n\t\t\t\t\tresult[i].cache[j] = std::max(0.f, std::min(1.f, seed[a].cache[j] + weight * (seed[b].cache[j] - seed[c].cache[j])));\n\t\t\t\telse\n\t\t\t\t\tresult[i].cache[j] = seed[i].cache[j];\n\t\t\t}\n\n\t\t\tfor (int j = 0; j < kValenceMax; ++j)\n\t\t\t{\n\t\t\t\tfloat r = rand01();\n\n\t\t\t\tif (r < crossover || j == rl)\n\t\t\t\t\tresult[i].live[j] = std::max(0.f, std::min(1.f, seed[a].live[j] + weight * (seed[b].live[j] - seed[c].live[j])));\n\t\t\t\telse\n\t\t\t\t\tresult[i].live[j] = seed[i].live[j];\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t}\n\n#pragma omp parallel for\n\tfor (size_t i = 0; i < seed.size(); ++i)\n\t{\n\t\tresult[i].fitness = fitness_score(result[i], meshes);\n\t}\n\n\tState best = {};\n\tfloat bestfit = 0;\n\n\tfor (size_t i = 0; i < seed.size(); ++i)\n\t{\n\t\tif (result[i].fitness > seed[i].fitness)\n\t\t\tseed[i] = result[i];\n\n\t\tif (seed[i].fitness > bestfit)\n\t\t{\n\t\t\tbest = seed[i];\n\t\t\tbestfit = seed[i].fitness;\n\t\t}\n\t}\n\n\treturn std::make_pair(best, bestfit);\n}\n\nbool load_state(const char* path, std::vector<State>& result)\n{\n\tFILE* file = fopen(path, \"rb\");\n\tif (!file)\n\t\treturn false;\n\n\tState state;\n\n\tresult.clear();\n\n\twhile (fread(&state, sizeof(State), 1, file) == 1)\n\t\tresult.push_back(state);\n\n\tfclose(file);\n\n\treturn true;\n}\n\nbool save_state(const char* path, const std::vector<State>& result)\n{\n\tFILE* file = fopen(path, \"wb\");\n\tif (!file)\n\t\treturn false;\n\n\tfor (auto& state : result)\n\t{\n\t\tif (fwrite(&state, sizeof(State), 1, file) != 1)\n\t\t{\n\t\t\tfclose(file);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn fclose(file) == 0;\n}\n\nvoid dump_state(const State& state)\n{\n\tprintf(\"cache:\");\n\tfor (int i = 0; i < kCacheSizeMax; ++i)\n\t{\n\t\tprintf(\" %.3f\", state.cache[i]);\n\t}\n\tprintf(\"\\n\");\n\n\tprintf(\"live:\");\n\tfor (int i = 0; i < kValenceMax; ++i)\n\t{\n\t\tprintf(\" %.3f\", state.live[i]);\n\t}\n\tprintf(\"\\n\");\n}\n\nvoid dump_stats(const State& state, const std::vector<Mesh>& meshes)\n{\n\tfloat improvement[Profile_Count] = {};\n\n\tfor (size_t i = 0; i < meshes.size(); ++i)\n\t{\n\t\tfloat metric[Profile_Count];\n\t\tcompute_metric(&state, meshes[i], metric);\n\n\t\tprintf(\" %s\", meshes[i].name);\n\t\tfor (int profile = 0; profile < Profile_Count; ++profile)\n\t\t\tprintf(\" %f\", metric[profile]);\n\n\t\tfor (int profile = 0; profile < Profile_Count; ++profile)\n\t\t\timprovement[profile] += meshes[i].metric_base[profile] / metric[profile];\n\t}\n\n\tprintf(\"; improvement\");\n\tfor (int profile = 0; profile < Profile_Count; ++profile)\n\t\tprintf(\" %f\", improvement[profile] / float(meshes.size()));\n\n\tprintf(\"\\n\");\n}\n\nint main(int argc, char** argv)\n{\n\tmeshopt_encodeIndexVersion(1);\n\n\tstd::vector<Mesh> meshes;\n\n\tmeshes.push_back(gridmesh(50));\n\n\tfor (int i = 1; i < argc; ++i)\n\t\tmeshes.push_back(objmesh(argv[i]));\n\n\tsize_t total_triangles = 0;\n\n\tfor (auto& mesh : meshes)\n\t{\n\t\tcompute_metric(nullptr, mesh, mesh.metric_base);\n\n\t\ttotal_triangles += mesh.indices.size() / 3;\n\t}\n\n\tstd::vector<State> pop;\n\tsize_t gen = 0;\n\n\tif (load_state(\"mutator.state\", pop))\n\t{\n\t\tprintf(\"Loaded %d state vectors\\n\", int(pop.size()));\n\t}\n\telse\n\t{\n\t\tpop = gen0(95, meshes);\n\t}\n\n\tprintf(\"%d meshes, %.1fM triangles\\n\", int(meshes.size()), double(total_triangles) / 1e6);\n\n\tfor (;;)\n\t{\n\t\tauto best = genN(pop, meshes);\n\t\tgen++;\n\n\t\tif (gen % 10 == 0)\n\t\t{\n\t\t\tprintf(\"%d: fitness %f;\", int(gen), best.second);\n\t\t\tdump_stats(best.first, meshes);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tprintf(\"%d: fitness %f\\n\", int(gen), best.second);\n\t\t}\n\n\t\tdump_state(best.first);\n\n\t\tif (save_state(\"mutator.state-temp\", pop) && rename(\"mutator.state-temp\", \"mutator.state\") == 0)\n\t\t{\n\t\t}\n\t\telse\n\t\t{\n\t\t\tprintf(\"ERROR: Can't save state\\n\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "tools/wasmpack.py",
    "content": "#!/usr/bin/python3\n\nimport re\nimport sys\n\n# regenerate with wasmpack.py generate\ntable = [32, 0, 65, 2, 1, 106, 34, 33, 3, 128, 11, 4, 13, 64, 6, 253, 10, 7, 15, 116, 127, 5, 8, 12, 40, 16, 19, 54, 20, 9, 27, 255, 113, 17, 42, 67, 24, 23, 146, 148, 18, 14, 22, 45, 70, 69, 56, 114, 101, 21, 25, 63, 75, 136, 108, 28, 118, 29, 73, 115]\n\npalette = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:;\";\n\ndef encode(buffer):\n\tresult = ''\n\n\tfor ch in buffer.read():\n\t\tif ch in table:\n\t\t\tindex = table.index(ch)\n\t\t\tresult += palette[index]\n\t\telse:\n\t\t\tresult += palette[60 + ch // 64]\n\t\t\tresult += palette[ch % 64]\n\n\treturn result\n\ndef stats(buffer):\n\thist = [0] * 256\n\tfor ch in buffer.read():\n\t\thist[ch] += 1\n\n\tresult = [i for i in range(256)]\n\tresult.sort(key=lambda i: hist[i], reverse=True)\n\n\treturn result\n\ndef patch(target, stem, code):\n\twith open(target, 'r') as f: content = f.read()\n\n\tpattern = r'([\"\\']).*\\1(;\\s*//\\s*embed! ' + stem + r')'\n\tresult = re.sub(pattern, r'\\1' + re.escape(code) + r'\\1\\2', content)\n\n\twith open(target, 'w') as f: f.write(result)\n\nif len(sys.argv) >= 2 and sys.argv[1] == 'generate':\n\tprint(stats(sys.stdin.buffer)[:60])\nelif len(sys.argv) >= 2 and sys.argv[1] == 'patch':\n\tcode = encode(sys.stdin.buffer)\n\tpatch(sys.argv[2], sys.argv[3], code)\nelse:\n\tprint(encode(sys.stdin.buffer))\n"
  },
  {
    "path": "tools/wasmstubs.cpp",
    "content": "#ifndef __wasi__\n#error This file contains libc stubs for WASI SDK and should only be used in non-Emscripten WebAssembly builds\n#endif\n\n#include <assert.h>\n#include <stddef.h>\n#include <stdint.h>\n\nextern unsigned char __heap_base;\nstatic intptr_t sbrkp = intptr_t(&__heap_base);\n\nstatic const int WASM_PAGE_SIZE = 64 * 1024;\n\nextern \"C\" void* sbrk(intptr_t increment)\n{\n\tintptr_t sbrko = sbrkp;\n\n\tincrement = (increment + 3) & ~3;\n\tsbrkp += increment;\n\n\tsize_t heap_size = __builtin_wasm_memory_size(0) * WASM_PAGE_SIZE;\n\n\tif (size_t(sbrkp) > heap_size)\n\t{\n\t\tsize_t diff = (sbrkp - heap_size + WASM_PAGE_SIZE - 1) / WASM_PAGE_SIZE;\n\n\t\tif (__builtin_wasm_memory_grow(0, diff) == size_t(-1))\n\t\t\treturn (void*)-1;\n\t}\n\n\treturn (void*)sbrko;\n}\n\nextern \"C\" void* memcpy(void* destination, const void* source, size_t num)\n{\n\tchar* d = (char*)destination;\n\tconst char* s = (const char*)source;\n\n\tif (((uintptr_t(d) | uintptr_t(s)) & 3) == 0)\n\t{\n\t\twhile (num > 15)\n\t\t{\n\t\t\t((uint32_t*)d)[0] = ((uint32_t*)s)[0];\n\t\t\t((uint32_t*)d)[1] = ((uint32_t*)s)[1];\n\t\t\t((uint32_t*)d)[2] = ((uint32_t*)s)[2];\n\t\t\t((uint32_t*)d)[3] = ((uint32_t*)s)[3];\n\t\t\td += 16;\n\t\t\ts += 16;\n\t\t\tnum -= 16;\n\t\t}\n\n\t\twhile (num > 3)\n\t\t{\n\t\t\t((uint32_t*)d)[0] = ((uint32_t*)s)[0];\n\t\t\td += 4;\n\t\t\ts += 4;\n\t\t\tnum -= 4;\n\t\t}\n\t}\n\n\twhile (num > 0)\n\t{\n\t\t*d++ = *s++;\n\t\tnum--;\n\t}\n\n\treturn destination;\n}\n\nextern \"C\" void* memset(void* ptr, int value, size_t num)\n{\n\tuint32_t v32 = ~0u / 255 * uint8_t(value);\n\n\tchar* d = (char*)ptr;\n\n\tif ((uintptr_t(d) & 3) == 0)\n\t{\n\t\twhile (num > 15)\n\t\t{\n\t\t\t((uint32_t*)d)[0] = v32;\n\t\t\t((uint32_t*)d)[1] = v32;\n\t\t\t((uint32_t*)d)[2] = v32;\n\t\t\t((uint32_t*)d)[3] = v32;\n\t\t\td += 16;\n\t\t\tnum -= 16;\n\t\t}\n\n\t\twhile (num > 3)\n\t\t{\n\t\t\t((uint32_t*)d)[0] = v32;\n\t\t\td += 4;\n\t\t\tnum -= 4;\n\t\t}\n\t}\n\n\twhile (num > 0)\n\t{\n\t\t*d++ = char(value);\n\t\tnum--;\n\t}\n\n\treturn ptr;\n}\n\nvoid* operator new(size_t size)\n{\n\treturn sbrk((size + 7) & ~7);\n}\n\nvoid operator delete(void* ptr) throw()\n{\n\tvoid* brk = reinterpret_cast<void*>(sbrkp);\n\tassert(ptr <= brk);\n\n\tsbrk((char*)ptr - (char*)brk);\n}\n"
  }
]