Full Code of zeux/meshoptimizer for AI

master f6ab3ec097a8 cached
108 files
2.1 MB
551.4k tokens
1303 symbols
1 requests
Download .txt
Showing preview only (2,204K chars total). Download the full file or copy to clipboard to get everything.
Repository: zeux/meshoptimizer
Branch: master
Commit: f6ab3ec097a8
Files: 108
Total size: 2.1 MB

Directory structure:
gitextract_gsw93301/

├── .clang-format
├── .editorconfig
├── .git-blame-ignore-revs
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   └── feature_request.md
│   ├── codecov.yml
│   └── workflows/
│       ├── build.yml
│       ├── cifuzz.yml
│       └── release.yml
├── .gitignore
├── .prettierrc
├── CMakeLists.txt
├── CONTRIBUTING.md
├── LICENSE.md
├── Makefile
├── README.md
├── demo/
│   ├── ansi.c
│   ├── clusterlod.h
│   ├── index.html
│   ├── main.cpp
│   ├── meshletdec.slang
│   ├── nanite.cpp
│   ├── pirate.glb
│   ├── pirate.obj
│   ├── simplify.html
│   └── tests.cpp
├── extern/
│   ├── .clang-format
│   ├── cgltf.h
│   ├── fast_obj.h
│   └── sdefl.h
├── gltf/
│   ├── README.md
│   ├── animation.cpp
│   ├── cli.js
│   ├── encodebasis.cpp
│   ├── encodewebp.cpp
│   ├── fileio.cpp
│   ├── fuzz.dict
│   ├── fuzz.glb
│   ├── gltfpack.cpp
│   ├── gltfpack.h
│   ├── gltfpack.manifest
│   ├── image.cpp
│   ├── json.cpp
│   ├── library.js
│   ├── material.cpp
│   ├── mesh.cpp
│   ├── node.cpp
│   ├── package.json
│   ├── parsegltf.cpp
│   ├── parselib.cpp
│   ├── parseobj.cpp
│   ├── stream.cpp
│   ├── wasistubs.cpp
│   ├── wasistubs.txt
│   └── write.cpp
├── js/
│   ├── README.md
│   ├── benchmark.js
│   ├── index.d.ts
│   ├── index.js
│   ├── meshopt_clusterizer.d.ts
│   ├── meshopt_clusterizer.js
│   ├── meshopt_clusterizer.test.js
│   ├── meshopt_decoder.cjs
│   ├── meshopt_decoder.d.ts
│   ├── meshopt_decoder.mjs
│   ├── meshopt_decoder.test.js
│   ├── meshopt_decoder_reference.js
│   ├── meshopt_encoder.d.ts
│   ├── meshopt_encoder.js
│   ├── meshopt_encoder.test.js
│   ├── meshopt_simplifier.d.ts
│   ├── meshopt_simplifier.js
│   ├── meshopt_simplifier.test.js
│   ├── package.json
│   └── wasi_trace.js
├── src/
│   ├── allocator.cpp
│   ├── clusterizer.cpp
│   ├── indexanalyzer.cpp
│   ├── indexcodec.cpp
│   ├── indexgenerator.cpp
│   ├── meshletcodec.cpp
│   ├── meshletutils.cpp
│   ├── meshoptimizer.h
│   ├── opacitymap.cpp
│   ├── overdrawoptimizer.cpp
│   ├── partition.cpp
│   ├── quantization.cpp
│   ├── rasterizer.cpp
│   ├── simplifier.cpp
│   ├── spatialorder.cpp
│   ├── stripifier.cpp
│   ├── vcacheoptimizer.cpp
│   ├── vertexcodec.cpp
│   ├── vertexfilter.cpp
│   └── vfetchoptimizer.cpp
└── tools/
    ├── bitmask.py
    ├── clusterfuzz.cpp
    ├── codecbench.cpp
    ├── codecfuzz.cpp
    ├── codectest.cpp
    ├── gltfbasis.py
    ├── objloader.cpp
    ├── simplifyfuzz.cpp
    ├── vcachetester.cpp
    ├── vcachetuner.cpp
    ├── wasmpack.py
    └── wasmstubs.cpp

================================================
FILE CONTENTS
================================================

================================================
FILE: .clang-format
================================================
Standard: Cpp03
UseTab: ForIndentation
TabWidth: 4
IndentWidth: 4
AccessModifierOffset: -4
BreakBeforeBraces: Allman
IndentCaseLabels: false
ColumnLimit: 0
PointerAlignment: Left
BreakConstructorInitializersBeforeComma: true
NamespaceIndentation: None
AlignEscapedNewlines: DontAlign
AlignAfterOpenBracket: DontAlign
IndentExternBlock: NoIndent
Macros: [MESHOPTIMIZER_ALLOC_CALLCONV=&_&]


================================================
FILE: .editorconfig
================================================
# See https://editorconfig.org/ for more info

[*]
charset = utf-8
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true


================================================
FILE: .git-blame-ignore-revs
================================================
# This file contains a list of Git commit hashes that should be hidden from the
# regular Git history. Typically, this includes commits involving mass auto-formatting
# or other normalizations. Commit hashes *must* use the full 40-character notation.
# To apply the ignore list in your local Git client, you must run:
#
#   git config blame.ignoreRevsFile .git-blame-ignore-revs
#
# This file is automatically used by GitHub.com's blame view.

# Convert CRLF to LF everywhere
bb4aa0e1372751b74425e77c9a42f972971568bf

# js: Reformat all sources with Prettier
3dea31b5c248594a62f49a3e41fc88d7ceae2de3

# Reformat workflow YAML files with Prettier
52e8e1f61712928a36b5257a894bb098a4a98b22


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report if you've found a bug in this project; please use GitHub Discussions instead if you are uncertain or the bug may be in your code.
title: ''
labels: bug
assignees: ''

---


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Questions and ideas
    url: https://github.com/zeux/meshoptimizer/discussions
    about: Please use GitHub Discussions if you have questions or ideas to discuss.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest a feature for this project; please use GitHub Discussions if your idea is not sufficiently concrete.
title: ''
labels: enhancement
assignees: ''

---


================================================
FILE: .github/codecov.yml
================================================
comment: false

coverage:
  status:
    project: off
    patch: off

ignore:
  - demo
  - extern
  - tools


================================================
FILE: .github/workflows/build.yml
================================================
name: build

on:
  push:
    branches:
      - 'master'
    paths-ignore:
      - '*.md'
  pull_request:
    paths-ignore:
      - '*.md'

jobs:
  unix:
    strategy:
      matrix:
        os:
          [
            { name: ubuntu, version: ubuntu-latest },
            { name: ubuntu-arm, version: ubuntu-24.04-arm },
            { name: ubuntu-clang, version: ubuntu-latest },
            { name: macos, version: macos-latest },
          ]
    name: ${{matrix.os.name}}
    runs-on: ${{matrix.os.version}}
    env:
      werror: 1
    steps:
      - uses: actions/checkout@v4
      - name: work around ASLR+ASAN compatibility
        run: sudo sysctl -w vm.mmap_rnd_bits=28
        if: matrix.os.name == 'ubuntu'
      - name: enable clang
        run: echo CXX=clang++ >Makefile.config
        if: matrix.os.name == 'ubuntu-clang'
      - name: make test
        run: |
          make -j2 config=sanitize test
          make -j2 config=debug test
          make -j2 config=release test
          make -j2 config=coverage test
          make -j2 config=coverage-scalar test
      - name: make gltfpack
        run: make -j2 config=release gltfpack
      - name: upload coverage
        run: |
          find . -type f -name '*.gcno' -exec gcov -p {} +
          sed -i -e "s/#####\(.*\)\(\/\/ unreachable.*\)/    -\1\2/" *.gcov
          bash <(curl -s https://codecov.io/bash) -f './src*.gcov' -X search -t ${{secrets.CODECOV_TOKEN}} -B ${{github.ref}}
        if: matrix.os.name != 'ubuntu-clang'

  windows:
    strategy:
      matrix:
        arch:
          [
            { name: windows, runner: windows-latest, arch: x64 },
            { name: windows-x86, runner: windows-latest, arch: Win32 },
            { name: windows-arm, runner: windows-11-arm, arch: ARM64 },
          ]
    name: ${{matrix.arch.name}}
    runs-on: ${{matrix.arch.runner}}
    steps:
      - uses: actions/checkout@v4
      - name: cmake configure
        run: cmake . -DMESHOPT_BUILD_DEMO=ON -DMESHOPT_BUILD_GLTFPACK=ON -DMESHOPT_WERROR=ON -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$<CONFIG:Debug>:Debug>" -A ${{matrix.arch.arch}}
      - name: cmake test
        shell: bash # necessary for fail-fast
        run: |
          cmake --build . -- -property:Configuration=Debug -verbosity:minimal
          Debug/meshoptdemo.exe demo/pirate.obj
          cmake --build . -- -property:Configuration=Release -verbosity:minimal
          Release/meshoptdemo.exe demo/pirate.obj

  nodejs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '16'
      - name: test decoder
        run: node js/meshopt_decoder.test.js
      - name: test simd decoder
        run: node --experimental-wasm-simd js/meshopt_decoder.test.js
      - name: test encoder
        run: node js/meshopt_encoder.test.js
      - name: test simplifier
        run: node js/meshopt_simplifier.test.js
      - name: test clusterizer
        run: node js/meshopt_clusterizer.test.js
      - name: check es5
        run: |
          npm install -g es-check@7.2.1
          npx es-check --module es5 js/meshopt_decoder.mjs js/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js
          npx es-check --module es2020 gltf/library.js

  gltfpack:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/checkout@v4
        with:
          repository: KhronosGroup/glTF-Sample-Assets
          path: glTF-Sample-Assets
      - name: work around ASLR+ASAN compatibility
        run: sudo sysctl -w vm.mmap_rnd_bits=28
      - name: make
        run: make -j2 config=sanitize gltfpack
      - name: test
        run: find glTF-Sample-Assets -name '*.gltf' -or -name '*.glb' | xargs -P 2 -L 16 -d '\n' ./gltfpack -cc -test
      - name: pack
        run: find glTF-Sample-Assets -name '*.gltf' | grep -v '\-Draco/' | xargs -P 2 -L 16 -d '\n' -I '{}' ./gltfpack -i '{}' -o '{}pack.gltf'
      - name: validate
        run: |
          curl -sL $VALIDATOR | tar xJ
          find glTF-Sample-Assets -name '*.gltfpack.gltf' | xargs -P 2 -L 1 -d '\n' ./gltf_validator -r -a
        env:
          VALIDATOR: https://github.com/KhronosGroup/glTF-Validator/releases/download/2.0.0-dev.3.10/gltf_validator-2.0.0-dev.3.10-linux64.tar.xz

  gltfpack-js:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
      - name: install wasi
        run: |
          curl -sL https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$VERSION/wasi-sdk-$VERSION.0-x86_64-linux.deb > wasi-sdk.deb
          sudo dpkg -i wasi-sdk.deb
        env:
          VERSION: 25
      - name: build
        run: |
          make -j2 -B gltf/library.wasm js
          git status
      - name: test
        run: |
          node gltf/cli.js -i demo/pirate.obj -o pirate.glb -v
          node gltf/cli.js -i `pwd`/pirate.glb -o pirate-repack.glb -cc -v
          wc -c pirate.glb pirate-repack.glb
          node js/meshopt_decoder.test.js
          node js/meshopt_encoder.test.js
          node js/meshopt_simplifier.test.js
          node js/meshopt_clusterizer.test.js

  gltfpack-full:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/checkout@v4
        with:
          repository: zeux/basis_universal
          ref: gltfpack
          path: basis_universal
      - uses: actions/checkout@v4
        with:
          repository: webmproject/libwebp
          ref: 1.6.0
          path: libwebp
      - name: cmake configure
        run: cmake . -DMESHOPT_BUILD_GLTFPACK=ON -DMESHOPT_GLTFPACK_BASISU_PATH=basis_universal -DMESHOPT_GLTFPACK_LIBWEBP_PATH=libwebp
      - name: cmake build
        run: cmake --build . --target gltfpack -j 4

  gltfpack-coverage:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/checkout@v4
        with:
          repository: zeux/basis_universal
          ref: gltfpack
          path: basis_universal
      - uses: actions/checkout@v4
        with:
          repository: KhronosGroup/glTF-Sample-Assets
          path: glTF-Sample-Assets
      - name: build basisu
        run: |
          cd basis_universal
          cmake . -D CMAKE_BUILD_TYPE=Debug
          make -j4 basisu_encoder
      - name: make
        run: make -j2 config=coverage BASISU=basis_universal gltfpack
      - name: test
        run: |
          find glTF-Sample-Assets -name '*.gltf' -or -name '*.glb' | xargs -P 2 -L 16 -d '\n' ./gltfpack -cc -test
          ./gltfpack -test demo/pirate.obj -si 0.5
          ./gltfpack -test demo/pirate.obj -si 0.5 -slb -se 0.02
          ./gltfpack -test demo/pirate.obj -si 0.1 -sa
          ./gltfpack -test demo/pirate.obj -noq
          ./gltfpack -test demo/pirate.obj -vp 16 -vt 16 -vn 16 -vc 16
          ./gltfpack -test demo/pirate.obj -vpf -vtf -vnf
          ./gltfpack -test demo/pirate.obj -vi -c
          ./gltfpack -test glTF-Sample-Assets/Models/ABeautifulGame/glTF/ABeautifulGame.gltf -mi -c
          ./gltfpack -test glTF-Sample-Assets/Models/ABeautifulGame/glTF/ABeautifulGame.gltf -kn -km -ke
          ./gltfpack -test glTF-Sample-Assets/Models/ABeautifulGame/glTF/ABeautifulGame.gltf -si 0.1 -sp
          ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -vpf -vtf -c
          ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -vpf -vtf -cc
          ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -tc
          ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -tc -tq color 10 -tu normal,attrib -ts attrib 0.5 -tl color 512
          ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -tr
          ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -tc -ts 0.5 -tl 64
          ./gltfpack -test glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -tc color -tfy -tq 4 -tj 1
          ./gltfpack -test glTF-Sample-Assets/Models/CesiumMan/glTF/CesiumMan.gltf -tu -ts 0.6 -tp -ar 0
          ./gltfpack -test glTF-Sample-Assets/Models/PrimitiveModeNormalsTest/glTF/PrimitiveModeNormalsTest.gltf -si 0.5
          ./gltfpack -test glTF-Sample-Assets/Models/VertexColorTest/glTF/VertexColorTest.gltf -si 0.5 -vc 12
          ./gltfpack -test glTF-Sample-Assets/Models/SimpleMeshes/glTF/SimpleMeshes.gltf -mm
          ./gltfpack -test glTF-Sample-Assets/Models/SimpleMeshes/glTF/SimpleMeshes.gltf -kn
          echo newmtl Leather > demo/pirate.mtl
          echo map_Kd leather.jpg >> demo/pirate.mtl
          echo map_d leather.jpg >> demo/pirate.mtl
          ./gltfpack -test demo/pirate.obj
      - name: test output
        run: |
          ./gltfpack || true
          ./gltfpack -h || true
          ./gltfpack -i glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -o box.glb -vv -r box.json
          ./gltfpack -i glTF-Sample-Assets/Models/BoxTextured/glTF/BoxTextured.gltf -o box.gltf -cf
      - name: upload coverage
        run: |
          find . -type f -name '*.gcno' -exec gcov -p {} +
          sed -i -e "s/#####\(.*\)\(\/\/ unreachable.*\)/    -\1\2/" *.gcov
          bash <(curl -s https://codecov.io/bash) -f './gltf*.gcov' -X search -t ${{secrets.CODECOV_TOKEN}} -B ${{github.ref}}

  macos-iphone:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - name: make
        run: make -j2 config=iphone


================================================
FILE: .github/workflows/cifuzz.yml
================================================
name: CIFuzz

on:
  push:
    branches:
      - 'master'
    paths-ignore:
      - '*.md'

jobs:
  Fuzzing:
    runs-on: ubuntu-latest
    steps:
      - name: Build Fuzzers
        id: build
        uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
        with:
          oss-fuzz-project-name: 'meshoptimizer'
          dry-run: false
          language: c++
      - name: Run Fuzzers
        uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
        with:
          oss-fuzz-project-name: 'meshoptimizer'
          fuzz-seconds: 30
          dry-run: false
          language: c++
      - name: Upload Crash
        uses: actions/upload-artifact@v4
        if: failure() && steps.build.outcome == 'success'
        with:
          name: artifacts
          path: ./out/artifacts


================================================
FILE: .github/workflows/release.yml
================================================
name: release

on:
  push:
    branches:
      - 'master'
    paths-ignore:
      - '*.md'

jobs:
  gltfpack:
    strategy:
      matrix:
        os:
          [
            { name: windows, version: windows-latest },
            { name: ubuntu, version: ubuntu-22.04 },
            { name: macos, version: macos-14 },
            { name: macos-intel, version: macos-14 },
          ]
    name: gltfpack-${{matrix.os.name}}
    runs-on: ${{matrix.os.version}}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/checkout@v4
        with:
          repository: zeux/basis_universal
          ref: gltfpack
          path: basis_universal
      - uses: actions/checkout@v4
        with:
          repository: webmproject/libwebp
          ref: 1.6.0
          path: libwebp
      - name: cmake configure
        run: cmake . -DMESHOPT_BUILD_GLTFPACK=ON -DMESHOPT_GLTFPACK_BASISU_PATH=basis_universal -D MESHOPT_GLTFPACK_LIBWEBP_PATH=libwebp -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded" -DCMAKE_BUILD_TYPE=Release
      - name: cmake configure x64
        run: cmake . -DSSE=ON
        if: matrix.os.name == 'ubuntu'
      - name: cmake configure mac-x64
        run: cmake . -DSSE=ON -DCMAKE_OSX_ARCHITECTURES=x86_64
        if: matrix.os.name == 'macos-intel'
      - name: cmake build
        run: cmake --build . --target gltfpack --config Release -j 3
      - uses: actions/upload-artifact@v4
        with:
          name: gltfpack-windows
          path: Release/gltfpack.exe
        if: matrix.os.name == 'windows'
      - uses: actions/upload-artifact@v4
        with:
          name: gltfpack-${{matrix.os.name}}
          path: gltfpack
        if: matrix.os.name != 'windows'

  nodejs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
      - name: install wasi
        run: |
          curl -sL https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$VERSION/wasi-sdk-$VERSION.0-x86_64-linux.deb > wasi-sdk.deb
          sudo dpkg -i wasi-sdk.deb
        env:
          VERSION: 25
      - name: build
        run: |
          make -j2 -B gltf/library.wasm js
          git status
      - name: npm pack
        run: |
          cp LICENSE.md gltf/
          cp LICENSE.md js/
          cd gltf && npm pack && cd ..
          cd js && npm pack && cd ..
      - uses: actions/upload-artifact@v4
        with:
          name: gltfpack-npm
          path: gltf/gltfpack-*.tgz
      - uses: actions/upload-artifact@v4
        with:
          name: meshoptimizer-npm
          path: js/meshoptimizer-*.tgz


================================================
FILE: .gitignore
================================================
# IDE integrations
/.cache/
/.idea/
/.vs/
/.vscode/
/.zed/

# Build files
/build/
/cmake*/
/out/
/*.dSYM/
/gltf/library.wasm
compile_commands.json

# Test files
/data/
/*.log
/perf.data*


================================================
FILE: .prettierrc
================================================
{
	"useTabs": true,
	"tabWidth": 4,
	"semi": true,
	"singleQuote": true,
	"printWidth": 150,
	"trailingComma": "es5",
	"overrides": [
		{
			"files": "*.yml",
			"options": {
				"tabWidth": 2
			}
		},
	]
}


================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.5...3.30)

if(POLICY CMP0077)
    cmake_policy(SET CMP0077 NEW) # Enables override of options from parent CMakeLists.txt
endif()

if(POLICY CMP0091)
    cmake_policy(SET CMP0091 NEW) # Enables use of MSVC_RUNTIME_LIBRARY
endif()

if(POLICY CMP0092)
    cmake_policy(SET CMP0092 NEW) # Enables clean /W4 override for MSVC
endif()

project(meshoptimizer VERSION 1.0 LANGUAGES CXX)

option(MESHOPT_BUILD_DEMO "Build demo" OFF)
option(MESHOPT_BUILD_GLTFPACK "Build gltfpack" OFF)
option(MESHOPT_BUILD_SHARED_LIBS "Build shared libraries" OFF)
option(MESHOPT_STABLE_EXPORTS "Only export stable APIs from shared library" OFF)
option(MESHOPT_WERROR "Treat warnings as errors" OFF)
option(MESHOPT_INSTALL "Install library" ON)

# Optional gltfpack components for texture compression support
set(MESHOPT_GLTFPACK_BASISU_PATH "" CACHE STRING "") # Basis Universal, https://github.com/BinomialLLC/basis_universal
set(MESHOPT_GLTFPACK_LIBWEBP_PATH "" CACHE STRING "") # libwebp, https://github.com/webmproject/libwebp

set(SOURCES
    src/meshoptimizer.h
    src/allocator.cpp
    src/clusterizer.cpp
    src/indexanalyzer.cpp
    src/indexcodec.cpp
    src/indexgenerator.cpp
    src/meshletcodec.cpp
    src/meshletutils.cpp
    src/opacitymap.cpp
    src/overdrawoptimizer.cpp
    src/partition.cpp
    src/quantization.cpp
    src/rasterizer.cpp
    src/simplifier.cpp
    src/spatialorder.cpp
    src/stripifier.cpp
    src/vcacheoptimizer.cpp
    src/vertexcodec.cpp
    src/vertexfilter.cpp
    src/vfetchoptimizer.cpp
)

set(GLTF_SOURCES
    gltf/animation.cpp
    gltf/encodebasis.cpp
    gltf/encodewebp.cpp
    gltf/fileio.cpp
    gltf/gltfpack.cpp
    gltf/image.cpp
    gltf/json.cpp
    gltf/material.cpp
    gltf/mesh.cpp
    gltf/node.cpp
    gltf/parseobj.cpp
    gltf/parselib.cpp
    gltf/parsegltf.cpp
    gltf/stream.cpp
    gltf/write.cpp
)

if(WIN32)
    list(APPEND GLTF_SOURCES gltf/gltfpack.manifest)
endif()

if(MSVC)
    add_compile_options(/W4)
else()
    add_compile_options(-Wall -Wextra -Wshadow -Wno-missing-field-initializers)
endif()

if(MESHOPT_WERROR)
    if(MSVC)
        add_compile_options(/WX)
    else()
        add_compile_options(-Werror)
    endif()
endif()

if(MESHOPT_BUILD_SHARED_LIBS)
    add_library(meshoptimizer SHARED ${SOURCES})
else()
    add_library(meshoptimizer STATIC ${SOURCES})
endif()

target_include_directories(meshoptimizer INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>")

if(MESHOPT_BUILD_SHARED_LIBS)
    set_target_properties(meshoptimizer PROPERTIES CXX_VISIBILITY_PRESET hidden)
    set_target_properties(meshoptimizer PROPERTIES VISIBILITY_INLINES_HIDDEN ON)

    # soversion may be requested via -DMESHOPT_SOVERSION=n; note that experimental APIs (marked with MESHOPTIMIZER_EXPERIMENTAL) are not ABI-stable
    if(MESHOPT_SOVERSION)
        set_target_properties(meshoptimizer PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${MESHOPT_SOVERSION})
    endif()

    if(WIN32)
        target_compile_definitions(meshoptimizer INTERFACE "MESHOPTIMIZER_API=__declspec(dllimport)")
        target_compile_definitions(meshoptimizer PRIVATE "MESHOPTIMIZER_API=__declspec(dllexport)")
    else()
        target_compile_definitions(meshoptimizer PUBLIC "MESHOPTIMIZER_API=__attribute__((visibility(\"default\")))")
    endif()

    target_compile_definitions(meshoptimizer PUBLIC MESHOPTIMIZER_ALLOC_EXPORT)

    if(MESHOPT_STABLE_EXPORTS)
		target_compile_definitions(meshoptimizer PUBLIC "MESHOPTIMIZER_EXPERIMENTAL=")
    endif()
endif()

if(MESHOPT_BUILD_DEMO)
    add_executable(demo demo/main.cpp demo/nanite.cpp demo/tests.cpp tools/objloader.cpp)
    set_target_properties(demo PROPERTIES CXX_STANDARD 11)
    set_target_properties(demo PROPERTIES OUTPUT_NAME meshoptdemo)
    target_link_libraries(demo meshoptimizer)
endif()

if(MESHOPT_BUILD_GLTFPACK)
    add_executable(gltfpack ${GLTF_SOURCES})
    set_target_properties(gltfpack PROPERTIES CXX_STANDARD 11)
    target_link_libraries(gltfpack meshoptimizer)

    if(MESHOPT_BUILD_SHARED_LIBS)
        string(CONCAT RPATH "$ORIGIN/../" ${CMAKE_INSTALL_LIBDIR})
        set_target_properties(gltfpack PROPERTIES INSTALL_RPATH ${RPATH})
    endif()

    if(NOT MESHOPT_GLTFPACK_BASISU_PATH STREQUAL "")
        get_filename_component(BASISU_PATH ${MESHOPT_GLTFPACK_BASISU_PATH} ABSOLUTE)
        if (NOT EXISTS ${BASISU_PATH})
            message(FATAL_ERROR "Basis Universal path ${BASISU_PATH} not found")
        endif()

        if (NOT SSE AND NOT MSVC AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
            message(WARNING "Building Basis Universal without SSE4.1 support; performance may be suboptimal")
        endif()

        add_subdirectory(${BASISU_PATH} ${CMAKE_CURRENT_BINARY_DIR}/basisu EXCLUDE_FROM_ALL)

        target_compile_definitions(gltfpack PRIVATE WITH_BASISU)
        target_link_libraries(gltfpack basisu_encoder)
        set_source_files_properties(gltf/encodebasis.cpp PROPERTIES INCLUDE_DIRECTORIES ${BASISU_PATH}) # necessary because basisu_encoder doesn't export include directories
    endif()

    if(NOT MESHOPT_GLTFPACK_LIBWEBP_PATH STREQUAL "")
        get_filename_component(LIBWEBP_PATH ${MESHOPT_GLTFPACK_LIBWEBP_PATH} ABSOLUTE)
        if (NOT EXISTS ${LIBWEBP_PATH})
            message(FATAL_ERROR "libwebp path ${LIBWEBP_PATH} not found")
        endif()

        add_subdirectory(${LIBWEBP_PATH} ${CMAKE_CURRENT_BINARY_DIR}/libwebp EXCLUDE_FROM_ALL)

        target_compile_definitions(gltfpack PRIVATE WITH_LIBWEBP)
        target_link_libraries(gltfpack webp)

        # when gltfpack is built with Basis Universal, we use Basis image decoders for PNG/JPEG support for ease of distribution
        if(NOT MESHOPT_GLTFPACK_BASISU_PATH STREQUAL "")
            target_compile_definitions(gltfpack PRIVATE WITH_LIBWEBP_BASIS)
        else()
            target_link_libraries(gltfpack imagedec)
        endif()
    endif()

    if(NOT MESHOPT_GLTFPACK_BASISU_PATH STREQUAL "" OR NOT MESHOPT_GLTFPACK_LIBWEBP_PATH STREQUAL "")
        find_package(Threads REQUIRED)
        target_link_libraries(gltfpack Threads::Threads)
    endif()
endif()

if(MESHOPT_INSTALL)
	include(GNUInstallDirs)

	install(TARGETS meshoptimizer EXPORT meshoptimizerTargets
	    COMPONENT meshoptimizer
	    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
	    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
	    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
	    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

	if(MESHOPT_BUILD_GLTFPACK)
	    install(TARGETS gltfpack RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
	endif()

	install(FILES src/meshoptimizer.h COMPONENT meshoptimizer DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
	install(EXPORT meshoptimizerTargets COMPONENT meshoptimizer DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/meshoptimizer NAMESPACE meshoptimizer::)

	if(MSVC)
		set(PDB_TARGETS meshoptimizer)
		if(MESHOPT_BUILD_GLTFPACK)
		    list(APPEND PDB_TARGETS gltfpack)
		endif()

	    foreach(TARGET ${PDB_TARGETS})
	        get_target_property(TARGET_TYPE ${TARGET} TYPE)
	        if(NOT ${TARGET_TYPE} STREQUAL "STATIC_LIBRARY")
	            install(FILES $<TARGET_PDB_FILE:${TARGET}> COMPONENT meshoptimizer DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
	        endif()
	    endforeach(TARGET)
	endif()

	include(CMakePackageConfigHelpers)

	# CMake 3.18+ supports file(CONFIGURE OUTPUT file CONTENT content @ONLY) but we only require 3.5+
	file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfig.cmake
		"include(\"\${CMAKE_CURRENT_LIST_DIR}/meshoptimizerTargets.cmake\")\n")

	write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfigVersion.cmake COMPATIBILITY ExactVersion)

	install(FILES
	    ${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfig.cmake
	    ${CMAKE_CURRENT_BINARY_DIR}/meshoptimizerConfigVersion.cmake
	    COMPONENT meshoptimizer
	    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/meshoptimizer)
endif()


================================================
FILE: CONTRIBUTING.md
================================================
Thanks for deciding to contribute to meshoptimizer! These guidelines will try to help make the process painless and efficient.

## Questions

If you have a question regarding the library usage, please [open a GitHub issue](https://github.com/zeux/meshoptimizer/issues/new).
Some questions just need answers, but it's nice to keep them for future reference in case other people want to know the same thing.
Some questions help improve the library interface or documentation by inspiring future changes.

## Bugs

If the library doesn't compile on your system, compiles with warnings, doesn't seem to run correctly for your input data or if anything else is amiss, please [open a GitHub issue](https://github.com/zeux/meshoptimizer/issues/new).
It helps if you note the version of the library this issue happens in, the version of your compiler for compilation issues, and a reproduction case for runtime bugs.

Of course, feel free to [create a pull request](https://help.github.com/articles/about-pull-requests/) to fix the bug yourself.

## Features

New algorithms and improvements to existing algorithms are always welcome; you can open an issue or make the change yourself and submit a pull request.

For major features, consider opening an issue describing an improvement you'd like to see or make before opening a pull request.
This will give us a chance to discuss the idea before implementing it - some algorithms may not be easy to integrate into existing programs, may not be robust to arbitrary meshes or may be expensive to run or implement/maintain, so a discussion helps make sure these don't block the algorithm development.

## Code style

Contributions to this project are expected to follow the existing code style.
`.clang-format` file mostly defines syntactic styling rules (you can run `make format` to format the code accordingly).

As for naming conventions, this library uses `snake_case` for variables, `lowerCamelCase` for functions, `UpperCamelCase` for types, `kCamelCase` for global constants and `SCARY_CASE` for macros. All public functions/types must additionally have an extra `meshopt_` prefix to avoid symbol conflicts.

## Dependencies

Please note that this library uses C89 interface for all APIs and a C++98 implementation - C++11 features can not be used.
This choice is made to maximize compatibility to make sure that any toolchain, including legacy proprietary gaming console toolchains, can compile this code.

Additionally, the library code has zero external dependencies, does not depend on STL and does not use RTTI or exceptions.
This, again, maximizes compatibility and makes sure the library can be used in environments where STL use is discouraged or prohibited, as well as maximizing runtime performance and minimizing compilation times.

The demo program uses STL since it serves as an example of usage and as a test harness, not as production-ready code.

## Testing

All pull requests will run through a continuous integration pipeline using GitHub Actions that will run the built-in unit tests and integration tests on Windows, macOS and Linux with gcc, clang and msvc compilers.
You can run the tests yourself using `make test` or building the demo program with `cmake -DBUILD_DEMO=ON` and running it.

Unit tests can be found in `demo/tests.cpp` and functional tests - in `demo/main.cpp`; when making code changes please try to make sure they are covered by an existing test or add a new test accordingly.

## Documentation

Documentation for this library resides in the `meshoptimizer.h` header, with examples as part of a usage manual available in `README.md`.
Changes to documentation are always welcome and should use issues/pull requests as outlined above; please note that `README.md` only contains documentation for stable algorithms, as experimental algorithms may change the interface without concern for backwards compatibility.

## Sensitive communication

If you prefer to not disclose the issues or information relevant to the issue such as reproduction case to the public, you can always contact the author via e-mail (arseny.kapoulkine@gmail.com).

## Contributor agreement

Any code you submit will become part of the repository and be distributed under the [meshoptimizer license](https://github.com/zeux/meshoptimizer/blob/master/LICENSE.md). By submitting code to the project you agree that the code is your work and that you can give it to the project.

You also agree by submitting your code that you grant all transferrable rights to the code to the project maintainer, including for example re-licensing the code, modifying the code, and distributing it in source or binary forms. Specifically, this includes a requirement that you assign copyright to the project maintainer.


================================================
FILE: LICENSE.md
================================================
MIT License

Copyright (c) 2016-2026 Arseny Kapoulkine

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
MAKEFLAGS+=-r -j

config=debug
files=demo/pirate.obj

BUILD=build/$(config)

LIBRARY_SOURCES=$(wildcard src/*.cpp)
LIBRARY_OBJECTS=$(LIBRARY_SOURCES:%=$(BUILD)/%.o)

DEMO_SOURCES=$(wildcard demo/*.c demo/*.cpp) tools/objloader.cpp
DEMO_OBJECTS=$(DEMO_SOURCES:%=$(BUILD)/%.o)

GLTFPACK_SOURCES=$(wildcard gltf/*.cpp)
GLTFPACK_OBJECTS=$(GLTFPACK_SOURCES:%=$(BUILD)/%.o)

OBJECTS=$(LIBRARY_OBJECTS) $(DEMO_OBJECTS) $(GLTFPACK_OBJECTS)

LIBRARY=$(BUILD)/libmeshoptimizer.a
DEMO=$(BUILD)/meshoptdemo

CFLAGS=-g -Wall -Wextra -std=c89
CXXFLAGS=-g -Wall -Wextra -Wshadow -Wno-missing-field-initializers
LDFLAGS=

$(LIBRARY_OBJECTS): CXXFLAGS+=-std=gnu++98
$(DEMO_OBJECTS): CXXFLAGS+=-std=c++11
$(GLTFPACK_OBJECTS): CXXFLAGS+=-std=c++11

ifdef BASISU
    $(GLTFPACK_OBJECTS): CXXFLAGS+=-DWITH_BASISU
    $(BUILD)/gltf/encodebasis.cpp.o: CXXFLAGS+=-I$(BASISU)
    gltfpack: LDFLAGS+=-lpthread $(BASISU)/libbasisu_encoder.a
endif

WASI_SDK?=/opt/wasi-sdk
WASMCC?=$(WASI_SDK)/bin/clang++
WASIROOT?=$(WASI_SDK)/share/wasi-sysroot

WASM_FLAGS=--target=wasm32-wasi --sysroot=$(WASIROOT)
WASM_FLAGS+=-Wall -Wextra
WASM_FLAGS+=-O3 -DNDEBUG -nostartfiles -nostdlib -Wl,--no-entry -Wl,-s
WASM_FLAGS+=-mcpu=mvp # make sure clang doesn't use post-MVP features like sign extension
WASM_FLAGS+=-fno-slp-vectorize -fno-vectorize -fno-unroll-loops
WASM_FLAGS+=-Wl,-z -Wl,stack-size=36864 -Wl,--initial-memory=65536
WASM_EXPORT_PREFIX=-Wl,--export

WASM_DECODER_SOURCES=src/vertexcodec.cpp src/indexcodec.cpp src/vertexfilter.cpp tools/wasmstubs.cpp
WASM_DECODER_EXPORTS=meshopt_decodeVertexBuffer meshopt_decodeIndexBuffer meshopt_decodeIndexSequence meshopt_decodeFilterOct meshopt_decodeFilterQuat meshopt_decodeFilterExp meshopt_decodeFilterColor sbrk __wasm_call_ctors

WASM_ENCODER_SOURCES=src/vertexcodec.cpp src/indexcodec.cpp src/vertexfilter.cpp src/vcacheoptimizer.cpp src/vfetchoptimizer.cpp src/spatialorder.cpp tools/wasmstubs.cpp
WASM_ENCODER_EXPORTS=meshopt_encodeVertexBuffer meshopt_encodeVertexBufferBound meshopt_encodeVertexBufferLevel meshopt_encodeIndexBuffer meshopt_encodeIndexBufferBound meshopt_encodeIndexSequence meshopt_encodeIndexSequenceBound meshopt_encodeVertexVersion meshopt_encodeIndexVersion meshopt_encodeFilterOct meshopt_encodeFilterQuat meshopt_encodeFilterExp meshopt_encodeFilterColor meshopt_optimizeVertexCache meshopt_optimizeVertexCacheStrip meshopt_optimizeVertexFetchRemap meshopt_spatialSortRemap sbrk __wasm_call_ctors

WASM_SIMPLIFIER_SOURCES=src/simplifier.cpp src/vfetchoptimizer.cpp src/indexgenerator.cpp tools/wasmstubs.cpp
WASM_SIMPLIFIER_EXPORTS=meshopt_simplify meshopt_simplifyWithAttributes meshopt_simplifyWithUpdate meshopt_simplifyScale meshopt_simplifyPoints meshopt_simplifySloppy meshopt_simplifyPrune meshopt_optimizeVertexFetchRemap meshopt_generatePositionRemap sbrk __wasm_call_ctors

WASM_CLUSTERIZER_SOURCES=src/clusterizer.cpp src/meshletutils.cpp tools/wasmstubs.cpp
WASM_CLUSTERIZER_EXPORTS=meshopt_buildMeshletsBound meshopt_buildMeshletsFlex meshopt_buildMeshletsSpatial meshopt_computeClusterBounds meshopt_computeMeshletBounds meshopt_computeSphereBounds meshopt_optimizeMeshlet sbrk __wasm_call_ctors

ifneq ($(werror),)
	CFLAGS+=-Werror
	CXXFLAGS+=-Werror
endif

ifeq ($(config),iphone)
	IPHONESDK=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk
	CFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK)
	CXXFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK) -stdlib=libc++
	LDFLAGS+=-arch armv7 -arch arm64 -isysroot $(IPHONESDK) -L $(IPHONESDK)/usr/lib -mios-version-min=7.0
endif

ifeq ($(config),trace)
	CXXFLAGS+=-DTRACE=1
endif

ifeq ($(config),tracev)
	CXXFLAGS+=-DTRACE=2
endif

ifeq ($(config),release)
	CXXFLAGS+=-O3 -DNDEBUG
endif

ifeq ($(config),coverage)
	CXXFLAGS+=-coverage
	LDFLAGS+=-coverage
endif

ifeq ($(config),release-avx)
	CXXFLAGS+=-O3 -DNDEBUG -mavx
endif

ifeq ($(config),release-avx512)
	CXXFLAGS+=-O3 -DNDEBUG -mavx512vl -mavx512vbmi -mavx512vbmi2
endif

ifeq ($(config),release-scalar)
	CXXFLAGS+=-O3 -DNDEBUG -DMESHOPTIMIZER_NO_SIMD
endif

ifeq ($(config),coverage-scalar)
	CXXFLAGS+=-coverage -DMESHOPTIMIZER_NO_SIMD
	LDFLAGS+=-coverage
endif

ifeq ($(config),sanitize)
	CXXFLAGS+=-fsanitize=address,undefined -fsanitize-undefined-trap-on-error
	LDFLAGS+=-fsanitize=address,undefined
endif

ifeq ($(config),analyze)
	CXXFLAGS+=--analyze
endif

ifeq ($(config),fuzz)
    CXXFLAGS+=-O1 -fsanitize=address,fuzzer
    LDFLAGS+=-fsanitize=address,fuzzer

    $(GLTFPACK_OBJECTS): CXXFLAGS+=-DGLTFFUZZ
endif

-include Makefile.config

all: $(DEMO)

test: $(DEMO)
	$(DEMO) $(files)

check: $(DEMO)
	$(DEMO)

dev: $(DEMO)
	$(DEMO) -d $(files)

nanite: $(DEMO)
	$(DEMO) -n $(files)

format:
	clang-format -i src/*.h src/*.cpp demo/*.cpp gltf/*.h gltf/*.cpp

formatjs:
	prettier -w js/*.js gltf/*.js demo/*.html js/*.ts

js: js/meshopt_decoder.cjs js/meshopt_decoder.mjs js/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js

symbols: $(BUILD)/amalgamated.so
	nm $< -U -g

gltfpack: $(BUILD)/gltfpack
	ln -fs $^ $@

ifeq ($(config),fuzz)
gltffuzz: $(BUILD)/gltfpack
	cp $^ $@
	mkdir -p /tmp/gltffuzz
	cp gltf/fuzz.glb /tmp/gltffuzz/
	./gltffuzz /tmp/gltffuzz -fork=16 -dict=gltf/fuzz.dict -ignore_crashes=1 -max_len=32768
endif

$(BUILD)/gltfpack: $(GLTFPACK_OBJECTS) $(LIBRARY)
	$(CXX) $^ $(LDFLAGS) -o $@

gltfpack.wasm: gltf/library.wasm

gltf/library.wasm: $(LIBRARY_SOURCES) $(GLTFPACK_SOURCES)
	$(WASMCC) $^ -o $@ -Wall -Os -DNDEBUG --target=wasm32-wasi --sysroot=$(WASIROOT) -nostartfiles -Wl,--no-entry -Wl,--export=pack -Wl,--export=malloc -Wl,--export=free -Wl,--export=__wasm_call_ctors -Wl,-s -Wl,--allow-undefined-file=gltf/wasistubs.txt

build/decoder_base.wasm: $(WASM_DECODER_SOURCES)
	@mkdir -p build
	$(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_DECODER_EXPORTS)) -o $@

build/decoder_simd.wasm: $(WASM_DECODER_SOURCES)
	@mkdir -p build
	$(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_DECODER_EXPORTS)) -o $@ -msimd128 -mbulk-memory

build/encoder.wasm: $(WASM_ENCODER_SOURCES)
	@mkdir -p build
	$(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_ENCODER_EXPORTS)) -lc -o $@

build/simplifier.wasm: $(WASM_SIMPLIFIER_SOURCES)
	@mkdir -p build
	$(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_SIMPLIFIER_EXPORTS)) -lc -o $@

build/clusterizer.wasm: $(WASM_CLUSTERIZER_SOURCES)
	@mkdir -p build
	$(WASMCC) $^ $(WASM_FLAGS) $(patsubst %,$(WASM_EXPORT_PREFIX)=%,$(WASM_CLUSTERIZER_EXPORTS)) -lc -o $@

js/meshopt_decoder.mjs: build/decoder_base.wasm build/decoder_simd.wasm tools/wasmpack.py
	sed -i "s#Built with clang.*#Built with $$($(WASMCC) --version | head -n 1 | sed 's/\s\+(.*//')#" $@
	sed -i "s#Built from meshoptimizer .*#Built from meshoptimizer $$(cat src/meshoptimizer.h | grep -Po '(?<=version )[0-9.]+')#" $@
	python3 tools/wasmpack.py patch $@ base <build/decoder_base.wasm
	python3 tools/wasmpack.py patch $@ simd <build/decoder_simd.wasm

js/meshopt_encoder.js: build/encoder.wasm tools/wasmpack.py
js/meshopt_simplifier.js: build/simplifier.wasm tools/wasmpack.py
js/meshopt_clusterizer.js: build/clusterizer.wasm tools/wasmpack.py

js/meshopt_encoder.js js/meshopt_simplifier.js js/meshopt_clusterizer.js:
	sed -i "s#Built with clang.*#Built with $$($(WASMCC) --version | head -n 1 | sed 's/\s\+(.*//')#" $@
	sed -i "s#Built from meshoptimizer .*#Built from meshoptimizer $$(cat src/meshoptimizer.h | grep -Po '(?<=version )[0-9.]+')#" $@
	python3 tools/wasmpack.py patch $@ wasm <$<

js/meshopt_decoder.cjs: js/meshopt_decoder.mjs
	sed '/export {/d' <$< >$@
	echo "if (typeof exports === 'object' && typeof module === 'object') module.exports = MeshoptDecoder;" >>$@
	echo "else if (typeof define === 'function' && define['amd']) define([], function () { return MeshoptDecoder; });" >>$@
	echo "else if (typeof exports === 'object') exports['MeshoptDecoder'] = MeshoptDecoder;" >>$@
	echo "else (typeof self !== 'undefined' ? self : this).MeshoptDecoder = MeshoptDecoder;" >>$@

$(DEMO): $(DEMO_OBJECTS) $(LIBRARY)
	$(CXX) $^ $(LDFLAGS) -o $@

vcachetuner: tools/vcachetuner.cpp tools/objloader.cpp $(LIBRARY)
	$(CXX) $^ -fopenmp $(CXXFLAGS) -std=c++11 $(LDFLAGS) -o $@

codecbench: tools/codecbench.cpp $(LIBRARY)
	$(CXX) $^ $(CXXFLAGS) $(LDFLAGS) -o $@

codecbench.js: tools/codecbench.cpp $(LIBRARY_SOURCES)
	emcc $^ -O3 -g -DNDEBUG -s TOTAL_MEMORY=268435456 -s SINGLE_FILE=1 -o $@

codecbench-simd.js: tools/codecbench.cpp $(LIBRARY_SOURCES)
	emcc $^ -O3 -g -DNDEBUG -s TOTAL_MEMORY=268435456 -s SINGLE_FILE=1 -msimd128 -o $@

codecbench.wasm: tools/codecbench.cpp $(LIBRARY_SOURCES)
	$(WASMCC) $^ -fno-exceptions --target=wasm32-wasi --sysroot=$(WASIROOT) -lc++ -lc++abi -O3 -g -DNDEBUG -o $@

codecbench-simd.wasm: tools/codecbench.cpp $(LIBRARY_SOURCES)
	$(WASMCC) $^ -fno-exceptions --target=wasm32-wasi --sysroot=$(WASIROOT) -lc++ -lc++abi -O3 -g -DNDEBUG -msimd128 -o $@

codectest: tools/codectest.cpp $(LIBRARY)
	$(CXX) $^ $(CXXFLAGS) $(LDFLAGS) -o $@

codecfuzz: tools/codecfuzz.cpp src/vertexcodec.cpp src/indexcodec.cpp src/meshletcodec.cpp
	$(CXX) $^ -fsanitize=fuzzer,address,undefined -O1 -g -o $@

clusterfuzz: tools/clusterfuzz.cpp src/clusterizer.cpp src/partition.cpp
	$(CXX) $^ -fsanitize=fuzzer,address,undefined -O1 -g -o $@

simplifyfuzz: tools/simplifyfuzz.cpp src/simplifier.cpp
	$(CXX) $^ -fsanitize=fuzzer,address,undefined -O1 -g -o $@

$(LIBRARY): $(LIBRARY_OBJECTS)
	ar rcs $@ $^

$(BUILD)/amalgamated.so: $(LIBRARY_SOURCES)
	@mkdir -p $(dir $@)
	cat $^ | $(CXX) $(CXXFLAGS) -x c++ - -I src/ -o $@ -shared -fPIC

$(BUILD)/%.cpp.o: %.cpp
	@mkdir -p $(dir $@)
	$(CXX) $< $(CXXFLAGS) -c -MMD -MP -o $@

$(BUILD)/%.c.o: %.c
	@mkdir -p $(dir $@)
	$(CC) $< $(CFLAGS) -c -MMD -MP -o $@

-include $(OBJECTS:.o=.d)

clean:
	rm -rf $(BUILD)

.PHONY: all clean format js


================================================
FILE: README.md
================================================
# 🐇 meshoptimizer [![Actions Status](https://github.com/zeux/meshoptimizer/workflows/build/badge.svg)](https://github.com/zeux/meshoptimizer/actions) [![codecov.io](https://codecov.io/github/zeux/meshoptimizer/coverage.svg?branch=master)](https://codecov.io/github/zeux/meshoptimizer?branch=master) [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.md) [![GitHub](https://img.shields.io/badge/repo-github-green.svg)](https://github.com/zeux/meshoptimizer)

## Purpose

When a GPU renders triangle meshes, various stages of the GPU pipeline have to process vertex and index data. The efficiency of these stages depends on the data you feed to them; this library provides algorithms to help optimize meshes for these stages, as well as algorithms to reduce the mesh complexity and storage overhead.

The library provides a C and C++ interface for all algorithms; you can use it from C/C++ or from other languages via FFI (such as P/Invoke). If you want to use this library from Rust, you should use [meshopt crate](https://crates.io/crates/meshopt). JavaScript interface for some algorithms is available through [meshoptimizer.js](https://www.npmjs.com/package/meshoptimizer).

Two companion projects are developed and distributed alongside the library: [gltfpack](./gltf/README.md), a command-line tool that automatically optimizes glTF files, and [clusterlod.h](./demo/clusterlod.h), a single-header C/C++ library for continuous level of detail using clustered simplification.

## Installing

meshoptimizer is hosted on GitHub; you can download the latest release using git:

```
git clone -b v1.0 https://github.com/zeux/meshoptimizer.git
```

Alternatively you can [download the .zip archive from GitHub](https://github.com/zeux/meshoptimizer/archive/v1.0.zip).

The library is also available as a Linux package in several distributions ([ArchLinux](https://aur.archlinux.org/packages/meshoptimizer/), [Debian](https://packages.debian.org/libmeshoptimizer), [FreeBSD](https://www.freshports.org/misc/meshoptimizer/), [Nix](https://mynixos.com/nixpkgs/package/meshoptimizer), [Ubuntu](https://packages.ubuntu.com/libmeshoptimizer)), as well as a [Vcpkg port](https://github.com/microsoft/vcpkg/tree/master/ports/meshoptimizer) (see [installation instructions](https://learn.microsoft.com/en-us/vcpkg/get_started/get-started)) and a [Conan package](https://conan.io/center/recipes/meshoptimizer).

[gltfpack](./gltf/README.md) is available as a pre-built binary on [Releases page](https://github.com/zeux/meshoptimizer/releases) or via [npm package](https://www.npmjs.com/package/gltfpack). Native binaries are recommended since they are more efficient and support texture compression.

## Building

meshoptimizer is distributed as a C/C++ header (`src/meshoptimizer.h`) and a set of C++ source files (`src/*.cpp`). To include it in your project, you can use one of two options:

* Use CMake to build the library (either as a standalone project or as part of your project)
* Add source files to your project's build system

The source files are organized in such a way that you don't need to change your build-system settings, and you only need to add the source files for the algorithms you use. They should build without warnings or special compilation options on all major compilers. If you prefer amalgamated builds, you can also concatenate the source files into a single `.cpp` file and build that instead.

To use meshoptimizer functions, simply `#include` the header `meshoptimizer.h`; the library source is C++, but the header is C-compatible.

## Core pipeline

When optimizing a mesh, to maximize rendering efficiency you should typically feed it through a set of optimizations (the order is important!):

1. Indexing
2. Vertex cache optimization
3. (optional) Overdraw optimization
4. Vertex fetch optimization
5. Vertex quantization
6. (optional) Shadow indexing

### Indexing

Most algorithms in this library assume that a mesh has a vertex buffer and an index buffer. For algorithms to work well and also for GPU to render your mesh efficiently, the vertex buffer has to have no redundant vertices; you can generate an index buffer from an unindexed vertex buffer or reindex an existing (potentially redundant) index buffer as follows:

> Note: meshoptimizer generally works with 32-bit (`unsigned int`) indices, however when using C++ APIs you can use any integer type for index data by using the provided template overloads. By convention, remap tables always use `unsigned int`.

First, generate a remap table from your existing vertex (and, optionally, index) data:

```c++
size_t index_count = face_count * 3;
size_t unindexed_vertex_count = face_count * 3;
std::vector<unsigned int> remap(unindexed_vertex_count); // temporary remap table
size_t vertex_count = meshopt_generateVertexRemap(&remap[0], NULL, index_count,
    &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex));
```

Note that in this case we only have an unindexed vertex buffer; when input mesh has an index buffer, it will need to be passed to `meshopt_generateVertexRemap` instead of `NULL`, along with the correct source vertex count. In either case, the remap table is generated based on binary equivalence of the input vertices, so the resulting mesh will render the same way. Binary equivalence considers all input bytes, including padding which should be zero-initialized if the vertex structure has gaps.

After generating the remap table, you can allocate space for the target vertex buffer (`vertex_count` elements) and index buffer (`index_count` elements) and generate them:

```c++
meshopt_remapIndexBuffer(indices, NULL, index_count, &remap[0]);
meshopt_remapVertexBuffer(vertices, &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex), &remap[0]);
```

You can then further optimize the resulting buffers by calling the other functions on them in-place.

`meshopt_generateVertexRemap` uses binary equivalence of vertex data, which is generally a reasonable default; however, in some cases some attributes may have floating point drift causing extra vertices to be generated. For such cases, it may be necessary to quantize some attributes (most importantly, normals and tangents) before generating the remap, or use `meshopt_generateVertexRemapCustom` algorithm that allows comparing individual attributes with tolerance by providing a custom comparison function:

```c++
size_t vertex_count = meshopt_generateVertexRemapCustom(&remap[0], NULL, index_count,
    &unindexed_vertices[0].px, unindexed_vertex_count, sizeof(Vertex),
    [&](unsigned int lhs, unsigned int rhs) -> bool {
        const Vertex& lv = unindexed_vertices[lhs];
        const Vertex& rv = unindexed_vertices[rhs];

        return fabsf(lv.tx - rv.tx) < 1e-3f && fabsf(lv.ty - rv.ty) < 1e-3f;
    });
```

### Vertex cache optimization

When the GPU renders the mesh, it runs the vertex shader for each vertex. Historically, GPUs used a small fixed-size post-transform cache (16-32 vertices) with different replacement policies to store the shader output and avoid redundant shader invocations. Modern GPUs still perform vertex reuse, but with substantially different mechanics: vertex invocations are batched into thread groups based on the input indices, and effective reuse depends on factors like vertex shader outputs and rasterizer throughput. To maximize the locality of reused vertex references, you have to reorder your triangles like so:

```c++
meshopt_optimizeVertexCache(indices, indices, index_count, vertex_count);
```

The details of vertex reuse vary between different GPU architectures, so vertex cache optimization uses an adaptive algorithm that produces a triangle sequence with good locality that works well across different GPUs. Alternatively, you can use an algorithm that optimizes specifically for fixed-size FIFO caches: `meshopt_optimizeVertexCacheFifo` (with a recommended cache size of 16). While it generally produces less performant results on most GPUs, it runs ~2x faster, which may benefit rapid content iteration.

### Overdraw optimization

After transforming the vertices, GPU sends the triangles for rasterization which results in generating pixels that are usually first run through the depth test, and pixels that pass it get the pixel shader executed to generate the final color. As pixel shaders get more expensive, it becomes more and more important to reduce overdraw. While in general improving overdraw requires view-dependent operations, this library provides an algorithm to reorder triangles to minimize the overdraw from all directions, which you can run after vertex cache optimization like this:

```c++
meshopt_optimizeOverdraw(indices, indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), 1.05f);
```

The overdraw optimizer needs to read vertex positions as a float3 from the vertex; the code snippet above assumes that the vertex stores position as `float x, y, z`.

When performing the overdraw optimization you have to specify a floating-point threshold parameter. The algorithm tries to maintain a balance between vertex cache efficiency and overdraw; the threshold determines how much the algorithm can compromise the vertex cache hit ratio, with 1.05 meaning that the resulting ratio should be at most 5% worse than before the optimization.

Note that depending on the renderer structure and target hardware, the optimization may or may not be beneficial; for example, mobile GPUs with tiled deferred rendering (PowerVR, Apple) would not benefit from this optimization. For vertex heavy scenes it's recommended to measure the performance impact to ensure that the reduced vertex cache efficiency is outweighed by the reduced overdraw.

### Vertex fetch optimization

After the final triangle order has been established, we still can optimize the vertex buffer for memory efficiency. Before running the vertex shader GPU has to fetch the vertex attributes from the vertex buffer; the fetch is usually backed by a memory cache, and as such optimizing the data for the locality of memory access is important. You can do this by running this code:

```c++
meshopt_optimizeVertexFetch(vertices, indices, index_count, vertices, vertex_count, sizeof(Vertex));
```

This will reorder the vertices in the vertex buffer to try to improve the locality of reference, and rewrite the indices in place to match; if the vertex data is stored using multiple streams, you should use `meshopt_optimizeVertexFetchRemap` instead. This optimization has to be performed on the final index buffer since the optimal vertex order depends on the triangle order.

Note that the algorithm does not try to model cache replacement precisely and instead just orders vertices in the order of use, which generally produces results that are close to optimal.

### Vertex quantization

To optimize memory bandwidth when fetching the vertex data even further, and to reduce the amount of memory required to store the mesh, it is often beneficial to quantize the vertex attributes to smaller types. While this optimization can technically run at any part of the pipeline (and sometimes doing quantization as the first step can improve indexing by merging almost identical vertices), it generally is easier to run this after all other optimizations since some of them require access to float3 positions.

Quantization is usually domain specific; it's common to quantize normals using 3 8-bit integers but you can use higher-precision quantization (for example using 10 bits per component in a 10_10_10_2 format), or a different encoding to use just 2 components. For positions and texture coordinate data the two most common storage formats are half precision floats, and 16-bit normalized integers that encode the position relative to the AABB of the mesh or the UV bounding rectangle.

The number of possible combinations here is very large but this library does provide the building blocks, specifically functions to quantize floating point values to normalized integers, as well as half-precision floats. For example, here's how you can quantize a normal using 10-10-10 SNORM encoding:

```c++
unsigned int normal =
    ((meshopt_quantizeSnorm(v.nx, 10) & 1023) << 20) |
    ((meshopt_quantizeSnorm(v.ny, 10) & 1023) << 10) |
     (meshopt_quantizeSnorm(v.nz, 10) & 1023);
```

and here's how you can quantize a position using half precision floats:

```c++
unsigned short px = meshopt_quantizeHalf(v.x);
unsigned short py = meshopt_quantizeHalf(v.y);
unsigned short pz = meshopt_quantizeHalf(v.z);
```

Since quantized vertex attributes often need to remain in their compact representations for efficient transfer and storage, they are usually dequantized during vertex processing by configuring the GPU vertex input correctly to expect normalized integers or half precision floats, which often needs no or minimal changes to the shader code. When CPU dequantization is required instead, `meshopt_dequantizeHalf` can be used to convert half precision values back to single precision; for normalized integer formats, the dequantization just requires dividing by 2^N-1 for unorm and 2^(N-1)-1 for snorm variants. For example, manually reversing `meshopt_quantizeUnorm(v, 10)` can be done by dividing by 1023.

### Shadow indexing

Many rendering pipelines require meshes to be rendered to depth-only targets, such as shadow maps or during a depth pre-pass, in addition to color/G-buffer targets. While using the same geometry data for both cases is possible, reducing the number of unique vertices for depth-only rendering can be beneficial, especially when the source geometry has many attribute seams due to faceted shading or lightmap texture seams.

To achieve this, this library provides the `meshopt_generateShadowIndexBuffer` algorithm, which generates a second (shadow) index buffer that can be used with the original vertex data:

```c++
std::vector<unsigned int> shadow_indices(index_count);
// note: this assumes Vertex starts with float3 positions and should be adjusted accordingly for quantized positions
meshopt_generateShadowIndexBuffer(&shadow_indices[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(float) * 3, sizeof(Vertex));
```

Because the vertex data is shared, shadow indexing should be done after other optimizations of the vertex/index data. However, it's possible (and recommended) to optimize the resulting shadow index buffer for vertex cache:

```c++
meshopt_optimizeVertexCache(&shadow_indices[0], &shadow_indices[0], index_count, vertex_count);
```

In some cases, it may be beneficial to split the vertex positions into a separate buffer to maximize efficiency for depth-only rendering. Note that the example above assumes only positions are relevant for shadow rendering, but more complex materials may require adding texture coordinates (for alpha testing) or skinning data to the vertex portion used as a key. `meshopt_generateShadowIndexBufferMulti` can be useful for these cases if the relevant data is not contiguous.

Note that for meshes with optimal indexing and few attribute seams, the shadow index buffer will be very similar to the original index buffer, so it may not be always worth generating a separate shadow index buffer even if the rendering pipeline relies on depth-only passes.

## Clusterization

While traditionally meshes have served as a unit of rendering, new approaches to rendering and raytracing are starting to use a smaller unit of work, such as clusters or meshlets. This allows more freedom in how the geometry is processed, and can lead to better performance and more efficient use of GPU hardware. This section describes algorithms designed to work with meshes as sets of clusters.

### Mesh shading

Modern GPUs are beginning to deviate from the traditional rasterization model. NVidia GPUs starting from Turing and AMD GPUs starting from RDNA2 provide a new programmable geometry pipeline that, instead of being built around index buffers and vertex shaders, is built around mesh shaders - a new shader type that allows to provide a batch of work to the rasterizer.

Using mesh shaders in context of traditional mesh rendering provides an opportunity to use a variety of optimization techniques, starting from more efficient vertex reuse, using various forms of culling (e.g. cluster frustum or occlusion culling) and in-memory compression to maximize the utilization of GPU hardware. Beyond traditional rendering mesh shaders provide a richer programming model that can synthesize new geometry more efficiently than common alternatives such as geometry shaders. Mesh shading can be accessed via Vulkan or Direct3D 12 APIs; please refer to [Introduction to Turing Mesh Shaders](https://developer.nvidia.com/blog/introduction-turing-mesh-shaders/) and [Mesh Shaders and Amplification Shaders: Reinventing the Geometry Pipeline](https://devblogs.microsoft.com/directx/coming-to-directx-12-mesh-shaders-and-amplification-shaders-reinventing-the-geometry-pipeline/) for additional information.

To use mesh shaders for conventional rendering efficiently, geometry needs to be converted into a series of meshlets; each meshlet represents a small subset of the original mesh and comes with a small set of vertices and a separate micro-index buffer that references vertices in the meshlet. This information can be directly fed to the rasterizer from the mesh shader. This library provides algorithms to create meshlet data for a mesh, and - assuming geometry is static - can compute bounding information that can be used to perform cluster culling, rejecting meshlets that are invisible on screen.

To generate meshlet data, this library provides `meshopt_buildMeshlets` algorithm, which tries to balance topological efficiency (by maximizing vertex reuse inside meshlets) with culling efficiency (by minimizing meshlet radius and triangle direction divergence) and produces GPU-friendly data. As an alternative (that can be useful for load-time processing), `meshopt_buildMeshletsScan` can create the meshlet data using a vertex cache-optimized index buffer as a starting point by greedily aggregating consecutive triangles until they go over the meshlet limits. `meshopt_buildMeshlets` is recommended for offline data processing even if cone culling is not used.

```c++
const size_t max_vertices = 64;
const size_t max_triangles = 126; // note: in v0.25 or prior, max_triangles needs to be divisible by 4
const float cone_weight = 0.0f;

size_t max_meshlets = meshopt_buildMeshletsBound(indices.size(), max_vertices, max_triangles);
std::vector<meshopt_Meshlet> meshlets(max_meshlets);
std::vector<unsigned int> meshlet_vertices(indices.size());
std::vector<unsigned char> meshlet_triangles(indices.size()); // note: in v0.25 or prior, use indices.size() + max_meshlets * 3

size_t meshlet_count = meshopt_buildMeshlets(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices.data(),
    indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex), max_vertices, max_triangles, cone_weight);
```

To generate the meshlet data, `max_vertices` and `max_triangles` need to be set within limits supported by the hardware; for NVidia the values of 64 and 126 are recommended. `cone_weight` should be left as 0 if cluster cone culling is not used, and set to a value between 0 and 1 to balance cone culling efficiency with other forms of culling like frustum or occlusion culling (`0.25` is a reasonable default).

> Note that for earlier AMD GPUs, the best configurations tend to use the same limits for `max_vertices` and `max_triangles`, such as 64 and 64, or 128 and 128. Additionally, while NVidia recommends 64/126 as a good configuration, consider using a different configuration like `max_vertices 64, max_triangles 96`, to provide more realistic limits that are achievable on real-world meshes, and to reduce the overhead on other GPUs.

Each resulting meshlet refers to a portion of `meshlet_vertices` and `meshlet_triangles` arrays; the arrays are overallocated for the worst case so it's recommended to trim them before saving them as an asset / uploading them to the GPU:

```c++
const meshopt_Meshlet& last = meshlets[meshlet_count - 1];

meshlet_vertices.resize(last.vertex_offset + last.vertex_count);
meshlet_triangles.resize(last.triangle_offset + last.triangle_count * 3);
meshlets.resize(meshlet_count);
```

Depending on the application, other strategies of storing the data can be useful; for example, `meshlet_vertices` serves as indices into the original vertex buffer but it might be worthwhile to generate a mini vertex buffer for each meshlet to remove the extra indirection when accessing vertex data, or it might be desirable to compress vertex data as vertices in each meshlet are likely to be very spatially coherent.

For optimal performance, it is recommended to further optimize each meshlet in isolation for better triangle and vertex locality by calling `meshopt_optimizeMeshlet` on vertex and index data like so:

```c++
meshopt_optimizeMeshlet(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset], m.triangle_count, m.vertex_count);
```

Different applications will choose different strategies for rendering meshlets; on a GPU capable of mesh shading, meshlets can be rendered directly; for example, a basic GLSL shader for `VK_EXT_mesh_shader` extension could look like this (parts omitted for brevity):

```glsl
layout(binding = 0) readonly buffer Meshlets { Meshlet meshlets[]; };
layout(binding = 1) readonly buffer MeshletVertices { uint meshlet_vertices[]; };
layout(binding = 2) readonly buffer MeshletTriangles { uint8_t meshlet_triangles[]; };

void main() {
    Meshlet meshlet = meshlets[gl_WorkGroupID.x];
    SetMeshOutputsEXT(meshlet.vertex_count, meshlet.triangle_count);

    for (uint i = gl_LocalInvocationIndex; i < meshlet.vertex_count; i += gl_WorkGroupSize.x) {
        uint index = meshlet_vertices[meshlet.vertex_offset + i];
        gl_MeshVerticesEXT[i].gl_Position = world_view_projection * vec4(vertex_positions[index], 1);
    }

    for (uint i = gl_LocalInvocationIndex; i < meshlet.triangle_count; i += gl_WorkGroupSize.x) {
        uint offset = meshlet.triangle_offset + i * 3;
        gl_PrimitiveTriangleIndicesEXT[i] = uvec3(
            meshlet_triangles[offset], meshlet_triangles[offset + 1], meshlet_triangles[offset + 2]);
    }
}
```

> Note that DirectX 12 mesh shaders cannot index raw buffers using arbitrary byte offsets. Use a typed SRV buffer (`Buffer<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`.

After generating the meshlet data, it's possible to generate extra data for each meshlet that can be saved and used at runtime to perform cluster culling, where each meshlet can be discarded if it's guaranteed to be invisible. To generate the data, `meshopt_computeMeshletBounds` can be used:

```c++
meshopt_Bounds bounds = meshopt_computeMeshletBounds(&meshlet_vertices[m.vertex_offset], &meshlet_triangles[m.triangle_offset],
    m.triangle_count, &vertices[0].x, vertices.size(), sizeof(Vertex));
```

The resulting `bounds` values can be used to perform frustum or occlusion culling using the bounding sphere, or cone culling using the cone axis/angle (which will reject the entire meshlet if all triangles are guaranteed to be back-facing from the camera point of view):

```c++
if (dot(normalize(cone_apex - camera_position), cone_axis) >= cone_cutoff) reject();
```

Cluster culling should ideally run at a lower frequency than mesh shading, either using amplification/task shaders, or using a separate compute dispatch.

By default, the meshlet builder tries to form complete meshlets even if that requires merging disconnected regions of the mesh into a single meshlet. In some cases, such as hierarchical level of detail, or when advanced culling is used, it may be beneficial to prioritize spatial locality of triangles in a meshlet even if that results in partially filled meshlets. To that end, `meshopt_buildMeshletsFlex` function can be used instead of `meshopt_buildMeshlets`; it provides two triangle limits, `min_triangles` and `max_triangles`, and uses an additional configuration parameter, `split_factor` (recommended value is 2.0), to decide whether increasing the meshlet radius is worth it to fit more triangles in the meshlet. When using this function, the worst case bound for the number of meshlets has to be computed using `meshopt_buildMeshletsBound` with `min_triangles` parameter instead of `max_triangles`.

### Clustered raytracing

In addition to rasterization, meshlets can also be used for ray tracing. NVidia GPUs starting from Turing with recent drivers provide support for cluster acceleration structures (via `VK_NV_cluster_acceleration_structure` extension / NVAPI); instead of building a traditional BLAS, a cluster acceleration structure can be built for each meshlet and combined into a single clustered BLAS. While this currently results in reduced ray tracing performance for static geometry (for which a traditional BLAS may be more suitable), it allows updating the individual clusters without having to rebuild or refit the entire BLAS, which can be useful for mesh deformation or hierarchical level of detail.

When using meshlets for raytracing, the performance characteristics that matter differ from when rendering meshes with rasterization. For raytracing, clusters with optimal spatial division that minimize ray-triangle intersection tests are preferred, while for rasterization, clusters with maximum triangle count within vertex limits are ideal.

To generate meshlets optimized for raytracing, this library provides `meshopt_buildMeshletsSpatial` algorithm, which builds clusters using surface area heuristic (SAH) to produce raytracing-friendly cluster distributions:

```c++
const size_t max_vertices = 64;
const size_t min_triangles = 16;
const size_t max_triangles = 64;
const float fill_weight = 0.5f;

size_t max_meshlets = meshopt_buildMeshletsBound(indices.size(), max_vertices, min_triangles); // note: use min_triangles to compute worst case bound
std::vector<meshopt_Meshlet> meshlets(max_meshlets);
std::vector<unsigned int> meshlet_vertices(indices.size());
std::vector<unsigned char> meshlet_triangles(indices.size()); // note: in v0.25 or prior, use indices.size() + max_meshlets * 3

size_t meshlet_count = meshopt_buildMeshletsSpatial(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices.data(),
    indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex), max_vertices, min_triangles, max_triangles, fill_weight);
```

The algorithm recursively subdivides the triangles into a BVH-like hierarchy using SAH for optimal spatial partitioning while balancing cluster size; this results in clusters that are significantly more efficient to raytrace compared to clusters generated by `meshopt_buildMeshlets`, but can still be used for rasterization (for example, to build visibility buffers or G-buffers).

The `min_triangles` and `max_triangles` parameters control the allowed range of triangles per cluster. For optimal raytracing performance, `min_triangles` should be at most `max_triangles/2` (or, ideally, `max_triangles/4`) to give the algorithm enough freedom to produce high-quality spatial partitioning. For meshes with few seams due to normal or UV discontinuities, using `max_vertices` equal to `max_triangles` is recommended when rasterization performance is a concern; for meshes with many seams or for renderers that primarily use meshlets for ray tracing, a higher `max_vertices` value should be used as it ensures that more clusters can fully utilize the triangle limit.

The `fill_weight` parameter (typically between 0 and 1, although values higher than 1 could be used to prioritize cluster fill even more) controls the trade-off between pure SAH optimization and triangle utilization. A value of 0 will optimize purely for SAH, resulting in best raytracing performance but potentially smaller clusters. Values between 0.25 and 0.75 typically provide a good balance of SAH quality vs triangle count.

When the resulting meshlets are used to generate hardware-specific acceleration structures, using fast trace (e.g. `VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR`) builds results in maximum performance; if build performance is important, using `meshopt_optimizeMeshlet` can help improve ray tracing performance when using fast build (e.g. `VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR`), although the tracing performance will still be lower than with fast trace builds.

### Point cloud clusterization

Both of the meshlet algorithms are designed to work with triangle meshes. In some cases, splitting a point cloud into fixed size clusters can be useful; the resulting point clusters could be rendered via mesh or compute shaders, or the resulting subdivision can be used to parallelize point processing while maintaining locality of points. To that end, this library provides `meshopt_spatialClusterPoints` algorithm:

```c++
const size_t cluster_size = 256;

std::vector<unsigned int> index(mesh.vertices.size());
meshopt_spatialClusterPoints(&index[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), cluster_size);
```

The resulting index buffer could be used to process the points directly, or reorganize the point data into flat contiguous arrays. Every consecutive chunk of `cluster_size` points in the index buffer refers to a single cluster, with just the last cluster containing fewer points if the total number of points is not a multiple of `cluster_size`. Note that the index buffer is not a remap table, so `meshopt_remapVertexBuffer` can't be used to flatten the point data.

### Cluster partitioning

When working with clustered geometry, it can be beneficial to organize clusters into larger groups (partitions) for more efficient processing or workload distribution. This library provides an algorithm to partition clusters into groups of similar size while prioritizing locality:

```c++
const size_t partition_size = 24;

std::vector<unsigned int> cluster_partitions(cluster_count);
size_t partition_count = meshopt_partitionClusters(&cluster_partitions[0], &cluster_indices[0], total_index_count,
    &cluster_index_counts[0], cluster_count, &vertices[0].x, vertex_count, sizeof(Vertex), partition_size);
```

The algorithm assigns each cluster to a partition, aiming for a target partition size while prioritizing topological locality (sharing vertices) and spatial locality. The resulting partitions can be used for more efficient batched processing of clusters, or for hierarchical simplification schemes similar to Nanite.

Two clusters are considered topologically adjacent if they reference the same indices. In some cases, it can be helpful to process the indices using `meshopt_generateShadowIndexBuffer` (or remap them manually using the remap table generated by `meshopt_generatePositionRemap`), which allows clusters to be considered adjacent even when boundary vertices have different indices due to attribute discontinuities.

If vertex positions are specified (not `NULL`), spatial locality will influence priority of merging clusters; otherwise, the algorithm will rely solely on topological connections and will not merge disconnected clusters into the same partition, which may result in smaller partitions for some inputs.

After partitioning, each element in the destination array contains the partition ID (ranging from 0 to the returned partition count minus 1) for the corresponding cluster. Note that the partitions may be both smaller and larger than the target size; given a target size, the maximum partition size returned currently is `target + target / 3`.

## Mesh compression

In case storage size or transmission bandwidth is of importance, you might want to additionally compress vertex and index data. While several mesh compression libraries, like Google Draco, are available, they typically are designed to maximize the compression ratio at the cost of disturbing the vertex/index order (which makes the meshes inefficient to render on GPU) or decompression performance. They also frequently don't support custom game-ready quantized vertex formats and thus require to re-quantize the data after loading it, introducing extra quantization errors and making decoding slower.

Alternatively you can use general purpose compression libraries like zstd or Oodle to compress vertex/index data - however these compressors aren't designed to exploit redundancies in vertex/index data and as such compression rates can be unsatisfactory.

To that end, this library provides algorithms to "encode" vertex and index data. The result of the encoding is generally significantly smaller than initial data, and remains compressible with general purpose compressors - so you can either store encoded data directly (for modest compression ratios and maximum decoding performance), or further compress it with LZ4/zstd/Oodle to maximize compression ratio.

> Note: this compression scheme is available as a glTF extension [EXT_meshopt_compression](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_meshopt_compression/README.md) as well as [KHR_meshopt_compression](https://github.com/KhronosGroup/glTF/pull/2517).

### Vertex compression

This library provides a lossless algorithm to encode/decode vertex data. To encode vertices, you need to allocate a target buffer (using the worst case bound) and call the encoding function:

```c++
std::vector<unsigned char> vbuf(meshopt_encodeVertexBufferBound(vertex_count, sizeof(Vertex)));
vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), vertices, vertex_count, sizeof(Vertex)));
```

To decode the data at runtime, call the decoding function:

```c++
int res = meshopt_decodeVertexBuffer(vertices, vertex_count, sizeof(Vertex), &vbuf[0], vbuf.size());
assert(res == 0);
```

Note that vertex encoding assumes that vertex buffer was optimized for vertex fetch, and that vertices are quantized. Feeding unoptimized data into the encoder may result in poor compression ratios. The codec is lossless by itself - the only lossy step is quantization/reordering or filters that you may apply before encoding. Additionally, if the vertex data contains padding bytes, they should be zero-initialized to ensure that the encoder does not need to store uninitialized data.

Decoder is heavily optimized and can directly target write-combined memory; you can expect it to run at 3-6 GB/s on modern desktop CPUs. Compression ratio depends on the data; vertex data compression ratio is typically around 2-4x (compared to already quantized and optimally packed data). General purpose lossless compressors can further improve the compression ratio at some cost to decoding performance.

The vertex codec tries to take advantage of the inherent locality of sequential vertices and identify bit patterns that repeat in consecutive vertices. Typically, vertex cache + vertex fetch provides a reasonably local vertex traversal order; without an index buffer, it is recommended to sort vertices spatially (via `meshopt_spatialSortRemap`) to improve the compression ratio.

It is crucial to correctly specify the stride when encoding vertex data; however, for compression ratio it does not matter whether the vertices are interleaved or deinterleaved, as the codecs perform full byte deinterleaving internally. The stride of each stream must be a multiple of 4 bytes.

For optimal compression results, the values should be quantized to small integers. It can be valuable to use bit counts that are not multiples of 8. For example, instead of using 16 bits to represent texture coordinates, use 12-bit integers and divide by 4095 in the shader. Alternatively, using half-precision floats can often achieve good results.
For single-precision floating-point data, it's recommended to use `meshopt_quantizeFloat` to remove entropy from the lower bits of the mantissa; for best results, consider using 15 bits or 7 bits for extreme compression.
For normal or tangent vectors, using octahedral encoding is recommended over three components as it reduces redundancy; similarly, consider using 10-12 bits per component instead of 16.

When data is bit packed, specifying compression level 3 (via `meshopt_encodeVertexBufferLevel`) can improve the compression further by redistributing bits between components.

### Index compression

This library also provides algorithms to encode/decode index data. To encode triangle indices, you need to allocate a target buffer (using the worst case bound) and call the encoding function:

```c++
std::vector<unsigned char> ibuf(meshopt_encodeIndexBufferBound(index_count, vertex_count));
ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), indices, index_count));
```

To decode the data at runtime, call the decoding function:

```c++
int res = meshopt_decodeIndexBuffer(indices, index_count, &ibuf[0], ibuf.size());
assert(res == 0);
```

Note that index encoding assumes that the index buffer was optimized for vertex cache and vertex fetch. Feeding unoptimized data into the encoder will result in poor compression ratios. Codec preserves the order of triangles, however it can rotate each triangle to improve compression ratio (which means the provoking vertex may change).

Decoder is heavily optimized and can directly target write-combined memory; you can expect it to run at 3-6 GB/s on modern desktop CPUs.

The index codec targets 1 byte per triangle as a best case (6x smaller than raw 16-bit index data); on real-world meshes, it's typical to achieve 1-1.2 bytes per triangle. To reach this, the index data needs to be optimized for vertex cache and vertex fetch. Optimizations that do not disrupt triangle locality (such as overdraw) are safe to use in between.
To reduce the data size further, it's possible to use `meshopt_optimizeVertexCacheStrip` instead of `meshopt_optimizeVertexCache` when optimizing for vertex cache. This trades off some efficiency in vertex transform for smaller index (and sometimes vertex) data.

When referenced vertex indices are not sequential, the index codec will use around 2 bytes per index. This can happen when the referenced vertices are a sparse subset of the vertex buffer, such as when encoding LODs. General-purpose compression can be especially helpful in this case.

Index buffer codec only supports triangle list topology; when encoding triangle strips or line lists, use `meshopt_encodeIndexSequence`/`meshopt_decodeIndexSequence` instead. This codec typically encodes indices into ~1 byte per index, but compressing the results further with a general purpose compressor can improve the results to 1-3 bits per index.


### Meshlet compression

When using mesh shading or clustered raytracing, meshlet vertex reference and triangle data can be compressed similarly to index data. This library provides a dedicated codec that exploits locality inherent in meshlet data. Unlike vertex and index buffer codecs that work on entire buffers, the meshlet codec encodes each meshlet independently; this allows applications to have more flexibility in structuring the runtime storage and adjust the decoded data during decoding. This also means that in some applications, additional data describing the meshlet (vertex/triangle count, encoded size) will need to be encoded into the meshlet stream, if it isn't already available during decoding.

To encode a meshlet, you need to allocate a target buffer (using the worst case bound) and call the encoding function with the vertex index references and micro-index buffer, as produced by `meshopt_buildMeshlets`:

```c++
std::vector<unsigned char> mbuf(meshopt_encodeMeshletBound(max_vertices, max_triangles));

for (const meshopt_Meshlet& m : meshlets)
{
    size_t msize = meshopt_encodeMeshlet(&mbuf[0], mbuf.size(),
        &meshlet_vertices[m.vertex_offset], m.vertex_count, &meshlet_triangles[m.triangle_offset], m.triangle_count);

    // write m.vertex_count, m.triangle_count, msize and mbuf[0..msize-1] to the output stream
}
```

To decode the data at runtime, call the decoding function:

```c++
uint16_t* vertices = ...;
uint8_t* triangles = ...;

// automatically deduces `vertex_size=2` and `triangle_size=3` based on pointer types
int res = meshopt_decodeMeshlet(vertices, m.vertex_count, triangles, m.triangle_count, stream, encoded_size);
assert(res == 0);
```

Vertex index references can be decoded as either 16-bit or 32-bit integers; triangle data can be decoded as 3 bytes per triangle (matching `meshopt_buildMeshlets` output format) or as a 32-bit integer per triangle (with indices packed as `a | (b << 8) | (c << 16)` and top byte unused). Output buffers must have available space aligned to 4 bytes; for example, decoding a 3-triangle stream using 3 bytes per triangle needs to be able to write 12 bytes to the output triangles array.

When using the C++ API, `meshopt_decodeMeshlet` will automatically deduce the element sizes based on the types of vertex and triangle pointers; when using the C API, the sizes need to be specified explicitly.

Decoder is heavily optimized and can directly target write-combined memory; you can expect it to run at 7-10 GB/s on modern desktop CPUs.

> Applications that do most of the streaming decompression on the GPU can also decode meshlet data on the GPU if CPU decoding is inconvenient; an example [meshletdec.slang](./demo/meshletdec.slang) shader is provided for 32-bit output format, and can be easily adapted to other formats, including custom ones.

Note that meshlet encoding assumes that the meshlet data was optimized; meshlets should be processed using `meshopt_optimizeMeshlet` before encoding. Additionally, vertex references should have a high degree of reference locality; this can be achieved by building meshlets from meshes optimized for vertex cache/fetch, or linearizing the vertex reference data (and reordering the vertex buffer accordingly). Feeding unoptimized data into the encoder will result in poor compression ratios. Codec preserves the order of triangles, however it can rotate each triangle to improve compression ratio (which means the provoking vertex may change).

Meshlets without vertex references are supported; passing `NULL` vertices and `0` vertex count during encoding and decoding will produce encoded meshlets with just triangle data. Note that parameters supplied during decoding must match those used during encoding; if a meshlet was encoded with vertex references, it must be decoded with the same number of vertex references.

The meshlet codec targets 5-7 bits per triangle for triangle data; when vertex references are encoded, the encoded size strongly depends on how linear the references are, but it's typical to see 9-12 bits per triangle in aggregate. To reduce the compressed size further, it's possible to compress the resulting encoded data with a general purpose compressor, which usually achieves 5-8 bits/triangle in aggregate; note that in this case general purpose compressors should be applied to a stream with many encoded meshlets at once to amortize their overhead.

> Note: this codec is currently experimental and the data format and APIs are subject to change.

### Point cloud compression

The vertex encoding algorithms can be used to compress arbitrary streams of attribute data; one other use case besides triangle meshes is point cloud data. Typically point clouds come with position, color and possibly other attributes but don't have an implied point order.

To compress point clouds efficiently, it's recommended to first preprocess the points by sorting them using the spatial sort algorithm:

```c++
std::vector<unsigned int> remap(point_count);
meshopt_spatialSortRemap(&remap[0], positions, point_count, sizeof(vec3));

// for each attribute stream
meshopt_remapVertexBuffer(positions, positions, point_count, sizeof(vec3), &remap[0]);
```

After this the resulting arrays should be quantized (e.g. using 16-bit fixed point numbers for positions and 8-bit color components), and the result can be compressed using `meshopt_encodeVertexBuffer` as described in the previous section. To decompress, `meshopt_decodeVertexBuffer` will recover the quantized data that can be used directly or converted back to original floating-point data. The compression ratio depends on the nature of source data, for colored points it's typical to get 35-40 bits per point.

### Vertex filters

To further leverage the inherent structure of some vertex data, it's possible to use filters that encode and decode the data in a lossy manner. This is similar to quantization but can be used without having to change the shader code. After decoding, the filter transformation needs to be reversed. For native game engine pipelines, it is usually more optimal to carefully prequantize and pretransform the vertex data, but sometimes (for example when serializing data in glTF format) this is not a practical option and filters are more convenient. This library provides four filters:

- Octahedral filter (`meshopt_encodeFilterOct`/`meshopt_decodeFilterOct`) encodes quantized (snorm) normal or tangent vectors using octahedral encoding. Any number of bits between 2 and 16 can be used with 4 bytes or 8 bytes per vector.
- Quaternion filter (`meshopt_encodeFilterQuat`/`meshopt_decodeFilterQuat`) encodes quantized (snorm) quaternion vectors; this can be used to encode rotations or tangent frames. Any number of bits between 4 and 16 can be used with 8 bytes per vector.
- Exponential filter (`meshopt_encodeFilterExp`/`meshopt_decodeFilterExp`) encodes single-precision floating-point vectors; this can be used to encode arbitrary floating-point data more efficiently. In addition to an arbitrary bit count (<= 24), the filter takes a "mode" parameter that allows specifying how the exponent sharing is performed to trade off compression ratio and quality:
    - `meshopt_EncodeExpSeparate` does not share exponents and results in the largest output
    - `meshopt_EncodeExpSharedVector` shares exponents between different components of the same vector
    - `meshopt_EncodeExpSharedComponent` shares exponents between the same component in different vectors
    - `meshopt_EncodeExpClamped` does not share exponents but clamps the exponent range to reduce exponent entropy
- Color filter (`meshopt_encodeFilterColor`/`meshopt_decodeFilterColor`) encodes quantized (unorm) RGBA colors using YCoCg encoding. Any number of bits between 2 and 16 can be used with 4 bytes or 8 bytes per vector.

Note that all filters are lossy and require the data to be deinterleaved with one attribute per stream; this facilitates efficient SIMD implementation of filter decoders, which decodes at 5-10 GB/s on modern desktop CPUs, allowing the overall decompression speed to be closer to that of the raw vertex codec.

### Versioning and compatibility

The following guarantees on data compatibility are provided for point releases (*no* guarantees are given for development branch):

- Data encoded with older versions of the library can always be decoded with newer versions;
- Data encoded with newer versions of the library can be decoded with older versions, provided that encoding versions are set correctly; if binary stability of encoded data is important, use `meshopt_encodeVertexVersion` and `meshopt_encodeIndexVersion` to 'pin' the data versions (or `version` argument of `meshopt_encodeVertexBufferLevel`).

By default, vertex data is encoded for format version 1 (compatible with meshoptimizer v0.23+), and index data is encoded for format version 1 (compatible with meshoptimizer v0.14+). When decoding the data, the decoder will automatically detect the version from the data header.

## Simplification

All algorithms presented so far don't affect visual appearance at all, with the exception of quantization that has minimal controlled impact. However, fundamentally the most effective way to reduce the rendering or transmission cost of a mesh is to reduce the number of triangles in the mesh.

### Basic simplification

This library provides a simplification algorithm, `meshopt_simplify`, that reduces the number of triangles in the mesh. Given a vertex and an index buffer, it generates a second index buffer that uses existing vertices in the vertex buffer. This index buffer can be used directly for rendering with the original vertex buffer (preferably after vertex cache optimization using `meshopt_optimizeVertexCache`), or a new compact vertex/index buffer can be generated using `meshopt_optimizeVertexFetch` that uses the optimal number and order of vertices.

```c++
float threshold = 0.2f;
size_t target_index_count = size_t(index_count * threshold);
float target_error = 1e-2f;

std::vector<unsigned int> lod(index_count);
float lod_error = 0.f;
lod.resize(meshopt_simplify(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex),
    target_index_count, target_error, /* options= */ 0, &lod_error));
```

Target error is an approximate measure of the deviation from the original mesh using distance normalized to `[0..1]` range (e.g. `1e-2f` means that simplifier will try to maintain the error to be below 1% of the mesh extents). Note that the simplifier attempts to produce the requested number of indices at minimal error, but because of topological restrictions and error limit it is not guaranteed to reach the target index count and can stop earlier.

To disable the error limit, `target_error` can be set to `FLT_MAX`. This makes it more likely that the simplifier will reach the target index count, but it may produce a mesh that looks significantly different from the original, so using the resulting error to control viewing distance would be required. Conversely, setting `target_index_count` to 0 will simplify the input mesh as much as possible within the specified error limit; this can be useful for generating LODs that should look good at a given viewing distance.

The algorithm follows the topology of the original mesh in an attempt to preserve attribute seams, borders and overall appearance. For meshes with inconsistent topology or many seams, such as faceted meshes, it can result in simplifier getting "stuck" and not being able to simplify the mesh fully. Therefore it's critical that identical vertices are "welded" together, that is, the input vertex buffer does not contain duplicates. Additionally, it may be worthwhile to weld the vertices without taking into account vertex attributes that aren't critical and can be rebuilt later, or use "permissive" mode described below.

Alternatively, the library provides another simplification algorithm, `meshopt_simplifySloppy`, which doesn't follow the topology of the original mesh. This means that it doesn't preserve attribute seams or borders, but it can collapse internal details that are too small to matter because it can merge mesh features that are topologically disjoint but spatially close. In general, this algorithm produces meshes with worse geometric quality and poor attribute quality compared to `meshopt_simplify`.

The algorithm can also return the resulting normalized deviation that can be used to choose the correct level of detail based on screen size or solid angle; the error can be converted to object space by multiplying by the scaling factor returned by `meshopt_simplifyScale`. For example, given a mesh with a precomputed LOD and a prescaled error, the screen-space normalized error can be computed and used for LOD selection:

```c++
// lod_factor can be 1 or can be adjusted for more or less aggressive LOD selection
float d = max(0, distance(camera_position, mesh_center) - mesh_radius);
float e = d * (tan(camera_fovy / 2) * 2 / screen_height); // 1px in mesh space
bool lod_ok = e * lod_factor >= lod_error;
```

When a sequence of LOD meshes is generated that all use the original vertex buffer, care must be taken to order vertices optimally to not penalize mobile GPU architectures that are only capable of transforming a sequential vertex buffer range. It's recommended in this case to first optimize each LOD for vertex cache, then assemble all LODs in one large index buffer starting from the coarsest LOD (the one with fewest triangles), and call `meshopt_optimizeVertexFetch` on the final large index buffer. This will make sure that coarser LODs require a smaller vertex range and are efficient with respect to vertex fetch and transform.

### Attribute-aware simplification

While `meshopt_simplify` is aware of attribute discontinuities by default (and infers them through the supplied index buffer) and tries to preserve them, it can be useful to provide information about attribute values. This allows the simplifier to take attribute error into account which can improve shading (by using vertex normals), texture deformation (by using texture coordinates), and may be necessary to preserve vertex colors when textures are not used in the first place. This can be done by using a variant of the simplification function that takes attribute values and weight factors, `meshopt_simplifyWithAttributes`:

```c++
const float nrm_weight = 0.5f;
const float attr_weights[3] = {nrm_weight, nrm_weight, nrm_weight};

std::vector<unsigned int> lod(index_count);
float lod_error = 0.f;
lod.resize(meshopt_simplifyWithAttributes(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex),
    &vertices[0].nx, sizeof(Vertex), attr_weights, 3, /* vertex_lock= */ NULL,
    target_index_count, target_error, /* options= */ 0, &lod_error));
```

The attributes are passed as a separate buffer (in the example above it's a subset of the same vertex buffer) and should be stored as consecutive floats; attribute weights are used to control the importance of each attribute in the simplification process. For normalized attributes like normals and vertex colors, a weight around 1.0 is usually appropriate; internally, a change of `1/weight` in attribute value over a distance `d` is approximately equivalent to a change of `d` in position. Using higher weights may be appropriate to preserve attribute quality at the cost of position quality. If the attribute has a different scale (e.g. unnormalized vertex colors in [0..255] range), the weight should be divided by the scaling factor (1/255 in this example).

Including texture coordinates in the attribute set is optional, as simplification generally preserves texture quality reasonably well by default; if included, a weight of around 10-100 is usually appropriate depending on the UV density. It's also possible to compute the weight automatically by setting it to the reciprocal average density of UVs, which can be computed as `1/sqrt(average UV area)` = `1/sqrt(sum(abs(uv area)) / triangle count)` over all triangles in the mesh, possibly scaled by a constant factor if necessary.

Both the target error and the resulting error combine positional error and attribute error, so the error can be used to control the LOD while taking attribute quality into account, assuming carefully chosen weights.

### Permissive simplification

By default, `meshopt_simplify` preserves attribute discontinuities inferred from the supplied index buffer. For meshes with many seams, the simplifier can get "stuck" and fail to fully simplify the mesh, as it cannot collapse vertices across attribute seams. This is especially problematic for meshes with faceted normals (flat shading), as the simplifier may not be able to reduce the triangle count at all. The `meshopt_SimplifyPermissive` option relaxes these restrictions, allowing the simplifier to collapse vertices across attribute discontinuities when the resulting error is acceptable:

```c++
std::vector<unsigned int> lod(index_count);
float lod_error = 0.f;
lod.resize(meshopt_simplifyWithAttributes(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex),
    &vertices[0].nx, sizeof(Vertex), attr_weights, 3, /* vertex_lock= */ NULL,
    target_index_count, target_error, /* options= */ meshopt_SimplifyPermissive, &lod_error));
```

To maintain appearance, it's highly recommended to use this option together with attribute-aware simplification, as shown above, as it allows the simplifier to maintain attribute quality. In this mode, it is often desirable to selectively preserve certain attribute seams, such as UV seams or sharp creases. This can be achieved by using the `vertex_lock` array with flag `meshopt_SimplifyVertex_Protect` set for individual vertices to protect specific discontinuities. To fill this array, use `meshopt_generatePositionRemap` to create a mapping table for vertices with identical positions, and then compare each vertex to the remapped vertex to determine which attributes are different:

```c++
std::vector<unsigned int> remap(vertices.size());
meshopt_generatePositionRemap(&remap[0], &vertices[0].px, vertices.size(), sizeof(Vertex));

std::vector<unsigned char> locks(vertices.size());
for (size_t i = 0; i < vertices.size(); ++i) {
    unsigned int r = remap[i];

    if (r != i && (vertices[r].tx != vertices[i].tx || vertices[r].ty != vertices[i].ty))
        locks[i] |= meshopt_SimplifyVertex_Protect; // protect UV seams
}
```

This approach provides fine-grained control over which discontinuities to preserve. The permissive mode combined with selective locking provides a balance between simplification quality and attribute preservation, and usually results in higher quality LODs for the same target triangle count (and dramatically higher quality compared to `meshopt_simplifySloppy`).

> Note: this functionality is currently experimental and is subject to future improvements. Certain collapses are restricted to protect the overall topology, and attribute quality may occasionally regress.

### Simplification with vertex update

All simplification functions described so far reuse the original vertex buffer and only produce a new index buffer. This means that the resulting mesh will have the same vertex positions and attributes as the original mesh; this is optimal for minimizing the memory consumption and for highly detailed meshes often provides good quality. However, for more aggressive simplification to retain visual quality, it may be necessary to adjust vertex data for optimal appearance. This can be done by using a variant of the simplification function that updates vertex positions and attributes, `meshopt_simplifyWithUpdate`:

```c++
indices.resize(meshopt_simplifyWithUpdate(&indices[0], indices.size(), &vertices[0].px, vertices.size(), sizeof(Vertex),
    &vertices[0].nx, sizeof(Vertex), attr_weights, 3, /* vertex_lock= */ NULL,
    target_index_count, target_error, /* options= */ 0, &result_error));
```

Unlike `meshopt_simplify`/`meshopt_simplifyWithAttributes`, this function updates the index buffer as well as vertex positions and attributes in place. The resulting indices still refer to the original vertex buffer; any attributes that are not passed to the simplifier can be left unchanged. However, since the original contents of `vertices` is no longer valid for rendering the original mesh, a new compact vertex/index buffer should be generated using `meshopt_optimizeVertexFetch` (after optimizing the index data with `meshopt_optimizeVertexCache`). If the original data was important, it should be copied before calling this function.

Since the vertex positions are updated, this may require updating some attributes that could previously be left as-is when using the original vertex buffer. Notably, texture coordinates need to be updated to avoid texture distortion; thus it's highly recommended to include texture coordinates in the attribute data passed to the simplifier. For attributes to be updated, the corresponding attribute weight must not be zero; for texture coordinates, a weight of 1.0 is usually sufficient in this case (although a higher or mesh dependent weight could be used with this function or other functions to reduce UV stretching).

Attributes that have specific constraints like normals and colors should be renormalized or clamped after the function returns new data. Attributes like bone indices/weights don't have to be updated for reasonable results (but regularization via `meshopt_SimplifyRegularize` may still be helpful to maintain deformation quality). If bone weights *are* provided as attributes, they will need to be clamped and renormalized after the update to ensure they continue to add up to 1.

Using unique vertex data for each LOD in a chain can improve visual quality, but it comes at a cost of ~doubling vertex memory used (if each LOD is using half the triangles of the previous LOD). To reduce the memory footprint, it is possible to use shared vertices with `meshopt_simplifyWithAttributes` for the first one or two LODs in the chain, and only switch to `meshopt_simplifyWithUpdate` for the remainder. In that case, similarly to the use of `meshopt_simplify` described earlier, care must be taken to optimally arrange the vertices in the original vertex buffer.

### Advanced simplification

`meshopt_simplify*` functions expose additional options and parameters that can be used to control the simplification process in more detail.

For basic customization, a number of options can be passed via `options` bitmask that adjust the behavior of the simplifier:

- `meshopt_SimplifyLockBorder` restricts the simplifier from collapsing edges that are on the border of the mesh. This can be useful for simplifying mesh subsets independently, so that the LODs can be combined without introducing cracks.
- `meshopt_SimplifyErrorAbsolute` changes the error metric from relative to absolute both for the input error limit as well as for the resulting error. This can be used instead of `meshopt_simplifyScale`.
- `meshopt_SimplifySparse` improves simplification performance assuming input indices are a sparse subset of the mesh. This can be useful when simplifying small mesh subsets independently, and is intended to be used for meshlet simplification. For consistency, it is recommended to use absolute errors when sparse simplification is desired, as this flag changes the meaning of the relative errors.
- `meshopt_SimplifyPrune` allows the simplifier to remove isolated components regardless of the topological restrictions inside the component. This is generally recommended for full-mesh simplification as it can improve quality and reduce triangle count; note that with this option, triangles connected to locked vertices may be removed as part of their component.
- `meshopt_SimplifyRegularize` produces more regular triangle sizes and shapes during simplification, at some cost to geometric quality. This can improve geometric quality under deformation such as skinning.
- `meshopt_SimplifyPermissive` allows collapses across attribute discontinuities, except for vertices that are tagged with `meshopt_SimplifyVertex_Protect` via `vertex_lock`.

When using `meshopt_simplifyWithAttributes`, it is also possible to lock certain vertices by providing a `vertex_lock` array that contains a value for each vertex in the mesh, with `meshopt_SimplifyVertex_Lock` set for vertices that should not be collapsed. This can be useful to preserve certain vertices, such as the boundary of the mesh, with more control than `meshopt_SimplifyLockBorder` option provides. When using `meshopt_simplifyWithUpdate`, locking vertices (whether via `vertex_lock` or `meshopt_SimplifyLockBorder`) will also prevent the simplifier from updating their positions and attributes; this can be useful together with `meshopt_SimplifySparse` for meshlet simplification, as meshlets at one level of hierarchy can be simplified together without excessive data copying.

In addition to the `meshopt_SimplifyPrune` flag, you can explicitly prune isolated components by calling the `meshopt_simplifyPrune` function. This can be done before regular simplification or as the only step, which is useful for scenarios like isosurface cleanup. Similar to other simplification functions, the `target_error` argument controls the cutoff of component radius and is specified in relative units (e.g., `1e-2f` will remove components under 1%). If an absolute cutoff is desired, divide the parameter by the factor returned by `meshopt_simplifyScale`.

Simplification currently assumes that the input mesh is using the same material for all triangles. If the mesh uses multiple materials, it is possible to split the mesh into subsets based on the material and simplify each subset independently, using `meshopt_SimplifyLockBorder` or `vertex_lock` to preserve material boundaries; however, this limits the collapses and may reduce the resulting quality. An alternative approach is to encode information about the material into the vertex buffer, ensuring that all three vertices referencing the same triangle have the same material ID; this may require duplicating vertices on the boundary between materials. After this, simplification can be performed as usual, and after simplification per-triangle material information can be computed from the vertex material IDs. There is no need to inform the simplifier of the value of the material ID: the implicit boundaries created by duplicating vertices with conflicting material IDs will be preserved automatically (unless permissive simplification is used, in which case material boundaries should be protected via `vertex_lock`). If the source mesh is already split into subsets with non-overlapping vertex indices, and permissive simplification is not used, it should be sufficient to concatenate the subsets into a single vertex/index buffer and simplify the entire mesh at once; the result can be split back into subsets after simplification.

When generating a LOD chain, you can either re-simplify each LOD from the original mesh or use the previous LOD as the starting point for the next level. The latter approach is more efficient and produces smoother visual transitions between LOD levels while preserving mesh attributes better. With this method, resulting error values from previous levels should be accumulated for LOD selection. Additionally, consider using `meshopt_SimplifySparse` to improve performance when generating deep LOD chains.

### Point cloud simplification

In addition to triangle mesh simplification, this library provides a function to simplify point clouds. The algorithm reduces the point cloud to a specified number of points while preserving the overall appearance, and can optionally take per-point colors into account:

```c++
const float color_weight = 1;
std::vector<unsigned int> indices(target_count);
indices.resize(meshopt_simplifyPoints(&indices[0], &points[0].x, points.size(), sizeof(Point),
    &points[0].r, sizeof(Point), color_weight, target_count));
```

The resulting indices can be used to render the simplified point cloud; to reduce the memory footprint, the point cloud can be reindexed to create an array of points from the indices.

## Efficiency analyzers

While the only way to get precise performance data is to measure performance on the target GPU, it can be valuable to measure the impact of these optimization in a GPU-independent manner. To this end, the library provides analyzers for all three major optimization routines. For each optimization there is a corresponding analyze function, like `meshopt_analyzeOverdraw`, that returns a struct with statistics.

`meshopt_analyzeVertexCache` returns vertex cache statistics. The common metric to use is ACMR - average cache miss ratio, which is the ratio of the total number of vertex invocations to the triangle count. The worst-case ACMR is 3 (GPU has to process 3 vertices for each triangle); on regular grids the optimal ACMR approaches 0.5. On real meshes it usually is in [0.5..1.5] range depending on the amount of vertex splits. One other useful metric is ATVR - average transformed vertex ratio - which represents the ratio of vertex shader invocations to the total vertices, and has the best case of 1.0 regardless of mesh topology (each vertex is transformed once).

`meshopt_analyzeVertexFetch` returns vertex fetch statistics. The main metric it uses is overfetch - the ratio between the number of bytes read from the vertex buffer to the total number of bytes in the vertex buffer. Assuming non-redundant vertex buffers, the best case is 1.0 - each byte is fetched once.

`meshopt_analyzeOverdraw` returns overdraw statistics. The main metric it uses is overdraw - the ratio between the number of pixel shader invocations to the total number of covered pixels, as measured from several different orthographic cameras. The best case for overdraw is 1.0 - each pixel is shaded once.

`meshopt_analyzeCoverage` returns coverage statistics: the ratio of covered pixels to the viewport extent from each cardinal axis. This is not an efficiency measure per se, but it can be used to measure silhouette change after simplification as well as more precise distance based culling, where the amount of view dependent coverage can be estimated by computing a dot product between the view direction and the coverage vector.

Note that all analyzers use approximate models for the relevant GPU units, so the numbers you will get as the result are only a rough approximation of the actual performance.

## Deinterleaved geometry

All of the examples above assume that geometry is represented as a single vertex buffer and a single index buffer. This requires storing all vertex attributes - position, normal, texture coordinate, skinning weights etc. - in a single contiguous struct. However, in some cases using multiple vertex streams may be preferable. In particular, if some passes require only positional data - such as depth pre-pass or shadow map - then it may be beneficial to split it from the rest of the vertex attributes to make sure the bandwidth use during these passes is optimal. On some mobile GPUs a position-only attribute stream also improves efficiency of tiling algorithms.

Most of the functions in this library either only need the index buffer (such as vertex cache optimization) or only need positional information (such as overdraw optimization). However, several tasks require knowledge about all vertex attributes.

For indexing, `meshopt_generateVertexRemap` assumes that there's just one vertex stream; when multiple vertex streams are used, it's necessary to use `meshopt_generateVertexRemapMulti` as follows:

```c++
meshopt_Stream streams[] = {
    {&unindexed_pos[0], sizeof(float) * 3, sizeof(float) * 3},
    {&unindexed_nrm[0], sizeof(float) * 3, sizeof(float) * 3},
    {&unindexed_uv[0], sizeof(float) * 2, sizeof(float) * 2},
};

std::vector<unsigned int> remap(index_count);
size_t vertex_count = meshopt_generateVertexRemapMulti(&remap[0], NULL, index_count, index_count, streams, sizeof(streams) / sizeof(streams[0]));
```

After this `meshopt_remapVertexBuffer` needs to be called once for each vertex stream to produce the correctly reindexed stream. For shadow indexing, similarly `meshopt_generateShadowIndexBufferMulti` is available as a replacement.

Instead of calling `meshopt_optimizeVertexFetch` for reordering vertices in a single vertex buffer for efficiency, calling `meshopt_optimizeVertexFetchRemap` and then calling `meshopt_remapVertexBuffer` for each stream again is recommended.

Finally, when compressing vertex data, `meshopt_encodeVertexBuffer` should be used on each vertex stream separately - this allows the encoder to best utilize correlation between attribute values for different vertices.

## Specialized processing

In addition to the core optimization techniques, the library provides several specialized algorithms for specific rendering techniques and pipeline optimizations that require a particular configuration of vertex and index data.

### Triangle strip conversion

On most hardware, indexed triangle lists are the most efficient way to drive the GPU. However, in some cases triangle strips might prove beneficial:

- On some older GPUs, triangle strips may be a bit more efficient to render
- On extremely memory constrained systems, index buffers for triangle strips could save a bit of memory

This library provides an algorithm for converting a vertex cache optimized triangle list to a triangle strip:

```c++
std::vector<unsigned int> strip(meshopt_stripifyBound(index_count));
unsigned int restart_index = ~0u;
size_t strip_size = meshopt_stripify(&strip[0], indices, index_count, vertex_count, restart_index);
```

Typically you should expect triangle strips to have ~50-60% of indices compared to triangle lists (~1.5-1.8 indices per triangle) and have ~5% worse ACMR.
Note that triangle strips can be stitched with or without restart index support. Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance.

To reduce the triangle strip size further, it's recommended to use `meshopt_optimizeVertexCacheStrip` instead of `meshopt_optimizeVertexCache` when optimizing for vertex cache. This trades off some efficiency in vertex transform for smaller index buffers.

### Geometry shader adjacency

For algorithms that use geometry shaders and require adjacency information, this library can generate an index buffer with adjacency data:

```c++
std::vector<unsigned int> adjacency(indices.size() * 2);
meshopt_generateAdjacencyIndexBuffer(&adjacency[0], &indices[0], indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex));
```

This creates an index buffer suitable for rendering with triangle-with-adjacency topology, providing 3 extra vertices per triangle that represent vertices opposite to each triangle's edge. This data can be used to compute silhouettes and perform other types of local geometric processing in geometry shaders. To render the mesh with adjacency data, the index buffer should be used with `D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ`/`VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY`/`GL_TRIANGLES_ADJACENCY` topology.

Note that the use of geometry shaders may have a performance impact on some GPUs; in some cases alternative implementation strategies may be more efficient.

### Tessellation with displacement mapping

For hardware tessellation with crack-free displacement mapping, this library can generate a special index buffer that supports PN-AEN tessellation:

```c++
std::vector<unsigned int> tess(indices.size() * 4);
meshopt_generateTessellationIndexBuffer(&tess[0], &indices[0], indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex));
```

This generates a 12-vertex patch for each input triangle with the following layout:

- 0, 1, 2: original triangle vertices
- 3, 4: opposing edge for edge 0, 1
- 5, 6: opposing edge for edge 1, 2
- 7, 8: opposing edge for edge 2, 0
- 9, 10, 11: dominant vertices for corners 0, 1, 2

This allows the use of hardware tessellation to implement PN-AEN and/or displacement mapping without cracks along UV seams or normal discontinuities. To render the mesh, the index buffer should be used with `D3D_PRIMITIVE_TOPOLOGY_12_CONTROL_POINT_PATCHLIST`/`VK_PRIMITIVE_TOPOLOGY_PATCH_LIST` (`patchControlPoints=12`) topology. For more details please refer to the following papers: [Crack-Free Point-Normal Triangles using Adjacent Edge Normals](https://developer.download.nvidia.com/whitepapers/2010/PN-AEN-Triangles-Whitepaper.pdf), [Tessellation on Any Budget](https://www.nvidia.com/content/pdf/gdc2011/john_mcdonald.pdf) and [My Tessellation Has Cracks!](https://developer.download.nvidia.com/assets/gamedev/files/gdc12/GDC12_DUDASH_MyTessellationHasCracks.pdf).

### Visibility buffers

To render geometry into visibility buffers, access to primitive index in fragment shader is required. While it is possible to use `SV_PrimitiveID`/`gl_PrimitiveID` in the fragment shader, this can result in suboptimal performance on some GPUs (notably, AMD RDNA1 and all NVidia GPUs), and may not be supported on mobile or console hardware. Using mesh shaders to generate primitive IDs is efficient but requires hardware support that is not universally available. To work around these limitations, this library provides a way to generate a special index buffer that uses provoking vertex to encode primitive IDs:

```c++
std::vector<unsigned int> provoke(indices.size());
std::vector<unsigned int> reorder(vertices.size() + indices.size() / 3);
reorder.resize(meshopt_generateProvokingIndexBuffer(&provoke[0], &reorder[0], &indices[0], indices.size(), vertices.size()));
```

This generates a special index buffer along with a reorder table that satisfies two constraints:

- `provoke[3 * tri] == tri`
- `reorder[provoke[x]]` refers to the original triangle vertices

To render the mesh with provoking vertex data, the application should use `provoke` as an index buffer and a vertex shader that passes vertex index (`SV_VertexID`/`gl_VertexIndex`) via a `flat`/`nointerpolation` attribute to the fragment shader as a primitive index, and loads vertex data manually by computing the real vertex index based on `reorder` table (`reorder[gl_VertexIndex]`). For more details please refer to [Variable Rate Shading with Visibility Buffer Rendering](https://advances.realtimerendering.com/s2024/content/Hable/Advances_SIGGRAPH_2024_VisibilityVRS-SIGGRAPH_Advances_2024.pptx); naturally, this technique does not require VRS.

> Note: This assumes the provoking vertex is the first vertex of a triangle, which is true for all graphics APIs except OpenGL/WebGL. For OpenGL/WebGL, you may need to rotate each triangle (abc -> bca) in the resulting index buffer, or use the `glProvokingVertex` function (OpenGL 3.2+) or `WEBGL_provoking_vertex` extension (WebGL2) to change the provoking vertex convention. For WebGL2, this is highly recommended to avoid a variety of emulation slowdowns that happen by default if `flat` attributes are used, such as an implicit use of geometry shaders.

Because the order of indices in the resulting index buffer must be preserved exactly for the technique to work, all optimizations that reorder indices (such as vertex cache optimization) must be applied before generating the provoking index buffer. Additionally, if index compression is used, `meshopt_encodeIndexSequence` should be used instead of `meshopt_encodeIndexBuffer` to ensure that the triangles are not rotated during encoding.

### Opacity micromaps

When using hardware raytracing with alpha-tested transparency, tracing the ray requires invoking any-hit shaders for each intersected surface; this can be inefficient as it requires extra communication and synchronization between raytracing hardware and shader units. Opacity micromaps (OMMs) can significantly accelerate the tests by providing opacity masks for each triangle; this requires subdividing each triangle using a uniform grid (4^N microtriangles for subdivision level N), with each microtriangle storing 1 bit (for 2-state micromaps) or 2 bits (for 4-state micromaps) of opacity data. To minimize the memory overhead, the maps can be reused between triangles using a per-triangle OMM index buffer. This library provides algorithms to generate OMM data for a given mesh from an alpha texture; the resulting data can be used directly in Vulkan via [VK_EXT_opacity_micromap](https://docs.vulkan.org/spec/latest/chapters/raytraversal.html#ray-opacity-micromap) or in DirectX via [DXR1.2](https://github.com/microsoft/DirectX-Specs/blob/master/d3d/Raytracing.md#opacity-micromaps).

Generating opacity micromaps happens in three stages: measure (and layout), rasterize and compact. Compaction is optional but recommended, and can be performed on each mesh individually or on all meshes in the same model/scene.

First, call `meshopt_opacityMapMeasure` to compute a subdivision level for each triangle based on its texel footprint; this also computes initial per-triangle OMM indices as it's common for triangles in the source mesh to refer to the same UVs:

```c++
const int states = 4; // 2-state or 4-state OMMs (used after measure)
const int max_level = 6; // max subdivision level
const float target_edge = 3.0f; // target 3x3px area for each microtriangle
std::vector<unsigned char> levels(indices.size() / 3);
std::vector<unsigned int> sources(indices.size() / 3);
std::vector<int> omm_indices(indices.size() / 3);
size_t omm_count = meshopt_opacityMapMeasure(&levels[0], &sources[0], &omm_indices[0], &indices[0], indices.size(),
    &vertices[0].u, vertices.size(), sizeof(Vertex), texture_width, texture_height, max_level, target_edge);
```

Each OMM entry requires separate storage which can be determined based on subdivision level and format (2-state or 4-state), and can be computed via `meshopt_opacityMapEntrySize`:

```c++
std::vector<unsigned int> offsets(omm_count);
size_t data_size = 0;

for (size_t i = 0; i < omm_count; ++i)
{
    offsets[i] = unsigned(data_size);
    data_size += meshopt_opacityMapEntrySize(levels[i], states);
}
```

Second, call `meshopt_opacityMapRasterize` for each triangle to compute the opacity state per microtriangle. This can be done sequentially or in parallel; it can use the original texture resolution or a smaller mip level to balance rasterization cost vs quality. When generating 4-state micromaps, using mip 0 is recommended to produce maximally conservative output so that enabling opacity micromaps does not noticeably change the raytraced output. For 2-state micromaps, or if the original textures are much higher resolution than the micromap subdivision, smaller mips (e.g. 1 or 2) can also work well.

```c++
for (size_t i = 0; i < omm_count; ++i)
{
    unsigned int tri = sources[i];
    const float* uv0 = &vertices[indices[tri * 3 + 0]].u;
    const float* uv1 = &vertices[indices[tri * 3 + 1]].u;
    const float* uv2 = &vertices[indices[tri * 3 + 2]].u;

    // texture addressing below assumes RGBA texture input without padding; +3 points to A
    meshopt_opacityMapRasterize(&data[offsets[i]], levels[i], states, uv0, uv1, uv2,
        texture.data() + 3, 4, texture_width * 4, texture_width, texture_height);
}
```

> Note: Opacity micromap data is sensitive to triangle corner order. If index or meshlet compression is used, to match runtime order the index data should be decompressed from the compressed representation first. Since per-triangle OMM indices use the original triangle order, it's recommended to perform OMM processing after the index order has been finalized.

After rasterization, the OMM data *can* be used as is; however, it's typical to see redundant entries that either can be reused between different triangles, or that have consistent states for all micro-triangles, which can be represented using "special" indices (-4..-1) per triangle. Thus it's recommended to compact the data - if it's already laid out sequentially similarly to the example above, then just calling `meshopt_opacityMapCompact` and trimming the output arrays is sufficient for optimal output:

```c++
omm_count = meshopt_opacityMapCompact(&data[0], data_size, &levels[0], &offsets[0], omm_count, &omm_indices[0], indices.size() / 3, states);
data_size = (omm_count == 0) ? 0 : offsets[omm_count - 1] + meshopt_opacityMapEntrySize(levels[omm_count - 1], states);
```

After compaction, `levels` and `offsets` (`omm_count` entries) and `data` (`data_size` bytes) can be serialized and later passed to the raytracing runtime to build the opacity micromap structures. Often, compacting OMM data across multiple meshes can produce smaller results; in that case, all resulting OMM indices will point to a single OMM array object.

Additionally, note that while the code above works with 32-bit OMM indices, after compaction it's typical to see each mesh refer to a small section of OMM array data which can be represented using 16-bit (or, sometimes, 8-bit indices). The index data can be narrowed to a shorter type in these cases; note that when using 16-bit OMM index data, due to special indices, the index values should be in range `[0..65531]` (and `[0..251]` for 8-bit indices, assuming 4-state OMMs are used).

When using 4-state OMMs, rasterization code produces both unknown-transparent and unknown-opaque states based on microtriangle coverage; this enables the use of forced 2-state flag during traversal for specific effects where micromap data is sufficient for reasonable quality; this is recommended for performance as this results in no any-hit invocations.

## Memory management

Many algorithms allocate temporary memory to store intermediate results or accelerate processing. The amount of memory allocated is a function of various input parameters such as vertex count and index count. By default memory is allocated using `operator new` and `operator delete`; if these operators are overloaded by the application, the overloads will be used instead. Alternatively it's possible to specify custom allocation/deallocation functions using `meshopt_setAllocator`, e.g.

```c++
meshopt_setAllocator(malloc, free);
```

> Note that the library expects the allocation function to either throw in case of out-of-memory (in which case the exception will propagate to the caller) or abort, so technically the use of `malloc` above isn't safe. If you want to handle out-of-memory errors without using C++ exceptions, you can use `setjmp`/`longjmp` instead.

Vertex and index decoders (`meshopt_decodeVertexBuffer`, `meshopt_decodeIndexBuffer`, `meshopt_decodeIndexSequence`) do not allocate memory and work completely within the buffer space provided via arguments.

All functions have bounded stack usage that does not exceed 32 KB for any algorithms.

## Experimental APIs

Several algorithms provided by this library are marked as "experimental"; this status is reflected in the comments as well as the annotation `MESHOPTIMIZER_EXPERIMENTAL` for each function.

APIs that are not experimental (annotated with `MESHOPTIMIZER_API`) are considered stable, which means that library updates will not break compatibility: existing calls should compile (API compatibility), existing binaries should link (ABI compatibility), and existing behavior should not change significantly (for example, floating point parameters will have similar behavior). This does not mean that the output of the algorithms will be identical: future versions may improve the algorithms and produce different results.

APIs that *are* experimental may have their interface change, both in ways that will cause existing calls to not compile, and in ways that may compile but have significantly different behavior (e.g., changes in parameter order, meaning, valid ranges). Experimental APIs may also, in rare cases, be removed from future library versions. It is recommended to carefully read release notes when updating the library if experimental APIs are in use. Some experimental APIs may also lack documentation in this README.

Applications may configure the library to change the attributes of experimental APIs, for example defining `MESHOPTIMIZER_EXPERIMENTAL` as `__attribute__((deprecated))` will emit compiler warnings when experimental APIs are used. When building a shared library with CMake, `MESHOPT_STABLE_EXPORTS` option can be set to only export stable APIs; this produces an ABI-stable shared library that can be updated without recompiling the application code.

Currently, the following APIs are experimental:

- `meshopt_SimplifyPermissive` mode for `meshopt_simplify*` functions (and associated `meshopt_SimplifyVertex_*` flags)
- `meshopt_encodeMeshlet` and `meshopt_encodeMeshletBound` functions
- `meshopt_decodeMeshlet` and `meshopt_decodeMeshletRaw` functions
- `meshopt_extractMeshletIndices` function
- `meshopt_opacityMap*` functions (`meshopt_opacityMapMeasure`, `meshopt_opacityMapRasterize`, `meshopt_opacityMapCompact`, `meshopt_opacityMapEntrySize`)

## License

This library is available to anybody free of charge, under the terms of [MIT License](LICENSE.md).

To honor the license agreement, please include attribution into the user-facing product documentation and/or credits, for example using this or similar text:

> Uses meshoptimizer. Copyright (c) 2016-2026, Arseny Kapoulkine


================================================
FILE: demo/ansi.c
================================================
/* This file makes sure the library can be used by C89 code */
#include "../src/meshoptimizer.h"


================================================
FILE: demo/clusterlod.h
================================================
/**
 * clusterlod - a small "library"/example built on top of meshoptimizer to generate cluster LOD hierarchies
 * This is intended to either be used as is, or as a reference for implementing similar functionality in your engine.
 *
 * To use this code, you need to have one source file which includes meshoptimizer.h and defines CLUSTERLOD_IMPLEMENTATION
 * before including this file. Other source files in your project can just include this file and use the provided functions.
 *
 * Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
 * This code is distributed under the MIT License. See notice at the end of this file.
 */
#pragma once

#include <stddef.h>

struct clodConfig
{
	// configuration of each cluster; maps to meshopt_buildMeshlets* parameters
	size_t max_vertices;
	size_t min_triangles;
	size_t max_triangles;

	// partitioning setup; maps to meshopt_partitionClusters parameters (plus optional partition sorting)
	// note: partition size is the target size, not maximum; actual partitions may be up to 1/3 larger (e.g. target 24 results in maximum 32)
	bool partition_spatial;
	bool partition_sort;
	size_t partition_size;

	// clusterization setup; maps to meshopt_buildMeshletsSpatial / meshopt_buildMeshletsFlex
	bool cluster_spatial;
	float cluster_fill_weight;
	float cluster_split_factor;

	// every level aims to reduce the number of triangles by ratio, and considers clusters that don't reach the threshold stuck
	float simplify_ratio;
	float simplify_threshold;

	// to compute the error of simplified clusters, we use the formula that combines previous accumulated error as follows:
	// max(previous_error * simplify_error_merge_previous, current_error) + current_error * simplify_error_merge_additive
	float simplify_error_merge_previous;
	float simplify_error_merge_additive;

	// amplify the error of clusters that go through sloppy simplification to account for appearance degradation
	float simplify_error_factor_sloppy;

	// experimental: limit error by edge length, aiming to remove subpixel triangles even if the attribute error is high
	float simplify_error_edge_limit;

	// use permissive simplification instead of regular simplification (make sure to use attribute_protect_mask if this is set!)
	bool simplify_permissive;

	// use permissive or sloppy simplification but only if regular simplification gets stuck
	bool simplify_fallback_permissive;
	bool simplify_fallback_sloppy;

	// use regularization during simplification to make triangle density more uniform, at some cost to overall triangle count; recommended for deformable objects
	bool simplify_regularize;

	// should clodCluster::bounds be computed based on the geometry of each cluster
	bool optimize_bounds;

	// should clodCluster::indices be optimized for locality; helps with rasterization performance and ray tracing performance in fast-build modes
	bool optimize_clusters;
};

struct clodMesh
{
	// input triangle indices
	const unsigned int* indices;
	size_t index_count;

	// total vertex count
	size_t vertex_count;

	// input vertex positions; must be 3 floats per vertex
	const float* vertex_positions;
	size_t vertex_positions_stride;

	// input vertex attributes; used for attribute-aware simplification and permissive simplification
	const float* vertex_attributes;
	size_t vertex_attributes_stride;

	// input vertex locks; allows to preserve additional seams (when not using attribute_protect_mask) or lock vertices via meshopt_SimplifyVertex_* flags
	const unsigned char* vertex_lock;

	// attribute weights for attribute-aware simplification; maps to meshopt_simplifyWithAttributes parameters
	const float* attribute_weights;
	size_t attribute_count;

	// attribute mask to flag attribute discontinuities for permissive simplification; mask (1<<K) corresponds to attribute K
	unsigned int attribute_protect_mask;
};

// To compute approximate (perspective) projection error of a cluster in screen space (0..1; multiply by screen height to get pixels):
// - camera_proj is projection[1][1], or cot(fovy/2); camera_znear is *positive* near plane distance
// - for simplicity, we ignore perspective distortion and use rotationally invariant projection size estimation
// - return: bounds.error / max(distance(bounds.center, camera_position) - bounds.radius, camera_znear) * (camera_proj * 0.5f)
struct clodBounds
{
	// sphere bounds, in mesh coordinate space
	float center[3];
	float radius;

	// combined simplification error, in mesh coordinate space
	float error;
};

struct clodCluster
{
	// index of more refined group (with more triangles) that produced this cluster during simplification, or -1 for original geometry
	int refined;

	// cluster bounds; should only be used for culling, as bounds.error is not monotonic across DAG
	clodBounds bounds;

	// cluster indices; refer to the original mesh vertex buffer
	const unsigned int* indices;
	size_t index_count;

	// cluster vertex count; indices[] has vertex_count unique entries
	size_t vertex_count;
};

struct clodGroup
{
	// DAG level the group was generated at
	int depth;

	// simplified group bounds (reflects error for clusters with clodCluster::refined == group id; error is FLT_MAX for terminal groups)
	// cluster should be rendered if:
	// 1. clodGroup::simplified for the group it's in is over error threshold
	// 2. cluster.refined is -1 *or* clodGroup::simplified for groups[cluster.refined].simplified is at or under error threshold
	clodBounds simplified;
};

// gets called for each group in sequence
// returned value gets saved for clusters emitted from this group (clodCluster::refined)
typedef int (*clodOutput)(void* output_context, clodGroup group, const clodCluster* clusters, size_t cluster_count);

#ifdef __cplusplus
extern "C"
{
#endif

// default configuration optimized for rasterization / raytracing
clodConfig clodDefaultConfig(size_t max_triangles);
clodConfig clodDefaultConfigRT(size_t max_triangles);

// build cluster LOD hierarchy, calling output callbacks as new clusters and groups are generated
// returns the total number of clusters produced
size_t clodBuild(clodConfig config, clodMesh mesh, void* output_context, clodOutput output_callback);

// extract meshlet-local indices from cluster indices produced by clodBuild
// fills triangles[] and vertices[] such that vertices[triangles[i]] == indices[i]
// returns number of unique vertices (which will be equal to clodCluster::vertex_count)
size_t clodLocalIndices(unsigned int* vertices, unsigned char* triangles, const unsigned int* indices, size_t index_count);

#ifdef __cplusplus
} // extern "C"

template <typename Output>
size_t clodBuild(clodConfig config, clodMesh mesh, Output output)
{
	struct Call
	{
		static int output(void* output_context, clodGroup group, const clodCluster* clusters, size_t cluster_count)
		{
			return (*static_cast<Output*>(output_context))(group, clusters, cluster_count);
		}
	};

	return clodBuild(config, mesh, &output, &Call::output);
}
#endif

#ifdef CLUSTERLOD_IMPLEMENTATION
// For reference, see the original Nanite paper:
// Brian Karis. Nanite: A Deep Dive. 2021
#include <float.h>
#include <math.h>
#include <string.h>

#include <algorithm>
#include <vector>

namespace clod
{

struct Cluster
{
	size_t vertices;
	std::vector<unsigned int> indices;

	int group;
	int refined;

	clodBounds bounds;
};

static clodBounds boundsCompute(const clodMesh& mesh, const std::vector<unsigned int>& indices, float error)
{
	meshopt_Bounds bounds = meshopt_computeClusterBounds(&indices[0], indices.size(), mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride);

	clodBounds result;
	result.center[0] = bounds.center[0];
	result.center[1] = bounds.center[1];
	result.center[2] = bounds.center[2];
	result.radius = bounds.radius;
	result.error = error;
	return result;
}

static clodBounds boundsMerge(const std::vector<Cluster>& clusters, const std::vector<int>& group)
{
	std::vector<clodBounds> bounds(group.size());
	for (size_t j = 0; j < group.size(); ++j)
		bounds[j] = clusters[group[j]].bounds;

	meshopt_Bounds merged = meshopt_computeSphereBounds(&bounds[0].center[0], bounds.size(), sizeof(clodBounds), &bounds[0].radius, sizeof(clodBounds));

	clodBounds result = {};
	result.center[0] = merged.center[0];
	result.center[1] = merged.center[1];
	result.center[2] = merged.center[2];
	result.radius = merged.radius;

	// merged bounds error must be conservative wrt cluster errors
	result.error = 0.f;
	for (size_t j = 0; j < group.size(); ++j)
		result.error = std::max(result.error, clusters[group[j]].bounds.error);

	return result;
}

static std::vector<Cluster> clusterize(const clodConfig& config, const clodMesh& mesh, const unsigned int* indices, size_t index_count)
{
	size_t max_meshlets = meshopt_buildMeshletsBound(index_count, config.max_vertices, config.min_triangles);

	std::vector<meshopt_Meshlet> meshlets(max_meshlets);
	std::vector<unsigned int> meshlet_vertices(index_count);

#if MESHOPTIMIZER_VERSION < 1000
	std::vector<unsigned char> meshlet_triangles(index_count + max_meshlets * 3); // account for 4b alignment
#else
	std::vector<unsigned char> meshlet_triangles(index_count);
#endif

	if (config.cluster_spatial)
		meshlets.resize(meshopt_buildMeshletsSpatial(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices, index_count,
		    mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride,
		    config.max_vertices, config.min_triangles, config.max_triangles, config.cluster_fill_weight));
	else
		meshlets.resize(meshopt_buildMeshletsFlex(meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices, index_count,
		    mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride,
		    config.max_vertices, config.min_triangles, config.max_triangles, 0.f, config.cluster_split_factor));

	std::vector<Cluster> clusters(meshlets.size());

	for (size_t i = 0; i < meshlets.size(); ++i)
	{
		const meshopt_Meshlet& meshlet = meshlets[i];

		if (config.optimize_clusters)
			meshopt_optimizeMeshlet(&meshlet_vertices[meshlet.vertex_offset], &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count, meshlet.vertex_count);

		clusters[i].vertices = meshlet.vertex_count;

		// note: we discard meshlet-local indices; they can be recovered by the caller using clodLocalIndices
		clusters[i].indices.resize(meshlet.triangle_count * 3);
		for (size_t j = 0; j < meshlet.triangle_count * 3; ++j)
			clusters[i].indices[j] = meshlet_vertices[meshlet.vertex_offset + meshlet_triangles[meshlet.triangle_offset + j]];

		clusters[i].group = -1;
		clusters[i].refined = -1;
	}

	return clusters;
}

static std::vector<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)
{
	if (pending.size() <= config.partition_size)
		return {pending};

	std::vector<unsigned int> cluster_indices;
	std::vector<unsigned int> cluster_counts(pending.size());

	// copy cluster index data into a flat array for partitioning
	size_t total_index_count = 0;
	for (size_t i = 0; i < pending.size(); ++i)
		total_index_count += clusters[pending[i]].indices.size();

	cluster_indices.reserve(total_index_count);

	for (size_t i = 0; i < pending.size(); ++i)
	{
		const Cluster& cluster = clusters[pending[i]];

		cluster_counts[i] = unsigned(cluster.indices.size());

		for (size_t j = 0; j < cluster.indices.size(); ++j)
			cluster_indices.push_back(remap[cluster.indices[j]]);
	}

	// partition clusters into groups; the output is a partition id per cluster
	std::vector<unsigned int> cluster_part(pending.size());
	size_t partition_count = meshopt_partitionClusters(&cluster_part[0], &cluster_indices[0], cluster_indices.size(), &cluster_counts[0], cluster_counts.size(),
	    config.partition_spatial ? mesh.vertex_positions : NULL, remap.size(), mesh.vertex_positions_stride, config.partition_size);

	// preallocate partitions for worst case
	std::vector<std::vector<int> > partitions(partition_count);
	for (size_t i = 0; i < partition_count; ++i)
		partitions[i].reserve(config.partition_size + config.partition_size / 3);

	std::vector<unsigned int> partition_remap;

	if (config.partition_sort)
	{
		// compute partition points for sorting; any representative point will do, we use last cluster center for simplicity
		std::vector<float> partition_point(partition_count * 3);
		for (size_t i = 0; i < pending.size(); ++i)
			memcpy(&partition_point[cluster_part[i] * 3], clusters[pending[i]].bounds.center, sizeof(float) * 3);

		// sort partitions spatially; the output is a remap table from old index (partition id) to new index
		partition_remap.resize(partition_count);
		meshopt_spatialSortRemap(partition_remap.data(), partition_point.data(), partition_count, sizeof(float) * 3);
	}

	// distribute clusters into partitions, applying spatial order if requested
	for (size_t i = 0; i < pending.size(); ++i)
		partitions[partition_remap.empty() ? cluster_part[i] : partition_remap[cluster_part[i]]].push_back(pending[i]);

	return partitions;
}

static void lockBoundary(std::vector<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)
{
	// for each remapped vertex, use bit 7 as temporary storage to indicate that the vertex has been used by a different group previously
	for (size_t i = 0; i < locks.size(); ++i)
		locks[i] &= ~((1 << 0) | (1 << 7));

	for (size_t i = 0; i < groups.size(); ++i)
	{
		// mark all remapped vertices as locked if seen by a prior group
		for (size_t j = 0; j < groups[i].size(); ++j)
		{
			const Cluster& cluster = clusters[groups[i][j]];

			for (size_t k = 0; k < cluster.indices.size(); ++k)
			{
				unsigned int v = cluster.indices[k];
				unsigned int r = remap[v];

				locks[r] |= locks[r] >> 7;
			}
		}

		// mark all remapped vertices as seen
		for (size_t j = 0; j < groups[i].size(); ++j)
		{
			const Cluster& cluster = clusters[groups[i][j]];

			for (size_t k = 0; k < cluster.indices.size(); ++k)
			{
				unsigned int v = cluster.indices[k];
				unsigned int r = remap[v];

				locks[r] |= 1 << 7;
			}
		}
	}

	for (size_t i = 0; i < locks.size(); ++i)
	{
		unsigned int r = remap[i];

		// consistently lock all vertices with the same position; keep protect bit if set
		locks[i] = (locks[r] & 1) | (locks[i] & meshopt_SimplifyVertex_Protect);

		if (vertex_lock)
			locks[i] |= vertex_lock[i];
	}
}

struct SloppyVertex
{
	float x, y, z;
	unsigned int id;
};

static void simplifyFallback(std::vector<unsigned int>& lod, const clodMesh& mesh, const std::vector<unsigned int>& indices, const std::vector<unsigned char>& locks, size_t target_count, float* error)
{
	std::vector<SloppyVertex> subset(indices.size());
	std::vector<unsigned char> subset_locks(indices.size());

	lod.resize(indices.size());

	size_t positions_stride = mesh.vertex_positions_stride / sizeof(float);

	// deindex the mesh subset to avoid calling simplifySloppy on the entire vertex buffer (which is prohibitively expensive without sparsity)
	for (size_t i = 0; i < indices.size(); ++i)
	{
		unsigned int v = indices[i];
		assert(v < mesh.vertex_count);

		subset[i].x = mesh.vertex_positions[v * positions_stride + 0];
		subset[i].y = mesh.vertex_positions[v * positions_stride + 1];
		subset[i].z = mesh.vertex_positions[v * positions_stride + 2];
		subset[i].id = v;

		subset_locks[i] = locks[v];
		lod[i] = unsigned(i);
	}

	lod.resize(meshopt_simplifySloppy(&lod[0], &lod[0], lod.size(), &subset[0].x, subset.size(), sizeof(SloppyVertex), subset_locks.data(), target_count, FLT_MAX, error));

	// convert error to absolute
	*error *= meshopt_simplifyScale(&subset[0].x, subset.size(), sizeof(SloppyVertex));

	// restore original vertex indices
	for (size_t i = 0; i < lod.size(); ++i)
		lod[i] = subset[lod[i]].id;
}

static std::vector<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)
{
	if (target_count > indices.size())
		return indices;

	std::vector<unsigned int> lod(indices.size());

	unsigned int options = meshopt_SimplifySparse | meshopt_SimplifyErrorAbsolute | (config.simplify_permissive ? meshopt_SimplifyPermissive : 0) | (config.simplify_regularize ? meshopt_SimplifyRegularize : 0);

	lod.resize(meshopt_simplifyWithAttributes(&lod[0], &indices[0], indices.size(),
	    mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride,
	    mesh.vertex_attributes, mesh.vertex_attributes_stride, mesh.attribute_weights, mesh.attribute_count,
	    &locks[0], target_count, FLT_MAX, options, error));

	if (lod.size() > target_count && config.simplify_fallback_permissive && !config.simplify_permissive)
		lod.resize(meshopt_simplifyWithAttributes(&lod[0], &indices[0], indices.size(),
		    mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride,
		    mesh.vertex_attributes, mesh.vertex_attributes_stride, mesh.attribute_weights, mesh.attribute_count,
		    &locks[0], target_count, FLT_MAX, options | meshopt_SimplifyPermissive, error));

	// while it's possible to call simplifySloppy directly, it doesn't support sparsity or absolute error, so we need to do some extra work
	if (lod.size() > target_count && config.simplify_fallback_sloppy)
	{
		simplifyFallback(lod, mesh, indices, locks, target_count, error);
		*error *= config.simplify_error_factor_sloppy; // scale error up to account for appearance degradation
	}

	// optionally limit error by edge length, aiming to remove subpixel triangles even if the attribute error is high
	if (config.simplify_error_edge_limit > 0)
	{
		float max_edge_sq = 0;

		for (size_t i = 0; i < indices.size(); i += 3)
		{
			unsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2];
			assert(a < mesh.vertex_count && b < mesh.vertex_count && c < mesh.vertex_count);

			const float* va = &mesh.vertex_positions[a * (mesh.vertex_positions_stride / sizeof(float))];
			const float* vb = &mesh.vertex_positions[b * (mesh.vertex_positions_stride / sizeof(float))];
			const float* vc = &mesh.vertex_positions[c * (mesh.vertex_positions_stride / sizeof(float))];

			// compute squared edge lengths
			float eab = (va[0] - vb[0]) * (va[0] - vb[0]) + (va[1] - vb[1]) * (va[1] - vb[1]) + (va[2] - vb[2]) * (va[2] - vb[2]);
			float eac = (va[0] - vc[0]) * (va[0] - vc[0]) + (va[1] - vc[1]) * (va[1] - vc[1]) + (va[2] - vc[2]) * (va[2] - vc[2]);
			float ebc = (vb[0] - vc[0]) * (vb[0] - vc[0]) + (vb[1] - vc[1]) * (vb[1] - vc[1]) + (vb[2] - vc[2]) * (vb[2] - vc[2]);

			float emax = std::max(std::max(eab, eac), ebc);
			float emin = std::min(std::min(eab, eac), ebc);

			// we prefer using min edge length to reduce the number of triangles <1px thick, but need some stopgap for thin and long triangles like wires
			max_edge_sq = std::max(max_edge_sq, std::max(emin, emax / 4));
		}

		// adjust the error to limit it for dense clusters based on edge lengths
		*error = std::min(*error, sqrtf(max_edge_sq) * config.simplify_error_edge_limit);
	}

	return lod;
}

static int outputGroup(const clodConfig& config, const clodMesh& mesh, const std::vector<Cluster>& clusters, const std::vector<int>& group, const clodBounds& simplified, int depth, void* output_context, clodOutput output_callback)
{
	std::vector<clodCluster> group_clusters(group.size());

	for (size_t i = 0; i < group.size(); ++i)
	{
		const Cluster& cluster = clusters[group[i]];
		clodCluster& result = group_clusters[i];

		result.refined = cluster.refined;
		result.bounds = (config.optimize_bounds && cluster.refined != -1) ? boundsCompute(mesh, cluster.indices, cluster.bounds.error) : cluster.bounds;
		result.indices = cluster.indices.data();
		result.index_count = cluster.indices.size();
		result.vertex_count = cluster.vertices;
	}

	return output_callback ? output_callback(output_context, {depth, simplified}, group_clusters.data(), group_clusters.size()) : -1;
}

} // namespace clod

clodConfig clodDefaultConfig(size_t max_triangles)
{
	assert(max_triangles >= 4 && max_triangles <= 256);

	clodConfig config = {};
	config.max_vertices = max_triangles;
	config.min_triangles = max_triangles / 3;
	config.max_triangles = max_triangles;

#if MESHOPTIMIZER_VERSION < 1000
	config.min_triangles &= ~3; // account for 4b alignment
#endif

	config.partition_spatial = true;
	config.partition_size = 16;

	config.cluster_spatial = false;
	config.cluster_split_factor = 2.0f;

	config.optimize_clusters = true;

	config.simplify_ratio = 0.5f;
	config.simplify_threshold = 0.85f;
	config.simplify_error_merge_previous = 1.0f;
	config.simplify_error_factor_sloppy = 2.0f;
	config.simplify_permissive = true;
	config.simplify_fallback_permissive = false; // note: by default we run in permissive mode, but it's also possible to disable that and use it only as a fallback
	config.simplify_fallback_sloppy = true;

	return config;
}

clodConfig clodDefaultConfigRT(size_t max_triangles)
{
	clodConfig config = clodDefaultConfig(max_triangles);

	// for ray tracing, we may want smaller clusters when that improves BVH quality further; for maximum ray tracing performance this could be reduced even further
	config.min_triangles = max_triangles / 4;

	// by default, we use larger max_vertices for RT; the vertex count is not important for ray tracing performance, and this helps improve cluster utilization
	config.max_vertices = std::min(size_t(256), max_triangles * 2);

	config.cluster_spatial = true;
	config.cluster_fill_weight = 0.5f;

	return config;
}

size_t clodBuild(clodConfig config, clodMesh mesh, void* output_context, clodOutput output_callback)
{
	using namespace clod;

	assert(mesh.vertex_attributes_stride % sizeof(float) == 0);
	assert(mesh.attribute_count * sizeof(float) <= mesh.vertex_attributes_stride);
	assert(mesh.attribute_protect_mask < (1u << (mesh.vertex_attributes_stride / sizeof(float))));

	std::vector<unsigned char> locks(mesh.vertex_count);

	// for cluster connectivity, we need a position-only remap that maps vertices with the same position to the same index
	std::vector<unsigned int> remap(mesh.vertex_count);
	meshopt_generatePositionRemap(&remap[0], mesh.vertex_positions, mesh.vertex_count, mesh.vertex_positions_stride);

	// set up protect bits on UV seams for permissive mode
	if (mesh.attribute_protect_mask)
	{
		size_t max_attributes = mesh.vertex_attributes_stride / sizeof(float);

		for (size_t i = 0; i < mesh.vertex_count; ++i)
		{
			unsigned int r = remap[i]; // canonical vertex with the same position

			for (size_t j = 0; j < max_attributes; ++j)
				if (r != i && (mesh.attribute_protect_mask & (1u << j)) && mesh.vertex_attributes[i * max_attributes + j] != mesh.vertex_attributes[r * max_attributes + j])
					locks[i] |= meshopt_SimplifyVertex_Protect;
		}
	}

	// initial clusterization splits the original mesh
	std::vector<Cluster> clusters = clusterize(config, mesh, mesh.indices, mesh.index_count);

	// compute initial precise bounds; subsequent bounds will be using group-merged bounds
	for (Cluster& cluster : clusters)
		cluster.bounds = boundsCompute(mesh, cluster.indices, 0.f);

	std::vector<int> pending(clusters.size());
	for (size_t i = 0; i < clusters.size(); ++i)
		pending[i] = int(i);

	int depth = 0;

	// merge and simplify clusters until we can't merge anymore
	while (pending.size() > 1)
	{
		std::vector<std::vector<int> > groups = partition(config, mesh, clusters, pending, remap);

		pending.clear();

		// mark boundaries between groups with a lock bit to avoid gaps in simplified result
		lockBoundary(locks, groups, clusters, remap, mesh.vertex_lock);

		// every group needs to be simplified now
		for (size_t i = 0; i < groups.size(); ++i)
		{
			std::vector<unsigned int> merged;
			merged.reserve(groups[i].size() * config.max_triangles * 3);
			for (size_t j = 0; j < groups[i].size(); ++j)
				merged.insert(merged.end(), clusters[groups[i][j]].indices.begin(), clusters[groups[i][j]].indices.end());

			size_t target_size = size_t((merged.size() / 3) * config.simplify_ratio) * 3;

			// enforce bounds and error monotonicity
			// note: it is incorrect to use the precise bounds of the merged or simplified mesh, because this may violate monotonicity
			clodBounds bounds = boundsMerge(clusters, groups[i]);

			float error = 0.f;
			std::vector<unsigned int> simplified = simplify(config, mesh, merged, locks, target_size, &error);
			if (simplified.size() > merged.size() * config.simplify_threshold)
			{
				bounds.error = FLT_MAX; // terminal group, won't simplify further
				outputGroup(config, mesh, clusters, groups[i], bounds, depth, output_context, output_callback);
				continue; // simplification is stuck; abandon the merge
			}

			// enforce error monotonicity (with an optional hierarchical factor to separate transitions more)
			bounds.error = std::max(bounds.error * config.simplify_error_merge_previous, error) + error * config.simplify_error_merge_additive;

			// output the new group with all clusters; the resulting id will be recorded in new clusters as clodCluster::refined
			int refined = outputGroup(config, mesh, clusters, groups[i], bounds, depth, output_context, output_callback);

			// discard clusters from the group - they won't be used anymore
			for (size_t j = 0; j < groups[i].size(); ++j)
				clusters[groups[i][j]].indices = std::vector<unsigned int>();

			std::vector<Cluster> split = clusterize(config, mesh, simplified.data(), simplified.size());

			for (Cluster& cluster : split)
			{
				cluster.refined = refined;

				// update cluster group bounds to the group-merged bounds; this ensures that we compute the group bounds for whatever group this cluster will be part of conservatively
				cluster.bounds = bounds;

				// enqueue new cluster for further processing
				clusters.push_back(std::move(cluster));
				pending.push_back(int(clusters.size()) - 1);
			}
		}

		depth++;
	}

	if (pending.size())
	{
		assert(pending.size() == 1);
		const Cluster& cluster = clusters[pending[0]];

		clodBounds bounds = cluster.bounds;
		bounds.error = FLT_MAX; // terminal group, won't simplify further

		outputGroup(config, mesh, clusters, pending, bounds, depth, output_context, output_callback);
	}

	return clusters.size();
}

size_t clodLocalIndices(unsigned int* vertices, unsigned char* triangles, const unsigned int* indices, size_t index_count)
{
	return meshopt_extractMeshletIndices(vertices, triangles, indices, index_count);
}
#endif

/**
 * Copyright (c) 2016-2026 Arseny Kapoulkine
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */


================================================
FILE: demo/index.html
================================================
<!doctype html>
<html lang="en">
	<head>
		<title>meshoptimizer - demo</title>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
		<style>
			body {
				font-family: Monospace;
				background-color: #000;
				color: #fff;
				margin: 0px;
				overflow: hidden;
			}
			#info {
				color: #fff;
				position: absolute;
				top: 10px;
				width: 100%;
				text-align: center;
				z-index: 100;
				display: block;
			}
			#info a,
			.button {
				color: #f00;
				font-weight: bold;
				text-decoration: underline;
				cursor: pointer;
			}
		</style>

		<script async src="https://cdn.jsdelivr.net/npm/es-module-shims@2.0.10/dist/es-module-shims.min.js"></script>
		<script type="importmap">
			{
				"imports": {
					"three": "https://cdn.jsdelivr.net/npm/three@0.174.0/build/three.module.js",
					"three-examples/": "https://cdn.jsdelivr.net/npm/three@0.174.0/examples/jsm/"
				}
			}
		</script>
	</head>

	<body>
		<div id="info">
			<a href="https://github.com/zeux/meshoptimizer" target="_blank" rel="noopener">meshoptimizer</a>
		</div>

		<script type="module">
			import * as THREE from 'three';
			import { GLTFLoader } from 'three-examples/loaders/GLTFLoader.js';
			import { MeshoptDecoder } from '../js/meshopt_decoder.mjs';

			var container;

			var camera, scene, renderer, mixer, clock;

			var windowHalfX = window.innerWidth / 2;
			var windowHalfY = window.innerHeight / 2;

			init();
			animate();

			function init() {
				container = document.createElement('div');
				document.body.appendChild(container);

				camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 100);
				camera.position.y = 1.0;
				camera.position.z = 3.0;

				scene = new THREE.Scene();
				scene.background = new THREE.Color(0x300a24);

				var ambientLight = new THREE.AmbientLight(0xcccccc, 1);
				scene.add(ambientLight);

				var pointLight = new THREE.PointLight(0xffffff, 4);
				pointLight.position.set(3, 3, 0);
				pointLight.decay = 0.5;
				camera.add(pointLight);
				scene.add(camera);

				var onProgress = function (xhr) {};
				var onError = function (e) {
					console.log(e);
				};

				var loader = new GLTFLoader();
				loader.setMeshoptDecoder(MeshoptDecoder);
				loader.load(
					'pirate.glb',
					function (gltf) {
						var bbox = new THREE.Box3().setFromObject(gltf.scene);
						var scale = 2 / (bbox.max.y - bbox.min.y);

						gltf.scene.scale.set(scale, scale, scale);
						gltf.scene.position.set(0, 0, 0);

						scene.add(gltf.scene);

						mixer = new THREE.AnimationMixer(gltf.scene);

						if (gltf.animations.length) {
							mixer.clipAction(gltf.animations[gltf.animations.length - 1]).play();
						}
					},
					onProgress,
					onError
				);

				renderer = new THREE.WebGLRenderer();
				renderer.setPixelRatio(window.devicePixelRatio);
				renderer.setSize(window.innerWidth, window.innerHeight);
				container.appendChild(renderer.domElement);

				window.addEventListener('resize', onWindowResize, false);

				clock = new THREE.Clock();
			}

			function onWindowResize() {
				windowHalfX = window.innerWidth / 2;
				windowHalfY = window.innerHeight / 2;

				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

				renderer.setSize(window.innerWidth, window.innerHeight);
			}

			function animate() {
				if (mixer) {
					mixer.update(clock.getDelta());
				}

				requestAnimationFrame(animate);
				render();
			}

			function render() {
				renderer.render(scene, camera);
			}
		</script>
	</body>
</html>


================================================
FILE: demo/main.cpp
================================================
#include "../src/meshoptimizer.h"

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include <vector>

#include "../extern/fast_obj.h"

#define SDEFL_IMPLEMENTATION
#include "../extern/sdefl.h"

// This file uses assert() to verify algorithm correctness
#undef NDEBUG
#include <assert.h>

#if defined(__linux__)
double timestamp()
{
	timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return double(ts.tv_sec) + 1e-9 * double(ts.tv_nsec);
}
#elif defined(_WIN32)
struct LARGE_INTEGER
{
	__int64 QuadPart;
};
extern "C" __declspec(dllimport) int __stdcall QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount);
extern "C" __declspec(dllimport) int __stdcall QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency);

double timestamp()
{
	LARGE_INTEGER freq, counter;
	QueryPerformanceFrequency(&freq);
	QueryPerformanceCounter(&counter);
	return double(counter.QuadPart) / double(freq.QuadPart);
}
#else
double timestamp()
{
	return double(clock()) / double(CLOCKS_PER_SEC);
}
#endif

struct Vertex
{
	float px, py, pz;
	float nx, ny, nz;
	float tx, ty;
};

struct Mesh
{
	std::vector<Vertex> vertices;
	std::vector<unsigned int> indices;
};

union Triangle
{
	Vertex v[3];
	char data[sizeof(Vertex) * 3];
};

Mesh parseObj(const char* path, double& reindex)
{
	fastObjMesh* obj = fast_obj_read(path);
	if (!obj)
	{
		printf("Error loading %s: file not found\n", path);
		return Mesh();
	}

	size_t total_indices = 0;

	for (unsigned int i = 0; i < obj->face_count; ++i)
		if (obj->face_vertices[i] > 2)
			total_indices += 3 * (obj->face_vertices[i] - 2);

	std::vector<Vertex> vertices(total_indices);

	size_t vertex_offset = 0;
	size_t index_offset = 0;

	for (unsigned int i = 0; i < obj->face_count; ++i)
	{
		if (obj->face_vertices[i] <= 2)
			continue;

		for (unsigned int j = 0; j < obj->face_vertices[i]; ++j)
		{
			fastObjIndex gi = obj->indices[index_offset + j];

			Vertex v =
			    {
			        obj->positions[gi.p * 3 + 0],
			        obj->positions[gi.p * 3 + 1],
			        obj->positions[gi.p * 3 + 2],
			        obj->normals[gi.n * 3 + 0],
			        obj->normals[gi.n * 3 + 1],
			        obj->normals[gi.n * 3 + 2],
			        obj->texcoords[gi.t * 2 + 0],
			        obj->texcoords[gi.t * 2 + 1],
			    };

			// triangulate polygon on the fly; offset-3 is always the first polygon vertex
			if (j >= 3)
			{
				vertices[vertex_offset + 0] = vertices[vertex_offset - 3];
				vertices[vertex_offset + 1] = vertices[vertex_offset - 1];
				vertex_offset += 2;
			}

			vertices[vertex_offset] = v;
			vertex_offset++;
		}

		index_offset += obj->face_vertices[i];
	}

	fast_obj_destroy(obj);

	reindex = timestamp();

	Mesh result;

	// empty mesh
	if (total_indices == 0)
		return result;

	std::vector<unsigned int> remap(total_indices);

	size_t total_vertices = meshopt_generateVertexRemap(&remap[0], NULL, total_indices, &vertices[0], total_indices, sizeof(Vertex));

	result.indices.resize(total_indices);
	meshopt_remapIndexBuffer(&result.indices[0], NULL, total_indices, &remap[0]);

	result.vertices.resize(total_vertices);
	meshopt_remapVertexBuffer(&result.vertices[0], &vertices[0], total_indices, sizeof(Vertex), &remap[0]);

	return result;
}

void dumpObj(const std::vector<Vertex>& vertices, const std::vector<unsigned int>& indices, bool recomputeNormals = false)
{
	std::vector<float> normals;

	if (recomputeNormals)
	{
		normals.resize(vertices.size() * 3);

		for (size_t i = 0; i < indices.size(); i += 3)
		{
			unsigned int a = indices[i], b = indices[i + 1], c = indices[i + 2];

			const Vertex& va = vertices[a];
			const Vertex& vb = vertices[b];
			const Vertex& vc = vertices[c];

			float nx = (vb.py - va.py) * (vc.pz - va.pz) - (vb.pz - va.pz) * (vc.py - va.py);
			float ny = (vb.pz - va.pz) * (vc.px - va.px) - (vb.px - va.px) * (vc.pz - va.pz);
			float nz = (vb.px - va.px) * (vc.py - va.py) - (vb.py - va.py) * (vc.px - va.px);

			for (int k = 0; k < 3; ++k)
			{
				unsigned int index = indices[i + k];

				normals[index * 3 + 0] += nx;
				normals[index * 3 + 1] += ny;
				normals[index * 3 + 2] += nz;
			}
		}
	}

	for (size_t i = 0; i < vertices.size(); ++i)
	{
		const Vertex& v = vertices[i];

		float nx = v.nx, ny = v.ny, nz = v.nz;

		if (recomputeNormals)
		{
			nx = normals[i * 3 + 0];
			ny = normals[i * 3 + 1];
			nz = normals[i * 3 + 2];

			float l = sqrtf(nx * nx + ny * ny + nz * nz);
			float s = l == 0.f ? 0.f : 1.f / l;

			nx *= s;
			ny *= s;
			nz *= s;
		}

		fprintf(stderr, "v %f %f %f\n", v.px, v.py, v.pz);
		fprintf(stderr, "vn %f %f %f\n", nx, ny, nz);
	}

	for (size_t i = 0; i < indices.size(); i += 3)
	{
		unsigned int a = indices[i], b = indices[i + 1], c = indices[i + 2];

		fprintf(stderr, "f %d//%d %d//%d %d//%d\n", a + 1, a + 1, b + 1, b + 1, c + 1, c + 1);
	}
}

void dumpObj(const char* section, const std::vector<unsigned int>& indices)
{
	fprintf(stderr, "o %s\n", section);

	for (size_t j = 0; j < indices.size(); j += 3)
	{
		unsigned int a = indices[j], b = indices[j + 1], c = indices[j + 2];

		fprintf(stderr, "f %d//%d %d//%d %d//%d\n", a + 1, a + 1, b + 1, b + 1, c + 1, c + 1);
	}
}

struct PackedVertex
{
	unsigned short px, py, pz;
	unsigned short pw; // padding to 4b boundary
	signed char nx, ny, nz, nw;
	unsigned short tx, ty;
};

void packMesh(std::vector<PackedVertex>& pv, const std::vector<Vertex>& vertices)
{
	for (size_t i = 0; i < vertices.size(); ++i)
	{
		const Vertex& vi = vertices[i];
		PackedVertex& pvi = pv[i];

		pvi.px = meshopt_quantizeHalf(vi.px);
		pvi.py = meshopt_quantizeHalf(vi.py);
		pvi.pz = meshopt_quantizeHalf(vi.pz);
		pvi.pw = 0;

		pvi.nx = char(meshopt_quantizeSnorm(vi.nx, 8));
		pvi.ny = char(meshopt_quantizeSnorm(vi.ny, 8));
		pvi.nz = char(meshopt_quantizeSnorm(vi.nz, 8));
		pvi.nw = 0;

		pvi.tx = meshopt_quantizeHalf(vi.tx);
		pvi.ty = meshopt_quantizeHalf(vi.ty);
	}
}

struct PackedVertexOct
{
	unsigned short px, py, pz;
	signed char nu, nv; // octahedron encoded normal, aliases .pw
	unsigned short tx, ty;
};

void packMesh(std::vector<PackedVertexOct>& pv, const std::vector<Vertex>& vertices)
{
	for (size_t i = 0; i < vertices.size(); ++i)
	{
		const Vertex& vi = vertices[i];
		PackedVertexOct& pvi = pv[i];

		pvi.px = meshopt_quantizeHalf(vi.px);
		pvi.py = meshopt_quantizeHalf(vi.py);
		pvi.pz = meshopt_quantizeHalf(vi.pz);

		float nsum = fabsf(vi.nx) + fabsf(vi.ny) + fabsf(vi.nz);
		float nx = vi.nx / nsum;
		float ny = vi.ny / nsum;
		float nz = vi.nz;

		float nu = nz >= 0 ? nx : (1 - fabsf(ny)) * (nx >= 0 ? 1 : -1);
		float nv = nz >= 0 ? ny : (1 - fabsf(nx)) * (ny >= 0 ? 1 : -1);

		pvi.nu = char(meshopt_quantizeSnorm(nu, 8));
		pvi.nv = char(meshopt_quantizeSnorm(nv, 8));

		pvi.tx = meshopt_quantizeHalf(vi.tx);
		pvi.ty = meshopt_quantizeHalf(vi.ty);
	}
}

void simplify(const Mesh& mesh, float threshold = 0.2f, unsigned int options = 0)
{
	Mesh lod;

	double start = timestamp();

	size_t target_index_count = size_t(mesh.indices.size() * threshold);
	float target_error = 1e-2f;
	float result_error = 0;

	lod.indices.resize(mesh.indices.size()); // note: simplify needs space for index_count elements in the destination array, not target_index_count
	lod.indices.resize(meshopt_simplify(&lod.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count, target_error, options, &result_error));

	lod.vertices.resize(lod.indices.size() < mesh.vertices.size() ? lod.indices.size() : mesh.vertices.size()); // note: this is just to reduce the cost of resize()
	lod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex)));

	double end = timestamp();

	printf("%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec\n",
	    "Simplify",
	    int(mesh.indices.size() / 3), int(lod.indices.size() / 3),
	    result_error * 100,
	    (end - start) * 1000);
}

void simplifyAttr(const Mesh& mesh, float threshold = 0.2f, unsigned int options = 0)
{
	Mesh lod;

	double start = timestamp();

	size_t target_index_count = size_t(mesh.indices.size() * threshold);
	float target_error = 1e-2f;
	float result_error = 0;

	const float nrm_weight = 0.5f;
	const float attr_weights[3] = {nrm_weight, nrm_weight, nrm_weight};

	lod.indices.resize(mesh.indices.size()); // note: simplify needs space for index_count elements in the destination array, not target_index_count
	lod.indices.resize(meshopt_simplifyWithAttributes(&lod.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), &mesh.vertices[0].nx, sizeof(Vertex), attr_weights, 3, NULL, target_index_count, target_error, options, &result_error));

	lod.vertices.resize(lod.indices.size() < mesh.vertices.size() ? lod.indices.size() : mesh.vertices.size()); // note: this is just to reduce the cost of resize()
	lod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex)));

	double end = timestamp();

	printf("%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec\n",
	    "SimplifyAttr",
	    int(mesh.indices.size() / 3), int(lod.indices.size() / 3),
	    result_error * 100,
	    (end - start) * 1000);
}

void simplifyUpdate(const Mesh& mesh, float threshold = 0.2f, unsigned int options = 0)
{
	Mesh lod;

	double start = timestamp();

	size_t target_index_count = size_t(mesh.indices.size() * threshold);
	float target_error = 1e-2f;
	float result_error = 0;

	const float nrm_weight = 0.5f;
	const float attr_weights[3] = {nrm_weight, nrm_weight, nrm_weight};

	lod = mesh; // start from the original mesh
	lod.indices.resize(meshopt_simplifyWithUpdate(&lod.indices[0], mesh.indices.size(), &lod.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), &lod.vertices[0].nx, sizeof(Vertex), attr_weights, 3, NULL, target_index_count, target_error, options, &result_error));

	lod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex)));

	for (size_t i = 0; i < lod.vertices.size(); ++i)
	{
		// update normals
		Vertex& v = lod.vertices[i];
		float nl = sqrtf(v.nx * v.nx + v.ny * v.ny + v.nz * v.nz);
		if (nl > 0)
		{
			v.nx /= nl;
			v.ny /= nl;
			v.nz /= nl;
		}
	}

	double end = timestamp();

	printf("%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec\n",
	    "SimplifyUpdt",
	    int(mesh.indices.size() / 3), int(lod.indices.size() / 3),
	    result_error * 100,
	    (end - start) * 1000);
}

void simplifySloppy(const Mesh& mesh, float threshold = 0.2f)
{
	Mesh lod;

	double start = timestamp();

	size_t target_index_count = size_t(mesh.indices.size() * threshold);
	float target_error = 1e-1f;
	float result_error = 0;

	lod.indices.resize(mesh.indices.size()); // note: simplify needs space for index_count elements in the destination array, not target_index_count
	lod.indices.resize(meshopt_simplifySloppy(&lod.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count, target_error, &result_error));

	lod.vertices.resize(lod.indices.size() < mesh.vertices.size() ? lod.indices.size() : mesh.vertices.size()); // note: this is just to reduce the cost of resize()
	lod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex)));

	double end = timestamp();

	printf("%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec\n",
	    "SimplifyS",
	    int(mesh.indices.size() / 3), int(lod.indices.size() / 3),
	    result_error * 100,
	    (end - start) * 1000);
}

void simplifyPoints(const Mesh& mesh, float threshold = 0.2f)
{
	double start = timestamp();

	size_t target_vertex_count = size_t(mesh.vertices.size() * threshold);
	if (target_vertex_count == 0)
		return;

	std::vector<unsigned int> indices(target_vertex_count);
	indices.resize(meshopt_simplifyPoints(&indices[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), NULL, 0, 0, target_vertex_count));

	double end = timestamp();

	printf("%-9s: %d points => %d points in %.2f msec\n",
	    "SimplifyP",
	    int(mesh.vertices.size()), int(indices.size()), (end - start) * 1000);
}

void simplifyComplete(const Mesh& mesh)
{
	static const size_t lod_count = 5;

	double start = timestamp();

	// generate 4 LOD levels (1-4), with each subsequent LOD using 70% triangles
	// note that each LOD uses the same (shared) vertex buffer
	std::vector<unsigned int> lods[lod_count];

	lods[0] = mesh.indices;

	for (size_t i = 1; i < lod_count; ++i)
	{
		std::vector<unsigned int>& lod = lods[i];

		float threshold = powf(0.7f, float(i));
		size_t target_index_count = size_t(mesh.indices.size() * threshold) / 3 * 3;
		float target_error = 1e-2f;

		// we can simplify all the way from base level or from the last result
		// simplifying from the base level sometimes produces better results, but simplifying from last level is faster
		const std::vector<unsigned int>& source = lods[i - 1];

		if (source.size() < target_index_count)
			target_index_count = source.size();

		lod.resize(source.size());
		lod.resize(meshopt_simplify(&lod[0], &source[0], source.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count, target_error));
	}

	double middle = timestamp();

	// optimize each individual LOD for vertex cache & overdraw
	for (size_t i = 0; i < lod_count; ++i)
	{
		std::vector<unsigned int>& lod = lods[i];

		meshopt_optimizeVertexCache(&lod[0], &lod[0], lod.size(), mesh.vertices.size());
		meshopt_optimizeOverdraw(&lod[0], &lod[0], lod.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), 1.0f);
	}

	// concatenate all LODs into one IB
	// note: the order of concatenation is important - since we optimize the entire IB for vertex fetch,
	// putting coarse LODs first makes sure that the vertex range referenced by them is as small as possible
	// some GPUs process the entire range referenced by the index buffer region so doing this optimizes the vertex transform
	// cost for coarse LODs
	// this order also produces much better vertex fetch cache coherency for coarse LODs (since they're essentially optimized first)
	// somewhat surprisingly, the vertex fetch cache coherency for fine LODs doesn't seem to suffer that much.
	size_t lod_index_offsets[lod_count] = {};
	size_t lod_index_counts[lod_count] = {};
	size_t total_index_count = 0;

	for (int i = lod_count - 1; i >= 0; --i)
	{
		lod_index_offsets[i] = total_index_count;
		lod_index_counts[i] = lods[i].size();

		total_index_count += lods[i].size();
	}

	std::vector<unsigned int> indices(total_index_count);

	for (size_t i = 0; i < lod_count; ++i)
	{
		memcpy(&indices[lod_index_offsets[i]], &lods[i][0], lods[i].size() * sizeof(lods[i][0]));
	}

	std::vector<Vertex> vertices = mesh.vertices;

	// vertex fetch optimization should go last as it depends on the final index order
	// note that the order of LODs above affects vertex fetch results
	meshopt_optimizeVertexFetch(&vertices[0], &indices[0], indices.size(), &vertices[0], vertices.size(), sizeof(Vertex));

	double end = timestamp();

	printf("%-9s: %d triangles => %d LOD levels down to %d triangles in %.2f msec, optimized in %.2f msec\n",
	    "SimplifyC",
	    int(lod_index_counts[0]) / 3, int(lod_count), int(lod_index_counts[lod_count - 1]) / 3,
	    (middle - start) * 1000, (end - middle) * 1000);

	// for using LOD data at runtime, in addition to vertices and indices you have to save lod_index_offsets/lod_index_counts.

	{
		meshopt_VertexCacheStatistics vcs0 = meshopt_analyzeVertexCache(&indices[lod_index_offsets[0]], lod_index_counts[0], vertices.size(), 16, 0, 0);
		meshopt_VertexFetchStatistics vfs0 = meshopt_analyzeVertexFetch(&indices[lod_index_offsets[0]], lod_index_counts[0], vertices.size(), sizeof(Vertex));
		meshopt_VertexCacheStatistics vcsN = meshopt_analyzeVertexCache(&indices[lod_index_offsets[lod_count - 1]], lod_index_counts[lod_count - 1], vertices.size(), 16, 0, 0);
		meshopt_VertexFetchStatistics vfsN = meshopt_analyzeVertexFetch(&indices[lod_index_offsets[lod_count - 1]], lod_index_counts[lod_count - 1], vertices.size(), sizeof(Vertex));

		typedef PackedVertexOct PV;

		std::vector<PV> pv(vertices.size());
		packMesh(pv, vertices);

		std::vector<unsigned char> vbuf(meshopt_encodeVertexBufferBound(vertices.size(), sizeof(PV)));
		vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &pv[0], vertices.size(), sizeof(PV)));

		std::vector<unsigned char> ibuf(meshopt_encodeIndexBufferBound(indices.size(), vertices.size()));
		ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), &indices[0], indices.size()));

		printf("%-9s  ACMR %f...%f Overfetch %f..%f Codec VB %.1f bits/vertex IB %.1f bits/triangle\n",
		    "",
		    vcs0.acmr, vcsN.acmr, vfs0.overfetch, vfsN.overfetch,
		    double(vbuf.size()) / double(vertices.size()) * 8,
		    double(ibuf.size()) / double(indices.size() / 3) * 8);
	}
}

void simplifyClusters(const Mesh& mesh, float threshold = 0.2f)
{
	const size_t max_vertices = 64;
	const size_t max_triangles = 64;
	const size_t target_group_size = 8;

	double start = timestamp();

	// build clusters (meshlets) out of the mesh
	size_t max_meshlets = meshopt_buildMeshletsBound(mesh.indices.size(), max_vertices, max_triangles);
	std::vector<meshopt_Meshlet> meshlets(max_meshlets);
	std::vector<unsigned int> meshlet_vertices(mesh.indices.size());
	std::vector<unsigned char> meshlet_triangles(mesh.indices.size());

	meshlets.resize(meshopt_buildMeshlets(&meshlets[0], &meshlet_vertices[0], &meshlet_triangles[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), max_vertices, max_triangles, 0.f));

	double middle = timestamp();

	// generate position remap; we'll use that to partition clusters using position-only adjacency
	std::vector<unsigned int> remap(mesh.vertices.size());
	meshopt_generatePositionRemap(&remap[0], &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex));

	// partition clusters in groups; each group will be simplified separately and the boundaries between groups will be preserved
	std::vector<unsigned int> cluster_indices;
	cluster_indices.reserve(mesh.indices.size()); // slight underestimate, vector should realloc once
	std::vector<unsigned int> cluster_sizes(meshlets.size());

	for (size_t i = 0; i < meshlets.size(); ++i)
	{
		const meshopt_Meshlet& m = meshlets[i];

		for (size_t j = 0; j < m.triangle_count * 3; ++j)
		{
			unsigned int v = meshlet_vertices[m.vertex_offset + meshlet_triangles[m.triangle_offset + j]];

			// use the first vertex with equivalent position so that cluster adjacency ignores attribute seams
			cluster_indices.push_back(remap[v]);
		}

		cluster_sizes[i] = m.triangle_count * 3;
	}

	std::vector<unsigned int> partition(meshlets.size());
	size_t partition_count = meshopt_partitionClusters(&partition[0], &cluster_indices[0], cluster_indices.size(), &cluster_sizes[0], cluster_sizes.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_group_size);

	// convert partitions to linked lists to make it easier to iterate over (vectors of vectors would work too)
	std::vector<int> partnext(meshlets.size(), -1);
	std::vector<int> partlast(partition_count, -1);

	for (size_t i = 0; i < meshlets.size(); ++i)
	{
		unsigned int part = partition[i];

		if (partlast[part] >= 0)
			partnext[partlast[part]] = int(i);

		partlast[part] = int(i);
		partnext[i] = -1;
	}

	double parttime = timestamp();

	float scale = meshopt_simplifyScale(&mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex));

	std::vector<unsigned int> lod;
	lod.reserve(mesh.indices.size());

	float error = 0.f;

	for (size_t i = 0; i < meshlets.size(); ++i)
	{
		if (partlast[partition[i]] < 0)
			continue; // part of a group that was already processed

		// mark group as processed
		partlast[partition[i]] = -1;

		size_t group_offset = lod.size();

		for (int j = int(i); j >= 0; j = partnext[j])
		{
			const meshopt_Meshlet& m = meshlets[j];

			for (size_t k = 0; k < m.triangle_count * 3; ++k)
				lod.push_back(meshlet_vertices[m.vertex_offset + meshlet_triangles[m.triangle_offset + k]]);
		}

		size_t group_triangles = (lod.size() - group_offset) / 3;

		// simplify the group, preserving the border vertices
		// note: this technically also locks the exterior border; a full mesh analysis (see clusterlod.h / lockBoundary) would work better for some meshes
		unsigned int options = meshopt_SimplifyLockBorder | meshopt_SimplifySparse | meshopt_SimplifyErrorAbsolute;

		float group_target_error = 1e-2f * scale;
		size_t group_target = size_t(float(group_triangles) * threshold) * 3;
		float group_error = 0.f;
		size_t group_size = meshopt_simplify(&lod[group_offset], &lod[group_offset], group_triangles * 3, &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), group_target, group_target_error, options, &group_error);

		error = group_error > error ? group_error : error;

		// simplified group is available in lod[group_offset..group_offset + group_size]
		lod.resize(group_offset + group_size);
	}

	double end = timestamp();

	printf("%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec, clusterized in %.2f msec, partitioned in %.2f msec (%d clusters in %d groups, %.1f avg)\n",
	    "SimplifyG",
	    int(mesh.indices.size() / 3), int(lod.size() / 3),
	    error / scale * 100,
	    (end - parttime) * 1000, (middle - start) * 1000, (parttime - middle) * 1000,
	    int(meshlets.size()), int(partition_count), double(meshlets.size()) / double(partition_count));
}

void optimize(const Mesh& mesh, bool fifo = false)
{
	Mesh copy = mesh;
	// note: we assume that the mesh is already optimally indexed (via parseObj); if that is not the case, you'd need to reindex first

	double start = timestamp();

	// vertex cache optimization should go first as it provides starting order for overdraw
	// note: fifo optimization is not recommended as a default, since it produces worse results, but it's faster to run so it can be useful for procedural meshes
	if (fifo)
		meshopt_optimizeVertexCacheFifo(&copy.indices[0], &copy.indices[0], copy.indices.size(), copy.vertices.size(), /* cache_size= */ 16);
	else
		meshopt_optimizeVertexCache(&copy.indices[0], &copy.indices[0], copy.indices.size(), copy.vertices.size());

	// reorder indices for overdraw, balancing overdraw and vertex cache efficiency
	const float kThreshold = 1.01f; // allow up to 1% worse ACMR to get more reordering opportunities for overdraw
	meshopt_optimizeOverdraw(&copy.indices[0], &copy.indices[0], copy.indices.size(), &copy.vertices[0].px, copy.vertices.size(), sizeof(Vertex), kThreshold);

	// vertex fetch optimization should go last as it depends on the final index order
	meshopt_optimizeVertexFetch(&copy.vertices[0], &copy.indices[0], copy.indices.size(), &copy.vertices[0], copy.vertices.size(), sizeof(Vertex));

	double end = timestamp();

	meshopt_VertexCacheStatistics vcs = meshopt_analyzeVertexCache(&copy.indices[0], copy.indices.size(), copy.vertices.size(), 16, 0, 0);
	meshopt_VertexFetchStatistics vfs = meshopt_analyzeVertexFetch(&copy.indices[0], copy.indices.size(), copy.vertices.size(), sizeof(Vertex));
	meshopt_OverdrawStatistics os = meshopt_analyzeOverdraw(&copy.indices[0], copy.indices.size(), &copy.vertices[0].px, copy.vertices.size(), sizeof(Vertex));

	meshopt_VertexCacheStatistics vcs_nv = meshopt_analyzeVertexCache(&copy.indices[0], copy.indices.size(), copy.vertices.size(), 32, 32, 32);
	meshopt_VertexCacheStatistics vcs_amd = meshopt_analyzeVertexCache(&copy.indices[0], copy.indices.size(), copy.vertices.size(), 14, 64, 128);
	meshopt_VertexCacheStatistics vcs_intel = meshopt_analyzeVertexCache(&copy.indices[0], copy.indices.size(), copy.vertices.size(), 128, 0, 0);

	printf("Optimize%s: ACMR %f ATVR %f (NV %f AMD %f Intel %f) overfetch %f overdraw %f in %.2f msec\n",
	    fifo ? "F" : " ",
	    vcs.acmr, vcs.atvr, vcs_nv.atvr, vcs_amd.atvr, vcs_intel.atvr, vfs.overfetch, os.overdraw, (end - start) * 1000);
}

template <typename T>
size_t compress(const std::vector<T>& data, int level = SDEFL_LVL_DEF)
{
	std::vector<unsigned char> cbuf(sdefl_bound(int(data.size() * sizeof(T))));
	sdefl s = {};
	return sdeflate(&s, &cbuf[0], reinterpret_cast<const unsigned char*>(&data[0]), int(data.size() * sizeof(T)), level);
}

void encodeIndex(const std::vector<unsigned int>& indices, size_t vertex_count, char desc)
{
	// allocate result outside of the timing loop to exclude memset() from decode timing
	std::vector<unsigned int> result(indices.size());

	double start = timestamp();

	std::vector<unsigned char> buffer(meshopt_encodeIndexBufferBound(indices.size(), vertex_count));
	buffer.resize(meshopt_encodeIndexBuffer(&buffer[0], buffer.size(), &indices[0], indices.size()));

	double middle = timestamp();

	int res = meshopt_decodeIndexBuffer(&result[0], indices.size(), &buffer[0], buffer.size());
	assert(res == 0);
	(void)res;

	double end = timestamp();

	size_t csize = compress(buffer);

	for (size_t i = 0; i < indices.size(); i += 3)
	{
		assert(
		    (result[i + 0] == indices[i + 0] && result[i + 1] == indices[i + 1] && result[i + 2] == indices[i + 2]) ||
		    (result[i + 1] == indices[i + 0] && result[i + 2] == indices[i + 1] && result[i + 0] == indices[i + 2]) ||
		    (result[i + 2] == indices[i + 0] && result[i + 0] == indices[i + 1] && result[i + 1] == indices[i + 2]));
	}

	printf("IdxCodec%c: %.1f bits/triangle (post-deflate %.1f bits/triangle); encode %.2f msec (%.3f GB/s), decode %.2f msec (%.2f GB/s)\n",
	    desc,
	    double(buffer.size() * 8) / double(indices.size() / 3),
	    double(csize * 8) / double(indices.size() / 3),
	    (middle - start) * 1000,
	    (double(result.size() * 4) / 1e9) / (middle - start),
	    (end - middle) * 1000,
	    (double(result.size() * 4) / 1e9) / (end - middle));
}

void encodeIndex(const Mesh& mesh, char desc)
{
	encodeIndex(mesh.indices, mesh.vertices.size(), desc);
}

void encodeIndexSequence(const std::vector<unsigned int>& data, size_t vertex_count, char desc)
{
	// allocate result outside of the timing loop to exclude memset() from decode timing
	std::vector<unsigned int> result(data.size());

	double start = timestamp();

	std::vector<unsigned char> buffer(meshopt_encodeIndexSequenceBound(data.size(), vertex_count));
	buffer.resize(meshopt_encodeIndexSequence(&buffer[0], buffer.size(), &data[0], data.size()));

	double middle = timestamp();

	int res = meshopt_decodeIndexSequence(&result[0], data.size(), &buffer[0], buffer.size());
	assert(res == 0);
	(void)res;

	double end = timestamp();

	size_t csize = compress(buffer);

	assert(memcmp(&data[0], &result[0], data.size() * sizeof(unsigned int)) == 0);

	printf("IdxCodec%c: %.1f bits/index (post-deflate %.1f bits/index); encode %.2f msec (%.3f GB/s), decode %.2f msec (%.2f GB/s)\n",
	    desc,
	    double(buffer.size() * 8) / double(data.size()),
	    double(csize * 8) / double(data.size()),
	    (middle - start) * 1000,
	    (double(result.size() * 4) / 1e9) / (middle - start),
	    (end - middle) * 1000,
	    (double(result.size() * 4) / 1e9) / (end - middle));
}

template <typename V, typename T>
static void validateDecodeMeshlet(const unsigned char* data, size_t size, const unsigned int* vertices, size_t vertex_count, const unsigned char* triangles, size_t triangle_count)
{
	V rv[256];
	T rt[sizeof(T) == 1 ? 256 * 3 : 256];

	int rc = meshopt_decodeMeshlet(rv, vertex_count, rt, triangle_count, data, size);
	assert(rc == 0);

	for (size_t j = 0; j < vertex_count; ++j)
		assert(rv[j] == V(vertices[j]));

	for (size_t j = 0; j < triangle_count; ++j)
	{
		unsigned int a = triangles[j * 3 + 0];
		unsigned int b = triangles[j * 3 + 1];
		unsigned int c = triangles[j * 3 + 2];

		unsigned int tri = sizeof(T) == 1 ? rt[j * 3] | (rt[j * 3 + 1] << 8) | (rt[j * 3 + 2] << 16) : rt[j];

		unsigned int abc = (a << 0) | (b << 8) | (c << 16);
		unsigned int bca = (b << 0) | (c << 8) | (a << 16);
		unsigned int cba = (c << 0) | (a << 8) | (b << 16);

		assert(tri == abc || tri == bca || tri == cba);
	}
}

void encodeMeshlets(const Mesh& mesh, size_t max_vertices, size_t max_triangles, bool reorder = true)
{
	size_t max_meshlets = meshopt_buildMeshletsBound(mesh.indices.size(), max_vertices, max_triangles);
	std::vector<meshopt_Meshlet> meshlets(max_meshlets);
	std::vector<unsigned int> meshlet_vertices(mesh.indices.size());
	std::vector<unsigned char> meshlet_triangles(mesh.indices.size());

	meshlets.resize(meshopt_buildMeshlets(&meshlets[0], &meshlet_vertices[0], &meshlet_triangles[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), max_vertices, max_triangles, 0.f));

	if (meshlets.size())
	{
		const meshopt_Meshlet& last = meshlets.back();

		// this is an example of how to trim the vertex/triangle arrays when copying data out to GPU storage
		meshlet_vertices.resize(last.vertex_offset + last.vertex_count);
		meshlet_triangles.resize(last.triangle_offset + last.triangle_count * 3);

		// TODO: over-allocate meshlet_vertices to multiple of 3 to make meshopt_optimizeVertexFetch below work without assertions
		meshlet_vertices.resize((meshlet_vertices.size() + 2) / 3 * 3);
	}

	std::vector<unsigned char> cbuf(meshopt_encodeMeshletBound(max_vertices, max_triangles));

	// optimize each meshlet for locality; this is important for performance, and critical for good compression
	for (size_t i = 0; i < meshlets.size(); ++i)
		meshopt_optimizeMeshlet(&meshlet_vertices[meshlets[i].vertex_offset], &meshlet_triangles[meshlets[i].triangle_offset], meshlets[i].triangle_count, meshlets[i].vertex_count);

	// optimize the order of vertex references within each meshlet and globally; this is valuable for access locality and critical for compression of vertex references
	// note that this reorders the vertex buffer too, so if a traditional index buffer is required it would need to be reconstructed from the meshlet data for optimal locality
	std::vector<Vertex> vertices = mesh.vertices;
	if (reorder)
		meshopt_optimizeVertexFetch(&vertices[0], &meshlet_vertices[0], meshlet_vertices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex));

	size_t mbst = 0;

	std::vector<unsigned char> packed;

	for (size_t i = 0; i < meshlets.size(); ++i)
	{
		const meshopt_Meshlet& meshlet = meshlets[i];

		size_t mbs = meshopt_encodeMeshlet(&cbuf[0], cbuf.size(), &meshlet_vertices[meshlet.vertex_offset], meshlet.vertex_count, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count);
		assert(mbs > 0);

		// 24-bit header: 7 bit (vertex_count-1), 7 bit (triangle_count-1), 10 bit size
		// fits up to 128v/128t meshlet with 1024 bytes of encoded data; meshopt_encodeMeshletBound(128,128) < 1000
		assert(size_t(meshlet.vertex_count - 1) < 128 && size_t(meshlet.triangle_count - 1) < 128 && mbs < 1024);
		unsigned int header = ((meshlet.vertex_count - 1) & 0x7f) | (((meshlet.triangle_count - 1) & 0x7f) << 7) | ((unsigned(mbs) & 0x3ff) << 14);
		packed.push_back((unsigned char)(header & 0xff));
		packed.push_back((unsigned char)((header >> 8) & 0xff));
		packed.push_back((unsigned char)((header >> 16) & 0xff));
		packed.insert(packed.end(), &cbuf[0], &cbuf[mbs]);

		validateDecodeMeshlet<unsigned int, unsigned int>(&cbuf[0], mbs, &meshlet_vertices[meshlet.vertex_offset], meshlet.vertex_count, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count);
		validateDecodeMeshlet<unsigned int, unsigned char>(&cbuf[0], mbs, &meshlet_vertices[meshlet.vertex_offset], meshlet.vertex_count, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count);
		validateDecodeMeshlet<unsigned short, unsigned int>(&cbuf[0], mbs, &meshlet_vertices[meshlet.vertex_offset], meshlet.vertex_count, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count);
		validateDecodeMeshlet<unsigned short, unsigned char>(&cbuf[0], mbs, &meshlet_vertices[meshlet.vertex_offset], meshlet.vertex_count, &meshlet_triangles[meshlet.triangle_offset], meshlet.triangle_count);

		mbst += mbs;
	}

	size_t mbc = compress(packed);

	printf("MeshletCodec (%d/%d): %d meshlets, %d bytes/meshlet; %d bytes, %.1f bits/triangle\n",
	    int(max_vertices), int(max_triangles),
	    int(meshlets.size()),
	    int(mbst / meshlets.size()),
	    int(mbst), double(mbst * 8) / double(mesh.indices.size() / 3));
	printf("MeshletCodec (%d/%d, packed): %d bytes/meshlet, %.1f bits/triangle; post-deflate: %d bytes/meshlet, %.1f bits/triangle)\n",
	    int(max_vertices), int(max_triangles),
	    int(packed.size() / meshlets.size()), double(packed.size() * 8) / double(mesh.indices.size() / 3),
	    int(mbc / meshlets.size()), double(mbc * 8) / double(mesh.indices.size() / 3));

#if !TRACE
	double mbtime = 0;

	for (int i = 0; i < 10; ++i)
	{
		unsigned int rv[256];
		unsigned int rt[256];
		double t0 = timestamp();
		unsigned char* p = &packed[0];
		for (size_t j = 0; j < meshlets.size(); ++j)
		{
			unsigned int header = p[0] | (p[1] << 8) | (p[2] << 16);
			size_t vertex_count = (header & 0x7f) + 1;
			size_t triangle_count = ((header >> 7) & 0x7f) + 1;
			size_t size = (header >> 14) & 0x3ff;
			meshopt_decodeMeshletRaw(rv, vertex_count, rt, triangle_count, p + 3, size);
			p += 3 + size;
		}
		double t1 = timestamp();

		mbtime = (mbtime == 0 || t1 - t0 < mbtime) ? (t1 - t0) : mbtime;
	}

	printf("MeshletCodec (%d/%d, packed): decode time %.3f msec, %.3fB tri/sec, %.1f ns/meshlet\n",
	    int(max_vertices), int(max_triangles),
	    mbtime * 1000, double(mesh.indices.size() / 3) / 1e9 / mbtime, mbtime * 1e9 / double(meshlets.size()));
#endif
}

template <typename PV>
void packVertex(const Mesh& mesh, const char* pvn)
{
	std::vector<PV> pv(mesh.vertices.size());
	packMesh(pv, mesh.vertices);

	size_t csize = compress(pv);

	printf("VtxPack%s  : %.1f bits/vertex (post-deflate %.1f bits/vertex)\n", pvn,
	    double(pv.size() * sizeof(PV) * 8) / double(mesh.vertices.size()),
	    double(csize * 8) / double(mesh.vertices.size()));
}

template <typename PV>
void encodeVertex(const Mesh& mesh, const char* pvn, int level = 2)
{
	std::vector<PV> pv(mesh.vertices.size());
	packMesh(pv, mesh.vertices);

	// allocate result outside of the timing loop to exclude memset() from decode timing
	std::vector<PV> result(mesh.vertices.size());

	double start = timestamp();

	std::vector<unsigned char> vbuf(meshopt_encodeVertexBufferBound(mesh.vertices.size(), sizeof(PV)));
	vbuf.resize(meshopt_encodeVertexBufferLevel(&vbuf[0], vbuf.size(), &pv[0], mesh.vertices.size(), sizeof(PV), level, -1));

	double middle = timestamp();

	int res = meshopt_decodeVertexBuffer(&result[0], mesh.vertices.size(), sizeof(PV), &vbuf[0], vbuf.size());
	assert(res == 0);
	(void)res;

	double end = timestamp();

	assert(memcmp(&pv[0], &result[0], pv.size() * sizeof(PV)) == 0);

	size_t csize = compress(vbuf);

	printf("VtxCodec%1s: %.1f bits/vertex (post-deflate %.1f bits/vertex); encode %.2f msec (%.3f GB/s), decode %.2f msec (%.2f GB/s)\n", pvn,
	    double(vbuf.size() * 8) / double(mesh.vertices.size()),
	    double(csize * 8) / double(mesh.vertices.size()),
	    (middle - start) * 1000,
	    (double(result.size() * sizeof(PV)) / 1e9) / (middle - start),
	    (end - middle) * 1000,
	    (double(result.size() * sizeof(PV)) / 1e9) / (end - middle));
}

void stripify(const Mesh& mesh, bool use_restart, char desc)
{
	unsigned int restart_index = use_restart ? ~0u : 0;

	// note: input mesh is assumed to be optimized for vertex cache and vertex fetch
	double start = timestamp();
	std::vector<unsigned int> strip(meshopt_stripifyBound(mesh.indices.size()));
	strip.resize(meshopt_stripify(&strip[0], &mesh.indices[0], mesh.indices.size(), mesh.vertices.size(), restart_index));
	double end = timestamp();

	size_t restarts = 0;
	for (size_t i = 0; i < strip.size(); ++i)
		restarts += use_restart && strip[i] == restart_index;

	Mesh copy = mesh;
	copy.indices.resize(meshopt_unstripify(&copy.indices[0], &strip[0], strip.size(), restart_index));
	assert(copy.indices.size() <= meshopt_unstripifyBound(strip.size()));

	meshopt_VertexCacheStatistics vcs = meshopt_analyzeVertexCache(&copy.indices[0], mesh.indices.size(), mesh.vertices.size(), 16, 0, 0);
	meshopt_VertexCacheStatistics vcs_nv = meshopt_analyzeVertexCache(&copy.indices[0], mesh.indices.size(), mesh.vertices.size(), 32, 32, 32);
	meshopt_VertexCacheStatistics vcs_amd = meshopt_analyzeVertexCache(&copy.indices[0], mesh.indices.size(), mesh.vertices.size(), 14, 64, 128);
	meshopt_VertexCacheStatistics vcs_intel = meshopt_analyzeVertexCache(&copy.indices[0], mesh.indices.size(), mesh.vertices.size(), 128, 0, 0);

	printf("Stripify%c: ACMR %f ATVR %f (NV %f AMD %f Intel %f); %.1f run avg, %d strip indices (%.1f%%) in %.2f msec\n",
	    desc,
	    vcs.acmr, vcs.atvr, vcs_nv.atvr, vcs_amd.atvr, vcs_intel.atvr,
	    use_restart ? double(strip.size() - restarts) / double(restarts + 1) : 0,
	    int(strip.size()), double(strip.size()) / double(mesh.indices.size()) * 100,
	    (end - start) * 1000);
}

void shadow(const Mesh& mesh)
{
	// note: input mesh is assumed to be optimized for vertex cache and vertex fetch

	double start = timestamp();
	// this index buffer can be used for position-only rendering using the same vertex data that the original index buffer uses
	std::vector<unsigned int> shadow_indices(mesh.indices.size());
	meshopt_generateShadowIndexBuffer(&shadow_indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(float) * 3, sizeof(Vertex));
	double end = timestamp();

	// while you can't optimize the vertex data after shadow IB was constructed, you can and should optimize the shadow IB for vertex cache
	// this is valuable even if the original indices array was optimized for vertex cache!
	meshopt_optimizeVertexCache(&shadow_indices[0], &shadow_indices[0], shadow_indices.size(), mesh.vertices.size(
Download .txt
gitextract_gsw93301/

├── .clang-format
├── .editorconfig
├── .git-blame-ignore-revs
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   └── feature_request.md
│   ├── codecov.yml
│   └── workflows/
│       ├── build.yml
│       ├── cifuzz.yml
│       └── release.yml
├── .gitignore
├── .prettierrc
├── CMakeLists.txt
├── CONTRIBUTING.md
├── LICENSE.md
├── Makefile
├── README.md
├── demo/
│   ├── ansi.c
│   ├── clusterlod.h
│   ├── index.html
│   ├── main.cpp
│   ├── meshletdec.slang
│   ├── nanite.cpp
│   ├── pirate.glb
│   ├── pirate.obj
│   ├── simplify.html
│   └── tests.cpp
├── extern/
│   ├── .clang-format
│   ├── cgltf.h
│   ├── fast_obj.h
│   └── sdefl.h
├── gltf/
│   ├── README.md
│   ├── animation.cpp
│   ├── cli.js
│   ├── encodebasis.cpp
│   ├── encodewebp.cpp
│   ├── fileio.cpp
│   ├── fuzz.dict
│   ├── fuzz.glb
│   ├── gltfpack.cpp
│   ├── gltfpack.h
│   ├── gltfpack.manifest
│   ├── image.cpp
│   ├── json.cpp
│   ├── library.js
│   ├── material.cpp
│   ├── mesh.cpp
│   ├── node.cpp
│   ├── package.json
│   ├── parsegltf.cpp
│   ├── parselib.cpp
│   ├── parseobj.cpp
│   ├── stream.cpp
│   ├── wasistubs.cpp
│   ├── wasistubs.txt
│   └── write.cpp
├── js/
│   ├── README.md
│   ├── benchmark.js
│   ├── index.d.ts
│   ├── index.js
│   ├── meshopt_clusterizer.d.ts
│   ├── meshopt_clusterizer.js
│   ├── meshopt_clusterizer.test.js
│   ├── meshopt_decoder.cjs
│   ├── meshopt_decoder.d.ts
│   ├── meshopt_decoder.mjs
│   ├── meshopt_decoder.test.js
│   ├── meshopt_decoder_reference.js
│   ├── meshopt_encoder.d.ts
│   ├── meshopt_encoder.js
│   ├── meshopt_encoder.test.js
│   ├── meshopt_simplifier.d.ts
│   ├── meshopt_simplifier.js
│   ├── meshopt_simplifier.test.js
│   ├── package.json
│   └── wasi_trace.js
├── src/
│   ├── allocator.cpp
│   ├── clusterizer.cpp
│   ├── indexanalyzer.cpp
│   ├── indexcodec.cpp
│   ├── indexgenerator.cpp
│   ├── meshletcodec.cpp
│   ├── meshletutils.cpp
│   ├── meshoptimizer.h
│   ├── opacitymap.cpp
│   ├── overdrawoptimizer.cpp
│   ├── partition.cpp
│   ├── quantization.cpp
│   ├── rasterizer.cpp
│   ├── simplifier.cpp
│   ├── spatialorder.cpp
│   ├── stripifier.cpp
│   ├── vcacheoptimizer.cpp
│   ├── vertexcodec.cpp
│   ├── vertexfilter.cpp
│   └── vfetchoptimizer.cpp
└── tools/
    ├── bitmask.py
    ├── clusterfuzz.cpp
    ├── codecbench.cpp
    ├── codecfuzz.cpp
    ├── codectest.cpp
    ├── gltfbasis.py
    ├── objloader.cpp
    ├── simplifyfuzz.cpp
    ├── vcachetester.cpp
    ├── vcachetuner.cpp
    ├── wasmpack.py
    └── wasmstubs.cpp
Download .txt
SYMBOL INDEX (1303 symbols across 65 files)

FILE: demo/clusterlod.h
  type clodConfig (line 15) | struct clodConfig
  type clodMesh (line 65) | struct clodMesh
  type clodBounds (line 97) | struct clodBounds
  type clodCluster (line 107) | struct clodCluster
  type clodGroup (line 123) | struct clodGroup
  type Call (line 163) | struct Call
  function namespace (line 185) | namespace clod
  function clodConfig (line 505) | clodConfig clodDefaultConfig(size_t max_triangles)
  function clodConfig (line 537) | clodConfig clodDefaultConfigRT(size_t max_triangles)

FILE: demo/main.cpp
  function timestamp (line 21) | double timestamp()
  type LARGE_INTEGER (line 28) | struct LARGE_INTEGER
  function timestamp (line 35) | double timestamp()
  function timestamp (line 43) | double timestamp()
  type Vertex (line 49) | struct Vertex
  type Mesh (line 56) | struct Mesh
  function Mesh (line 68) | Mesh parseObj(const char* path, double& reindex)
  function dumpObj (line 147) | void dumpObj(const std::vector<Vertex>& vertices, const std::vector<unsi...
  function dumpObj (line 210) | void dumpObj(const char* section, const std::vector<unsigned int>& indices)
  type PackedVertex (line 222) | struct PackedVertex
  function packMesh (line 230) | void packMesh(std::vector<PackedVertex>& pv, const std::vector<Vertex>& ...
  type PackedVertexOct (line 252) | struct PackedVertexOct
  function packMesh (line 259) | void packMesh(std::vector<PackedVertexOct>& pv, const std::vector<Vertex...
  function simplify (line 286) | void simplify(const Mesh& mesh, float threshold = 0.2f, unsigned int opt...
  function simplifyAttr (line 311) | void simplifyAttr(const Mesh& mesh, float threshold = 0.2f, unsigned int...
  function simplifyUpdate (line 339) | void simplifyUpdate(const Mesh& mesh, float threshold = 0.2f, unsigned i...
  function simplifySloppy (line 379) | void simplifySloppy(const Mesh& mesh, float threshold = 0.2f)
  function simplifyPoints (line 404) | void simplifyPoints(const Mesh& mesh, float threshold = 0.2f)
  function simplifyComplete (line 422) | void simplifyComplete(const Mesh& mesh)
  function simplifyClusters (line 530) | void simplifyClusters(const Mesh& mesh, float threshold = 0.2f)
  function optimize (line 644) | void optimize(const Mesh& mesh, bool fifo = false)
  function compress (line 681) | size_t compress(const std::vector<T>& data, int level = SDEFL_LVL_DEF)
  function encodeIndex (line 688) | void encodeIndex(const std::vector<unsigned int>& indices, size_t vertex...
  function encodeIndex (line 726) | void encodeIndex(const Mesh& mesh, char desc)
  function encodeIndexSequence (line 731) | void encodeIndexSequence(const std::vector<unsigned int>& data, size_t v...
  function validateDecodeMeshlet (line 764) | static void validateDecodeMeshlet(const unsigned char* data, size_t size...
  function encodeMeshlets (line 791) | void encodeMeshlets(const Mesh& mesh, size_t max_vertices, size_t max_tr...
  function packVertex (line 894) | void packVertex(const Mesh& mesh, const char* pvn)
  function encodeVertex (line 907) | void encodeVertex(const Mesh& mesh, const char* pvn, int level = 2)
  function stripify (line 941) | void stripify(const Mesh& mesh, bool use_restart, char desc)
  function shadow (line 972) | void shadow(const Mesh& mesh)
  function follow (line 1005) | static int follow(int* parents, int index)
  function meshlets (line 1017) | void meshlets(const Mesh& mesh, bool scan = false, bool uniform = false,...
  function spatialSort (line 1196) | void spatialSort(const Mesh& mesh)
  function spatialSortTriangles (line 1223) | void spatialSortTriangles(const Mesh& mesh)
  function spatialClusterPoints (line 1258) | void spatialClusterPoints(const Mesh& mesh, size_t cluster_size)
  function tessellationAdjacency (line 1287) | void tessellationAdjacency(const Mesh& mesh)
  function provoking (line 1307) | void provoking(const Mesh& mesh)
  function reindexCompare (line 1344) | static int reindexCompare(void* context, unsigned int lhs, unsigned int ...
  function reindexFuzzy (line 1359) | void reindexFuzzy(const Mesh& mesh)
  function coverage (line 1382) | void coverage(const Mesh& mesh)
  function loadMesh (line 1396) | bool loadMesh(Mesh& mesh, const char* path)
  function processDeinterleaved (line 1413) | void processDeinterleaved(const char* path)
  function process (line 1515) | void process(const char* path)
  function processDev (line 1582) | void processDev(const char* path)
  function processNanite (line 1593) | void processNanite(const char* path)
  function main (line 1602) | int main(int argc, char** argv)

FILE: demo/nanite.cpp
  type Vertex (line 16) | struct Vertex
  function boundsError (line 27) | static float boundsError(const clodBounds& bounds, float camera_x, float...
  function nanite (line 41) | void nanite(const std::vector<Vertex>& vertices, const std::vector<unsig...
  function follow (line 211) | static int follow(std::vector<int>& parents, int index)
  function measureComponents (line 223) | static int measureComponents(std::vector<int>& parents, const std::vecto...
  function measureBoundary (line 261) | static size_t measureBoundary(std::vector<int>& used, const std::vector<...
  type Box (line 306) | struct Box
  function mergeBox (line 314) | static void mergeBox(Box& box, const Box& other)
  function surface (line 323) | inline float surface(const Box& box)
  function sahCost (line 329) | static float sahCost(const Box* boxes, unsigned int* order, unsigned int...
  function sahCost (line 416) | static float sahCost(const Box* boxes, size_t count)
  function sahOverhead (line 429) | static float sahOverhead(const std::vector<std::vector<unsigned int> >& ...

FILE: demo/tests.cpp
  type PV (line 14) | struct PV
  function decodeIndexV0 (line 80) | static void decodeIndexV0()
  function decodeIndexV1 (line 91) | static void decodeIndexV1()
  function decodeIndexV1More (line 102) | static void decodeIndexV1More()
  function decodeIndexV1ThreeEdges (line 119) | static void decodeIndexV1ThreeEdges()
  function decodeIndex16 (line 136) | static void decodeIndex16()
  function encodeIndexMemorySafe (line 151) | static void encodeIndexMemorySafe()
  function decodeIndexMemorySafe (line 172) | static void decodeIndexMemorySafe()
  function decodeIndexRejectExtraBytes (line 195) | static void decodeIndexRejectExtraBytes()
  function decodeIndexRejectMalformedHeaders (line 211) | static void decodeIndexRejectMalformedHeaders()
  function decodeIndexRejectInvalidVersion (line 227) | static void decodeIndexRejectInvalidVersion()
  function decodeIndexMalformedVByte (line 243) | static void decodeIndexMalformedVByte()
  function roundtripIndexTricky (line 256) | static void roundtripIndexTricky()
  function encodeIndexEmpty (line 269) | static void encodeIndexEmpty()
  function decodeIndexSequence (line 277) | static void decodeIndexSequence()
  function decodeIndexSequence16 (line 288) | static void decodeIndexSequence16()
  function encodeIndexSequenceMemorySafe (line 303) | static void encodeIndexSequenceMemorySafe()
  function decodeIndexSequenceMemorySafe (line 324) | static void decodeIndexSequenceMemorySafe()
  function decodeIndexSequenceRejectExtraBytes (line 347) | static void decodeIndexSequenceRejectExtraBytes()
  function decodeIndexSequenceRejectMalformedHeaders (line 363) | static void decodeIndexSequenceRejectMalformedHeaders()
  function decodeIndexSequenceRejectInvalidVersion (line 379) | static void decodeIndexSequenceRejectInvalidVersion()
  function encodeIndexSequenceEmpty (line 395) | static void encodeIndexSequenceEmpty()
  function decodeVertexV0 (line 403) | static void decodeVertexV0()
  function decodeVertexV0More (line 414) | static void decodeVertexV0More()
  function decodeVertexV0Mode2 (line 436) | static void decodeVertexV0Mode2()
  function decodeVertexV1 (line 458) | static void decodeVertexV1()
  function decodeVertexV1Custom (line 469) | static void decodeVertexV1Custom()
  function decodeVertexV1Deltas (line 480) | static void decodeVertexV1Deltas()
  function encodeVertexMemorySafe (line 501) | static void encodeVertexMemorySafe()
  function decodeVertexMemorySafe (line 521) | static void decodeVertexMemorySafe()
  function decodeVertexRejectExtraBytes (line 544) | static void decodeVertexRejectExtraBytes()
  function decodeVertexRejectMalformedHeaders (line 559) | static void decodeVertexRejectMalformedHeaders()
  function decodeVertexBitGroups (line 574) | static void decodeVertexBitGroups()
  function decodeVertexBitGroupSentinels (line 595) | static void decodeVertexBitGroupSentinels()
  function decodeVertexDeltas (line 626) | static void decodeVertexDeltas()
  function decodeVertexBitXor (line 647) | static void decodeVertexBitXor()
  function decodeVertexLarge (line 668) | static void decodeVertexLarge()
  function decodeVertexSmall (line 689) | static void decodeVertexSmall()
  function encodeVertexEmpty (line 710) | static void encodeVertexEmpty()
  function decodeVersion (line 718) | static void decodeVersion()
  function decodeFilterOct8 (line 737) | static void decodeFilterOct8()
  function decodeFilterOct12 (line 766) | static void decodeFilterOct12()
  function decodeFilterQuat12 (line 795) | static void decodeFilterQuat12()
  function decodeFilterExp (line 824) | static void decodeFilterExp()
  function encodeFilterOct8 (line 853) | static void encodeFilterOct8()
  function encodeFilterOct12 (line 882) | static void encodeFilterOct12()
  function encodeFilterQuat12 (line 911) | static void encodeFilterQuat12()
  function encodeFilterExp (line 953) | static void encodeFilterExp()
  function encodeFilterExpZero (line 1019) | static void encodeFilterExpZero()
  function encodeFilterExpAlias (line 1047) | static void encodeFilterExpAlias()
  function encodeFilterExpClamp (line 1097) | static void encodeFilterExpClamp()
  function encodeFilterColor8 (line 1128) | static void encodeFilterColor8()
  function encodeFilterColor12 (line 1160) | static void encodeFilterColor12()
  function clusterBoundsDegenerate (line 1192) | static void clusterBoundsDegenerate()
  function sphereBounds (line 1225) | static void sphereBounds()
  function meshletsEmpty (line 1249) | static void meshletsEmpty()
  function meshletsDense (line 1260) | static void meshletsDense()
  function meshletsSparse (line 1282) | static void meshletsSparse()
  function meshletsFlex (line 1304) | static void meshletsFlex()
  function meshletsMax (line 1352) | static void meshletsMax()
  function extractMeshlet (line 1398) | static void extractMeshlet()
  function meshletsSpatial (line 1416) | static void meshletsSpatial()
  function meshletsSpatialDeep (line 1455) | static void meshletsSpatialDeep()
  function partitionBasic (line 1483) | static void partitionBasic()
  function partitionSpatial (line 1510) | static void partitionSpatial()
  function partitionSpatialMerge (line 1535) | static void partitionSpatialMerge()
  function remapCustomFalse (line 1559) | static int remapCustomFalse(void*, unsigned int, unsigned int)
  function remapCustomTrue (line 1564) | static int remapCustomTrue(void*, unsigned int, unsigned int)
  function remapCustom (line 1569) | static void remapCustom()
  function customFree (line 1613) | static void customFree(void* ptr)
  function customAllocator (line 1620) | static void customAllocator()
  function emptyMesh (line 1655) | static void emptyMesh()
  function simplify (line 1662) | static void simplify()
  function simplifyStuck (line 1695) | static void simplifyStuck()
  function simplifySloppyStuck (line 1724) | static void simplifySloppyStuck()
  function simplifySloppyLocks (line 1738) | static void simplifySloppyLocks()
  function simplifyPointsStuck (line 1777) | static void simplifyPointsStuck()
  function simplifyFlip (line 1785) | static void simplifyFlip()
  function simplifyScale (line 1832) | static void simplifyScale()
  function simplifyDegenerate (line 1839) | static void simplifyDegenerate()
  function simplifyLockBorder (line 1872) | static void simplifyLockBorder()
  function simplifyAttr (line 1914) | static void simplifyAttr(bool skip_g)
  function simplifyLockFlags (line 1971) | static void simplifyLockFlags()
  function simplifyLockFlagsSeam (line 2019) | static void simplifyLockFlagsSeam()
  function simplifySparse (line 2091) | static void simplifySparse()
  function simplifyErrorAbsolute (line 2162) | static void simplifyErrorAbsolute()
  function simplifySeam (line 2196) | static void simplifySeam()
  function simplifySeamFake (line 2263) | static void simplifySeamFake()
  function simplifySeamAttr (line 2281) | static void simplifySeamAttr()
  function simplifyDebug (line 2336) | static void simplifyDebug()
  function simplifyPrune (line 2371) | static void simplifyPrune()
  function simplifyPruneCleanup (line 2415) | static void simplifyPruneCleanup()
  function simplifyPruneFunc (line 2447) | static void simplifyPruneFunc()
  function simplifyUpdate (line 2477) | static void simplifyUpdate()
  function simplifyUpdateLocked (line 2515) | static void simplifyUpdateLocked(unsigned int options)
  function adjacency (line 2554) | static void adjacency()
  function tessellation (line 2581) | static void tessellation()
  function provoking (line 2612) | static void provoking()
  function quantizeFloat (line 2648) | static void quantizeFloat()
  function quantizeHalf (line 2668) | static void quantizeHalf()
  function dequantizeHalf (line 2712) | static void dequantizeHalf()
  function encodeMeshletBound (line 2745) | static void encodeMeshletBound()
  function validateDecodeMeshlet (line 2775) | static void validateDecodeMeshlet(const unsigned char* data, size_t size...
  function decodeMeshletSafety (line 2802) | static void decodeMeshletSafety()
  function decodeMeshletBasic (line 2872) | static void decodeMeshletBasic()
  function decodeMeshletTypical (line 2907) | static void decodeMeshletTypical()
  function opacityMap (line 2939) | static void opacityMap()
  function runTests (line 3065) | void runTests()

FILE: extern/cgltf.h
  type cgltf_size (line 104) | typedef size_t cgltf_size;
  type cgltf_ssize (line 105) | typedef long long int cgltf_ssize;
  type cgltf_float (line 106) | typedef float cgltf_float;
  type cgltf_int (line 107) | typedef int cgltf_int;
  type cgltf_uint (line 108) | typedef unsigned int cgltf_uint;
  type cgltf_bool (line 109) | typedef int cgltf_bool;
  type cgltf_file_type (line 111) | typedef enum cgltf_file_type
  type cgltf_result (line 119) | typedef enum cgltf_result
  type cgltf_memory_options (line 134) | typedef struct cgltf_memory_options
  type cgltf_file_options (line 141) | typedef struct cgltf_file_options
  type cgltf_options (line 148) | typedef struct cgltf_options
  type cgltf_buffer_view_type (line 156) | typedef enum cgltf_buffer_view_type
  type cgltf_attribute_type (line 164) | typedef enum cgltf_attribute_type
  type cgltf_component_type (line 178) | typedef enum cgltf_component_type
  type cgltf_type (line 190) | typedef enum cgltf_type
  type cgltf_primitive_type (line 203) | typedef enum cgltf_primitive_type
  type cgltf_alpha_mode (line 216) | typedef enum cgltf_alpha_mode
  type cgltf_animation_path_type (line 224) | typedef enum cgltf_animation_path_type {
  type cgltf_interpolation_type (line 233) | typedef enum cgltf_interpolation_type {
  type cgltf_camera_type (line 240) | typedef enum cgltf_camera_type {
  type cgltf_light_type (line 247) | typedef enum cgltf_light_type {
  type cgltf_data_free_method (line 255) | typedef enum cgltf_data_free_method {
  type cgltf_extras (line 262) | typedef struct cgltf_extras {
  type cgltf_extension (line 269) | typedef struct cgltf_extension {
  type cgltf_buffer (line 274) | typedef struct cgltf_buffer
  type cgltf_meshopt_compression_mode (line 286) | typedef enum cgltf_meshopt_compression_mode {
  type cgltf_meshopt_compression_filter (line 294) | typedef enum cgltf_meshopt_compression_filter {
  type cgltf_meshopt_compression (line 303) | typedef struct cgltf_meshopt_compression
  type cgltf_buffer_view (line 315) | typedef struct cgltf_buffer_view
  type cgltf_accessor_sparse (line 331) | typedef struct cgltf_accessor_sparse
  type cgltf_accessor (line 341) | typedef struct cgltf_accessor
  type cgltf_attribute (line 362) | typedef struct cgltf_attribute
  type cgltf_image (line 370) | typedef struct cgltf_image
  type cgltf_filter_type (line 381) | typedef enum cgltf_filter_type {
  type cgltf_wrap_mode (line 391) | typedef enum cgltf_wrap_mode {
  type cgltf_sampler (line 397) | typedef struct cgltf_sampler
  type cgltf_texture (line 409) | typedef struct cgltf_texture
  type cgltf_texture_transform (line 423) | typedef struct cgltf_texture_transform
  type cgltf_texture_view (line 432) | typedef struct cgltf_texture_view
  type cgltf_pbr_metallic_roughness (line 441) | typedef struct cgltf_pbr_metallic_roughness
  type cgltf_pbr_specular_glossiness (line 451) | typedef struct cgltf_pbr_specular_glossiness
  type cgltf_clearcoat (line 461) | typedef struct cgltf_clearcoat
  type cgltf_transmission (line 471) | typedef struct cgltf_transmission
  type cgltf_ior (line 477) | typedef struct cgltf_ior
  type cgltf_specular (line 482) | typedef struct cgltf_specular
  type cgltf_volume (line 490) | typedef struct cgltf_volume
  type cgltf_sheen (line 498) | typedef struct cgltf_sheen
  type cgltf_emissive_strength (line 506) | typedef struct cgltf_emissive_strength
  type cgltf_iridescence (line 511) | typedef struct cgltf_iridescence
  type cgltf_diffuse_transmission (line 521) | typedef struct cgltf_diffuse_transmission
  type cgltf_anisotropy (line 529) | typedef struct cgltf_anisotropy
  type cgltf_dispersion (line 536) | typedef struct cgltf_dispersion
  type cgltf_material (line 541) | typedef struct cgltf_material
  type cgltf_material_mapping (line 583) | typedef struct cgltf_material_mapping
  type cgltf_morph_target (line 590) | typedef struct cgltf_morph_target {
  type cgltf_draco_mesh_compression (line 595) | typedef struct cgltf_draco_mesh_compression {
  type cgltf_mesh_gpu_instancing (line 601) | typedef struct cgltf_mesh_gpu_instancing {
  type cgltf_primitive (line 606) | typedef struct cgltf_primitive {
  type cgltf_mesh (line 623) | typedef struct cgltf_mesh {
  type cgltf_node (line 636) | typedef struct cgltf_node cgltf_node;
  type cgltf_skin (line 638) | typedef struct cgltf_skin {
  type cgltf_camera_perspective (line 649) | typedef struct cgltf_camera_perspective {
  type cgltf_camera_orthographic (line 659) | typedef struct cgltf_camera_orthographic {
  type cgltf_camera (line 667) | typedef struct cgltf_camera {
  type cgltf_light (line 679) | typedef struct cgltf_light {
  type cgltf_node (line 690) | struct cgltf_node {
  type cgltf_scene (line 716) | typedef struct cgltf_scene {
  type cgltf_animation_sampler (line 725) | typedef struct cgltf_animation_sampler {
  type cgltf_animation_channel (line 734) | typedef struct cgltf_animation_channel {
  type cgltf_animation (line 743) | typedef struct cgltf_animation {
  type cgltf_material_variant (line 754) | typedef struct cgltf_material_variant
  type cgltf_asset (line 760) | typedef struct cgltf_asset {
  type cgltf_data (line 770) | typedef struct cgltf_data
  type jsmntype_t (line 950) | typedef enum {
  type jsmnerr (line 957) | enum jsmnerr {
  type jsmntok_t (line 965) | typedef struct {
  type jsmn_parser (line 974) | typedef struct {
  function cgltf_default_free (line 1021) | static void cgltf_default_free(void* user, void* ptr)
  function cgltf_result (line 1042) | static cgltf_result cgltf_default_file_read(const struct cgltf_memory_op...
  function cgltf_default_file_release (line 1105) | static void cgltf_default_file_release(const struct cgltf_memory_options...
  function cgltf_result (line 1115) | cgltf_result cgltf_parse(const cgltf_options* options, const void* data,...
  function cgltf_result (line 1247) | cgltf_result cgltf_parse_file(const cgltf_options* options, const char* ...
  function cgltf_combine_paths (line 1279) | static void cgltf_combine_paths(char* path, const char* base, const char...
  function cgltf_result (line 1298) | static cgltf_result cgltf_load_buffer_file(const cgltf_options* options,...
  function cgltf_result (line 1325) | cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cglt...
  function cgltf_unhex (line 1372) | static int cgltf_unhex(char ch)
  function cgltf_size (line 1381) | cgltf_size cgltf_decode_string(char* string)
  function cgltf_size (line 1452) | cgltf_size cgltf_decode_uri(char* uri)
  function cgltf_result (line 1483) | cgltf_result cgltf_load_buffers(const cgltf_options* options, cgltf_data...
  function cgltf_size (line 1553) | static cgltf_size cgltf_calc_index_bound(cgltf_buffer_view* buffer_view,...
  function cgltf_result (line 1597) | cgltf_result cgltf_validate(cgltf_data* data)
  function cgltf_result (line 1808) | cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_...
  function cgltf_free_extras (line 1836) | static void cgltf_free_extras(cgltf_data* data, cgltf_extras* extras)
  function cgltf_free_extensions (line 1841) | static void cgltf_free_extensions(cgltf_data* data, cgltf_extension* ext...
  function cgltf_free (line 1851) | void cgltf_free(cgltf_data* data)
  function cgltf_node_transform_local (line 2137) | void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out...
  function cgltf_node_transform_world (line 2182) | void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out...
  function cgltf_ssize (line 2217) | static cgltf_ssize cgltf_component_read_integer(const void* in, cgltf_co...
  function cgltf_size (line 2236) | static cgltf_size cgltf_component_read_index(const void* in, cgltf_compo...
  function cgltf_float (line 2251) | static cgltf_float cgltf_component_read_float(const void* in, cgltf_comp...
  function cgltf_bool (line 2279) | static cgltf_bool cgltf_element_read_float(const uint8_t* element, cgltf...
  function cgltf_accessor (line 2348) | const cgltf_accessor* cgltf_find_accessor(const cgltf_primitive* prim, c...
  function cgltf_bool (line 2393) | cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgl...
  function cgltf_size (line 2415) | cgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, ...
  function cgltf_uint (line 2491) | static cgltf_uint cgltf_component_read_uint(const void* in, cgltf_compon...
  function cgltf_bool (line 2515) | static cgltf_bool cgltf_element_read_uint(const uint8_t* element, cgltf_...
  function cgltf_bool (line 2539) | cgltf_bool cgltf_accessor_read_uint(const cgltf_accessor* accessor, cglt...
  function cgltf_size (line 2561) | cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgl...
  function cgltf_size (line 2582) | cgltf_size cgltf_mesh_index(const cgltf_data* data, const cgltf_mesh* ob...
  function cgltf_size (line 2588) | cgltf_size cgltf_material_index(const cgltf_data* data, const cgltf_mate...
  function cgltf_size (line 2594) | cgltf_size cgltf_accessor_index(const cgltf_data* data, const cgltf_acce...
  function cgltf_size (line 2600) | cgltf_size cgltf_buffer_view_index(const cgltf_data* data, const cgltf_b...
  function cgltf_size (line 2606) | cgltf_size cgltf_buffer_index(const cgltf_data* data, const cgltf_buffer...
  function cgltf_size (line 2612) | cgltf_size cgltf_image_index(const cgltf_data* data, const cgltf_image* ...
  function cgltf_size (line 2618) | cgltf_size cgltf_texture_index(const cgltf_data* data, const cgltf_textu...
  function cgltf_size (line 2624) | cgltf_size cgltf_sampler_index(const cgltf_data* data, const cgltf_sampl...
  function cgltf_size (line 2630) | cgltf_size cgltf_skin_index(const cgltf_data* data, const cgltf_skin* ob...
  function cgltf_size (line 2636) | cgltf_size cgltf_camera_index(const cgltf_data* data, const cgltf_camera...
  function cgltf_size (line 2642) | cgltf_size cgltf_light_index(const cgltf_data* data, const cgltf_light* ...
  function cgltf_size (line 2648) | cgltf_size cgltf_node_index(const cgltf_data* data, const cgltf_node* ob...
  function cgltf_size (line 2654) | cgltf_size cgltf_scene_index(const cgltf_data* data, const cgltf_scene* ...
  function cgltf_size (line 2660) | cgltf_size cgltf_animation_index(const cgltf_data* data, const cgltf_ani...
  function cgltf_size (line 2666) | cgltf_size cgltf_animation_sampler_index(const cgltf_animation* animatio...
  function cgltf_size (line 2672) | cgltf_size cgltf_animation_channel_index(const cgltf_animation* animatio...
  function cgltf_size (line 2678) | cgltf_size cgltf_accessor_unpack_indices(const cgltf_accessor* accessor,...
  function cgltf_json_strcmp (line 2758) | static int cgltf_json_strcmp(jsmntok_t const* tok, const uint8_t* json_c...
  function cgltf_json_to_int (line 2766) | static int cgltf_json_to_int(jsmntok_t const* tok, const uint8_t* json_c...
  function cgltf_size (line 2776) | static cgltf_size cgltf_json_to_size(jsmntok_t const* tok, const uint8_t...
  function cgltf_float (line 2787) | static cgltf_float cgltf_json_to_float(jsmntok_t const* tok, const uint8...
  function cgltf_bool (line 2797) | static cgltf_bool cgltf_json_to_bool(jsmntok_t const* tok, const uint8_t...
  function cgltf_skip_json (line 2803) | static int cgltf_skip_json(jsmntok_t const* tokens, int i)
  function cgltf_fill_float_array (line 2833) | static void cgltf_fill_float_array(float* out_array, int size, float value)
  function cgltf_parse_json_float_array (line 2841) | static int cgltf_parse_json_float_array(jsmntok_t const* tokens, int i, ...
  function cgltf_parse_json_string (line 2858) | static int cgltf_parse_json_string(cgltf_options* options, jsmntok_t con...
  function cgltf_parse_json_array (line 2877) | static int cgltf_parse_json_array(cgltf_options* options, jsmntok_t cons...
  function cgltf_parse_json_string_array (line 2899) | static int cgltf_parse_json_string_array(cgltf_options* options, jsmntok...
  function cgltf_parse_attribute_type (line 2919) | static void cgltf_parse_attribute_type(const char* name, cgltf_attribute...
  function cgltf_parse_json_attribute_list (line 2974) | static int cgltf_parse_json_attribute_list(cgltf_options* options, jsmnt...
  function cgltf_parse_json_extras (line 3011) | static int cgltf_parse_json_extras(cgltf_options* options, jsmntok_t con...
  function cgltf_parse_json_unprocessed_extension (line 3036) | static int cgltf_parse_json_unprocessed_extension(cgltf_options* options...
  function cgltf_parse_json_unprocessed_extensions (line 3070) | static int cgltf_parse_json_unprocessed_extensions(cgltf_options* option...
  function cgltf_parse_json_draco_mesh_compression (line 3107) | static int cgltf_parse_json_draco_mesh_compression(cgltf_options* option...
  function cgltf_parse_json_mesh_gpu_instancing (line 3142) | static int cgltf_parse_json_mesh_gpu_instancing(cgltf_options* options, ...
  function cgltf_parse_json_material_mapping_data (line 3171) | static int cgltf_parse_json_material_mapping_data(cgltf_options* options...
  function cgltf_parse_json_material_mappings (line 3258) | static int cgltf_parse_json_material_mappings(cgltf_options* options, js...
  function cgltf_primitive_type (line 3303) | static cgltf_primitive_type cgltf_json_to_primitive_type(jsmntok_t const...
  function cgltf_parse_json_primitive (line 3328) | static int cgltf_parse_json_primitive(cgltf_options* options, jsmntok_t ...
  function cgltf_parse_json_mesh (line 3442) | static int cgltf_parse_json_mesh(cgltf_options* options, jsmntok_t const...
  function cgltf_parse_json_meshes (line 3538) | static int cgltf_parse_json_meshes(cgltf_options* options, jsmntok_t con...
  function cgltf_component_type (line 3557) | static cgltf_component_type cgltf_json_to_component_type(jsmntok_t const...
  function cgltf_parse_json_accessor_sparse (line 3580) | static int cgltf_parse_json_accessor_sparse(jsmntok_t const* tokens, int...
  function cgltf_parse_json_accessor (line 3687) | static int cgltf_parse_json_accessor(cgltf_options* options, jsmntok_t c...
  function cgltf_parse_json_texture_transform (line 3809) | static int cgltf_parse_json_texture_transform(jsmntok_t const* tokens, i...
  function cgltf_parse_json_texture_view (line 3855) | static int cgltf_parse_json_texture_view(cgltf_options* options, jsmntok...
  function cgltf_parse_json_pbr_metallic_roughness (line 3938) | static int cgltf_parse_json_pbr_metallic_roughness(cgltf_options* option...
  function cgltf_parse_json_pbr_specular_glossiness (line 3989) | static int cgltf_parse_json_pbr_specular_glossiness(cgltf_options* optio...
  function cgltf_parse_json_clearcoat (line 4035) | static int cgltf_parse_json_clearcoat(cgltf_options* options, jsmntok_t ...
  function cgltf_parse_json_ior (line 4083) | static int cgltf_parse_json_ior(jsmntok_t const* tokens, int i, const ui...
  function cgltf_parse_json_specular (line 4116) | static int cgltf_parse_json_specular(cgltf_options* options, jsmntok_t c...
  function cgltf_parse_json_transmission (line 4162) | static int cgltf_parse_json_transmission(cgltf_options* options, jsmntok...
  function cgltf_parse_json_volume (line 4196) | static int cgltf_parse_json_volume(cgltf_options* options, jsmntok_t con...
  function cgltf_parse_json_sheen (line 4240) | static int cgltf_parse_json_sheen(cgltf_options* options, jsmntok_t cons...
  function cgltf_parse_json_emissive_strength (line 4282) | static int cgltf_parse_json_emissive_strength(jsmntok_t const* tokens, i...
  function cgltf_parse_json_iridescence (line 4315) | static int cgltf_parse_json_iridescence(cgltf_options* options, jsmntok_...
  function cgltf_parse_json_diffuse_transmission (line 4376) | static int cgltf_parse_json_diffuse_transmission(cgltf_options* options,...
  function cgltf_parse_json_anisotropy (line 4422) | static int cgltf_parse_json_anisotropy(cgltf_options* options, jsmntok_t...
  function cgltf_parse_json_dispersion (line 4463) | static int cgltf_parse_json_dispersion(jsmntok_t const* tokens, int i, c...
  function cgltf_parse_json_image (line 4494) | static int cgltf_parse_json_image(cgltf_options* options, jsmntok_t cons...
  function cgltf_parse_json_sampler (line 4545) | static int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t co...
  function cgltf_parse_json_texture (line 4614) | static int cgltf_parse_json_texture(cgltf_options* options, jsmntok_t co...
  function cgltf_parse_json_material (line 4750) | static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t c...
  function cgltf_parse_json_accessors (line 4950) | static int cgltf_parse_json_accessors(cgltf_options* options, jsmntok_t ...
  function cgltf_parse_json_materials (line 4969) | static int cgltf_parse_json_materials(cgltf_options* options, jsmntok_t ...
  function cgltf_parse_json_images (line 4988) | static int cgltf_parse_json_images(cgltf_options* options, jsmntok_t con...
  function cgltf_parse_json_textures (line 5007) | static int cgltf_parse_json_textures(cgltf_options* options, jsmntok_t c...
  function cgltf_parse_json_samplers (line 5026) | static int cgltf_parse_json_samplers(cgltf_options* options, jsmntok_t c...
  function cgltf_parse_json_meshopt_compression (line 5045) | static int cgltf_parse_json_meshopt_compression(cgltf_options* options, ...
  function cgltf_parse_json_buffer_view (line 5143) | static int cgltf_parse_json_buffer_view(cgltf_options* options, jsmntok_...
  function cgltf_parse_json_buffer_views (line 5268) | static int cgltf_parse_json_buffer_views(cgltf_options* options, jsmntok...
  function cgltf_parse_json_buffer (line 5287) | static int cgltf_parse_json_buffer(cgltf_options* options, jsmntok_t con...
  function cgltf_parse_json_buffers (line 5335) | static int cgltf_parse_json_buffers(cgltf_options* options, jsmntok_t co...
  function cgltf_parse_json_skin (line 5354) | static int cgltf_parse_json_skin(cgltf_options* options, jsmntok_t const...
  function cgltf_parse_json_skins (line 5419) | static int cgltf_parse_json_skins(cgltf_options* options, jsmntok_t cons...
  function cgltf_parse_json_camera (line 5438) | static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t con...
  function cgltf_parse_json_cameras (line 5595) | static int cgltf_parse_json_cameras(cgltf_options* options, jsmntok_t co...
  function cgltf_parse_json_light (line 5614) | static int cgltf_parse_json_light(cgltf_options* options, jsmntok_t cons...
  function cgltf_parse_json_lights (line 5724) | static int cgltf_parse_json_lights(cgltf_options* options, jsmntok_t con...
  function cgltf_parse_json_node (line 5743) | static int cgltf_parse_json_node(cgltf_options* options, jsmntok_t const...
  function cgltf_parse_json_nodes (line 5922) | static int cgltf_parse_json_nodes(cgltf_options* options, jsmntok_t cons...
  function cgltf_parse_json_scene (line 5941) | static int cgltf_parse_json_scene(cgltf_options* options, jsmntok_t cons...
  function cgltf_parse_json_scenes (line 5992) | static int cgltf_parse_json_scenes(cgltf_options* options, jsmntok_t con...
  function cgltf_parse_json_animation_sampler (line 6011) | static int cgltf_parse_json_animation_sampler(cgltf_options* options, js...
  function cgltf_parse_json_animation_channel (line 6074) | static int cgltf_parse_json_animation_channel(cgltf_options* options, js...
  function cgltf_parse_json_animation (line 6165) | static int cgltf_parse_json_animation(cgltf_options* options, jsmntok_t ...
  function cgltf_parse_json_animations (line 6236) | static int cgltf_parse_json_animations(cgltf_options* options, jsmntok_t...
  function cgltf_parse_json_variant (line 6255) | static int cgltf_parse_json_variant(cgltf_options* options, jsmntok_t co...
  function cgltf_parse_json_variants (line 6288) | static int cgltf_parse_json_variants(cgltf_options* options, jsmntok_t c...
  function cgltf_parse_json_asset (line 6307) | static int cgltf_parse_json_asset(cgltf_options* options, jsmntok_t cons...
  function cgltf_size (line 6361) | cgltf_size cgltf_num_components(cgltf_type type) {
  function cgltf_size (line 6383) | cgltf_size cgltf_component_size(cgltf_component_type component_type) {
  function cgltf_size (line 6401) | cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type compone...
  function cgltf_parse_json_root (line 6417) | static int cgltf_parse_json_root(cgltf_options* options, jsmntok_t const...
  function cgltf_result (line 6608) | cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* jso...
  function cgltf_fixup_pointers (line 6687) | static int cgltf_fixup_pointers(cgltf_data* data)
  function jsmntok_t (line 6905) | static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
  function jsmn_fill_token (line 6923) | static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
  function jsmn_parse_primitive (line 6934) | static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
  function jsmn_parse_string (line 6983) | static int jsmn_parse_string(jsmn_parser *parser, const char *js,
  function jsmn_parse (line 7050) | static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
  function jsmn_init (line 7208) | static void jsmn_init(jsmn_parser *parser) {

FILE: extern/fast_obj.h
  type fastObjTexture (line 40) | typedef struct
  type fastObjMaterial (line 51) | typedef struct
  type FAST_OBJ_UINT_TYPE (line 89) | typedef FAST_OBJ_UINT_TYPE fastObjUInt;
  type fastObjIndex (line 91) | typedef struct
  type fastObjGroup (line 100) | typedef struct
  type fastObjMesh (line 121) | typedef struct
  type fastObjCallbacks (line 164) | typedef struct
  type fastObjData (line 215) | typedef struct
  function memory_dealloc (line 257) | static
  function file_close (line 309) | static
  function file_read (line 320) | static
  function file_size (line 331) | static
  function string_equal (line 400) | static
  function string_fix_separators (line 410) | static
  function is_whitespace (line 422) | static
  function is_newline (line 428) | static
  function is_digit (line 435) | static
  function is_exponent (line 442) | static
  function fastObjGroup (line 484) | static
  function object_clean (line 498) | static
  function flush_object (line 505) | static
  function fastObjGroup (line 522) | static
  function group_clean (line 536) | static
  function flush_group (line 543) | static
  function fastObjTexture (line 857) | static
  function fastObjMaterial (line 869) | static
  function map_clean (line 958) | static
  function mtl_clean (line 966) | static
  function read_mtllib (line 1043) | static
  function parse_buffer (line 1266) | static
  function fast_obj_destroy (line 1404) | void fast_obj_destroy(fastObjMesh* m)
  function fastObjMesh (line 1438) | fastObjMesh* fast_obj_read(const char* path)
  function fastObjMesh (line 1450) | fastObjMesh* fast_obj_read_with_callbacks(const char* path, const fastOb...

FILE: extern/sdefl.h
  type sdefl_freq (line 138) | struct sdefl_freq {
  type sdefl_code_words (line 142) | struct sdefl_code_words {
  type sdefl_lens (line 146) | struct sdefl_lens {
  type sdefl_codes (line 150) | struct sdefl_codes {
  type sdefl_seqt (line 154) | struct sdefl_seqt {
  type sdefl (line 157) | struct sdefl {
  type sdefl (line 168) | struct sdefl
  type sdefl (line 169) | struct sdefl
  function sdefl_ilog2 (line 202) | static int
  function sdefl_uload32 (line 225) | static unsigned
  function sdefl_hash32 (line 232) | static unsigned
  function sdefl_put (line 237) | static void
  function sdefl_heap_sub (line 249) | static void
  function sdefl_heap_array (line 260) | static void
  function sdefl_heap_sort (line 266) | static void
  function sdefl_sort_sym (line 277) | static unsigned
  function sdefl_build_tree (line 301) | static void
  function sdefl_gen_len_cnt (line 319) | static void
  function sdefl_gen_codes (line 344) | static void
  function sdefl_rev (line 358) | static unsigned
  function sdefl_huff (line 366) | static void
  type sdefl_symcnt (line 387) | struct sdefl_symcnt {
  function sdefl_precode (line 392) | static void
  type sdefl_match_codest (line 447) | struct sdefl_match_codest {
  function sdefl_match_codes (line 451) | static void
  type sdefl_blk_type (line 480) | enum sdefl_blk_type {
  function sdefl_blk_type (line 484) | static enum sdefl_blk_type
  function sdefl_put16 (line 511) | static void
  function sdefl_match (line 518) | static void
  function sdefl_flush (line 533) | static void
  function sdefl_seq (line 612) | static void
  function sdefl_reg_match (line 619) | static void
  type sdefl_match (line 630) | struct sdefl_match {
  function sdefl_fnd (line 634) | static void
  function sdefl_compr (line 667) | static int
  function sdeflate (line 734) | extern int
  function sdefl_adler32 (line 739) | static unsigned
  function zsdeflate (line 770) | extern int
  function sdefl_bound (line 789) | extern int

FILE: gltf/animation.cpp
  function getDelta (line 10) | static float getDelta(const Attr& l, const Attr& r, cgltf_animation_path...
  function getDeltaTolerance (line 32) | static float getDeltaTolerance(cgltf_animation_path_type type)
  function Attr (line 54) | static Attr interpolateLinear(const Attr& l, const Attr& r, float t, cgl...
  function Attr (line 103) | static Attr interpolateHermite(const Attr& v0, const Attr& t0, const Att...
  function resampleKeyframes (line 136) | static void resampleKeyframes(std::vector<Attr>& data, const std::vector...
  function getMaxDelta (line 210) | static float getMaxDelta(const std::vector<Attr>& data, cgltf_animation_...
  function getBaseTransform (line 229) | static void getBaseTransform(Attr* result, size_t components, cgltf_anim...
  function getWorldScale (line 263) | static float getWorldScale(cgltf_node* node)
  function processAnimation (line 277) | void processAnimation(Animation& animation, const Settings& settings)

FILE: gltf/encodebasis.cpp
  type BasisSettings (line 27) | struct BasisSettings
  function fillParams (line 48) | static void fillParams(basisu::basis_compressor_params& params, const ch...
  function encodeImagesBasis (line 168) | void encodeImagesBasis(std::string* encoded, const cgltf_data* data, con...

FILE: gltf/encodewebp.cpp
  function writeWebP (line 16) | static int writeWebP(const uint8_t* data, size_t data_size, const WebPPi...
  type pv_png (line 25) | namespace pv_png
  type jpgd (line 30) | namespace jpgd
  type WebPPicture (line 36) | struct WebPPicture
  type Metadata (line 36) | struct Metadata
  function readPngBasis (line 38) | static int readPngBasis(const uint8_t* const data, size_t data_size, str...
  function readJpegBasis (line 55) | static int readJpegBasis(const uint8_t* const data, size_t data_size, st...
  function encodeImagesWebP (line 134) | void encodeImagesWebP(std::string* encoded, const cgltf_data* data, cons...

FILE: gltf/fileio.cpp
  function getTempPrefix (line 14) | std::string getTempPrefix()
  function getFullPath (line 31) | std::string getFullPath(const char* path, const char* base_path)
  function getFileName (line 43) | std::string getFileName(const char* path)
  function getExtension (line 58) | std::string getExtension(const char* path)
  function readFile (line 77) | bool readFile(const char* path, std::string& data)
  function writeFile (line 100) | bool writeFile(const char* path, const std::string& data)
  function removeFile (line 112) | void removeFile(const char* path)

FILE: gltf/gltfpack.cpp
  function getVersion (line 19) | std::string getVersion()
  function finalizeBufferViews (line 26) | static void finalizeBufferViews(std::string& json, std::vector<BufferVie...
  function printMeshStats (line 79) | static void printMeshStats(const std::vector<Mesh>& meshes, const char* ...
  function printSceneStats (line 108) | static void printSceneStats(const std::vector<BufferView>& views, const ...
  function printAttributeStats (line 126) | static void printAttributeStats(const std::vector<BufferView>& views, Bu...
  function printImageStats (line 168) | static void printImageStats(const std::vector<BufferView>& views, Textur...
  function printReport (line 191) | static bool printReport(const char* path, const std::vector<BufferView>&...
  function canTransformMesh (line 252) | static bool canTransformMesh(const Mesh& mesh)
  function detachMesh (line 261) | static void detachMesh(Mesh& mesh, cgltf_data* data, const std::vector<N...
  function isExtensionSupported (line 332) | static bool isExtensionSupported(const ExtensionInfo* extensions, size_t...
  type std (line 341) | namespace std
    type hash<std::pair<uint64_t, uint64_t> > (line 344) | struct hash<std::pair<uint64_t, uint64_t> >
  function process (line 353) | static size_t process(cgltf_data* data, const char* input_path, const ch...
  function writeU32 (line 956) | static void writeU32(FILE* out, uint32_t data)
  function getBufferSpec (line 972) | static std::string getBufferSpec(const char* bin_path, size_t bin_size, ...
  function gltfpack (line 1013) | int gltfpack(const char* input, const char* output, const char* report, ...
  function Settings (line 1220) | Settings defaults()
  function T (line 1249) | T clamp(T v, T min, T max)
  function textureMask (line 1254) | unsigned int textureMask(const char* arg)
  function applySetting (line 1279) | void applySetting(T (&data)[TextureKind__Count], T value, unsigned int m...
  function main (line 1287) | int main(int argc, char** argv)
  function pack (line 1743) | int pack(int argc, char** argv)
  function LLVMFuzzerTestOneInput (line 1754) | int LLVMFuzzerTestOneInput(const uint8_t* buffer, size_t size)

FILE: gltf/gltfpack.h
  type Attr (line 26) | struct Attr
  type Stream (line 31) | struct Stream
  type Instance (line 42) | struct Instance
  type Mesh (line 48) | struct Mesh
  type Track (line 76) | struct Track
  type Animation (line 92) | struct Animation
  type TextureKind (line 102) | enum TextureKind
  type TextureMode (line 112) | enum TextureMode
  type Settings (line 120) | struct Settings
  type QuantizationPosition (line 182) | struct QuantizationPosition
  type QuantizationTexture (line 192) | struct QuantizationTexture
  type StreamFormat (line 200) | struct StreamFormat
  type NodeInfo (line 218) | struct NodeInfo
  type MaterialInfo (line 236) | struct MaterialInfo
  type TextureInfo (line 250) | struct TextureInfo
  type ImageInfo (line 257) | struct ImageInfo
  type ExtensionInfo (line 266) | struct ExtensionInfo
  type BufferView (line 274) | struct BufferView

FILE: gltf/image.cpp
  function parseDataUri (line 26) | static bool parseDataUri(const char* uri, std::string& mime_type, std::s...
  function fixupMimeType (line 64) | static std::string fixupMimeType(const std::string& data, const std::str...
  function readImage (line 75) | bool readImage(const cgltf_image& image, const char* input_path, std::st...
  function readInt16BE (line 112) | static int readInt16BE(const std::string& data, size_t offset)
  function readInt32BE (line 117) | static int readInt32BE(const std::string& data, size_t offset)
  function readInt32LE (line 125) | static int readInt32LE(const std::string& data, size_t offset)
  function getDimensionsPng (line 134) | static bool getDimensionsPng(const std::string& data, int& width, int& h...
  function getDimensionsJpeg (line 153) | static bool getDimensionsJpeg(const std::string& data, int& width, int& ...
  function hasTransparencyPng (line 197) | static bool hasTransparencyPng(const std::string& data)
  function hasTransparencyKtx2 (line 233) | static bool hasTransparencyKtx2(const std::string& data)
  function hasTransparencyWebP (line 271) | static bool hasTransparencyWebP(const std::string& data)
  function hasAlpha (line 301) | bool hasAlpha(const std::string& data, const char* mime_type)
  function getDimensions (line 313) | bool getDimensions(const std::string& data, const char* mime_type, int& ...
  function roundPow2 (line 323) | static int roundPow2(int value)
  function roundBlock (line 337) | static int roundBlock(int value, bool pow2)
  function adjustDimensions (line 348) | void adjustDimensions(int& width, int& height, float scale, int limit, b...

FILE: gltf/json.cpp
  function comma (line 8) | void comma(std::string& s)
  function append (line 16) | void append(std::string& s, size_t v)
  function append (line 23) | void append(std::string& s, float v)
  function append (line 34) | void append(std::string& s, const char* v)
  function append (line 39) | void append(std::string& s, const std::string& v)
  function append (line 44) | void append(std::string& s, const float* data, size_t count)
  function appendJson (line 56) | void appendJson(std::string& s, const char* data)

FILE: gltf/library.js
  function init (line 10) | function init(wasm) {
  function pack (line 36) | function pack(args, iface) {
  function nextFd (line 291) | function nextFd() {
  function getHeap (line 299) | function getHeap() {
  function getString (line 303) | function getString(buffer, offset, length) {
  function stringBuffer (line 307) | function stringBuffer(string) {
  function growArray (line 311) | function growArray(data, len) {
  function uploadArgv (line 323) | function uploadArgv(argv) {

FILE: gltf/material.cpp
  function areTexturesEqual (line 6) | static bool areTexturesEqual(const cgltf_texture& lhs, const cgltf_textu...
  function areTextureViewsEqual (line 23) | static bool areTextureViewsEqual(const cgltf_texture_view& lhs, const cg...
  function areMaterialComponentsEqual (line 58) | static bool areMaterialComponentsEqual(const cgltf_pbr_metallic_roughnes...
  function areMaterialComponentsEqual (line 78) | static bool areMaterialComponentsEqual(const cgltf_pbr_specular_glossine...
  function areMaterialComponentsEqual (line 98) | static bool areMaterialComponentsEqual(const cgltf_clearcoat& lhs, const...
  function areMaterialComponentsEqual (line 118) | static bool areMaterialComponentsEqual(const cgltf_transmission& lhs, co...
  function areMaterialComponentsEqual (line 129) | static bool areMaterialComponentsEqual(const cgltf_ior& lhs, const cgltf...
  function areMaterialComponentsEqual (line 137) | static bool areMaterialComponentsEqual(const cgltf_specular& lhs, const ...
  function areMaterialComponentsEqual (line 154) | static bool areMaterialComponentsEqual(const cgltf_sheen& lhs, const cgl...
  function areMaterialComponentsEqual (line 171) | static bool areMaterialComponentsEqual(const cgltf_volume& lhs, const cg...
  function areMaterialComponentsEqual (line 188) | static bool areMaterialComponentsEqual(const cgltf_emissive_strength& lh...
  function areMaterialComponentsEqual (line 196) | static bool areMaterialComponentsEqual(const cgltf_iridescence& lhs, con...
  function areMaterialComponentsEqual (line 219) | static bool areMaterialComponentsEqual(const cgltf_anisotropy& lhs, cons...
  function areMaterialComponentsEqual (line 233) | static bool areMaterialComponentsEqual(const cgltf_dispersion& lhs, cons...
  function areMaterialComponentsEqual (line 241) | static bool areMaterialComponentsEqual(const cgltf_diffuse_transmission&...
  function areMaterialsEqual (line 258) | static bool areMaterialsEqual(const cgltf_material& lhs, const cgltf_mat...
  function mergeMeshMaterials (line 368) | void mergeMeshMaterials(cgltf_data* data, std::vector<Mesh>& meshes, con...
  function markNeededMaterials (line 406) | void markNeededMaterials(cgltf_data* data, std::vector<MaterialInfo>& ma...
  function hasValidTransform (line 443) | bool hasValidTransform(const cgltf_texture_view& view)
  function cgltf_image (line 459) | static const cgltf_image* getTextureImage(const cgltf_texture* texture)
  function analyzeMaterialTexture (line 473) | static void analyzeMaterialTexture(const cgltf_texture_view& view, Textu...
  function analyzeMaterial (line 499) | static void analyzeMaterial(const cgltf_material& material, MaterialInfo...
  function analyzeMaterials (line 567) | void analyzeMaterials(cgltf_data* data, std::vector<MaterialInfo>& mater...
  function getChannels (line 575) | static int getChannels(const cgltf_image& image, ImageInfo& info, const ...
  function shouldKeepAlpha (line 590) | static bool shouldKeepAlpha(const cgltf_texture_view& color, float alpha...
  function optimizeMaterials (line 600) | void optimizeMaterials(cgltf_data* data, std::vector<MaterialInfo>& mate...
  function mergeTextures (line 621) | void mergeTextures(cgltf_data* data, std::vector<TextureInfo>& textures)

FILE: gltf/mesh.cpp
  function inverseTranspose (line 13) | static float inverseTranspose(float* result, const float* transform)
  function transformPosition (line 44) | static void transformPosition(float* res, const float* ptr, const float*...
  function transformNormal (line 55) | static void transformNormal(float* res, const float* ptr, const float* t...
  function Stream (line 69) | static Stream* getStream(Mesh& mesh, cgltf_attribute_type type, int inde...
  function transformMesh (line 79) | static void transformMesh(Mesh& target, const Mesh& mesh, const cgltf_no...
  function compareMeshTargets (line 126) | bool compareMeshTargets(const Mesh& lhs, const Mesh& rhs)
  function compareMeshVariants (line 148) | bool compareMeshVariants(const Mesh& lhs, const Mesh& rhs)
  function compareMeshNodes (line 165) | bool compareMeshNodes(const Mesh& lhs, const Mesh& rhs)
  function compareInstances (line 177) | static bool compareInstances(const Instance& lhs, const Instance& rhs)
  function canMergeMeshNodes (line 182) | static bool canMergeMeshNodes(cgltf_node* lhs, cgltf_node* rhs, const Se...
  function canMergeMeshes (line 210) | static bool canMergeMeshes(const Mesh& lhs, const Mesh& rhs, const Setti...
  function mergeMeshes (line 260) | static void mergeMeshes(Mesh& target, const Mesh& mesh)
  function hashUpdate (line 278) | static void hashUpdate(uint64_t hash[2], const void* data, size_t size)
  function hashMesh (line 328) | void hashMesh(Mesh& mesh)
  function canDedupMesh (line 352) | static bool canDedupMesh(const Mesh& mesh, const Settings& settings)
  function dedupMeshes (line 373) | void dedupMeshes(std::vector<Mesh>& meshes, const Settings& settings)
  function mergeMeshInstances (line 439) | void mergeMeshInstances(Mesh& mesh)
  function mergeMeshes (line 473) | void mergeMeshes(std::vector<Mesh>& meshes, const Settings& settings)
  function filterEmptyMeshes (line 524) | void filterEmptyMeshes(std::vector<Mesh>& meshes)
  function isConstant (line 558) | static bool isConstant(const std::vector<Attr>& data, const Attr& value,...
  function filterStreams (line 571) | void filterStreams(Mesh& mesh, const MaterialInfo& mi)
  type QuantizedTBN (line 636) | struct QuantizedTBN
  function quantizeTBN (line 642) | static void quantizeTBN(QuantizedTBN* target, size_t offset, const Attr*...
  function reindexMesh (line 655) | static void reindexMesh(Mesh& mesh, bool quantize_tbn)
  function filterTriangles (line 709) | static void filterTriangles(Mesh& mesh)
  function simplifyAttributes (line 734) | static void simplifyAttributes(std::vector<float>& attrs, float* attrw, ...
  function simplifyProtect (line 772) | static void simplifyProtect(std::vector<unsigned char>& locks, Mesh& mes...
  function simplifyUvSplit (line 803) | static void simplifyUvSplit(Mesh& mesh, std::vector<unsigned int>& remap)
  function simplifyMesh (line 875) | static void simplifyMesh(Mesh& mesh, float threshold, float error, bool ...
  function optimizeMesh (line 943) | static void optimizeMesh(Mesh& mesh, bool compressmore)
  type BoneInfluence (line 972) | struct BoneInfluence
  type BoneInfluenceWeightPredicate (line 978) | struct BoneInfluenceWeightPredicate
  function filterBones (line 986) | static void filterBones(Mesh& mesh)
  function simplifyPointMesh (line 1068) | static void simplifyPointMesh(Mesh& mesh, float threshold)
  function sortPointMesh (line 1106) | static void sortPointMesh(Mesh& mesh)
  function processMesh (line 1131) | void processMesh(Mesh& mesh, const Settings& settings)
  function getScale (line 1163) | static float getScale(const float* transform)
  function computeMeshQuality (line 1172) | void computeMeshQuality(std::vector<Mesh>& meshes)
  function hasVertexAlpha (line 1207) | bool hasVertexAlpha(const Mesh& mesh)
  function hasInstanceAlpha (line 1220) | bool hasInstanceAlpha(const std::vector<Instance>& instances)

FILE: gltf/node.cpp
  function markScenes (line 7) | void markScenes(cgltf_data* data, std::vector<NodeInfo>& nodes)
  function markAnimated (line 33) | void markAnimated(cgltf_data* data, std::vector<NodeInfo>& nodes, const ...
  function markNeededNodes (line 62) | void markNeededNodes(cgltf_data* data, std::vector<NodeInfo>& nodes, con...
  function remapNodes (line 135) | void remapNodes(cgltf_data* data, std::vector<NodeInfo>& nodes, size_t& ...
  function decomposeTransform (line 161) | void decomposeTransform(float translation[3], float rotation[4], float s...

FILE: gltf/parsegltf.cpp
  function readAccessor (line 45) | static void readAccessor(std::vector<float>& data, const cgltf_accessor*...
  function readAccessor (line 53) | static void readAccessor(std::vector<Attr>& data, const cgltf_accessor* ...
  function readAccessor (line 69) | static void readAccessor(std::vector<Attr>& data, const cgltf_accessor* ...
  function fixupIndices (line 77) | static void fixupIndices(std::vector<unsigned int>& indices, cgltf_primi...
  function isIdAttribute (line 151) | static bool isIdAttribute(const char* name)
  function parseMeshesGltf (line 158) | static void parseMeshesGltf(cgltf_data* data, std::vector<Mesh>& meshes,...
  function parseMeshInstancesGltf (line 304) | static void parseMeshInstancesGltf(std::vector<Instance>& instances, cgl...
  function parseMeshNodesGltf (line 362) | static void parseMeshNodesGltf(cgltf_data* data, std::vector<Mesh>& mesh...
  function parseAnimationsGltf (line 411) | static void parseAnimationsGltf(cgltf_data* data, std::vector<Animation>...
  function requiresExtension (line 458) | static bool requiresExtension(cgltf_data* data, const char* name)
  function needsDummyBuffers (line 467) | static bool needsDummyBuffers(cgltf_data* data)
  function freeFile (line 498) | static void freeFile(cgltf_data* data)
  function freeUnusedBuffers (line 507) | static bool freeUnusedBuffers(cgltf_data* data)
  function cgltf_result (line 553) | static cgltf_result decompressMeshopt(cgltf_data* data)
  function cgltf_data (line 621) | static cgltf_data* parseGltf(cgltf_data* data, cgltf_result result, std:...
  function cgltf_data (line 657) | cgltf_data* parseGltf(const char* path, std::vector<Mesh>& meshes, std::...
  function cgltf_data (line 674) | cgltf_data* parseGlb(const void* buffer, size_t size, std::vector<Mesh>&...
  function areExtrasEqual (line 690) | bool areExtrasEqual(const cgltf_extras& lhs, const cgltf_extras& rhs)

FILE: gltf/parseobj.cpp
  function defaultFree (line 10) | static void defaultFree(void*, void* p)
  function textureIndex (line 15) | static int textureIndex(const std::vector<unsigned int>& textures, unsig...
  function fixupUri (line 24) | static void fixupUri(char* uri)
  function parseMaterialsObj (line 32) | static void parseMaterialsObj(fastObjMesh* obj, cgltf_data* data)
  function parseNodesObj (line 117) | static void parseNodesObj(fastObjMesh* obj, cgltf_data* data)
  function parseMeshObj (line 148) | static void parseMeshObj(fastObjMesh* obj, unsigned int face_offset, uns...
  function parseMeshGroupObj (line 252) | static void parseMeshGroupObj(fastObjMesh* obj, const fastObjGroup& og, ...
  function cgltf_data (line 295) | cgltf_data* parseObj(const char* path, std::vector<Mesh>& meshes, const ...

FILE: gltf/stream.cpp
  type Bounds (line 13) | struct Bounds
    method Bounds (line 17) | Bounds()
    method isValid (line 23) | bool isValid() const
    method getExtent (line 28) | float getExtent() const
    method merge (line 33) | void merge(const Bounds& other)
  function Bounds (line 43) | static Bounds computeBounds(const Mesh& mesh, cgltf_attribute_type type)
    method Bounds (line 17) | Bounds()
    method isValid (line 23) | bool isValid() const
    method getExtent (line 28) | float getExtent() const
    method merge (line 33) | void merge(const Bounds& other)
  function computeUvArea (line 95) | static float computeUvArea(const Mesh& mesh)
  function QuantizationPosition (line 130) | QuantizationPosition prepareQuantizationPosition(const std::vector<Mesh>...
  function follow (line 172) | static size_t follow(std::vector<size_t>& parents, size_t index)
  function prepareQuantizationTexture (line 185) | void prepareQuantizationTexture(cgltf_data* data, std::vector<Quantizati...
  function getPositionBounds (line 281) | void getPositionBounds(float min[3], float max[3], const Stream& stream,...
  function renormalizeWeights (line 331) | static void renormalizeWeights(uint8_t (&w)[4])
  function encodeSnorm (line 349) | static void encodeSnorm(void* destination, size_t count, size_t stride, ...
  function quantizeColor (line 383) | static int quantizeColor(float v, int bytebits, int bits)
  function encodeColor (line 393) | static void encodeColor(void* destination, size_t count, size_t stride, ...
  function StreamFormat (line 422) | static StreamFormat writeVertexStreamRaw(std::string& bin, const Stream&...
  function StreamFormat (line 437) | static StreamFormat writeVertexStreamFloat(std::string& bin, const Strea...
  function StreamFormat (line 469) | StreamFormat writeVertexStream(std::string& bin, const Stream& stream, c...
  function StreamFormat (line 759) | StreamFormat writeIndexStream(std::string& bin, const std::vector<unsign...
  function StreamFormat (line 790) | StreamFormat writeTimeStream(std::string& bin, const std::vector<float>&...
  function StreamFormat (line 802) | StreamFormat writeKeyframeStream(std::string& bin, cgltf_animation_path_...
  function compressVertexStream (line 869) | void compressVertexStream(std::string& bin, const std::string& data, siz...
  function compressIndexStream (line 879) | void compressIndexStream(std::string& bin, const std::string& data, size...
  function compressIndexSequence (line 896) | void compressIndexSequence(std::string& bin, const std::string& data, si...

FILE: gltf/wasistubs.cpp
  function __cxa_throw (line 7) | void __cxa_throw(void* ptr, void* type, void* destructor)
  function __imported_wasi_snapshot_preview1_path_open (line 23) | int32_t __imported_wasi_snapshot_preview1_path_open(int32_t arg0, int32_...
  function __imported_wasi_snapshot_preview1_fd_seek (line 34) | int32_t __imported_wasi_snapshot_preview1_fd_seek(int32_t arg0, int64_t ...
  function __imported_wasi_snapshot_preview1_clock_time_get (line 40) | int32_t __imported_wasi_snapshot_preview1_clock_time_get(int32_t arg0, i...

FILE: gltf/write.cpp
  function writeTextureInfo (line 182) | static void writeTextureInfo(std::string& json, const cgltf_data* data, ...
  function writeMaterialComponent (line 239) | static void writeMaterialComponent(std::string& json, const cgltf_data* ...
  function writeMaterialComponent (line 276) | static void writeMaterialComponent(std::string& json, const cgltf_data* ...
  function writeMaterialComponent (line 313) | static void writeMaterialComponent(std::string& json, const cgltf_data* ...
  function writeMaterialComponent (line 350) | static void writeMaterialComponent(std::string& json, const cgltf_data* ...
  function writeMaterialComponent (line 369) | static void writeMaterialComponent(std::string& json, const cgltf_data* ...
  function writeMaterialComponent (line 380) | static void writeMaterialComponent(std::string& json, const cgltf_data* ...
  function writeMaterialComponent (line 411) | static void writeMaterialComponent(std::string& json, const cgltf_data* ...
  function writeMaterialComponent (line 442) | static void writeMaterialComponent(std::string& json, const cgltf_data* ...
  function writeMaterialComponent (line 474) | static void writeMaterialComponent(std::string& json, const cgltf_data* ...
  function writeMaterialComponent (line 489) | static void writeMaterialComponent(std::string& json, const cgltf_data* ...
  function writeMaterialComponent (line 532) | static void writeMaterialComponent(std::string& json, const cgltf_data* ...
  function writeMaterialComponent (line 557) | static void writeMaterialComponent(std::string& json, const cgltf_data* ...
  function writeMaterialComponent (line 568) | static void writeMaterialComponent(std::string& json, const cgltf_data* ...
  function writeMaterial (line 599) | void writeMaterial(std::string& json, const cgltf_data* data, const cglt...
  function getBufferView (line 716) | size_t getBufferView(std::vector<BufferView>& views, BufferView::Kind ki...
  function writeBufferView (line 735) | void writeBufferView(std::string& json, BufferView::Kind kind, StreamFor...
  function writeAccessor (line 788) | static void writeAccessor(std::string& json, size_t view, size_t offset,...
  function writeEmbeddedImage (line 820) | static void writeEmbeddedImage(std::string& json, std::vector<BufferView...
  function decodeUri (line 834) | static std::string decodeUri(const char* uri)
  function writeSampler (line 847) | void writeSampler(std::string& json, const cgltf_sampler& sampler)
  function writeImageError (line 875) | static void writeImageError(std::string& json, const char* action, size_...
  function writeImageData (line 884) | static void writeImageData(std::string& json, std::vector<BufferView>& v...
  function writeImage (line 910) | void writeImage(std::string& json, std::vector<BufferView>& views, const...
  function writeTexture (line 953) | void writeTexture(std::string& json, const cgltf_texture& texture, const...
  function writePrimitiveAccessor (line 1000) | static void writePrimitiveAccessor(std::string& json_accessors, const St...
  function writePrimitiveAttribute (line 1017) | static void writePrimitiveAttribute(std::string& json, const Stream& str...
  function writeMeshAttributesInterleaved (line 1038) | static void writeMeshAttributesInterleaved(std::string& json, std::vecto...
  function writeMeshAttributes (line 1096) | void writeMeshAttributes(std::string& json, std::vector<BufferView>& vie...
  function writeMeshIndices (line 1125) | size_t writeMeshIndices(std::vector<BufferView>& views, std::string& jso...
  function writeMeshGeometry (line 1142) | void writeMeshGeometry(std::string& json, std::vector<BufferView>& views...
  function writeAnimationTime (line 1174) | static size_t writeAnimationTime(std::vector<BufferView>& views, std::st...
  function writeAnimationTime (line 1191) | static size_t writeAnimationTime(std::vector<BufferView>& views, std::st...
  function writeJointBindMatrices (line 1201) | size_t writeJointBindMatrices(std::vector<BufferView>& views, std::strin...
  function writeInstanceData (line 1242) | static void writeInstanceData(std::vector<BufferView>& views, std::strin...
  function writeInstances (line 1257) | size_t writeInstances(std::vector<BufferView>& views, std::string& json_...
  function writeMeshNode (line 1317) | void writeMeshNode(std::string& json, size_t mesh_offset, cgltf_node* no...
  function writeMeshNodeInstanced (line 1347) | void writeMeshNodeInstanced(std::string& json, size_t mesh_offset, size_...
  function writeSkin (line 1377) | void writeSkin(std::string& json, const cgltf_skin& skin, size_t matrix_...
  function writeNode (line 1405) | void writeNode(std::string& json, const cgltf_node& node, const std::vec...
  function writeAnimation (line 1497) | void writeAnimation(std::string& json, std::vector<BufferView>& views, s...
  function writeCamera (line 1649) | void writeCamera(std::string& json, const cgltf_camera& camera)
  function writeLight (line 1695) | void writeLight(std::string& json, const cgltf_light& light)
  function writeArray (line 1732) | void writeArray(std::string& json, const char* name, const std::string& ...
  function writeExtensions (line 1745) | void writeExtensions(std::string& json, const ExtensionInfo* extensions,...
  function writeExtras (line 1787) | void writeExtras(std::string& json, const cgltf_extras& extras)
  function writeScene (line 1797) | void writeScene(std::string& json, const cgltf_scene& scene, const std::...

FILE: js/benchmark.js
  function bytes (line 10) | function bytes(view) {

FILE: js/meshopt_clusterizer.d.ts
  class Bounds (line 4) | class Bounds {
  class MeshletBuffers (line 18) | class MeshletBuffers {
  class Meshlet (line 25) | class Meshlet {

FILE: js/meshopt_clusterizer.js
  function unpack (line 27) | function unpack(data) {
  function assert (line 40) | function assert(cond) {
  function bytes (line 46) | function bytes(view) {
  function extractMeshlet (line 53) | function extractMeshlet(buffers, index) {
  function buildMeshlets (line 65) | function buildMeshlets(
  function extractBounds (line 150) | function extractBounds(boundsp) {
  function computeMeshletBounds (line 169) | function computeMeshletBounds(buffers, vertex_positions, vertex_count, v...
  function computeClusterBounds (line 210) | function computeClusterBounds(indices, vertex_positions, vertex_count, v...
  function computeSphereBounds (line 233) | function computeSphereBounds(positions, count, positions_stride, radii, ...

FILE: js/meshopt_decoder.cjs
  function unpack (line 35) | function unpack(data) {
  function decode (line 48) | function decode(instance, fun, target, count, size, source, filter) {
  function createWorker (line 83) | function createWorker(url) {
  function initWorkers (line 101) | function initWorkers(count) {
  function decodeWorker (line 129) | function decodeWorker(count, size, source, mode, filter) {
  function workerProcess (line 148) | function workerProcess(event) {

FILE: js/meshopt_decoder.mjs
  function unpack (line 35) | function unpack(data) {
  function decode (line 48) | function decode(instance, fun, target, count, size, source, filter) {
  function createWorker (line 83) | function createWorker(url) {
  function initWorkers (line 101) | function initWorkers(count) {
  function decodeWorker (line 129) | function decodeWorker(count, size, source, mode, filter) {
  function workerProcess (line 148) | function workerProcess(event) {

FILE: js/meshopt_decoder_reference.js
  function assert (line 12) | function assert(cond) {
  function dezig (line 18) | function dezig(v) {
  function pushfifo (line 276) | function pushfifo(fifo, n) {
  function readLEB128 (line 296) | function readLEB128() {
  function decodeIndex (line 311) | function decodeIndex(v) {
  function readLEB128 (line 421) | function readLEB128() {

FILE: js/meshopt_encoder.d.ts
  type ExpMode (line 3) | type ExpMode = 'Separate' | 'SharedVector' | 'SharedComponent' | 'Clamped';

FILE: js/meshopt_encoder.js
  function unpack (line 29) | function unpack(data) {
  function assert (line 42) | function assert(cond) {
  function bytes (line 48) | function bytes(view) {
  function reorder (line 52) | function reorder(fun, indices, vertices, optf) {
  function spatialsort (line 75) | function spatialsort(fun, positions, count, stride) {
  function encode (line 90) | function encode(fun, bound, source, count, size, level, version) {
  function maxindex (line 103) | function maxindex(source) {
  function index32 (line 112) | function index32(source, size) {
  function filter (line 122) | function filter(fun, source, count, stride, bits, insize, mode) {

FILE: js/meshopt_encoder.test.js
  function bytes (line 10) | function bytes(view) {

FILE: js/meshopt_simplifier.d.ts
  type Flags (line 3) | type Flags = 'LockBorder' | 'Sparse' | 'ErrorAbsolute' | 'Prune' | 'Regu...

FILE: js/meshopt_simplifier.js
  function unpack (line 27) | function unpack(data) {
  function assert (line 40) | function assert(cond) {
  function bytes (line 46) | function bytes(view) {
  function genremap (line 50) | function genremap(fun, positions, vertices, stride) {
  function reorder (line 65) | function reorder(fun, indices, vertices) {
  function maxindex (line 85) | function maxindex(source) {
  function simplify (line 94) | function simplify(fun, indices, index_count, vertex_positions, vertex_co...
  function simplifyAttr (line 114) | function simplifyAttr(
  function simplifyUpdate (line 172) | function simplifyUpdate(
  function simplifyScale (line 229) | function simplifyScale(fun, vertex_positions, vertex_count, vertex_posit...
  function simplifyPoints (line 239) | function simplifyPoints(
  function simplifySloppy (line 267) | function simplifySloppy(
  function simplifyPrune (line 301) | function simplifyPrune(fun, indices, index_count, vertex_positions, vert...

FILE: src/allocator.cpp
  function meshopt_setAllocator (line 12) | void meshopt_setAllocator(void* (MESHOPTIMIZER_ALLOC_CALLCONV* allocate)...

FILE: src/clusterizer.cpp
  type meshopt (line 23) | namespace meshopt
    type TriangleAdjacency2 (line 33) | struct TriangleAdjacency2
    function buildTriangleAdjacency (line 40) | static void buildTriangleAdjacency(TriangleAdjacency2& adjacency, cons...
    function buildTriangleAdjacencySparse (line 88) | static void buildTriangleAdjacencySparse(TriangleAdjacency2& adjacency...
    function clearUsed (line 154) | static void clearUsed(short* used, size_t vertex_count, const unsigned...
    type Cone (line 167) | struct Cone
    function getMeshletScore (line 173) | static float getMeshletScore(float distance, float spread, float cone_...
    function Cone (line 181) | static Cone getMeshletCone(const Cone& acc, unsigned int triangle_count)
    function computeTriangleCones (line 201) | static float computeTriangleCones(Cone* triangles, const unsigned int*...
    function appendMeshlet (line 243) | static bool appendMeshlet(meshopt_Meshlet& meshlet, unsigned int a, un...
    function getNeighborTriangle (line 294) | static unsigned int getNeighborTriangle(const meshopt_Meshlet& meshlet...
    function appendSeedTriangles (line 356) | static size_t appendSeedTriangles(unsigned int* seeds, const meshopt_M...
    function pruneSeedTriangles (line 424) | static size_t pruneSeedTriangles(unsigned int* seeds, size_t seed_coun...
    function selectSeedTriangle (line 439) | static unsigned int selectSeedTriangle(const unsigned int* seeds, size...
    type KDNode (line 465) | struct KDNode
    function kdtreePartition (line 479) | static size_t kdtreePartition(unsigned int* indices, size_t count, con...
    function kdtreeBuildLeaf (line 500) | static size_t kdtreeBuildLeaf(size_t offset, KDNode* nodes, size_t nod...
    function kdtreeBuild (line 524) | static size_t kdtreeBuild(size_t offset, KDNode* nodes, size_t node_co...
    function kdtreeNearest (line 575) | static void kdtreeNearest(KDNode* nodes, unsigned int root, const floa...
    type BVHBoxT (line 633) | struct BVHBoxT
    type BVHBox (line 639) | struct BVHBox
    function boxMerge (line 646) | static float boxMerge(BVHBoxT& box, const BVHBox& other)
    function boxMerge (line 667) | static float boxMerge(BVHBoxT& box, const BVHBox& other)
    function boxMerge (line 688) | static float boxMerge(BVHBoxT& box, const BVHBox& other)
    function radixFloat (line 701) | inline unsigned int radixFloat(unsigned int v)
    function computeHistogram (line 709) | static void computeHistogram(unsigned int (&hist)[1024][3], const floa...
    function radixPass (line 744) | static void radixPass(unsigned int* destination, const unsigned int* s...
    function bvhPrepare (line 757) | static void bvhPrepare(BVHBox* boxes, float* centroids, const unsigned...
    function bvhCountVertices (line 785) | static size_t bvhCountVertices(const unsigned int* order, size_t count...
    function bvhPackLeaf (line 813) | static void bvhPackLeaf(unsigned char* boundary, size_t count)
    function bvhPackTail (line 822) | static void bvhPackTail(unsigned char* boundary, const unsigned int* o...
    function bvhDivisible (line 843) | static bool bvhDivisible(size_t count, size_t min, size_t max)
    function bvhComputeArea (line 851) | static void bvhComputeArea(float* areas, const BVHBox* boxes, const un...
    function bvhPivot (line 866) | static size_t bvhPivot(const float* areas, const unsigned int* vertice...
    function bvhPartition (line 916) | static void bvhPartition(unsigned int* target, const unsigned int* ord...
    function bvhSplit (line 932) | static void bvhSplit(const BVHBox* boxes, unsigned int* orderx, unsign...
  function meshopt_buildMeshletsBound (line 1009) | size_t meshopt_buildMeshletsBound(size_t index_count, size_t max_vertice...
  function meshopt_buildMeshletsFlex (line 1027) | size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int...
  function meshopt_buildMeshlets (line 1218) | size_t meshopt_buildMeshlets(meshopt_Meshlet* meshlets, unsigned int* me...
  function meshopt_buildMeshletsScan (line 1223) | size_t meshopt_buildMeshletsScan(meshopt_Meshlet* meshlets, unsigned int...
  function meshopt_buildMeshletsSpatial (line 1258) | size_t meshopt_buildMeshletsSpatial(struct meshopt_Meshlet* meshlets, un...

FILE: src/indexanalyzer.cpp
  function meshopt_VertexCacheStatistics (line 7) | meshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const unsigned ...
  function meshopt_VertexFetchStatistics (line 75) | meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch(const unsigned ...

FILE: src/indexcodec.cpp
  type meshopt (line 10) | namespace meshopt
    function rotateTriangle (line 27) | static int rotateTriangle(unsigned int a, unsigned int b, unsigned int...
    function getEdgeFifo (line 34) | static int getEdgeFifo(EdgeFifo fifo, unsigned int a, unsigned int b, ...
    function pushEdgeFifo (line 54) | static void pushEdgeFifo(EdgeFifo fifo, unsigned int a, unsigned int b...
    function getVertexFifo (line 61) | static int getVertexFifo(VertexFifo fifo, unsigned int v, size_t offset)
    function pushVertexFifo (line 74) | static void pushVertexFifo(VertexFifo fifo, unsigned int v, size_t& of...
    function encodeVByte (line 80) | static void encodeVByte(unsigned char*& data, unsigned int v)
    function decodeVByte (line 90) | static unsigned int decodeVByte(const unsigned char*& data)
    function encodeIndex (line 116) | static void encodeIndex(unsigned char*& data, unsigned int index, unsi...
    function decodeIndex (line 124) | static unsigned int decodeIndex(const unsigned char*& data, unsigned i...
    function getCodeAuxIndex (line 132) | static int getCodeAuxIndex(unsigned char v, const unsigned char* table)
    function writeTriangle (line 141) | static void writeTriangle(void* destination, size_t offset, size_t ind...
  function meshopt_encodeIndexBuffer (line 159) | size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_si...
  function meshopt_encodeIndexBufferBound (line 338) | size_t meshopt_encodeIndexBufferBound(size_t index_count, size_t vertex_...
  function meshopt_encodeIndexVersion (line 354) | void meshopt_encodeIndexVersion(int version)
  function meshopt_decodeIndexVersion (line 361) | int meshopt_decodeIndexVersion(const unsigned char* buffer, size_t buffe...
  function meshopt_decodeIndexBuffer (line 378) | int meshopt_decodeIndexBuffer(void* destination, size_t index_count, siz...
  function meshopt_encodeIndexSequence (line 559) | size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_...
  function meshopt_encodeIndexSequenceBound (line 614) | size_t meshopt_encodeIndexSequenceBound(size_t index_count, size_t verte...
  function meshopt_decodeIndexSequence (line 628) | int meshopt_decodeIndexSequence(void* destination, size_t index_count, s...

FILE: src/indexgenerator.cpp
  type meshopt (line 11) | namespace meshopt
    function hashUpdate4 (line 14) | static unsigned int hashUpdate4(unsigned int h, const unsigned char* k...
    type VertexHasher (line 38) | struct VertexHasher
      method hash (line 44) | size_t hash(unsigned int index) const
      method equal (line 49) | bool equal(unsigned int lhs, unsigned int rhs) const
    type VertexStreamHasher (line 55) | struct VertexStreamHasher
      method hash (line 60) | size_t hash(unsigned int index) const
      method equal (line 75) | bool equal(unsigned int lhs, unsigned int rhs) const
    type VertexCustomHasher (line 90) | struct VertexCustomHasher
      method hash (line 98) | size_t hash(unsigned int index) const
      method equal (line 118) | bool equal(unsigned int lhs, unsigned int rhs) const
    type EdgeHasher (line 130) | struct EdgeHasher
      method hash (line 134) | size_t hash(unsigned long long edge) const
      method equal (line 157) | bool equal(unsigned long long lhs, unsigned long long rhs) const
    function hashBuckets (line 169) | static size_t hashBuckets(size_t count)
    function T (line 179) | static T* hashLookup(T* table, size_t buckets, const Hash& hash, const...
    function buildPositionRemap (line 205) | static void buildPositionRemap(unsigned int* remap, const float* verte...
    function generateVertexRemap (line 228) | static size_t generateVertexRemap(unsigned int* remap, const unsigned ...
    function remapVertices (line 265) | static void remapVertices(void* destination, const void* vertices, siz...
    function generateShadowBuffer (line 279) | static void generateShadowBuffer(unsigned int* destination, const unsi...
  function meshopt_generateVertexRemap (line 309) | size_t meshopt_generateVertexRemap(unsigned int* destination, const unsi...
  function meshopt_generateVertexRemapMulti (line 323) | size_t meshopt_generateVertexRemapMulti(unsigned int* destination, const...
  function meshopt_generateVertexRemapCustom (line 343) | size_t meshopt_generateVertexRemapCustom(unsigned int* destination, cons...
  function meshopt_remapVertexBuffer (line 358) | void meshopt_remapVertexBuffer(void* destination, const void* vertices, ...
  function meshopt_remapIndexBuffer (line 394) | void meshopt_remapIndexBuffer(unsigned int* destination, const unsigned ...
  function meshopt_generateShadowIndexBuffer (line 407) | void meshopt_generateShadowIndexBuffer(unsigned int* destination, const ...
  function meshopt_generateShadowIndexBufferMulti (line 422) | void meshopt_generateShadowIndexBufferMulti(unsigned int* destination, c...
  function meshopt_generatePositionRemap (line 442) | void meshopt_generatePositionRemap(unsigned int* destination, const floa...
  function meshopt_generateAdjacencyIndexBuffer (line 467) | void meshopt_generateAdjacencyIndexBuffer(unsigned int* destination, con...
  function meshopt_generateTessellationIndexBuffer (line 538) | void meshopt_generateTessellationIndexBuffer(unsigned int* destination, ...
  function meshopt_generateProvokingIndexBuffer (line 610) | size_t meshopt_generateProvokingIndexBuffer(unsigned int* destination, u...

FILE: src/meshletcodec.cpp
  type meshopt (line 71) | namespace meshopt
    function rotateTriangle (line 76) | static int rotateTriangle(unsigned int a, unsigned int b, unsigned int c)
    function getEdgeFifo8 (line 81) | static int getEdgeFifo8(EdgeFifo8 fifo, unsigned int a, unsigned int b...
    function pushEdgeFifo8 (line 101) | static void pushEdgeFifo8(EdgeFifo8 fifo, unsigned int a, unsigned int...
    function encodeTriangles (line 108) | static size_t encodeTriangles(unsigned char* codes, unsigned char* ext...
    function encodeVertices (line 197) | static size_t encodeVertices(unsigned char* ctrl, unsigned char* data,...
    function writeTriangle (line 248) | inline void writeTriangle(unsigned int* triangles, size_t i, unsigned ...
    function writeTriangle (line 254) | inline void writeTriangle(unsigned char* triangles, size_t i, unsigned...
    function decodeMeshlet (line 359) | static int decodeMeshlet(void* vertices, void* triangles, const unsign...
    function decodeBuildTables (line 397) | static bool decodeBuildTables()
    function SIMD_TARGET (line 522) | SIMD_TARGET
    function SIMD_TARGET (line 538) | SIMD_TARGET
    function SIMD_TARGET (line 563) | SIMD_TARGET
    function SIMD_TARGET (line 580) | SIMD_TARGET
    function SIMD_TARGET (line 613) | SIMD_TARGET
    function SIMD_TARGET (line 670) | SIMD_TARGET
    function SIMD_TARGET (line 748) | SIMD_TARGET
    function SIMD_TARGET (line 805) | SIMD_TARGET
    function SIMD_TARGET (line 863) | SIMD_TARGET SIMD_FLATTEN static int
  function meshopt_encodeMeshletBound (line 898) | size_t meshopt_encodeMeshletBound(size_t max_vertices, size_t max_triang...
  function meshopt_encodeMeshlet (line 911) | size_t meshopt_encodeMeshlet(unsigned char* buffer, size_t buffer_size, ...
  function meshopt_decodeMeshlet (line 981) | int meshopt_decodeMeshlet(void* vertices, size_t vertex_count, size_t ve...
  function meshopt_decodeMeshletRaw (line 1015) | int meshopt_decodeMeshletRaw(unsigned int* vertices, size_t vertex_count...

FILE: src/meshletutils.cpp
  type meshopt (line 13) | namespace meshopt
    function computeBoundingSphere (line 22) | static void computeBoundingSphere(float result[4], const float* points...
    function meshopt_Bounds (line 134) | static meshopt_Bounds computeClusterBounds(const unsigned int* indices...
  function meshopt_Bounds (line 278) | meshopt_Bounds meshopt_computeClusterBounds(const unsigned int* indices,...
  function meshopt_Bounds (line 311) | meshopt_Bounds meshopt_computeMeshletBounds(const unsigned int* meshlet_...
  function meshopt_Bounds (line 339) | meshopt_Bounds meshopt_computeSphereBounds(const float* positions, size_...
  function meshopt_optimizeMeshlet (line 366) | void meshopt_optimizeMeshlet(unsigned int* meshlet_vertices, unsigned ch...
  function meshopt_extractMeshletIndices (line 507) | size_t meshopt_extractMeshletIndices(unsigned int* vertices, unsigned ch...

FILE: src/meshoptimizer.h
  type meshopt_Stream (line 46) | struct meshopt_Stream
  type meshopt_Stream (line 75) | struct meshopt_Stream
  type meshopt_Stream (line 125) | struct meshopt_Stream
  type meshopt_EncodeExpMode (line 423) | enum meshopt_EncodeExpMode
  type meshopt_EncodeExpMode (line 437) | enum meshopt_EncodeExpMode
  type meshopt_VertexCacheStatistics (line 609) | struct meshopt_VertexCacheStatistics
  type meshopt_VertexFetchStatistics (line 624) | struct meshopt_VertexFetchStatistics
  type meshopt_OverdrawStatistics (line 637) | struct meshopt_OverdrawStatistics
  type meshopt_CoverageStatistics (line 653) | struct meshopt_CoverageStatistics
  type meshopt_Meshlet (line 674) | struct meshopt_Meshlet
  type meshopt_Meshlet (line 700) | struct meshopt_Meshlet
  type meshopt_Meshlet (line 701) | struct meshopt_Meshlet
  type meshopt_Meshlet (line 717) | struct meshopt_Meshlet
  type meshopt_Meshlet (line 730) | struct meshopt_Meshlet
  type meshopt_Bounds (line 741) | struct meshopt_Bounds
  function meshopt_quantizeUnorm (line 1032) | inline int meshopt_quantizeUnorm(float v, int N)
  function meshopt_quantizeSnorm (line 1042) | inline int meshopt_quantizeSnorm(float v, int N)
  function class (line 1057) | class meshopt_Allocator
  function meshopt_generateVertexRemap (line 1162) | size_t meshopt_generateVertexRemap(unsigned int* destination, const T* i...
  function meshopt_generateVertexRemapMulti (line 1170) | size_t meshopt_generateVertexRemapMulti(unsigned int* destination, const...
  function meshopt_generateVertexRemapCustom (line 1178) | size_t meshopt_generateVertexRemapCustom(unsigned int* destination, cons...
  function meshopt_generateVertexRemapCustom (line 1189) | size_t meshopt_generateVertexRemapCustom(unsigned int* destination, cons...
  function meshopt_remapIndexBuffer (line 1202) | void meshopt_remapIndexBuffer(T* destination, const T* indices, size_t i...
  function meshopt_generateShadowIndexBuffer (line 1211) | void meshopt_generateShadowIndexBuffer(T* destination, const T* indices,...
  function meshopt_generateShadowIndexBufferMulti (line 1220) | void meshopt_generateShadowIndexBufferMulti(T* destination, const T* ind...
  function meshopt_generateAdjacencyIndexBuffer (line 1229) | void meshopt_generateAdjacencyIndexBuffer(T* destination, const T* indic...
  function meshopt_generateTessellationIndexBuffer (line 1238) | void meshopt_generateTessellationIndexBuffer(T* destination, const T* in...
  function meshopt_generateProvokingIndexBuffer (line 1247) | size_t meshopt_generateProvokingIndexBuffer(T* destination, unsigned int...
  function meshopt_optimizeVertexCache (line 1260) | void meshopt_optimizeVertexCache(T* destination, const T* indices, size_...
  function meshopt_optimizeVertexCacheStrip (line 1269) | void meshopt_optimizeVertexCacheStrip(T* destination, const T* indices, ...
  function meshopt_optimizeVertexCacheFifo (line 1278) | void meshopt_optimizeVertexCacheFifo(T* destination, const T* indices, s...
  function meshopt_optimizeOverdraw (line 1287) | void meshopt_optimizeOverdraw(T* destination, const T* indices, size_t i...
  function meshopt_optimizeVertexFetchRemap (line 1296) | size_t meshopt_optimizeVertexFetchRemap(unsigned int* destination, const...
  function meshopt_optimizeVertexFetch (line 1304) | size_t meshopt_optimizeVertexFetch(void* destination, T* indices, size_t...
  function meshopt_encodeIndexBuffer (line 1312) | size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_si...
  function meshopt_decodeIndexBuffer (line 1320) | int meshopt_decodeIndexBuffer(T* destination, size_t index_count, const ...
  function meshopt_encodeIndexSequence (line 1329) | size_t meshopt_encodeIndexSequence(unsigned char* buffer, size_t buffer_...
  function meshopt_decodeIndexSequence (line 1337) | int meshopt_decodeIndexSequence(T* destination, size_t index_count, cons...
  function meshopt_decodeMeshlet (line 1346) | int meshopt_decodeMeshlet(V* vertices, size_t vertex_count, T* triangles...
  function meshopt_encodeVertexBufferLevel (line 1354) | inline size_t meshopt_encodeVertexBufferLevel(unsigned char* buffer, siz...
  function meshopt_simplify (line 1360) | size_t meshopt_simplify(T* destination, const T* indices, size_t index_c...
  function meshopt_simplifyWithAttributes (line 1369) | size_t meshopt_simplifyWithAttributes(T* destination, const T* indices, ...
  function meshopt_simplifyWithUpdate (line 1378) | size_t meshopt_simplifyWithUpdate(T* indices, size_t index_count, float*...
  function meshopt_simplifySloppy (line 1386) | size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t i...
  function meshopt_simplifySloppy (line 1395) | size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t i...
  function meshopt_simplifyPrune (line 1404) | size_t meshopt_simplifyPrune(T* destination, const T* indices, size_t in...
  function meshopt_stripify (line 1413) | size_t meshopt_stripify(T* destination, const T* indices, size_t index_c...
  function meshopt_unstripify (line 1422) | size_t meshopt_unstripify(T* destination, const T* indices, size_t index...
  function meshopt_VertexCacheStatistics (line 1431) | meshopt_VertexCacheStatistics meshopt_analyzeVertexCache(const T* indice...
  function meshopt_VertexFetchStatistics (line 1439) | meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch(const T* indice...
  function meshopt_OverdrawStatistics (line 1447) | meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const T* indices, siz...
  function meshopt_CoverageStatistics (line 1455) | meshopt_CoverageStatistics meshopt_analyzeCoverage(const T* indices, siz...
  function meshopt_buildMeshlets (line 1463) | size_t meshopt_buildMeshlets(meshopt_Meshlet* meshlets, unsigned int* me...
  function meshopt_buildMeshletsScan (line 1471) | size_t meshopt_buildMeshletsScan(meshopt_Meshlet* meshlets, unsigned int...
  function meshopt_buildMeshletsFlex (line 1479) | size_t meshopt_buildMeshletsFlex(meshopt_Meshlet* meshlets, unsigned int...
  function meshopt_buildMeshletsSpatial (line 1487) | size_t meshopt_buildMeshletsSpatial(meshopt_Meshlet* meshlets, unsigned ...
  function meshopt_Bounds (line 1495) | meshopt_Bounds meshopt_computeClusterBounds(const T* indices, size_t ind...
  function meshopt_partitionClusters (line 1503) | size_t meshopt_partitionClusters(unsigned int* destination, const T* clu...
  function meshopt_spatialSortTriangles (line 1511) | void meshopt_spatialSortTriangles(T* destination, const T* indices, size...

FILE: src/opacitymap.cpp
  type meshopt (line 8) | namespace meshopt
    function getLevelSize (line 15) | inline size_t getLevelSize(int level, int states)
    type Texture (line 21) | struct Texture
    function sampleTexture (line 28) | static float sampleTexture(const Texture& texture, float u, float v)
    function hashUpdate4u (line 68) | static unsigned int hashUpdate4u(unsigned int h, const unsigned char* ...
    type TriangleOMM (line 93) | struct TriangleOMM
    type TriangleOMMHasher (line 99) | struct TriangleOMMHasher
      method hash (line 103) | size_t hash(unsigned int index) const
      method equal (line 110) | bool equal(unsigned int lhs, unsigned int rhs) const
    type OMMHasher (line 119) | struct OMMHasher
      method hash (line 126) | size_t hash(unsigned int index) const
      method equal (line 146) | bool equal(unsigned int lhs, unsigned int rhs) const
    function hashBuckets3 (line 154) | static size_t hashBuckets3(size_t count)
    function T (line 164) | static T* hashLookup3(T* table, size_t buckets, const Hash& hash, cons...
    function quantizeSubpixel (line 190) | inline int quantizeSubpixel(float v, unsigned int size)
    function rasterizeEdge (line 195) | static int rasterizeEdge(float u0, float v0, float u1, float v1, int e...
    function rasterizeOpacity0 (line 219) | static void rasterizeOpacity0(unsigned char* result, size_t index, flo...
    function rasterizeOpacity1 (line 261) | static void rasterizeOpacity1(unsigned char* result, size_t index, int...
    function rasterizeOpacityRec (line 304) | static void rasterizeOpacityRec(unsigned char* result, size_t index, i...
    function getSpecialIndex (line 348) | static int getSpecialIndex(const unsigned char* data, int level, int s...
  function meshopt_opacityMapMeasure (line 374) | size_t meshopt_opacityMapMeasure(unsigned char* levels, unsigned int* so...
  function meshopt_opacityMapEntrySize (line 450) | size_t meshopt_opacityMapEntrySize(int level, int states)
  function meshopt_opacityMapRasterize (line 458) | void meshopt_opacityMapRasterize(unsigned char* result, int level, int s...
  function meshopt_opacityMapCompact (line 490) | size_t meshopt_opacityMapCompact(unsigned char* data, size_t data_size, ...

FILE: src/overdrawoptimizer.cpp
  type meshopt (line 10) | namespace meshopt
    function calculateSortData (line 13) | static void calculateSortData(float* sort_data, const unsigned int* in...
    function calculateSortOrderRadix (line 85) | static void calculateSortOrderRadix(unsigned int* sort_order, const fl...
    function updateCache (line 135) | static unsigned int updateCache(unsigned int a, unsigned int b, unsign...
    function generateHardBoundaries (line 161) | static size_t generateHardBoundaries(unsigned int* destination, const ...
    function generateSoftBoundaries (line 190) | static size_t generateSoftBoundaries(unsigned int* destination, const ...
  function meshopt_optimizeOverdraw (line 270) | void meshopt_optimizeOverdraw(unsigned int* destination, const unsigned ...

FILE: src/partition.cpp
  type meshopt (line 10) | namespace meshopt
    type ClusterAdjacency (line 16) | struct ClusterAdjacency
    function filterClusterIndices (line 23) | static void filterClusterIndices(unsigned int* data, unsigned int* off...
    function computeClusterBounds (line 58) | static float computeClusterBounds(const unsigned int* indices, size_t ...
    function buildClusterAdjacency (line 98) | static void buildClusterAdjacency(ClusterAdjacency& adjacency, const u...
    type ClusterGroup (line 206) | struct ClusterGroup
    type GroupOrder (line 217) | struct GroupOrder
    function heapPush (line 223) | static void heapPush(GroupOrder* heap, size_t size, GroupOrder item)
    function GroupOrder (line 241) | static GroupOrder heapPop(GroupOrder* heap, size_t size)
    function countShared (line 271) | static unsigned int countShared(const ClusterGroup* groups, int group1...
    function mergeBounds (line 289) | static void mergeBounds(ClusterGroup& target, const ClusterGroup& source)
    function boundsScore (line 315) | static float boundsScore(const ClusterGroup& target, const ClusterGrou...
    function pickGroupToMerge (line 326) | static int pickGroupToMerge(const ClusterGroup* groups, int id, const ...
    function mergeLeaf (line 368) | static void mergeLeaf(ClusterGroup* groups, unsigned int* order, size_...
    function mergePartition (line 419) | static size_t mergePartition(unsigned int* order, size_t count, const ...
    function mergeSpatial (line 440) | static void mergeSpatial(ClusterGroup* groups, unsigned int* order, si...
  function meshopt_partitionClusters (line 484) | size_t meshopt_partitionClusters(unsigned int* destination, const unsign...

FILE: src/quantization.cpp
  function meshopt_quantizeHalf (line 12) | unsigned short meshopt_quantizeHalf(float v)
  function meshopt_quantizeFloat (line 35) | float meshopt_quantizeFloat(float v, int N)
  function meshopt_dequantizeHalf (line 58) | float meshopt_dequantizeHalf(unsigned short h)

FILE: src/rasterizer.cpp
  type meshopt (line 10) | namespace meshopt
    type OverdrawBuffer (line 15) | struct OverdrawBuffer
    function computeDepthGradients (line 21) | static float computeDepthGradients(float& dzdx, float& dzdy, float x1,...
    function rasterize (line 38) | static void rasterize(OverdrawBuffer* buffer, float v1x, float v1y, fl...
    function transformTriangles (line 149) | static float transformTriangles(float* triangles, const unsigned int* ...
    function rasterizeTriangles (line 192) | static void rasterizeTriangles(OverdrawBuffer* buffer, const float* tr...
  function meshopt_OverdrawStatistics (line 217) | meshopt_OverdrawStatistics meshopt_analyzeOverdraw(const unsigned int* i...
  function meshopt_CoverageStatistics (line 255) | meshopt_CoverageStatistics meshopt_analyzeCoverage(const unsigned int* i...

FILE: src/simplifier.cpp
  type meshopt (line 31) | namespace meshopt
    type EdgeAdjacency (line 34) | struct EdgeAdjacency
      type Edge (line 36) | struct Edge
    function prepareEdgeAdjacency (line 46) | static void prepareEdgeAdjacency(EdgeAdjacency& adjacency, size_t inde...
    function updateEdgeAdjacency (line 52) | static void updateEdgeAdjacency(EdgeAdjacency& adjacency, const unsign...
    type PositionHasher (line 111) | struct PositionHasher
      method hash (line 117) | size_t hash(unsigned int index) const
      method equal (line 138) | bool equal(unsigned int lhs, unsigned int rhs) const
    type RemapHasher (line 150) | struct RemapHasher
      method hash (line 154) | size_t hash(unsigned int id) const
      method equal (line 159) | bool equal(unsigned int lhs, unsigned int rhs) const
    function hashBuckets2 (line 165) | static size_t hashBuckets2(size_t count)
    function T (line 175) | static T* hashLookup2(T* table, size_t buckets, const Hash& hash, cons...
    function buildPositionRemap (line 201) | static void buildPositionRemap(unsigned int* remap, unsigned int* wedg...
    type VertexKind (line 296) | enum VertexKind
    function hasEdge (line 333) | static bool hasEdge(const EdgeAdjacency& adjacency, unsigned int a, un...
    function hasEdge (line 345) | static bool hasEdge(const EdgeAdjacency& adjacency, unsigned int a, un...
    function classifyVertices (line 364) | static void classifyVertices(unsigned char* result, unsigned int* loop...
    type Vector3 (line 544) | struct Vector3
    function rescalePositions (line 549) | static float rescalePositions(Vector3* result, const float* vertex_pos...
    function rescaleAttributes (line 605) | static void rescaleAttributes(float* result, const float* vertex_attri...
    function finalizeVertices (line 623) | static void finalizeVertices(float* vertex_positions_data, size_t vert...
    type Quadric (line 667) | struct Quadric
    type QuadricGrad (line 676) | struct QuadricGrad
    type Reservoir (line 682) | struct Reservoir
    type Collapse (line 689) | struct Collapse
    function normalize (line 702) | static float normalize(Vector3& v)
    function quadricAdd (line 716) | static void quadricAdd(Quadric& Q, const Quadric& R)
    function quadricAdd (line 731) | static void quadricAdd(QuadricGrad& G, const QuadricGrad& R)
    function quadricAdd (line 739) | static void quadricAdd(QuadricGrad* G, const QuadricGrad* R, size_t at...
    function quadricEval (line 750) | static float quadricEval(const Quadric& Q, const Vector3& v)
    function quadricError (line 776) | static float quadricError(const Quadric& Q, const Vector3& v)
    function quadricError (line 784) | static float quadricError(const Quadric& Q, const QuadricGrad* G, size...
    function quadricFromPlane (line 801) | static void quadricFromPlane(Quadric& Q, float a, float b, float c, fl...
    function quadricFromPoint (line 821) | static void quadricFromPoint(Quadric& Q, float x, float y, float z, fl...
    function quadricFromTriangle (line 832) | static void quadricFromTriangle(Quadric& Q, const Vector3& p0, const V...
    function quadricFromTriangleEdge (line 847) | static void quadricFromTriangleEdge(Quadric& Q, const Vector3& p0, con...
    function quadricFromAttributes (line 870) | static void quadricFromAttributes(Quadric& Q, QuadricGrad* G, const Ve...
    function quadricVolumeGradient (line 952) | static void quadricVolumeGradient(QuadricGrad& G, const Vector3& p0, c...
    function quadricSolve (line 967) | static bool quadricSolve(Vector3& p, const Quadric& Q, const QuadricGr...
    function quadricReduceAttributes (line 1024) | static void quadricReduceAttributes(Quadric& Q, const Quadric& A, cons...
    function fillFaceQuadrics (line 1057) | static void fillFaceQuadrics(Quadric* vertex_quadrics, QuadricGrad* vo...
    function fillVertexQuadrics (line 1084) | static void fillVertexQuadrics(Quadric* vertex_quadrics, const Vector3...
    function fillEdgeQuadrics (line 1104) | static void fillEdgeQuadrics(Quadric* vertex_quadrics, const unsigned ...
    function fillAttributeQuadrics (line 1155) | static void fillAttributeQuadrics(Quadric* attribute_quadrics, Quadric...
    function hasTriangleFlip (line 1178) | static bool hasTriangleFlip(const Vector3& a, const Vector3& b, const ...
    function hasTriangleFlips (line 1196) | static bool hasTriangleFlips(const EdgeAdjacency& adjacency, const Vec...
    function hasTriangleFlips (line 1230) | static bool hasTriangleFlips(const EdgeAdjacency& adjacency, const Vec...
    function getNeighborhoodRadius (line 1248) | static float getNeighborhoodRadius(const EdgeAdjacency& adjacency, con...
    function getComplexTarget (line 1274) | static unsigned int getComplexTarget(unsigned int v, unsigned int targ...
    function boundEdgeCollapses (line 1288) | static size_t boundEdgeCollapses(const EdgeAdjacency& adjacency, size_...
    function pickEdgeCollapses (line 1306) | static size_t pickEdgeCollapses(Collapse* collapses, size_t collapse_c...
    function rankEdgeCollapses (line 1370) | static void rankEdgeCollapses(Collapse* collapses, size_t collapse_cou...
    function sortEdgeCollapses (line 1448) | static void sortEdgeCollapses(unsigned int* sort_order, const Collapse...
    function performEdgeCollapses (line 1494) | static size_t performEdgeCollapses(unsigned int* collapse_remap, unsig...
    function updateQuadrics (line 1634) | static void updateQuadrics(const unsigned int* collapse_remap, size_t ...
    function solvePositions (line 1671) | static void solvePositions(Vector3* vertex_positions, size_t vertex_co...
    function solveAttributes (line 1761) | static void solveAttributes(Vector3* vertex_positions, float* vertex_a...
    function remapIndexBuffer (line 1807) | static size_t remapIndexBuffer(unsigned int* indices, size_t index_cou...
    function remapEdgeLoops (line 1841) | static void remapEdgeLoops(unsigned int* loop, size_t vertex_count, co...
    function follow (line 1862) | static unsigned int follow(unsigned int* parents, unsigned int index)
    function buildComponents (line 1874) | static size_t buildComponents(unsigned int* components, size_t vertex_...
    function measureComponents (line 1930) | static void measureComponents(float* component_errors, size_t componen...
    function pruneComponents (line 1985) | static size_t pruneComponents(unsigned int* indices, size_t index_coun...
    type CellHasher (line 2022) | struct CellHasher
      method hash (line 2026) | size_t hash(unsigned int i) const
      method equal (line 2037) | bool equal(unsigned int lhs, unsigned int rhs) const
    type IdHasher (line 2043) | struct IdHasher
      method hash (line 2045) | size_t hash(unsigned int id) const
      method equal (line 2056) | bool equal(unsigned int lhs, unsigned int rhs) const
    type TriangleHasher (line 2062) | struct TriangleHasher
      method hash (line 2066) | size_t hash(unsigned int i) const
      method equal (line 2074) | bool equal(unsigned int lhs, unsigned int rhs) const
    function computeVertexIds (line 2083) | static void computeVertexIds(unsigned int* vertex_ids, const Vector3* ...
    function countTriangles (line 2103) | static size_t countTriangles(const unsigned int* vertex_ids, const uns...
    function fillVertexCells (line 2119) | static size_t fillVertexCells(unsigned int* table, size_t table_size, ...
    function countVertexCells (line 2145) | static size_t countVertexCells(unsigned int* table, size_t table_size,...
    function fillCellQuadrics (line 2165) | static void fillCellQuadrics(Quadric* cell_quadrics, const unsigned in...
    function fillCellReservoirs (line 2195) | static void fillCellReservoirs(Reservoir* cell_reservoirs, size_t cell...
    function fillCellRemap (line 2233) | static void fillCellRemap(unsigned int* cell_remap, float* cell_errors...
    function fillCellRemap (line 2250) | static void fillCellRemap(unsigned int* cell_remap, float* cell_errors...
    function filterTriangles (line 2278) | static size_t filterTriangles(unsigned int* destination, unsigned int*...
    function interpolate (line 2323) | static float interpolate(float y, float x0, float y0, float x1, float ...
  function meshopt_simplifyEdge (line 2340) | size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned in...
  function meshopt_simplify (line 2625) | size_t meshopt_simplify(unsigned int* destination, const unsigned int* i...
  function meshopt_simplifyWithAttributes (line 2632) | size_t meshopt_simplifyWithAttributes(unsigned int* destination, const u...
  function meshopt_simplifyWithUpdate (line 2639) | size_t meshopt_simplifyWithUpdate(unsigned int* indices, size_t index_co...
  function meshopt_simplifySloppy (line 2644) | size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned ...
  function meshopt_simplifyPrune (line 2776) | size_t meshopt_simplifyPrune(unsigned int* destination, const unsigned i...
  function meshopt_simplifyPoints (line 2810) | size_t meshopt_simplifyPoints(unsigned int* destination, const float* ve...
  function meshopt_simplifyScale (line 2934) | float meshopt_simplifyScale(const float* vertex_positions, size_t vertex...

FILE: src/spatialorder.cpp
  type meshopt (line 10) | namespace meshopt
    function part1By2 (line 14) | inline unsigned long long part1By2(unsigned long long x)
    function computeOrder (line 25) | static void computeOrder(unsigned long long* result, const float* vert...
    function radixSort10 (line 70) | static void radixSort10(unsigned int* destination, const unsigned int*...
    function computeHistogram (line 100) | static void computeHistogram(unsigned int (&hist)[256][2], const unsig...
    function radixPass (line 130) | static void radixPass(unsigned int* destination, const unsigned int* s...
    function partitionPoints (line 142) | static void partitionPoints(unsigned int* target, const unsigned int* ...
    function splitPoints (line 158) | static void splitPoints(unsigned int* destination, unsigned int* order...
  function meshopt_spatialSortRemap (line 218) | void meshopt_spatialSortRemap(unsigned int* destination, const float* ve...
  function meshopt_spatialSortTriangles (line 253) | void meshopt_spatialSortTriangles(unsigned int* destination, const unsig...
  function meshopt_spatialClusterPoints (line 307) | void meshopt_spatialClusterPoints(unsigned int* destination, const float...

FILE: src/stripifier.cpp
  type meshopt (line 10) | namespace meshopt
    function findStripFirst (line 13) | static unsigned int findStripFirst(const unsigned int buffer[][3], uns...
    function findStripNext (line 33) | static int findStripNext(const unsigned int buffer[][3], unsigned int ...
  function meshopt_stripify (line 52) | size_t meshopt_stripify(unsigned int* destination, const unsigned int* i...
  function meshopt_stripifyBound (line 244) | size_t meshopt_stripifyBound(size_t index_count)
  function meshopt_unstripify (line 253) | size_t meshopt_unstripify(unsigned int* destination, const unsigned int*...
  function meshopt_unstripifyBound (line 291) | size_t meshopt_unstripifyBound(size_t index_count)

FILE: src/vcacheoptimizer.cpp
  type meshopt (line 10) | namespace meshopt
    type VertexScoreTable (line 16) | struct VertexScoreTable
    type TriangleAdjacency (line 34) | struct TriangleAdjacency
    function buildTriangleAdjacency (line 41) | static void buildTriangleAdjacency(TriangleAdjacency& adjacency, const...
    function getNextVertexDeadEnd (line 90) | static unsigned int getNextVertexDeadEnd(const unsigned int* dead_end,...
    function getNextVertexNeighbor (line 113) | static unsigned int getNextVertexNeighbor(const unsigned int* next_can...
    function vertexScore (line 144) | static float vertexScore(const VertexScoreTable* table, int cache_posi...
    function getNextTriangleDeadEnd (line 153) | static unsigned int getNextTriangleDeadEnd(unsigned int& input_cursor,...
  function meshopt_optimizeVertexCacheTable (line 169) | void meshopt_optimizeVertexCacheTable(unsigned int* destination, const u...
  function meshopt_optimizeVertexCache (line 345) | void meshopt_optimizeVertexCache(unsigned int* destination, const unsign...
  function meshopt_optimizeVertexCacheStrip (line 350) | void meshopt_optimizeVertexCacheStrip(unsigned int* destination, const u...
  function meshopt_optimizeVertexCacheFifo (line 355) | void meshopt_optimizeVertexCacheFifo(unsigned int* destination, const un...

FILE: src/vertexcodec.cpp
  function getVertexBlockSize (line 140) | static size_t getVertexBlockSize(size_t vertex_size)
  function rotate (line 149) | inline unsigned int rotate(unsigned int v, int r)
  function T (line 155) | inline T zigzag(T v)
  function T (line 161) | inline T unzigzag(T v)
  type Stats (line 167) | struct Stats
  function encodeBytesGroupZero (line 180) | static bool encodeBytesGroupZero(const unsigned char* buffer)
  function encodeBytesGroupMeasure (line 190) | static size_t encodeBytesGroupMeasure(const unsigned char* buffer, int b...
  function encodeDeltas1 (line 324) | static void encodeDeltas1(unsigned char* buffer, const unsigned char* ve...
  function encodeDeltas (line 349) | static void encodeDeltas(unsigned char* buffer, const unsigned char* ver...
  function estimateBits (line 364) | static int estimateBits(unsigned char v)
  function estimateRotate (line 369) | static int estimateRotate(const unsigned char* vertex_data, size_t verte...
  function estimateChannel (line 412) | static int estimateChannel(const unsigned char* vertex_data, size_t vert...
  function estimateControlZero (line 462) | static bool estimateControlZero(const unsigned char* buffer, size_t vert...
  function estimateControl (line 471) | static int estimateControl(const unsigned char* buffer, size_t vertex_co...
  function decodeDeltas1 (line 670) | static void decodeDeltas1(const unsigned char* buffer, unsigned char* tr...
  function decodeBytesGroupBuildTables (line 779) | __attribute__((cold)) // this saves 500 bytes in the output binary - we ...
  function SIMD_TARGET (line 807) | SIMD_TARGET
  function SIMD_TARGET (line 825) | SIMD_TARGET
  function SIMD_TARGET (line 958) | SIMD_TARGET
  function SIMD_TARGET (line 1015) | SIMD_TARGET
  function SIMD_TARGET (line 1027) | SIMD_TARGET
  function SIMD_TARGET (line 1039) | SIMD_TARGET
  function SIMD_TARGET (line 1159) | SIMD_TARGET
  function SIMD_TARGET (line 1171) | SIMD_TARGET
  function SIMD_TARGET (line 1181) | SIMD_TARGET
  function SIMD_TARGET (line 1273) | SIMD_TARGET
  function SIMD_TARGET (line 1287) | SIMD_TARGET
  function SIMD_TARGET (line 1296) | SIMD_TARGET
  function SIMD_TARGET (line 1305) | SIMD_TARGET
  function SIMD_TARGET (line 1313) | SIMD_TARGET
  function SIMD_TARGET (line 1328) | SIMD_TARGET
  function SIMD_TARGET (line 1337) | SIMD_TARGET
  function SIMD_TARGET (line 1347) | SIMD_TARGET
  function SIMD_TARGET (line 1355) | SIMD_TARGET inline uint8x8_t rebase(uint8x8_t npi, uint8x16_t r0, uint8x...
  function SIMD_TARGET (line 1384) | SIMD_TARGET
  function SIMD_TARGET (line 1398) | SIMD_TARGET
  function SIMD_TARGET (line 1407) | SIMD_TARGET
  function SIMD_TARGET (line 1416) | SIMD_TARGET
  function SIMD_TARGET (line 1424) | SIMD_TARGET
  function SIMD_TARGET (line 1468) | SIMD_TARGET static void
  function SIMD_TARGET (line 1553) | SIMD_TARGET
  function getCpuFeatures (line 1630) | static unsigned int getCpuFeatures()
  function meshopt_encodeVertexBufferLevel (line 1646) | size_t meshopt_encodeVertexBufferLevel(unsigned char* buffer, size_t buf...
  function meshopt_encodeVertexBuffer (line 1779) | size_t meshopt_encodeVertexBuffer(unsigned char* buffer, size_t buffer_s...
  function meshopt_encodeVertexBufferBound (line 1784) | size_t meshopt_encodeVertexBufferBound(size_t vertex_count, size_t verte...
  function meshopt_encodeVertexVersion (line 1806) | void meshopt_encodeVertexVersion(int version)
  function meshopt_decodeVertexVersion (line 1813) | int meshopt_decodeVertexVersion(const unsigned char* buffer, size_t buff...
  function meshopt_decodeVertexBuffer (line 1830) | int meshopt_decodeVertexBuffer(void* destination, size_t vertex_count, s...

FILE: src/vertexfilter.cpp
  type meshopt (line 73) | namespace meshopt
    function decodeFilterOct (line 78) | static void decodeFilterOct(T* data, size_t count)
    function decodeFilterQuat (line 110) | static void decodeFilterQuat(short* data, size_t count)
    function decodeFilterExp (line 150) | static void decodeFilterExp(unsigned int* data, size_t count)
    function decodeFilterColor (line 175) | static void decodeFilterColor(T* data, size_t count)
    function dispatchSimd (line 218) | static void dispatchSimd(void (*process)(T*, size_t), T* data, size_t ...
    function rotateleft64 (line 237) | inline uint64_t rotateleft64(uint64_t v, int x)
    function decodeFilterOctSimd8 (line 250) | static void decodeFilterOctSimd8(signed char* data, size_t count)
    function decodeFilterOctSimd16 (line 295) | static void decodeFilterOctSimd16(short* data, size_t count)
    function decodeFilterQuatSimd (line 353) | static void decodeFilterQuatSimd(short* data, size_t count)
    function decodeFilterExpSimd (line 419) | static void decodeFilterExpSimd(unsigned int* data, size_t count)
    function decodeFilterColorSimd8 (line 439) | static void decodeFilterColorSimd8(unsigned char* data, size_t count)
    function decodeFilterColorSimd16 (line 484) | static void decodeFilterColorSimd16(unsigned short* data, size_t count)
    function float32x4_t (line 540) | inline float32x4_t vsqrtq_f32(float32x4_t x)
    function float32x4_t (line 547) | inline float32x4_t vdivq_f32(float32x4_t x, float32x4_t y)
    function float32x4_t (line 555) | inline float32x4_t vfmaq_f32(float32x4_t x, float32x4_t y, float32x4_t z)
    function decodeFilterOctSimd8 (line 563) | static void decodeFilterOctSimd8(signed char* data, size_t count)
    function decodeFilterOctSimd16 (line 610) | static void decodeFilterOctSimd16(short* data, size_t count)
    function decodeFilterQuatSimd (line 676) | static void decodeFilterQuatSimd(short* data, size_t count)
    function decodeFilterExpSimd (line 746) | static void decodeFilterExpSimd(unsigned int* data, size_t count)
    function decodeFilterColorSimd8 (line 766) | static void decodeFilterColorSimd8(unsigned char* data, size_t count)
    function decodeFilterColorSimd16 (line 811) | static void decodeFilterColorSimd16(unsigned short* data, size_t count)
    function decodeFilterOctSimd8 (line 870) | static void decodeFilterOctSimd8(signed char* data, size_t count)
    function decodeFilterOctSimd16 (line 919) | static void decodeFilterOctSimd16(short* data, size_t count)
    function decodeFilterQuatSimd (line 982) | static void decodeFilterQuatSimd(short* data, size_t count)
    function decodeFilterExpSimd (line 1050) | static void decodeFilterExpSimd(unsigned int* data, size_t count)
    function decodeFilterColorSimd8 (line 1070) | static void decodeFilterColorSimd8(unsigned char* data, size_t count)
    function decodeFilterColorSimd16 (line 1121) | static void decodeFilterColorSimd16(unsigned short* data, size_t count)
    function optlog2 (line 1183) | inline int optlog2(float v)
    function optexp2 (line 1197) | inline float optexp2(int e)
  function meshopt_decodeFilterOct (line 1211) | void meshopt_decodeFilterOct(void* buffer, size_t count, size_t stride)
  function meshopt_decodeFilterQuat (line 1230) | void meshopt_decodeFilterQuat(void* buffer, size_t count, size_t stride)
  function meshopt_decodeFilterExp (line 1244) | void meshopt_decodeFilterExp(void* buffer, size_t count, size_t stride)
  function meshopt_decodeFilterColor (line 1257) | void meshopt_decodeFilterColor(void* buffer, size_t count, size_t stride)
  function meshopt_encodeFilterOct (line 1276) | void meshopt_encodeFilterOct(void* destination, size_t count, size_t str...
  function meshopt_encodeFilterQuat (line 1323) | void meshopt_encodeFilterQuat(void* destination_, size_t count, size_t s...
  function meshopt_encodeFilterExp (line 1355) | void meshopt_encodeFilterExp(void* destination_, size_t count, size_t st...
  function meshopt_encodeFilterColor (line 1446) | void meshopt_encodeFilterColor(void* destination, size_t count, size_t s...

FILE: src/vfetchoptimizer.cpp
  function meshopt_optimizeVertexFetchRemap (line 7) | size_t meshopt_optimizeVertexFetchRemap(unsigned int* destination, const...
  function meshopt_optimizeVertexFetch (line 31) | size_t meshopt_optimizeVertexFetch(void* destination, unsigned int* indi...

FILE: tools/bitmask.py
  function same8 (line 5) | def same8(v):

FILE: tools/clusterfuzz.cpp
  function LLVMFuzzerTestOneInput (line 7) | int LLVMFuzzerTestOneInput(const uint8_t* buffer, size_t size)

FILE: tools/codecbench.cpp
  function timestamp (line 14) | double timestamp()
  type LARGE_INTEGER (line 19) | struct LARGE_INTEGER
  function timestamp (line 26) | double timestamp()
  function timestamp (line 34) | double timestamp()
  type Vertex (line 42) | struct Vertex
  function murmur3 (line 47) | uint32_t murmur3(uint32_t h)
  function benchCodecs (line 58) | void benchCodecs(const std::vector<Vertex>& vertices, const std::vector<...
  function benchFilters (line 108) | void benchFilters(size_t count, double& besto8, double& besto12, double&...
  function benchMeshlets (line 164) | void benchMeshlets(const std::vector<float>& positions, const std::vecto...
  type File (line 256) | struct File
  function File (line 263) | File readFile(const char* path)
  function main (line 303) | int main(int argc, char** argv)

FILE: tools/codecfuzz.cpp
  function fuzzDecoder (line 7) | void fuzzDecoder(const uint8_t* data, size_t size, size_t stride, int (*...
  function fuzzRoundtrip (line 20) | void fuzzRoundtrip(const uint8_t* data, size_t size, size_t stride, int ...
  function align (line 46) | size_t align(size_t value, size_t alignment)
  function fuzzDecodeMeshlet (line 51) | void fuzzDecodeMeshlet(size_t vertex_count, size_t triangle_count, const...
  function fuzzRoundtripMeshlet (line 69) | void fuzzRoundtripMeshlet(const uint8_t* data, size_t size)
  function fuzzRoundtripMeshletV (line 115) | void fuzzRoundtripMeshletV(const uint8_t* data, size_t size)
  function LLVMFuzzerTestOneInput (line 143) | int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)

FILE: tools/codectest.cpp
  function measure (line 15) | size_t measure(const char* cmd, const std::vector<unsigned char>& data)
  function measure_lz4 (line 40) | size_t measure_lz4(const std::vector<unsigned char>& data)
  function measure_zstd (line 45) | size_t measure_zstd(const std::vector<unsigned char>& data)
  type Stats (line 50) | struct Stats
  function testFile (line 65) | void testFile(FILE* file, size_t count, size_t stride, int level, Stats*...
  function testFile (line 123) | void testFile(const char* path, int level, Stats* stats = 0)
  function main (line 154) | int main(int argc, char** argv)

FILE: tools/gltfbasis.py
  function compress (line 18) | def compress(path, flags):
  function stats (line 32) | def stats(path):

FILE: tools/simplifyfuzz.cpp
  function LLVMFuzzerTestOneInput (line 7) | int LLVMFuzzerTestOneInput(const uint8_t* buffer, size_t size)

FILE: tools/vcachetester.cpp
  function stripGen (line 20) | void stripGen(std::vector<unsigned int>& indices, int x0, int x1, int y0...
  function gridGen (line 47) | void gridGen(std::vector<unsigned int>& indices, int x0, int x1, int y0,...
  function queryVSInvocations (line 63) | unsigned int queryVSInvocations(ID3D11Device* device, ID3D11DeviceContex...
  function setupShaders (line 109) | void setupShaders(ID3D11Device* device, ID3D11DeviceContext* context)
  function inspectCache (line 133) | void inspectCache(Cache cache)
  function testCache (line 169) | void testCache(IDXGIAdapter* adapter)
  function testCacheSequence (line 181) | void testCacheSequence(IDXGIAdapter* adapter, int argc, char** argv)
  function testCacheMeshes (line 374) | void testCacheMeshes(IDXGIAdapter* adapter, int argc, char** argv)
  function main (line 483) | int main(int argc, char** argv)

FILE: tools/vcachetuner.cpp
  type meshopt (line 19) | namespace meshopt
    type VertexScoreTable (line 21) | struct VertexScoreTable
  type Profile (line 30) | struct Profile
  type pcg32_random_t (line 49) | struct pcg32_random_t
  function pcg32_random_r (line 57) | uint32_t pcg32_random_r(pcg32_random_t* rng)
  function rand01 (line 70) | float rand01()
  function rand32 (line 75) | uint32_t rand32()
  type State (line 80) | struct State
  type Mesh (line 87) | struct Mesh
  function Mesh (line 97) | Mesh gridmesh(unsigned int N)
  function Mesh (line 121) | Mesh objmesh(const char* path)
  function compress (line 203) | size_t compress(const std::vector<T>& data, int level = SDEFL_LVL_DEF)
  function compute_metric (line 210) | void compute_metric(const State* state, const Mesh& mesh, float result[P...
  function fitness_score (line 256) | float fitness_score(const State& state, const std::vector<Mesh>& meshes)
  function gen0 (line 276) | std::vector<State> gen0(size_t count, const std::vector<Mesh>& meshes)
  function genN (line 300) | std::pair<State, float> genN(std::vector<State>& seed, const std::vector...
  function load_state (line 366) | bool load_state(const char* path, std::vector<State>& result)
  function save_state (line 384) | bool save_state(const char* path, const std::vector<State>& result)
  function dump_state (line 402) | void dump_state(const State& state)
  function dump_stats (line 419) | void dump_stats(const State& state, const std::vector<Mesh>& meshes)
  function main (line 443) | int main(int argc, char** argv)

FILE: tools/wasmpack.py
  function encode (line 11) | def encode(buffer):
  function stats (line 24) | def stats(buffer):
  function patch (line 34) | def patch(target, stem, code):
Condensed preview — 108 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,321K chars).
[
  {
    "path": ".clang-format",
    "chars": 388,
    "preview": "Standard: Cpp03\nUseTab: ForIndentation\nTabWidth: 4\nIndentWidth: 4\nAccessModifierOffset: -4\nBreakBeforeBraces: Allman\nInd"
  },
  {
    "path": ".editorconfig",
    "chars": 162,
    "preview": "# See https://editorconfig.org/ for more info\n\n[*]\ncharset = utf-8\nindent_style = tab\nindent_size = 4\ntrim_trailing_whit"
  },
  {
    "path": ".git-blame-ignore-revs",
    "chars": 687,
    "preview": "# This file contains a list of Git commit hashes that should be hidden from the\n# regular Git history. Typically, this i"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 215,
    "preview": "---\nname: Bug report\nabout: Create a report if you've found a bug in this project; please use GitHub Discussions instead"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 216,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Questions and ideas\n    url: https://github.com/zeux/meshoptimizer/"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 191,
    "preview": "---\nname: Feature request\nabout: Suggest a feature for this project; please use GitHub Discussions if your idea is not s"
  },
  {
    "path": ".github/codecov.yml",
    "chars": 107,
    "preview": "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",
    "chars": 9563,
    "preview": "name: build\n\non:\n  push:\n    branches:\n      - 'master'\n    paths-ignore:\n      - '*.md'\n  pull_request:\n    paths-ignor"
  },
  {
    "path": ".github/workflows/cifuzz.yml",
    "chars": 809,
    "preview": "name: CIFuzz\n\non:\n  push:\n    branches:\n      - 'master'\n    paths-ignore:\n      - '*.md'\n\njobs:\n  Fuzzing:\n    runs-on:"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 2640,
    "preview": "name: release\n\non:\n  push:\n    branches:\n      - 'master'\n    paths-ignore:\n      - '*.md'\n\njobs:\n  gltfpack:\n    strate"
  },
  {
    "path": ".gitignore",
    "chars": 187,
    "preview": "# 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."
  },
  {
    "path": ".prettierrc",
    "chars": 208,
    "preview": "{\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\""
  },
  {
    "path": "CMakeLists.txt",
    "chars": 7935,
    "preview": "cmake_minimum_required(VERSION 3.5...3.30)\n\nif(POLICY CMP0077)\n    cmake_policy(SET CMP0077 NEW) # Enables override of o"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 4757,
    "preview": "Thanks for deciding to contribute to meshoptimizer! These guidelines will try to help make the process painless and effi"
  },
  {
    "path": "LICENSE.md",
    "chars": 1079,
    "preview": "MIT License\n\nCopyright (c) 2016-2026 Arseny Kapoulkine\n\nPermission is hereby granted, free of charge, to any person obta"
  },
  {
    "path": "Makefile",
    "chars": 9874,
    "preview": "MAKEFLAGS+=-r -j\n\nconfig=debug\nfiles=demo/pirate.obj\n\nBUILD=build/$(config)\n\nLIBRARY_SOURCES=$(wildcard src/*.cpp)\nLIBRA"
  },
  {
    "path": "README.md",
    "chars": 88621,
    "preview": "# 🐇 meshoptimizer [![Actions Status](https://github.com/zeux/meshoptimizer/workflows/build/badge.svg)](https://github.co"
  },
  {
    "path": "demo/ansi.c",
    "chars": 97,
    "preview": "/* This file makes sure the library can be used by C89 code */\n#include \"../src/meshoptimizer.h\"\n"
  },
  {
    "path": "demo/clusterlod.h",
    "chars": 27784,
    "preview": "/**\n * clusterlod - a small \"library\"/example built on top of meshoptimizer to generate cluster LOD hierarchies\n * This "
  },
  {
    "path": "demo/index.html",
    "chars": 3638,
    "preview": "<!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=\""
  },
  {
    "path": "demo/main.cpp",
    "chars": 62783,
    "preview": "#include \"../src/meshoptimizer.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <stdio.h>\n#include <string.h>\n#include"
  },
  {
    "path": "demo/meshletdec.slang",
    "chars": 7279,
    "preview": "/**\n * meshletdec.slang - an example GPU decoder for meshlet data encoded using meshopt_encodeMeshlet\n * This is intende"
  },
  {
    "path": "demo/nanite.cpp",
    "chars": 14226,
    "preview": "// For reference, see the original Nanite paper:\n// Brian Karis. Nanite: A Deep Dive. 2021\n#include \"../src/meshoptimize"
  },
  {
    "path": "demo/pirate.obj",
    "chars": 368962,
    "preview": "# Blender v2.79 (sub 0) OBJ File: 'pirate.blend'\n# www.blender.org\n# Pirate by Clint Bellanger\n# https://opengameart.org"
  },
  {
    "path": "demo/simplify.html",
    "chars": 33969,
    "preview": "<!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=\""
  },
  {
    "path": "demo/tests.cpp",
    "chars": 93493,
    "preview": "#include \"../src/meshoptimizer.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <stdlib.h>\n#include <string.h>\n\n#inclu"
  },
  {
    "path": "extern/.clang-format",
    "chars": 20,
    "preview": "DisableFormat: true\n"
  },
  {
    "path": "extern/cgltf.h",
    "chars": 205754,
    "preview": "/**\n * cgltf - a single-file glTF 2.0 parser written in C99.\n *\n * Version: 1.15\n *\n * Website: https://github.com/jkuhl"
  },
  {
    "path": "extern/fast_obj.h",
    "chars": 35433,
    "preview": "/*\n * fast_obj\n *\n * Version 1.3\n *\n * MIT License\n *\n * Copyright (c) 2018-2021 Richard Knight\n *\n * Permission is here"
  },
  {
    "path": "extern/sdefl.h",
    "chars": 28129,
    "preview": "/*# Small Deflate\n`sdefl` is a small bare bone lossless compression library in ANSI C (ISO C90)\nwhich implements the Def"
  },
  {
    "path": "gltf/README.md",
    "chars": 9434,
    "preview": "# 📦 gltfpack\n\ngltfpack is a tool that can automatically optimize glTF files to reduce the download size and improve load"
  },
  {
    "path": "gltf/animation.cpp",
    "chars": 9702,
    "preview": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <algorithm>"
  },
  {
    "path": "gltf/cli.js",
    "chars": 525,
    "preview": "#!/usr/bin/env node\n// This file is part of gltfpack and is distributed under the terms of MIT License.\nimport { pack } "
  },
  {
    "path": "gltf/encodebasis.cpp",
    "chars": 6889,
    "preview": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#ifdef WITH_BASISU\n#include \"gltfpack.h\"\n\n#"
  },
  {
    "path": "gltf/encodewebp.cpp",
    "chars": 5192,
    "preview": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#ifdef WITH_LIBWEBP\n#include \"gltfpack.h\"\n\n"
  },
  {
    "path": "gltf/fileio.cpp",
    "chars": 2351,
    "preview": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <stdio.h>\n#"
  },
  {
    "path": "gltf/fuzz.dict",
    "chars": 3149,
    "preview": "#\n# AFL dictionary for JSON\n# -----------------------\n#\n# Just the very basics.\n#\n# Inspired by a dictionary by Jakub Wi"
  },
  {
    "path": "gltf/gltfpack.cpp",
    "chars": 55429,
    "preview": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <algorithm>"
  },
  {
    "path": "gltf/gltfpack.h",
    "chars": 14143,
    "preview": "/**\n * gltfpack - version 1.0\n *\n * Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\n * Repor"
  },
  {
    "path": "gltf/gltfpack.manifest",
    "chars": 391,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com"
  },
  {
    "path": "gltf/image.cpp",
    "chars": 9287,
    "preview": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <stdlib.h>\n"
  },
  {
    "path": "gltf/json.cpp",
    "chars": 1685,
    "preview": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <float.h>\n#"
  },
  {
    "path": "gltf/library.js",
    "chars": 7958,
    "preview": "// This file is part of gltfpack and is distributed under the terms of MIT License.\n\n/**\n * Initialize the library with "
  },
  {
    "path": "gltf/material.cpp",
    "chars": 18891,
    "preview": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <string.h>\n"
  },
  {
    "path": "gltf/mesh.cpp",
    "chars": 33938,
    "preview": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <algorithm>"
  },
  {
    "path": "gltf/node.cpp",
    "chars": 5528,
    "preview": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <math.h>\n#i"
  },
  {
    "path": "gltf/package.json",
    "chars": 600,
    "preview": "{\n\t\"name\": \"gltfpack\",\n\t\"version\": \"1.0.0\",\n\t\"description\": \"A command-line tool that can optimize glTF files for size a"
  },
  {
    "path": "gltf/parsegltf.cpp",
    "chars": 19955,
    "preview": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <stdio.h>\n#"
  },
  {
    "path": "gltf/parselib.cpp",
    "chars": 255,
    "preview": "#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#define CGLTF_IMPLEMENTATION\n#include \"../extern"
  },
  {
    "path": "gltf/parseobj.cpp",
    "chars": 9826,
    "preview": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include \"../extern/"
  },
  {
    "path": "gltf/stream.cpp",
    "chars": 27302,
    "preview": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <algorithm>"
  },
  {
    "path": "gltf/wasistubs.cpp",
    "chars": 1412,
    "preview": "#ifdef __wasi__\n#include <stdlib.h>\n#include <string.h>\n\n#include <wasi/api.h>\n\nextern \"C\" void __cxa_throw(void* ptr, v"
  },
  {
    "path": "gltf/wasistubs.txt",
    "chars": 36,
    "preview": "__wasi_path_open32\n__wasi_fd_seek32\n"
  },
  {
    "path": "gltf/write.cpp",
    "chars": 52396,
    "preview": "// This file is part of gltfpack; see gltfpack.h for version/license details\n#include \"gltfpack.h\"\n\n#include <float.h>\n#"
  },
  {
    "path": "js/README.md",
    "chars": 29145,
    "preview": "# meshoptimizer.js\n\nThis folder contains JavaScript/WebAssembly modules that can be used to access parts of functionalit"
  },
  {
    "path": "js/benchmark.js",
    "chars": 3686,
    "preview": "import { MeshoptEncoder as encoder } from './meshopt_encoder.js';\nimport { MeshoptDecoder as decoder } from './meshopt_d"
  },
  {
    "path": "js/index.d.ts",
    "chars": 159,
    "preview": "export * from './meshopt_encoder.js';\nexport * from './meshopt_decoder.js';\nexport * from './meshopt_simplifier.js';\nexp"
  },
  {
    "path": "js/index.js",
    "chars": 160,
    "preview": "export * from './meshopt_encoder.js';\nexport * from './meshopt_decoder.mjs';\nexport * from './meshopt_simplifier.js';\nex"
  },
  {
    "path": "js/meshopt_clusterizer.d.ts",
    "chars": 1853,
    "preview": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2"
  },
  {
    "path": "js/meshopt_clusterizer.js",
    "chars": 30832,
    "preview": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2"
  },
  {
    "path": "js/meshopt_clusterizer.test.js",
    "chars": 4600,
    "preview": "import assert from 'assert/strict';\nimport { MeshoptClusterizer as clusterizer } from './meshopt_clusterizer.js';\n\nproce"
  },
  {
    "path": "js/meshopt_decoder.cjs",
    "chars": 32733,
    "preview": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2"
  },
  {
    "path": "js/meshopt_decoder.d.ts",
    "chars": 870,
    "preview": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2"
  },
  {
    "path": "js/meshopt_decoder.mjs",
    "chars": 32392,
    "preview": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2"
  },
  {
    "path": "js/meshopt_decoder.test.js",
    "chars": 15409,
    "preview": "import assert from 'assert/strict';\nimport { MeshoptDecoder as decoder } from './meshopt_decoder.mjs';\n\nprocess.on('unha"
  },
  {
    "path": "js/meshopt_decoder_reference.js",
    "chars": 14948,
    "preview": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2"
  },
  {
    "path": "js/meshopt_encoder.d.ts",
    "chars": 1448,
    "preview": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2"
  },
  {
    "path": "js/meshopt_encoder.js",
    "chars": 24381,
    "preview": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2"
  },
  {
    "path": "js/meshopt_encoder.test.js",
    "chars": 8354,
    "preview": "import assert from 'assert/strict';\nimport { MeshoptEncoder as encoder } from './meshopt_encoder.js';\nimport { MeshoptDe"
  },
  {
    "path": "js/meshopt_simplifier.d.ts",
    "chars": 2029,
    "preview": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2"
  },
  {
    "path": "js/meshopt_simplifier.js",
    "chars": 55064,
    "preview": "// This file is part of meshoptimizer library and is distributed under the terms of MIT License.\n// Copyright (C) 2016-2"
  },
  {
    "path": "js/meshopt_simplifier.test.js",
    "chars": 6351,
    "preview": "import assert from 'assert/strict';\nimport { MeshoptSimplifier as simplifier } from './meshopt_simplifier.js';\n\nprocess."
  },
  {
    "path": "js/package.json",
    "chars": 1311,
    "preview": "{\n\t\"name\": \"meshoptimizer\",\n\t\"version\": \"1.0.1\",\n\t\"description\": \"Mesh optimization library that makes meshes smaller an"
  },
  {
    "path": "js/wasi_trace.js",
    "chars": 1264,
    "preview": "// Usage:\n// 1. import { wasi_trace } from './wasi_trace.js';\n// 2. Pass wasi_trace as an import object to WebAssembly.i"
  },
  {
    "path": "src/allocator.cpp",
    "chars": 552,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/clusterizer.cpp",
    "chars": 47051,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/indexanalyzer.cpp",
    "chars": 3851,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/indexcodec.cpp",
    "chars": 20833,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/indexgenerator.cpp",
    "chars": 21283,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/meshletcodec.cpp",
    "chars": 33784,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/meshletutils.cpp",
    "chars": 19089,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/meshoptimizer.h",
    "chars": 95924,
    "preview": "/**\n * meshoptimizer - version 1.0\n *\n * Copyright (C) 2016-2026, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)\n * "
  },
  {
    "path": "src/opacitymap.cpp",
    "chars": 18478,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/overdrawoptimizer.cpp",
    "chars": 11671,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/partition.cpp",
    "chars": 18839,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/quantization.cpp",
    "chars": 1847,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/rasterizer.cpp",
    "chars": 8717,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/simplifier.cpp",
    "chars": 103870,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/spatialorder.cpp",
    "chars": 11093,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/stripifier.cpp",
    "chars": 7752,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/vcacheoptimizer.cpp",
    "chars": 14171,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/vertexcodec.cpp",
    "chars": 56915,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/vertexfilter.cpp",
    "chars": 55168,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "src/vfetchoptimizer.cpp",
    "chars": 1978,
    "preview": "// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details\n#include \"meshoptimizer.h"
  },
  {
    "path": "tools/bitmask.py",
    "chars": 515,
    "preview": "#!/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 = Bit"
  },
  {
    "path": "tools/clusterfuzz.cpp",
    "chars": 2904,
    "preview": "#include \"../src/meshoptimizer.h\"\n\n#include <float.h>\n#include <stdint.h>\n#include <stdlib.h>\n\nextern \"C\" int LLVMFuzzer"
  },
  {
    "path": "tools/codecbench.cpp",
    "chars": 13409,
    "preview": "#include \"../src/meshoptimizer.h\"\n\n#include <algorithm>\n#include <vector>\n\n#include <stdint.h>\n#include <stdio.h>\n#inclu"
  },
  {
    "path": "tools/codecfuzz.cpp",
    "chars": 6885,
    "preview": "#include \"../src/meshoptimizer.h\"\n\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n\nvoid fuzzDecoder(const u"
  },
  {
    "path": "tools/codectest.cpp",
    "chars": 6008,
    "preview": "#include \"../src/meshoptimizer.h\"\n\n#include <vector>\n\n#include <math.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include "
  },
  {
    "path": "tools/gltfbasis.py",
    "chars": 2858,
    "preview": "#!/usr/bin/python3\n\nimport argparse\nimport concurrent.futures\nimport matplotlib.pyplot as plt\nimport os\nimport os.path\ni"
  },
  {
    "path": "tools/objloader.cpp",
    "chars": 136,
    "preview": "#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#define FAST_OBJ_IMPLEMENTATION\n#include \"../ext"
  },
  {
    "path": "tools/simplifyfuzz.cpp",
    "chars": 1550,
    "preview": "#include \"../src/meshoptimizer.h\"\n\n#include <float.h>\n#include <stdint.h>\n#include <stdlib.h>\n\nextern \"C\" int LLVMFuzzer"
  },
  {
    "path": "tools/vcachetester.cpp",
    "chars": 13388,
    "preview": "#ifdef _WIN32\n#include <assert.h>\n#include <d3d11.h>\n#include <d3dcompiler.h>\n#include <stdio.h>\n\n#include <cassert>\n#in"
  },
  {
    "path": "tools/vcachetuner.cpp",
    "chars": 11475,
    "preview": "#include \"../extern/fast_obj.h\"\n#include \"../src/meshoptimizer.h\"\n\n#define SDEFL_IMPLEMENTATION\n#include \"../extern/sdef"
  },
  {
    "path": "tools/wasmpack.py",
    "chars": 1329,
    "preview": "#!/usr/bin/python3\n\nimport re\nimport sys\n\n# regenerate with wasmpack.py generate\ntable = [32, 0, 65, 2, 1, 106, 34, 33, "
  },
  {
    "path": "tools/wasmstubs.cpp",
    "chars": 2042,
    "preview": "#ifndef __wasi__\n#error This file contains libc stubs for WASI SDK and should only be used in non-Emscripten WebAssembly"
  }
]

// ... and 2 more files (download for full content)

About this extraction

This page contains the full source code of the zeux/meshoptimizer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 108 files (2.1 MB), approximately 551.4k tokens, and a symbol index with 1303 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!