Showing preview only (647K chars total). Download the full file or copy to clipboard to get everything.
Repository: mosure/bevy_gaussian_splatting
Branch: main
Commit: 5ed892614d9d
Files: 133
Total size: 609.7 KB
Directory structure:
gitextract_ni3w1v_t/
├── .cargo/
│ └── config.toml
├── .devcontainer/
│ ├── Dockerfile
│ └── docker-compose.yaml
├── .gitattributes
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── bench.yml
│ ├── build.yml
│ ├── clippy.yml
│ ├── deploy-pages.yml
│ ├── test.yml
│ └── todo_tracker.yml
├── .gitignore
├── .vscode/
│ └── bevy_gaussian_splatting.code-workspace
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── benches/
│ └── io.rs
├── docs/
│ └── credits.md
├── examples/
│ ├── headless.rs
│ ├── minimal.rs
│ └── multi_camera.rs
├── src/
│ ├── camera.rs
│ ├── gaussian/
│ │ ├── cloud.rs
│ │ ├── covariance.rs
│ │ ├── f16.rs
│ │ ├── f32.rs
│ │ ├── formats/
│ │ │ ├── mod.rs
│ │ │ ├── planar_3d.rs
│ │ │ ├── planar_3d_chunked.rs
│ │ │ ├── planar_3d_lod.rs
│ │ │ ├── planar_3d_quantized.rs
│ │ │ ├── planar_3d_spz.rs
│ │ │ ├── planar_4d.rs
│ │ │ ├── planar_4d_hierarchy.rs
│ │ │ ├── planar_4d_quantized.rs
│ │ │ └── spacetime.rs
│ │ ├── interface.rs
│ │ ├── iter.rs
│ │ ├── mod.rs
│ │ └── settings.rs
│ ├── io/
│ │ ├── codec.rs
│ │ ├── gcloud/
│ │ │ ├── bincode2.rs
│ │ │ ├── flexbuffers.rs
│ │ │ ├── mod.rs
│ │ │ └── texture.rs
│ │ ├── loader.rs
│ │ ├── mod.rs
│ │ ├── ply.rs
│ │ └── scene.rs
│ ├── lib.rs
│ ├── lighting/
│ │ ├── environmental.rs
│ │ └── mod.rs
│ ├── material/
│ │ ├── classification.rs
│ │ ├── classification.wgsl
│ │ ├── depth.rs
│ │ ├── depth.wgsl
│ │ ├── mod.rs
│ │ ├── noise.rs
│ │ ├── noise.wgsl
│ │ ├── optical_flow.rs
│ │ ├── optical_flow.wgsl
│ │ ├── pbr.rs
│ │ ├── pbr.wgsl
│ │ ├── position.rs
│ │ ├── position.wgsl
│ │ ├── spherical_harmonics.rs
│ │ ├── spherical_harmonics.wgsl
│ │ ├── spherindrical_harmonics.rs
│ │ └── spherindrical_harmonics.wgsl
│ ├── math/
│ │ └── mod.rs
│ ├── morph/
│ │ ├── interpolate.rs
│ │ ├── interpolate.wgsl
│ │ ├── mod.rs
│ │ ├── particle.rs
│ │ └── particle.wgsl
│ ├── noise/
│ │ ├── mod.rs
│ │ └── noise.wgsl
│ ├── query/
│ │ ├── mod.rs
│ │ ├── raycast.rs
│ │ ├── select.rs
│ │ └── sparse.rs
│ ├── render/
│ │ ├── bindings.wgsl
│ │ ├── gaussian.wgsl
│ │ ├── gaussian_2d.wgsl
│ │ ├── gaussian_3d.wgsl
│ │ ├── gaussian_4d.wgsl
│ │ ├── helpers.wgsl
│ │ ├── mod.rs
│ │ ├── packed.rs
│ │ ├── packed.wgsl
│ │ ├── planar.rs
│ │ ├── planar.wgsl
│ │ ├── texture.rs
│ │ ├── texture.wgsl
│ │ └── transform.wgsl
│ ├── sort/
│ │ ├── bitonic.rs
│ │ ├── bitonic.wgsl
│ │ ├── mod.rs
│ │ ├── radix.rs
│ │ ├── radix.wgsl
│ │ ├── rayon.rs
│ │ ├── std_sort.rs
│ │ └── temporal.wgsl
│ ├── stream/
│ │ ├── hierarchy.rs
│ │ ├── mod.rs
│ │ └── slice.rs
│ └── utils.rs
├── tests/
│ ├── fixtures/
│ │ └── khr_gaussian_splatting/
│ │ ├── khr_conformance_matrix.glb
│ │ ├── khr_conformance_matrix.gltf
│ │ └── khr_extensible_fallback.gltf
│ ├── gaussian.rs
│ ├── gpu/
│ │ ├── _harness.rs
│ │ ├── gaussian.rs
│ │ └── radix.rs
│ ├── headless_examples.rs
│ ├── io.rs
│ ├── khr_loader_conformance.rs
│ └── radix.rs
├── tools/
│ ├── README.md
│ ├── build_www.ps1
│ ├── build_www.sh
│ ├── compare_aabb_obb.rs
│ ├── ply_to_gcloud.rs
│ ├── render_trellis_thumbnails.rs
│ └── surfel_plane.rs
├── viewer/
│ └── viewer.rs
└── www/
├── README.md
├── examples/
│ ├── examples.css
│ ├── examples.js
│ └── index.html
└── index.html
================================================
FILE CONTENTS
================================================
================================================
FILE: .cargo/config.toml
================================================
# alternatively, `export CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER=wasm-server-runner`
[target.wasm32-unknown-unknown]
runner = "wasm-server-runner"
rustflags = [
"--cfg=web_sys_unstable_apis",
"--cfg=getrandom_backend=\"wasm_js\"",
# "--cfg=wasm_js",
# "-C",
# "target-feature=+atomics,+bulk-memory,+mutable-globals", # for wasm-bindgen-rayon
]
# fix spurious network error on windows
# [source.crates-io]
# registry = "https://github.com/rust-lang/crates.io-index"
[http]
proxy = ""
# offline development
# [source.crates-io]
# replace-with = "vendored-sources"
# [source.vendored-sources]
# directory = "vendor"
================================================
FILE: .devcontainer/Dockerfile
================================================
FROM mcr.microsoft.com/devcontainers/rust:0-1
WORKDIR /workspace
RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y \
build-essential \
libpulse-dev \
libdbus-1-dev \
libudev-dev \
libssl-dev \
xorg \
openbox \
alsa-tools \
librust-alsa-sys-dev \
&& rm -rf /var/lib/apt/lists/*
RUN rustup target install wasm32-unknown-unknown
RUN cargo install flamegraph
RUN cargo install wasm-server-runner
================================================
FILE: .devcontainer/docker-compose.yaml
================================================
services:
devcontainer:
build:
context: .
dockerfile: ./Dockerfile
volumes:
- type: bind
source: ..
target: /workspace
command: sleep infinity
================================================
FILE: .gitattributes
================================================
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf
*.sh text eol=lf
*.conf text eol=lf
*.ply binary
*.splat binary
*.lock -diff
================================================
FILE: .github/dependabot.yml
================================================
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
ignore:
# These are peer deps of Cargo and should not be automatically bumped
- dependency-name: "semver"
- dependency-name: "crates-io"
rebase-strategy: "disabled"
================================================
FILE: .github/workflows/bench.yml
================================================
name: bench
on:
pull_request:
types: [ labeled, synchronize ]
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
bench:
if: contains(github.event.pull_request.labels.*.name, 'bench')
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest, macos-14]
runs-on: ${{ matrix.os }}
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-build-stable-${{ hashFiles('**/Cargo.toml') }}
- name: io benchmark
uses: boa-dev/criterion-compare-action@v3.2.4
with:
benchName: "io"
branchName: ${{ github.base_ref }}
token: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/build.yml
================================================
name: build
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest, macos-14]
rust-toolchain:
- nightly
runs-on: ${{ matrix.os }}
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: Setup ${{ matrix.rust-toolchain }} rust toolchain with caching
uses: brndnmtthws/rust-action@v1
with:
toolchain: ${{ matrix.rust-toolchain }}
components: rustfmt, clippy
enable-sccache: "false"
- name: build
run: cargo build
build_tools:
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest, macos-14]
rust-toolchain:
- nightly
runs-on: ${{ matrix.os }}
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: Setup ${{ matrix.rust-toolchain }} rust toolchain with caching
uses: brndnmtthws/rust-action@v1
with:
toolchain: ${{ matrix.rust-toolchain }}
components: rustfmt, clippy
enable-sccache: "false"
- name: build_tools
run: cargo build --bin ply_to_gcloud
================================================
FILE: .github/workflows/clippy.yml
================================================
name: clippy
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
clippy:
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest, macos-14]
rust-toolchain:
- nightly
runs-on: ${{ matrix.os }}
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: Setup ${{ matrix.rust-toolchain }} rust toolchain with caching
uses: brndnmtthws/rust-action@v1
with:
toolchain: ${{ matrix.rust-toolchain }}
components: rustfmt, clippy
enable-sccache: "false"
- name: lint
run: cargo clippy --all-features --all-targets -- -D warnings
================================================
FILE: .github/workflows/deploy-pages.yml
================================================
name: deploy github pages
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: write
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
deploy:
runs-on: macos-latest
steps:
- name: checkout repository
uses: actions/checkout@v4
- name: setup nightly rust toolchain with caching
uses: brndnmtthws/rust-action@v1
with:
toolchain: nightly
components: rustfmt, clippy
enable-sccache: "false"
- name: install wasm32-unknown-unknown
run: rustup target add wasm32-unknown-unknown
- name: install wasm-bindgen-cli
run: cargo install wasm-bindgen-cli --version 0.2.108 --locked --force
- name: build web output and thumbnails
env:
RUSTFLAGS: ""
CARGO_ENCODED_RUSTFLAGS: ""
RUSTDOCFLAGS: ""
THUMBNAIL_SCENE_CACHE_CLEANUP: "1"
run: bash ./tools/build_www.sh
- name: copy assets
run: |
mkdir -p ./www/assets
rsync -a --exclude '.thumbnail_cache' ./assets/ ./www/assets/
- name: deploy to github pages
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: ./www
branch: www
================================================
FILE: .github/workflows/test.yml
================================================
name: test
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
test:
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest, macos-14]
rust-toolchain:
- nightly
runs-on: ${{ matrix.os }}
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: Setup ${{ matrix.rust-toolchain }} rust toolchain with caching
uses: brndnmtthws/rust-action@v1
with:
toolchain: ${{ matrix.rust-toolchain }}
components: rustfmt, clippy
enable-sccache: "false"
- name: test (default)
run: cargo test
test_web:
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest, macos-14]
rust-toolchain:
- nightly
runs-on: ${{ matrix.os }}
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: Setup ${{ matrix.rust-toolchain }} rust toolchain with caching
uses: brndnmtthws/rust-action@v1
with:
toolchain: ${{ matrix.rust-toolchain }}
components: rustfmt, clippy
enable-sccache: "false"
- name: test (web)
run: cargo test --no-default-features --features="web io_ply tooling"
================================================
FILE: .github/workflows/todo_tracker.yml
================================================
name: 'todo tracker'
on:
push:
branches: [ main ]
jobs:
build:
permissions:
issues: write
name: todo_tracker
runs-on: [ubuntu-latest]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: "TODO to Issue"
uses: "alstr/todo-to-issue-action@v5"
id: "todo"
with:
AUTO_ASSIGN: true
================================================
FILE: .gitignore
================================================
debug/
target/
vendor/
out/
**/*.rs.bk
*.pdb
*.py
*.gc4d
*.gcloud
*.glb
*.ply
*.ply4d
*.json
.DS_Store
exports/
screenshots/
headless_output/
www/assets/
www/examples/thumbnails/
# Keep generated GLBs out of git while allowing conformance fixtures.
!tests/fixtures/**/*.glb
================================================
FILE: .vscode/bevy_gaussian_splatting.code-workspace
================================================
{
"folders": [
{
"path": "../"
},
{
"path": "../../bevy"
}
],
"settings": {
"liveServer.settings.multiRootWorkspaceName": "bevy_gaussian_splatting"
}
}
================================================
FILE: CONTRIBUTING.md
================================================
# contributing to bevy_gaussian_splatting

thank you for your interest in contributing to `bevy_gaussian_splatting`! contributions of all forms are welcome and appreciated. this includes code contributions, bug reports, documentation, or any other form of support.
## getting started
as `bevy_gaussian_splatting` is a smaller project, the contribution process is more informal. feel free to contribute in a way that aligns with your interests and expertise. if you have any questions or need guidance, don't hesitate to reach out.
## ways to contribute
1. **code contributions**: if you have improvements or new features in mind, feel free to open a pull request. for larger changes, it's a good idea to discuss them first through a github issue.
2. **bug reports**: if you find any bugs, please open an issue describing the problem, including any relevant details that could help in resolving it.
3. **documentation**: improvements or additions to documentation are always welcome. this can include both inline code comments and updates to readme or other markdown files.
4. **ideas and suggestions**: have ideas for new features or ways to improve the project? open an issue to discuss your suggestions!
## pull request process
1. ensure that any new code complies with the existing code style and structure.
2. update the readme.md or other documentation with details of changes, if applicable.
3. open a pull request with a clear description of the changes.
## questions?
if you're unsure about something or need help, feel free to open an issue asking for guidance. as the project grows, we aim to make contributing as accessible and straightforward as possible.
## code of conduct
while we don't have a formal code of conduct, we expect contributors to be respectful, open-minded, and collaborative. let's work together to maintain a welcoming and inclusive environment.
================================================
FILE: Cargo.toml
================================================
[package]
name = "bevy_gaussian_splatting"
description = "bevy gaussian splatting render pipeline plugin"
version = "7.0.1"
edition = "2024"
rust-version = "1.89.0"
authors = ["mosure <mitchell@mosure.me>"]
license = "MIT OR Apache-2.0"
keywords = [
"bevy",
"gaussian-splatting",
"render-pipeline",
"ply",
]
categories = [
"computer-vision",
"graphics",
"rendering",
"rendering::data-formats",
]
homepage = "https://github.com/mosure/bevy_gaussian_splatting"
repository = "https://github.com/mosure/bevy_gaussian_splatting"
readme = "README.md"
exclude = [
".devcontainer",
".github",
"docs",
"dist",
"build",
"assets",
"credits",
]
default-run = "bevy_gaussian_splatting"
# TODO: add a feature flag for each gaussian format
# TODO: resolve one-hot feature flags through runtime configuration
[features]
default = [
"io_flexbuffers",
"io_ply",
# "packed",
"planar",
"buffer_storage",
# "buffer_texture",
"sh3",
# "precompute_covariance_3d",
"query_select",
# "query_sparse",
# TODO: bevy_interleave storage bind group read_only per plane attribute support
# "morph_particles",
"morph_interpolate",
"nightly_generic_alias",
"sort_bitonic",
"sort_radix",
"sort_rayon",
"sort_std",
"tooling",
"viewer",
"file_asset",
"web_asset",
]
debug_gpu = []
io_bincode2 = ["dep:bincode2", "dep:flate2"]
io_flexbuffers = ["dep:flexbuffers"]
io_ply = ["dep:ply-rs"]
material_noise = ["noise", "dep:noise"]
morph_particles = []
morph_interpolate = []
nightly_generic_alias = []
noise = []
sh0 = []
sh1 = []
sh2 = []
sh3 = []
sh4 = []
precompute_covariance_3d = []
packed = []
planar = []
buffer_storage = []
buffer_texture = []
query_raycast = []
query_select = []
query_sparse = ["dep:kd-tree", "query_select"]
sort_bitonic = []
sort_radix = []
sort_rayon = ["dep:rayon"]
sort_std = []
testing = []
tooling = ["dep:byte-unit"]
debug_tooling = ["tooling"]
perftest = []
headless = [
"bevy/png",
"io_flexbuffers",
"io_ply",
"planar",
"buffer_storage",
"sh3",
"sort_rayon",
"sort_std",
"sort_bitonic",
"sort_radix",
]
viewer = [
"dep:bevy-inspector-egui",
"dep:bevy_panorbit_camera",
# "bevy_transform_gizmo",
"bevy/bevy_gizmos",
"bevy/bevy_text",
"bevy/bevy_ui",
"bevy/bevy_ui_render",
"bevy/multi_threaded", # bevy screenshot functionality requires bevy/multi_threaded as of 0.12.1
"bevy/png",
]
web = [
"buffer_storage",
"sh0",
"io_flexbuffers",
"io_ply",
"planar",
"sort_radix",
"sort_std",
"viewer",
"web_asset",
"webgpu",
]
file_asset = []
web_asset = [
"bevy/http",
"bevy/https",
]
# note: webgl2/buffer_texture are deprecated
webgl2 = ["bevy/webgl2"]
webgpu = ["bevy/webgpu"]
[dependencies]
base64 = "0.22"
bevy_args = { version = "3.0.0" }
bevy-inspector-egui = { version = "0.36.0", optional = true }
bevy_interleave = { version = "0.9.0" }
bevy_panorbit_camera = { version = "0.34.0", optional = true, features = ["bevy_egui"] }
bevy_transform_gizmo = { version = "0.12.1", optional = true }
bincode2 = { version = "2.0", optional = true }
byte-unit = { version = "5.2", optional = true }
bytemuck = "1.23"
clap = { version = "4.5", features = ["derive"] }
flate2 = { version = "1.1", optional = true }
flexbuffers = { version = "25.2", optional = true }
gltf = "1.4"
half = { version = "2.7", features = ["serde"] }
# image = { version = "0.25.6", default-features = false, features = ["png"] }
kd-tree = { version = "0.6", optional = true }
noise = { version = "0.9.0", optional = true }
ply-rs = { version = "0.1", optional = true }
rand = "0.9"
rayon = { version = "1.8", optional = true }
serde = "1.0"
serde_json = "1.0"
static_assertions = "1.1"
typenum = "1.18"
wgpu = "27"
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1"
getrandom = { version = "0.3", default-features = false, features = ["wasm_js"] }
wasm-bindgen = "0.2"
[dependencies.bevy]
version = "0.18"
default-features = false
features = [
"bevy_asset",
"bevy_camera",
"bevy_core_pipeline",
"bevy_log",
"bevy_pbr",
"bevy_render",
"bevy_winit",
"serialize",
"std",
"zstd_rust",
"x11",
]
[dependencies.web-sys]
version = "0.3"
features = [
'Document',
'Element',
'HtmlElement',
'Location',
'Node',
'Window',
]
[dev-dependencies]
criterion = { version = "0.8", features = ["html_reports"] }
crossbeam-channel = "0.5.15"
futures-intrusive = { version = "0.5.0" }
pollster = { version = "0.4.0" }
[profile.dev.package."*"]
opt-level = 3
[profile.dev]
opt-level = 1
[profile.release]
lto = "thin"
codegen-units = 1
opt-level = 3
[profile.wasm-release]
inherits = "release"
opt-level = "z"
lto = "fat"
codegen-units = 1
[lib]
path = "src/lib.rs"
[[bin]]
name = "bevy_gaussian_splatting"
path = "viewer/viewer.rs"
required-features = ["viewer"]
[[bin]]
name = "ply_to_gcloud"
path = "tools/ply_to_gcloud.rs"
required-features = ["io_ply", "tooling"]
[[bin]]
name = "compare_aabb_obb"
path = "tools/compare_aabb_obb.rs"
required-features = ["debug_tooling"]
[[bin]]
name = "surfel_plane"
path = "tools/surfel_plane.rs"
required-features = ["debug_tooling"]
[[bin]]
name = "render_trellis_thumbnails"
path = "tools/render_trellis_thumbnails.rs"
required-features = ["io_ply"]
[[bin]]
name = "test_gaussian"
path = "tests/gpu/gaussian.rs"
required-features = ["testing"]
[[bin]]
name = "test_radix"
path = "tests/gpu/radix.rs"
required-features = ["debug_gpu", "sort_radix", "testing"]
[[example]]
name = "minimal"
path = "examples/minimal.rs"
[[example]]
name = "headless"
path = "examples/headless.rs"
required-features = ["headless"]
[[example]]
name = "multi_camera"
path = "examples/multi_camera.rs"
required-features = ["viewer"]
[[bench]]
name = "io"
harness = false
================================================
FILE: LICENSE-APACHE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
================================================
FILE: LICENSE-MIT
================================================
MIT License
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: README.md
================================================
# bevy_gaussian_splatting 🌌
[](https://github.com/Mosure/bevy_gaussian_splatting/actions?query=workflow%3Atest)
[](https://raw.githubusercontent.com/mosure/bevy_gaussian_splatting/main/LICENSE-MIT)
[](https://crates.io/crates/bevy_gaussian_splatting)
bevy gaussian splatting render pipeline plugin. view the [live demo gallery](https://mosure.github.io/bevy_gaussian_splatting/examples/) or open [`trellis.glb`](https://mosure.github.io/bevy_gaussian_splatting/index.html?input_scene=https%3A%2F%2Fmitchell.mosure.me%2Ftrellis.glb&rasterization_mode=Color) directly.


## install
```bash
cargo +nightly install bevy_gaussian_splatting
bevy_gaussian_splatting --input-cloud [file://gaussian.ply | https://mitchell.mosure.me/go_trimmed.ply]
bevy_gaussian_splatting --input-scene [file://scene.glb | https://mitchell.mosure.me/trellis.glb]
```
> note: default bevy_gaussian_splatting features require nightly rust for generic associated types. to use on stable, disable default features and `nightly_generic_alias` feature
## viewer hotkeys
- `esc`: close viewer
- `s`: save screenshot to `screenshots/`
- `g`: export the loaded gaussian scene to `exports/gaussian_scene_<frame>.glb` (cloud transforms + active camera)
## capabilities
- [X] ply to gcloud converter
- [X] gcloud and ply asset loaders
- [X] bevy gaussian cloud render pipeline
- [X] gaussian cloud particle effects
- [X] wasm support /w [live demo](https://mosure.github.io/bevy_gaussian_splatting/index.html)
- [X] depth colorization
- [X] normal rendering
- [X] f16 and f32 gcloud
- [X] wgl2 and webgpu
- [X] multi-format scenes
- [X] 2dgs
- [X] 3dgs
- [x] 4dgs
- [X] [glTF `KHR_gaussian_splatting`](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_gaussian_splatting) scene load/save
- [ ] 4dgs motion blur
- [ ] [deformable radial kernel](https://github.com/VAST-AI-Research/Deformable-Radial-Kernel-Splatting)
- [ ] implicit mlp node (isotropic rotation, color)
- [ ] temporal gaussian hierarchy
- [ ] gcloud, spherical harmonic coefficients Huffman encoding
- [ ] [spz](https://github.com/nianticlabs/spz) format io
- [ ] spherical harmonic coefficients clustering
- [ ] 4D gaussian cloud wavelet compression
- [ ] accelerated spatial queries
- [ ] temporal depth sorting
- [ ] skeletons
- [ ] volume masks
- [ ] level of detail
- [ ] lighting and shadows
- [ ] bevy_openxr support
- [ ] bevy 3D camera to gaussian cloud pipeline
## usage
```rust
use bevy::prelude::*;
use bevy_gaussian_splatting::{
CloudSettings,
GaussianSplattingPlugin,
PlanarGaussian3dHandle,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(GaussianSplattingPlugin)
.add_systems(Startup, setup_gaussian_cloud)
.run();
}
fn setup_gaussian_cloud(
mut commands: Commands,
asset_server: Res<AssetServer>,
) {
// CloudSettings and Visibility are automatically added
commands.spawn((
PlanarGaussian3dHandle(asset_server.load("scenes/icecream.gcloud")),
CloudSettings::default(),
));
commands.spawn(Camera3d::default());
}
```
## tools
- [ply to gcloud converter](tools/README.md#ply-to-gcloud-converter)
- [gaussian cloud training pipeline](https://github.com/mosure/burn_gaussian_splatting)
- aabb vs. obb gaussian comparison via `cargo run --bin compare_aabb_obb`
### creating gaussian clouds
the following tools are compatible with `bevy_gaussian_splatting`:
- [X] 2d gaussian clouds:
- [gsplat](https://docs.gsplat.studio/main/)
- [X] 3d gaussian clouds:
- [brush](https://github.com/ArthurBrussee/brush)
- [gsplat](https://docs.gsplat.studio/main/)
- [gaussian-splatting](https://github.com/graphdeco-inria/gaussian-splatting)
- [X] 4d gaussian clouds:
- [4d-gaussian-splatting](https://fudan-zvg.github.io/4d-gaussian-splatting/)
- [4dgs ply-export](https://gist.github.com/mosure/d9d4d271e05a106157ce39db62ec4f84)
- [easy-volcap](https://github.com/zju3dv/EasyVolcap)
## compatible bevy versions
| `bevy_gaussian_splatting` | `bevy` |
| :-- | :-- |
| `7.0` | `0.18` |
| `6.0` | `0.17` |
| `5.0` | `0.16` |
| `3.0` | `0.15` |
| `2.3` | `0.14` |
| `2.1` | `0.13` |
| `0.4 - 2.0` | `0.12` |
| `0.1 - 0.3` | `0.11` |
## license
licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
## contribution
unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
additional terms or conditions.
## analytics

================================================
FILE: benches/io.rs
================================================
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use bevy::prelude::Transform;
use bevy_gaussian_splatting::{
CloudSettings, Gaussian3d, Gaussian4d, GaussianPrimitiveMetadata, PlanarGaussian3d,
PlanarGaussian4d, SceneExportCloud, io::codec::CloudCodec,
io::scene::encode_khr_gaussian_scene_gltf_bytes, random_gaussians_3d, random_gaussians_4d,
};
const GAUSSIAN_COUNTS: [usize; 4] = [
1000, 10000, 84_348, 1_244_819,
// 6_131_954,
];
fn gaussian_cloud_3d_decode_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("encode 3d gaussian clouds");
for count in GAUSSIAN_COUNTS.iter() {
group.throughput(Throughput::Bytes(
*count as u64 * std::mem::size_of::<Gaussian3d>() as u64,
));
group.bench_with_input(BenchmarkId::new("decode/3d", count), &count, |b, &count| {
let gaussians = random_gaussians_3d(*count);
let bytes = gaussians.encode();
b.iter(|| PlanarGaussian3d::decode(bytes.as_slice()));
});
}
}
fn gaussian_cloud_4d_decode_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("encode 4d gaussian clouds");
for count in GAUSSIAN_COUNTS.iter() {
group.throughput(Throughput::Bytes(
*count as u64 * std::mem::size_of::<Gaussian4d>() as u64,
));
group.bench_with_input(BenchmarkId::new("decode/4d", count), &count, |b, &count| {
let gaussians = random_gaussians_4d(*count);
let bytes = gaussians.encode();
b.iter(|| PlanarGaussian4d::decode(bytes.as_slice()));
});
}
}
fn khr_gltf_scene_encode_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("encode khr gltf gaussian scenes");
for count in GAUSSIAN_COUNTS.iter() {
group.throughput(Throughput::Bytes(
*count as u64 * std::mem::size_of::<Gaussian3d>() as u64,
));
group.bench_with_input(
BenchmarkId::new("encode/khr_gltf_scene", count),
&count,
|b, &count| {
let cloud = random_gaussians_3d(*count);
let export_cloud = SceneExportCloud {
cloud,
name: "benchmark_cloud".to_owned(),
settings: CloudSettings::default(),
transform: Transform::default(),
metadata: GaussianPrimitiveMetadata::default(),
};
b.iter(|| {
encode_khr_gaussian_scene_gltf_bytes(std::slice::from_ref(&export_cloud), None)
.expect("benchmark scene encoding should succeed");
});
},
);
}
}
criterion_group! {
name = io_benches;
config = Criterion::default().sample_size(10);
targets = gaussian_cloud_3d_decode_benchmark,
gaussian_cloud_4d_decode_benchmark,
khr_gltf_scene_encode_benchmark,
}
criterion_main!(io_benches);
================================================
FILE: docs/credits.md
================================================
- [2d-gaussian-splatting](https://github.com/hbb1/2d-gaussian-splatting)
- [4d gaussians](https://github.com/hustvl/4DGaussians)
- [4d-gaussian-splatting](https://fudan-zvg.github.io/4d-gaussian-splatting/)
- [bevy](https://github.com/bevyengine/bevy)
- [bevy-hanabi](https://github.com/djeedai/bevy_hanabi)
- [d3ga](https://zielon.github.io/d3ga/)
- [deformable-3d-gaussians](https://github.com/ingra14m/Deformable-3D-Gaussians)
- [diff-gaussian-rasterization](https://github.com/graphdeco-inria/diff-gaussian-rasterization)
- [dreamgaussian](https://github.com/dreamgaussian/dreamgaussian)
- [dynamic-3d-gaussians](https://github.com/JonathonLuiten/Dynamic3DGaussians)
- [ewa splatting](https://www.cs.umd.edu/~zwicker/publications/EWASplatting-TVCG02.pdf)
- [gaussian-grouping](https://github.com/lkeab/gaussian-grouping)
- [gaussian-splatting](https://github.com/graphdeco-inria/gaussian-splatting)
- [gaussian-splatting-viewer](https://github.com/limacv/GaussianSplattingViewer/tree/main)
- [gaussian-splatting-web](https://github.com/cvlab-epfl/gaussian-splatting-web)
- [gir](https://3dgir.github.io/)
- [making gaussian splats smaller](https://aras-p.info/blog/2023/09/13/Making-Gaussian-Splats-smaller/)
- [masked-spacetime-hashing](https://github.com/masked-spacetime-hashing/msth)
- [onesweep](https://arxiv.org/ftp/arxiv/papers/2206/2206.01784.pdf)
- [pasture](https://github.com/Mortano/pasture)
- [phys-gaussian](https://xpandora.github.io/PhysGaussian/)
- [point-visualizer](https://github.com/mosure/point-visualizer)
- [rusty-automata](https://github.com/mosure/rusty-automata)
- [scaffold-gs](https://city-super.github.io/scaffold-gs/)
- [shader-one-sweep](https://github.com/b0nes164/ShaderOneSweep)
- [spacetime-gaussians](https://github.com/oppo-us-research/SpacetimeGaussians)
- [splat](https://github.com/antimatter15/splat)
- [splatter](https://github.com/Lichtso/splatter)
- [sturdy-dollop](https://github.com/mosure/sturdy-dollop)
- [sugar](https://github.com/Anttwo/SuGaR)
- [taichi_3d_gaussian_splatting](https://github.com/wanmeihuali/taichi_3d_gaussian_splatting)
- [temporal-gaussian-hierarchy](https://zju3dv.github.io/longvolcap/)
================================================
FILE: examples/headless.rs
================================================
//! Headless rendering for gaussian splatting
//!
//! Renders gaussian splatting to images without creating a window.
//! Based on Bevy's headless_renderer example.
//!
//! Usage: cargo run --example headless --no-default-features --features "headless" -- [filename]
use bevy::{
app::{AppExit, ScheduleRunnerPlugin},
camera::RenderTarget,
core_pipeline::tonemapping::Tonemapping,
image::TextureFormatPixelInfo,
prelude::*,
render::{
Extract, Render, RenderApp, RenderSystems,
render_asset::RenderAssets,
render_graph::{self, NodeRunError, RenderGraph, RenderGraphContext, RenderLabel},
render_resource::{
Buffer, BufferDescriptor, BufferUsages, CommandEncoderDescriptor, Extent3d, MapMode,
PollType, TexelCopyBufferInfo, TexelCopyBufferLayout, TextureFormat, TextureUsages,
},
renderer::{RenderContext, RenderDevice, RenderQueue},
texture::GpuImage,
},
window::ExitCondition,
winit::WinitPlugin,
};
use bevy_args::BevyArgsPlugin;
use bevy_gaussian_splatting::{
CloudSettings, GaussianCamera, GaussianMode, GaussianSplattingPlugin, PlanarGaussian3d,
PlanarGaussian3dHandle, PlanarGaussian4d, PlanarGaussian4dHandle,
gaussian::interface::TestCloud, random_gaussians_3d, random_gaussians_3d_seeded,
random_gaussians_4d, random_gaussians_4d_seeded, utils::GaussianSplattingViewer,
};
use crossbeam_channel::{Receiver, Sender};
use std::{
path::PathBuf,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
time::Duration,
};
#[derive(Resource, Deref)]
struct MainWorldReceiver(Receiver<Vec<u8>>);
#[derive(Resource, Deref)]
struct RenderWorldSender(Sender<Vec<u8>>);
#[derive(Debug, Default, Resource)]
struct CaptureController {
frames_to_wait: u32,
width: u32,
height: u32,
}
impl CaptureController {
pub fn new(width: u32, height: u32) -> Self {
Self {
frames_to_wait: 40,
width,
height,
}
}
}
fn main() {
App::new()
.insert_resource(CaptureController::new(1920, 1080))
.insert_resource(ClearColor(Color::srgb_u8(0, 0, 0)))
.add_plugins(
DefaultPlugins
.set(ImagePlugin::default_nearest())
.set(WindowPlugin {
primary_window: None,
exit_condition: ExitCondition::DontExit,
..default()
})
// Disable WinitPlugin for headless environments
.disable::<WinitPlugin>(),
)
.add_plugins(BevyArgsPlugin::<GaussianSplattingViewer>::default())
.add_plugins(ImageCopyPlugin)
.add_plugins(CaptureFramePlugin)
.add_plugins(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f64(
1.0 / 60.0,
)))
.add_plugins(GaussianSplattingPlugin)
.add_systems(Startup, setup_gaussian_cloud)
.run();
}
#[allow(clippy::too_many_arguments)]
fn setup_gaussian_cloud(
mut commands: Commands,
asset_server: Res<AssetServer>,
args: Res<GaussianSplattingViewer>,
mut gaussian_assets: ResMut<Assets<PlanarGaussian3d>>,
mut gaussian_4d_assets: ResMut<Assets<PlanarGaussian4d>>,
mut images: ResMut<Assets<Image>>,
render_device: Res<RenderDevice>,
controller: Res<CaptureController>,
) {
let cloud_transform = args.cloud_transform();
let cloud_settings = CloudSettings {
gaussian_mode: args.gaussian_mode,
playback_mode: args.playback_mode,
rasterize_mode: args.rasterization_mode,
..default()
};
// Setup render target
let size = Extent3d {
width: controller.width,
height: controller.height,
..default()
};
let mut render_target_image =
Image::new_target_texture(size.width, size.height, TextureFormat::bevy_default(), None);
render_target_image.texture_descriptor.usage |= TextureUsages::COPY_SRC;
let render_target_handle = images.add(render_target_image);
let cpu_image =
Image::new_target_texture(size.width, size.height, TextureFormat::bevy_default(), None);
let cpu_image_handle = images.add(cpu_image);
match args.gaussian_mode {
GaussianMode::Gaussian2d | GaussianMode::Gaussian3d => {
// Load or generate gaussian cloud
let cloud = if args.gaussian_count > 0 {
println!("Generating {} gaussians", args.gaussian_count);
if let Some(seed) = args.gaussian_seed {
gaussian_assets.add(random_gaussians_3d_seeded(args.gaussian_count, seed))
} else {
gaussian_assets.add(random_gaussians_3d(args.gaussian_count))
}
} else if args.input_cloud.is_some() && !args.input_cloud.as_ref().unwrap().is_empty() {
println!("Loading {:?}", args.input_cloud);
asset_server.load(args.input_cloud.as_ref().unwrap())
} else {
gaussian_assets.add(PlanarGaussian3d::test_model())
};
commands.spawn((
PlanarGaussian3dHandle(cloud),
cloud_settings.clone(),
Name::new("gaussian_cloud"),
cloud_transform,
));
}
GaussianMode::Gaussian4d => {
let cloud = if args.gaussian_count > 0 {
println!("Generating {} gaussians", args.gaussian_count);
if let Some(seed) = args.gaussian_seed {
gaussian_4d_assets.add(random_gaussians_4d_seeded(args.gaussian_count, seed))
} else {
gaussian_4d_assets.add(random_gaussians_4d(args.gaussian_count))
}
} else if args.input_cloud.is_some() && !args.input_cloud.as_ref().unwrap().is_empty() {
println!("Loading {:?}", args.input_cloud);
asset_server.load(args.input_cloud.as_ref().unwrap())
} else {
gaussian_4d_assets.add(PlanarGaussian4d::test_model())
};
commands.spawn((
PlanarGaussian4dHandle(cloud),
cloud_settings,
Name::new("gaussian_cloud"),
cloud_transform,
));
}
}
commands.spawn((
Camera3d::default(),
Camera::default(),
RenderTarget::Image(render_target_handle.clone().into()),
Transform::from_translation(Vec3::new(0.0, 1.5, 5.0)),
Tonemapping::None,
GaussianCamera::default(),
));
// Spawn image copier for GPU->CPU transfer
commands.spawn(ImageCopier::new(render_target_handle, size, &render_device));
// Spawn image to save
commands.spawn(ImageToSave(cpu_image_handle));
}
/// Plugin for copying images from GPU to CPU
pub struct ImageCopyPlugin;
impl Plugin for ImageCopyPlugin {
fn build(&self, app: &mut App) {
let (sender, receiver) = crossbeam_channel::unbounded();
let render_app = app
.insert_resource(MainWorldReceiver(receiver))
.sub_app_mut(RenderApp);
let mut graph = render_app.world_mut().resource_mut::<RenderGraph>();
graph.add_node(ImageCopy, ImageCopyDriver);
graph.add_node_edge(bevy::render::graph::CameraDriverLabel, ImageCopy);
render_app
.insert_resource(RenderWorldSender(sender))
.add_systems(ExtractSchedule, extract_image_copiers)
.add_systems(
Render,
receive_image_from_buffer.after(RenderSystems::Render),
);
}
}
pub struct CaptureFramePlugin;
impl Plugin for CaptureFramePlugin {
fn build(&self, app: &mut App) {
app.add_systems(PostUpdate, save_captured_frame);
}
}
#[derive(Clone, Component)]
struct ImageCopier {
buffer: Buffer,
enabled: Arc<AtomicBool>,
src_image: Handle<Image>,
}
impl ImageCopier {
pub fn new(src_image: Handle<Image>, size: Extent3d, render_device: &RenderDevice) -> Self {
let padded_bytes_per_row = RenderDevice::align_copy_bytes_per_row(size.width as usize) * 4;
let buffer = render_device.create_buffer(&BufferDescriptor {
label: Some("image_copier_buffer"),
size: padded_bytes_per_row as u64 * size.height as u64,
usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
mapped_at_creation: false,
});
Self {
buffer,
src_image,
enabled: Arc::new(AtomicBool::new(true)),
}
}
pub fn enabled(&self) -> bool {
self.enabled.load(Ordering::Relaxed)
}
}
#[derive(Clone, Default, Resource, Deref)]
struct ImageCopiers(Vec<ImageCopier>);
fn extract_image_copiers(mut commands: Commands, image_copiers: Extract<Query<&ImageCopier>>) {
commands.insert_resource(ImageCopiers(image_copiers.iter().cloned().collect()));
}
/// RenderGraph label
#[derive(Debug, PartialEq, Eq, Clone, Hash, RenderLabel)]
struct ImageCopy;
#[derive(Default)]
struct ImageCopyDriver;
impl render_graph::Node for ImageCopyDriver {
fn run(
&self,
_graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
world: &World,
) -> Result<(), NodeRunError> {
let image_copiers = world.get_resource::<ImageCopiers>().unwrap();
let gpu_images = world.get_resource::<RenderAssets<GpuImage>>().unwrap();
for image_copier in image_copiers.iter() {
if !image_copier.enabled() {
continue;
}
let Some(src_image) = gpu_images.get(&image_copier.src_image) else {
continue;
};
let mut encoder = render_context
.render_device()
.create_command_encoder(&CommandEncoderDescriptor::default());
let block_dimensions = src_image.texture_format.block_dimensions();
let block_size = src_image.texture_format.block_copy_size(None).unwrap();
let padded_bytes_per_row = RenderDevice::align_copy_bytes_per_row(
(src_image.size.width as usize / block_dimensions.0 as usize) * block_size as usize,
);
encoder.copy_texture_to_buffer(
src_image.texture.as_image_copy(),
TexelCopyBufferInfo {
buffer: &image_copier.buffer,
layout: TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(
std::num::NonZero::<u32>::new(padded_bytes_per_row as u32)
.unwrap()
.into(),
),
rows_per_image: None,
},
},
src_image.size,
);
let render_queue = world.get_resource::<RenderQueue>().unwrap();
render_queue.submit(std::iter::once(encoder.finish()));
}
Ok(())
}
}
fn receive_image_from_buffer(
image_copiers: Res<ImageCopiers>,
render_device: Res<RenderDevice>,
sender: Res<RenderWorldSender>,
) {
for image_copier in image_copiers.0.iter() {
if !image_copier.enabled() {
continue;
}
let buffer_slice = image_copier.buffer.slice(..);
let (tx, rx) = crossbeam_channel::bounded(1);
buffer_slice.map_async(MapMode::Read, move |result| match result {
Ok(()) => tx.send(()).expect("Failed to send map result"),
Err(err) => panic!("Failed to map buffer: {err}"),
});
render_device
.poll(PollType::wait_indefinitely())
.expect("Failed to poll device");
rx.recv().expect("Failed to receive buffer map");
let _ = sender.send(buffer_slice.get_mapped_range().to_vec());
image_copier.buffer.unmap();
}
}
#[derive(Component, Deref)]
struct ImageToSave(Handle<Image>);
fn save_captured_frame(
images_to_save: Query<&ImageToSave>,
receiver: Res<MainWorldReceiver>,
mut images: ResMut<Assets<Image>>,
mut controller: ResMut<CaptureController>,
mut app_exit: MessageWriter<AppExit>,
) {
if controller.frames_to_wait > 0 {
controller.frames_to_wait -= 1;
while receiver.try_recv().is_ok() {}
return;
}
// Try to receive image data
let mut image_data = Vec::new();
while let Ok(data) = receiver.try_recv() {
image_data = data;
}
if image_data.is_empty() {
return;
}
for image_handle in images_to_save.iter() {
let Some(image) = images.get_mut(image_handle.id()) else {
continue;
};
let row_bytes =
image.width() as usize * image.texture_descriptor.format.pixel_size().unwrap();
let aligned_row_bytes = RenderDevice::align_copy_bytes_per_row(row_bytes);
if row_bytes == aligned_row_bytes {
image.data.as_mut().unwrap().clone_from(&image_data);
} else {
// Shrink to original size
image.data = Some(
image_data
.chunks(aligned_row_bytes)
.take(image.height() as usize)
.flat_map(|row| &row[..row_bytes.min(row.len())])
.cloned()
.collect(),
);
}
let img = match image.clone().try_into_dynamic() {
Ok(img) => img.to_rgba8(),
Err(e) => panic!("Failed to create image: {e:?}"),
};
let output_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("headless_output");
std::fs::create_dir_all(&output_dir).unwrap();
let output_path = output_dir.join("0.png");
info!("Saving screenshot to {:?}", output_path);
if let Err(e) = img.save(&output_path) {
panic!("Failed to save image: {e}");
}
}
app_exit.write(AppExit::Success);
}
================================================
FILE: examples/minimal.rs
================================================
// TODO: minimal app
fn main() {
println!("Hello, world!");
}
================================================
FILE: examples/multi_camera.rs
================================================
use bevy::{
app::AppExit, camera::Viewport, core_pipeline::tonemapping::Tonemapping, prelude::*,
window::WindowResized,
};
use bevy_args::{BevyArgsPlugin, parse_args};
use bevy_inspector_egui::{bevy_egui::EguiPlugin, quick::WorldInspectorPlugin};
use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin};
use bevy_gaussian_splatting::{
CloudSettings, Gaussian3d, GaussianCamera, GaussianMode, GaussianSplattingPlugin,
PlanarGaussian3d, PlanarGaussian3dHandle, SphericalHarmonicCoefficients,
gaussian::f32::Rotation,
utils::{GaussianSplattingViewer, setup_hooks},
};
fn multi_camera_app() {
let config = parse_args::<GaussianSplattingViewer>();
let mut app = App::new();
// setup for gaussian viewer app
app.insert_resource(ClearColor(Color::srgb_u8(0, 0, 0)));
app.add_plugins(
DefaultPlugins
.set(ImagePlugin::default_nearest())
.set(WindowPlugin {
primary_window: Some(Window {
mode: bevy::window::WindowMode::Windowed,
present_mode: bevy::window::PresentMode::AutoVsync,
prevent_default_event_handling: false,
resolution: bevy::window::WindowResolution::new(
config.width as u32,
config.height as u32,
),
title: config.name.clone(),
..default()
}),
..default()
}),
);
app.add_plugins(BevyArgsPlugin::<GaussianSplattingViewer>::default());
app.add_plugins(PanOrbitCameraPlugin);
if config.editor {
app.add_plugins(EguiPlugin::default());
app.add_plugins(WorldInspectorPlugin::new());
}
if config.press_esc_close {
app.add_systems(Update, esc_close);
}
app.add_plugins(GaussianSplattingPlugin);
app.add_systems(Startup, setup_multi_camera);
app.add_systems(Update, (press_s_to_spawn_camera, set_camera_viewports));
app.run();
}
pub fn setup_multi_camera(
mut commands: Commands,
_asset_server: Res<AssetServer>,
mut gaussian_assets: ResMut<Assets<PlanarGaussian3d>>,
) {
let grid_size_x = 10;
let grid_size_y = 10;
let spacing = 12.0;
// let mut blue_gaussians = Vec::new();
// let mut blue_sh = SphericalHarmonicCoefficients::default();
// blue_sh.set(2, 5.0);
// for i in 0..grid_size_x {
// for j in 0..grid_size_y {
// let x = i as f32 * spacing - (grid_size_x as f32 * spacing) / 2.0;
// let y = j as f32 * spacing - (grid_size_y as f32 * spacing) / 2.0;
// let position = [x, y, 0.0, 1.0];
// let scale = [2.0, 1.0, 0.01, 0.5];
// let angle = std::f32::consts::PI / 2.0 * i as f32 / grid_size_x as f32;
// let rotation = Quat::from_rotation_z(angle).to_array();
// let rotation = [3usize, 0usize, 1usize, 2usize]
// .iter()
// .map(|i| rotation[*i])
// .collect::<Vec<_>>()
// .try_into()
// .unwrap();
// let gaussian = Gaussian {
// position_visibility: position.into(),
// rotation: Rotation {
// rotation,
// },
// scale_opacity: scale.into(),
// spherical_harmonic: blue_sh,
// };
// blue_gaussians.push(gaussian);
// }
// }
// let cloud = asset_server.load("office.ply");
// commands.spawn((
// GaussianSplattingBundle {
// cloud,//: gaussian_assets.add(blue_gaussians.into()),
// ..default()
// },
// Name::new("gaussian_cloud_3dgs"),
// ));
let mut red_gaussians = Vec::new();
let mut red_sh = SphericalHarmonicCoefficients::default();
red_sh.set(0, 5.0);
for i in 0..grid_size_x {
for j in 0..grid_size_y {
let x = i as f32 * spacing - (grid_size_x as f32 * spacing) / 2.0;
let y = j as f32 * spacing - (grid_size_y as f32 * spacing) / 2.0;
let position = [x, y, 0.0, 1.0];
let scale = [2.0, 1.0, 0.01, 0.5];
let angle = std::f32::consts::PI / 2.0 * (i + 1) as f32 / grid_size_x as f32;
let rotation = Quat::from_rotation_z(angle).to_array();
let rotation = [3usize, 0usize, 1usize, 2usize]
.iter()
.map(|i| rotation[*i])
.collect::<Vec<_>>()
.try_into()
.unwrap();
let gaussian = Gaussian3d {
position_visibility: position.into(),
rotation: Rotation { rotation },
scale_opacity: scale.into(),
spherical_harmonic: red_sh,
};
red_gaussians.push(gaussian);
}
}
commands.spawn((
Transform::from_translation(Vec3::new(spacing, spacing, 0.0)),
PlanarGaussian3dHandle(gaussian_assets.add(red_gaussians)),
CloudSettings {
aabb: true,
gaussian_mode: GaussianMode::Gaussian2d,
..default()
},
Name::new("gaussian_cloud_2dgs"),
));
commands.spawn((
GaussianCamera { warmup: true },
Camera3d::default(),
Camera {
order: 0,
..default()
},
Transform::from_translation(Vec3::new(0.0, 1.5, 20.0)),
Tonemapping::None,
CameraPosition {
pos: UVec2::new(0, 0),
},
PanOrbitCamera {
allow_upside_down: true,
..default()
},
));
// commands.spawn((
// GaussianCamera,
// Camera3dBundle {
// camera: Camera{
// order: 1,
// ..default()
// },
// transform: Transform::from_translation(Vec3::new(0.0, 0.0, 40.0)),
// tonemapping: Tonemapping::None,
// ..default()
// },
// CameraPosition {
// pos: UVec2::new(1, 0),
// },
// PanOrbitCamera {
// allow_upside_down: true,
// ..default()
// },
// ));
}
fn press_s_to_spawn_camera(
keys: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
windows: Query<&Window>,
) {
if keys.just_pressed(KeyCode::KeyS) {
let window = windows.single().unwrap();
let size = window.physical_size() / UVec2::new(2, 1);
let pos = UVec2::new(1, 0);
commands.spawn((
GaussianCamera { warmup: true },
Camera3d::default(),
Camera {
order: 1,
viewport: Viewport {
physical_position: pos * size,
physical_size: size,
..default()
}
.into(),
..default()
},
Transform::from_translation(Vec3::new(0.0, 0.0, 40.0)),
Tonemapping::None,
CameraPosition { pos },
PanOrbitCamera {
allow_upside_down: true,
..default()
},
));
}
}
#[derive(Component)]
struct CameraPosition {
pos: UVec2,
}
fn set_camera_viewports(
windows: Query<&Window>,
mut resize_events: MessageReader<WindowResized>,
mut cameras: Query<(&CameraPosition, &mut Camera), With<GaussianCamera>>,
) {
for resize_event in resize_events.read() {
let window = windows.get(resize_event.window).unwrap();
let size = window.physical_size() / UVec2::new(2, 1);
for (position, mut camera) in &mut cameras {
camera.viewport = Some(Viewport {
physical_position: position.pos * size,
physical_size: size,
..default()
});
}
}
}
fn esc_close(keys: Res<ButtonInput<KeyCode>>, mut exit: MessageWriter<AppExit>) {
if keys.just_pressed(KeyCode::Escape) {
exit.write(AppExit::Success);
}
}
pub fn main() {
setup_hooks();
multi_camera_app();
}
================================================
FILE: src/camera.rs
================================================
use bevy::{
prelude::*,
render::extract_component::{ExtractComponent, ExtractComponentPlugin},
};
#[derive(Clone, Component, Debug, Default, ExtractComponent, Reflect)]
pub struct GaussianCamera {
pub warmup: bool,
}
#[derive(Default)]
pub struct GaussianCameraPlugin;
impl Plugin for GaussianCameraPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(ExtractComponentPlugin::<GaussianCamera>::default());
app.add_systems(Update, apply_camera_warmup);
}
}
// TODO: remove camera warmup when extracted view dynamic uniform offset synchronization is fixed
fn apply_camera_warmup(mut cameras: Query<&mut GaussianCamera>) {
for mut camera in cameras.iter_mut() {
if camera.warmup {
camera.warmup = false;
}
}
}
================================================
FILE: src/gaussian/cloud.rs
================================================
use bevy::{
camera::{
primitives::Aabb,
visibility::{NoFrustumCulling, VisibilityClass, VisibilitySystems, add_visibility_class},
},
ecs::{lifecycle::HookContext, world::DeferredWorld},
math::bounding::BoundingVolume,
prelude::*,
};
use bevy_interleave::prelude::*;
use crate::gaussian::interface::CommonCloud;
#[derive(Default)]
pub struct CloudPlugin<R: PlanarSync> {
_phantom: std::marker::PhantomData<R>,
}
pub struct CloudVisibilityClass;
fn add_planar_class(world: DeferredWorld, ctx: HookContext) {
add_visibility_class::<CloudVisibilityClass>(world, ctx);
}
impl<R: PlanarSync + Reflect + TypePath> Plugin for CloudPlugin<R>
where
R::PlanarType: CommonCloud,
R::PlanarTypeHandle: FromReflect + bevy::reflect::Typed,
{
fn build(&self, app: &mut App) {
app.register_required_components::<R::PlanarTypeHandle, VisibilityClass>();
app.world_mut()
.register_component_hooks::<R::PlanarTypeHandle>()
.on_add(add_planar_class);
app.add_systems(
PostUpdate,
(calculate_bounds::<R>.in_set(VisibilitySystems::CalculateBounds),),
);
}
}
// TODO: handle aabb updates (e.g. gaussian particle movements)
#[allow(clippy::type_complexity)]
pub fn calculate_bounds<R: PlanarSync>(
mut commands: Commands,
gaussian_clouds: Res<Assets<R::PlanarType>>,
without_aabb: Query<(Entity, &R::PlanarTypeHandle), (Without<Aabb>, Without<NoFrustumCulling>)>,
) where
R::PlanarType: CommonCloud,
{
for (entity, cloud_handle) in &without_aabb {
if let Some(cloud) = gaussian_clouds.get(cloud_handle.handle())
&& let Some(aabb3d) = cloud.compute_aabb()
{
commands.entity(entity).try_insert(Aabb {
center: aabb3d.center(),
half_extents: aabb3d.half_size(),
});
}
}
}
================================================
FILE: src/gaussian/covariance.rs
================================================
use bevy::math::{Mat3, Vec3, Vec4};
#[allow(non_snake_case)]
pub fn compute_covariance_3d(rotation: Vec4, scale: Vec3) -> [f32; 6] {
let S = Mat3::from_diagonal(scale);
let r = rotation.x;
let x = rotation.y;
let y = rotation.z;
let z = rotation.w;
let R = Mat3::from_cols(
Vec3::new(
1.0 - 2.0 * (y * y + z * z),
2.0 * (x * y - r * z),
2.0 * (x * z + r * y),
),
Vec3::new(
2.0 * (x * y + r * z),
1.0 - 2.0 * (x * x + z * z),
2.0 * (y * z - r * x),
),
Vec3::new(
2.0 * (x * z - r * y),
2.0 * (y * z + r * x),
1.0 - 2.0 * (x * x + y * y),
),
);
let M = S * R;
let Sigma = M.transpose() * M;
[
Sigma.row(0).x,
Sigma.row(0).y,
Sigma.row(0).z,
Sigma.row(1).y,
Sigma.row(1).z,
Sigma.row(2).z,
]
}
================================================
FILE: src/gaussian/f16.rs
================================================
#![allow(dead_code)] // ShaderType derives emit unused check helpers
use std::marker::Copy;
use half::f16;
use bevy::{prelude::*, render::render_resource::ShaderType};
use bytemuck::{Pod, Zeroable};
use serde::{Deserialize, Serialize};
use crate::gaussian::{
f32::{Covariance3dOpacity, Rotation, ScaleOpacity},
formats::{planar_3d::Gaussian3d, planar_4d::Gaussian4d},
};
#[allow(dead_code)]
#[derive(
Clone,
Debug,
Default,
Copy,
PartialEq,
Reflect,
ShaderType,
Pod,
Zeroable,
Serialize,
Deserialize,
)]
#[repr(C)]
pub struct RotationScaleOpacityPacked128 {
#[reflect(ignore)]
pub rotation: [u32; 2],
#[reflect(ignore)]
pub scale_opacity: [u32; 2],
}
impl RotationScaleOpacityPacked128 {
pub fn from_gaussian(gaussian: &Gaussian3d) -> Self {
Self {
rotation: [
pack_f32s_to_u32(gaussian.rotation.rotation[0], gaussian.rotation.rotation[1]),
pack_f32s_to_u32(gaussian.rotation.rotation[2], gaussian.rotation.rotation[3]),
],
scale_opacity: [
pack_f32s_to_u32(
gaussian.scale_opacity.scale[0],
gaussian.scale_opacity.scale[1],
),
pack_f32s_to_u32(
gaussian.scale_opacity.scale[2],
gaussian.scale_opacity.opacity,
),
],
}
}
pub fn rotation(&self) -> Rotation {
let (u0, l0) = unpack_u32_to_f32s(self.rotation[0]);
let (u1, l1) = unpack_u32_to_f32s(self.rotation[1]);
Rotation {
rotation: [u0, l0, u1, l1],
}
}
pub fn scale_opacity(&self) -> ScaleOpacity {
let (u0, l0) = unpack_u32_to_f32s(self.scale_opacity[0]);
let (u1, l1) = unpack_u32_to_f32s(self.scale_opacity[1]);
ScaleOpacity {
scale: [u0, l0, u1],
opacity: l1,
}
}
}
impl From<[f32; 8]> for RotationScaleOpacityPacked128 {
fn from(rotation_scale_opacity: [f32; 8]) -> Self {
Self {
rotation: [
pack_f32s_to_u32(rotation_scale_opacity[0], rotation_scale_opacity[1]),
pack_f32s_to_u32(rotation_scale_opacity[2], rotation_scale_opacity[3]),
],
scale_opacity: [
pack_f32s_to_u32(rotation_scale_opacity[4], rotation_scale_opacity[5]),
pack_f32s_to_u32(rotation_scale_opacity[6], rotation_scale_opacity[7]),
],
}
}
}
impl From<[f16; 8]> for RotationScaleOpacityPacked128 {
fn from(rotation_scale_opacity: [f16; 8]) -> Self {
Self {
rotation: [
pack_f16s_to_u32(rotation_scale_opacity[0], rotation_scale_opacity[1]),
pack_f16s_to_u32(rotation_scale_opacity[2], rotation_scale_opacity[3]),
],
scale_opacity: [
pack_f16s_to_u32(rotation_scale_opacity[4], rotation_scale_opacity[5]),
pack_f16s_to_u32(rotation_scale_opacity[6], rotation_scale_opacity[7]),
],
}
}
}
impl From<[u32; 4]> for RotationScaleOpacityPacked128 {
fn from(rotation_scale_opacity: [u32; 4]) -> Self {
Self {
rotation: [rotation_scale_opacity[0], rotation_scale_opacity[1]],
scale_opacity: [rotation_scale_opacity[2], rotation_scale_opacity[3]],
}
}
}
#[allow(dead_code)]
#[derive(
Clone,
Debug,
Default,
Copy,
PartialEq,
Reflect,
ShaderType,
Pod,
Zeroable,
Serialize,
Deserialize,
)]
#[repr(C)]
pub struct Covariance3dOpacityPacked128 {
#[reflect(ignore)]
pub cov3d: [u32; 3],
pub opacity: u32,
}
impl Covariance3dOpacityPacked128 {
pub fn from_gaussian(gaussian: &Gaussian3d) -> Self {
let cov3d: Covariance3dOpacity = gaussian.into();
let cov3d = cov3d.cov3d;
let opacity = gaussian.scale_opacity.opacity;
Self {
cov3d: [
pack_f32s_to_u32(cov3d[0], cov3d[1]),
pack_f32s_to_u32(cov3d[2], cov3d[3]),
pack_f32s_to_u32(cov3d[4], cov3d[5]),
],
opacity: pack_f32s_to_u32(opacity, opacity), // TODO: benefit from 32-bit opacity
}
}
pub fn covariance_3d_opacity(&self) -> Covariance3dOpacity {
let (c0, c1) = unpack_u32_to_f32s(self.cov3d[0]);
let (c2, c3) = unpack_u32_to_f32s(self.cov3d[1]);
let (c4, c5) = unpack_u32_to_f32s(self.cov3d[2]);
let (opacity, _) = unpack_u32_to_f32s(self.opacity);
let cov3d: [f32; 6] = [c0, c1, c2, c3, c4, c5];
Covariance3dOpacity {
cov3d,
opacity,
pad: 0.0,
}
}
}
impl From<[u32; 4]> for Covariance3dOpacityPacked128 {
fn from(cov3d_opacity: [u32; 4]) -> Self {
Self {
cov3d: [cov3d_opacity[0], cov3d_opacity[1], cov3d_opacity[2]],
opacity: cov3d_opacity[3],
}
}
}
#[allow(dead_code)]
#[derive(
Clone,
Debug,
Default,
Copy,
PartialEq,
Reflect,
ShaderType,
Pod,
Zeroable,
Serialize,
Deserialize,
)]
#[repr(C)]
pub struct IsotropicRotations {
pub rotation: [u32; 2],
pub rotation_r: [u32; 2],
}
impl IsotropicRotations {
pub fn from_gaussian(gaussian: &Gaussian4d) -> Self {
let rotation = gaussian.isotropic_rotations.rotation;
let rotation_r = gaussian.isotropic_rotations.rotation_r;
Self {
rotation: [
pack_f32s_to_u32(rotation[0], rotation[1]),
pack_f32s_to_u32(rotation[2], rotation[3]),
],
rotation_r: [
pack_f32s_to_u32(rotation_r[0], rotation_r[1]),
pack_f32s_to_u32(rotation_r[2], rotation_r[3]),
],
}
}
pub fn rotations(&self) -> [Rotation; 2] {
let (u0, l0) = unpack_u32_to_f32s(self.rotation[0]);
let (u1, l1) = unpack_u32_to_f32s(self.rotation[1]);
let (u0_r, l0_r) = unpack_u32_to_f32s(self.rotation_r[0]);
let (u1_r, l1_r) = unpack_u32_to_f32s(self.rotation_r[1]);
[
Rotation {
rotation: [u0, l0, u1, l1],
},
Rotation {
rotation: [u0_r, l0_r, u1_r, l1_r],
},
]
}
}
impl From<[u32; 4]> for IsotropicRotations {
fn from(rotations: [u32; 4]) -> Self {
Self {
rotation: [rotations[0], rotations[1]],
rotation_r: [rotations[2], rotations[3]],
}
}
}
pub fn pack_f32s_to_u32(upper: f32, lower: f32) -> u32 {
pack_f16s_to_u32(f16::from_f32(upper), f16::from_f32(lower))
}
pub fn pack_f16s_to_u32(upper: f16, lower: f16) -> u32 {
let upper_bits = (upper.to_bits() as u32) << 16;
let lower_bits = lower.to_bits() as u32;
upper_bits | lower_bits
}
pub fn unpack_u32_to_f16s(value: u32) -> (f16, f16) {
let upper = f16::from_bits((value >> 16) as u16);
let lower = f16::from_bits((value & 0xFFFF) as u16);
(upper, lower)
}
pub fn unpack_u32_to_f32s(value: u32) -> (f32, f32) {
let (upper, lower) = unpack_u32_to_f16s(value);
(upper.to_f32(), lower.to_f32())
}
================================================
FILE: src/gaussian/f32.rs
================================================
#![allow(dead_code)] // ShaderType derives emit unused check helpers
use std::marker::Copy;
use bevy::{prelude::*, render::render_resource::ShaderType};
use bytemuck::{Pod, Zeroable};
use serde::{Deserialize, Serialize};
use crate::gaussian::{
covariance::compute_covariance_3d,
formats::{planar_3d::Gaussian3d, planar_4d::Gaussian4d},
};
pub type Position = [f32; 3];
#[allow(dead_code)]
#[derive(
Clone,
Debug,
Default,
Copy,
PartialEq,
Reflect,
ShaderType,
Pod,
Zeroable,
Serialize,
Deserialize,
)]
#[repr(C)]
pub struct PositionTimestamp {
pub position: Position,
pub timestamp: f32,
}
impl From<[f32; 4]> for PositionTimestamp {
fn from(position_timestamp: [f32; 4]) -> Self {
Self {
position: [
position_timestamp[0],
position_timestamp[1],
position_timestamp[2],
],
timestamp: position_timestamp[3],
}
}
}
#[allow(dead_code)]
#[derive(
Clone, Debug, Copy, PartialEq, Reflect, ShaderType, Pod, Zeroable, Serialize, Deserialize,
)]
#[repr(C)]
pub struct PositionVisibility {
pub position: Position,
pub visibility: f32,
}
impl Default for PositionVisibility {
fn default() -> Self {
Self {
position: Position::default(),
visibility: 1.0,
}
}
}
impl From<[f32; 4]> for PositionVisibility {
fn from(position_visibility: [f32; 4]) -> Self {
Self {
position: [
position_visibility[0],
position_visibility[1],
position_visibility[2],
],
visibility: position_visibility[3],
}
}
}
#[allow(dead_code)]
#[derive(
Clone,
Debug,
Default,
Copy,
PartialEq,
Reflect,
ShaderType,
Pod,
Zeroable,
Serialize,
Deserialize,
)]
#[repr(C)]
pub struct Rotation {
pub rotation: [f32; 4],
}
impl From<[f32; 4]> for Rotation {
fn from(rotation: [f32; 4]) -> Self {
Self { rotation }
}
}
#[allow(dead_code)]
#[derive(
Clone,
Debug,
Default,
Copy,
PartialEq,
Reflect,
ShaderType,
Pod,
Zeroable,
Serialize,
Deserialize,
)]
#[repr(C)]
pub struct IsotropicRotations {
pub rotation: [f32; 4],
pub rotation_r: [f32; 4],
}
impl IsotropicRotations {
pub fn from_gaussian(gaussian: &Gaussian4d) -> Self {
let rotation = gaussian.isotropic_rotations.rotation;
let rotation_r = gaussian.isotropic_rotations.rotation_r;
Self {
rotation,
rotation_r,
}
}
pub fn rotations(&self) -> [Rotation; 2] {
[
Rotation {
rotation: self.rotation,
},
Rotation {
rotation: self.rotation_r,
},
]
}
}
impl From<[f32; 8]> for IsotropicRotations {
fn from(rotations: [f32; 8]) -> Self {
Self {
rotation: [rotations[0], rotations[1], rotations[2], rotations[3]],
rotation_r: [rotations[4], rotations[5], rotations[6], rotations[7]],
}
}
}
#[allow(dead_code)]
#[derive(
Clone,
Debug,
Default,
Copy,
PartialEq,
Reflect,
ShaderType,
Pod,
Zeroable,
Serialize,
Deserialize,
)]
#[repr(C)]
pub struct ScaleOpacity {
pub scale: [f32; 3],
pub opacity: f32,
}
impl From<[f32; 4]> for ScaleOpacity {
fn from(scale_opacity: [f32; 4]) -> Self {
Self {
scale: [scale_opacity[0], scale_opacity[1], scale_opacity[2]],
opacity: scale_opacity[3],
}
}
}
#[allow(dead_code)]
#[derive(
Clone,
Debug,
Default,
Copy,
PartialEq,
Reflect,
ShaderType,
Pod,
Zeroable,
Serialize,
Deserialize,
)]
#[repr(C)]
pub struct TimestampTimescale {
pub timestamp: f32,
pub timescale: f32,
pub _pad: [f32; 2],
}
impl From<[f32; 4]> for TimestampTimescale {
fn from(timestamp_timescale: [f32; 4]) -> Self {
Self {
timestamp: timestamp_timescale[0],
timescale: timestamp_timescale[1],
_pad: [0.0, 0.0],
}
}
}
#[allow(dead_code)]
#[derive(
Clone,
Debug,
Default,
Copy,
PartialEq,
Reflect,
ShaderType,
Pod,
Zeroable,
Serialize,
Deserialize,
)]
#[repr(C)]
pub struct Covariance3dOpacity {
pub cov3d: [f32; 6],
pub opacity: f32,
pub pad: f32,
}
impl From<&Gaussian3d> for Covariance3dOpacity {
fn from(gaussian: &Gaussian3d) -> Self {
let cov3d = compute_covariance_3d(
Vec4::from_slice(gaussian.rotation.rotation.as_slice()),
Vec3::from_slice(gaussian.scale_opacity.scale.as_slice()),
);
Covariance3dOpacity {
cov3d,
opacity: gaussian.scale_opacity.opacity,
pad: 0.0,
}
}
}
================================================
FILE: src/gaussian/formats/mod.rs
================================================
// TODO: move all format specific code here (e.g. rand, packed)
pub mod planar_3d;
pub mod planar_3d_chunked;
pub mod planar_3d_lod;
pub mod planar_3d_quantized;
pub mod planar_3d_spz;
pub mod planar_4d;
pub mod planar_4d_hierarchy;
pub mod planar_4d_quantized;
pub mod spacetime;
================================================
FILE: src/gaussian/formats/planar_3d.rs
================================================
use rand::{
Rng, SeedableRng,
distr::{Distribution, StandardUniform},
rng,
rngs::StdRng,
seq::SliceRandom,
};
use std::marker::Copy;
use bevy::prelude::*;
use bevy_interleave::prelude::*;
use bytemuck::{Pod, Zeroable};
use serde::{Deserialize, Serialize};
#[allow(unused_imports)]
use crate::{
gaussian::{
f32::{Covariance3dOpacity, PositionVisibility, Rotation, ScaleOpacity},
interface::{CommonCloud, TestCloud},
iter::PositionIter,
settings::CloudSettings,
},
material::spherical_harmonics::{
HALF_SH_COEFF_COUNT, SH_COEFF_COUNT, SphericalHarmonicCoefficients,
},
};
#[derive(
Clone,
Debug,
Default,
Copy,
PartialEq,
Planar,
ReflectInterleaved,
StorageBindings,
Reflect,
Pod,
Zeroable,
Serialize,
Deserialize,
)]
#[serde(default)]
#[repr(C)]
pub struct Gaussian3d {
#[serde(default)]
pub position_visibility: PositionVisibility,
#[serde(default)]
pub spherical_harmonic: SphericalHarmonicCoefficients,
#[serde(default)]
pub rotation: Rotation,
#[serde(default)]
pub scale_opacity: ScaleOpacity,
}
pub type Gaussian2d = Gaussian3d; // GaussianMode::Gaussian2d /w Gaussian3d structure
// #[allow(unused_imports)]
// #[cfg(feature = "f16")]
// use crate::gaussian::f16::{
// Covariance3dOpacityPacked128,
// RotationScaleOpacityPacked128,
// pack_f32s_to_u32,
// };
// #[cfg(feature = "f16")]
// #[derive(
// Debug,
// Default,
// PartialEq,
// Reflect,
// Serialize,
// Deserialize,
// )]
// pub struct Cloud3d {
// pub position_visibility: Vec<PositionVisibility>,
// pub spherical_harmonic: Vec<SphericalHarmonicCoefficients>,
// #[cfg(not(feature = "precompute_covariance_3d"))]
// pub rotation_scale_opacity_packed128: Vec<RotationScaleOpacityPacked128>,
// #[cfg(feature = "precompute_covariance_3d")]
// pub covariance_3d_opacity_packed128: Vec<Covariance3dOpacityPacked128>,
// }
impl CommonCloud for PlanarGaussian3d {
type PackedType = Gaussian3d;
fn visibility(&self, index: usize) -> f32 {
self.position_visibility[index].visibility
}
fn visibility_mut(&mut self, index: usize) -> &mut f32 {
&mut self.position_visibility[index].visibility
}
fn position_iter(&self) -> PositionIter<'_> {
PositionIter::new(&self.position_visibility)
}
#[cfg(feature = "sort_rayon")]
fn position_par_iter(&self) -> crate::gaussian::iter::PositionParIter<'_> {
crate::gaussian::iter::PositionParIter::new(&self.position_visibility)
}
}
impl FromIterator<Gaussian3d> for PlanarGaussian3d {
fn from_iter<I: IntoIterator<Item = Gaussian3d>>(iter: I) -> Self {
iter.into_iter().collect::<Vec<Gaussian3d>>().into()
}
}
impl From<Vec<Gaussian3d>> for PlanarGaussian3d {
fn from(packed: Vec<Gaussian3d>) -> Self {
Self::from_interleaved(packed)
}
}
impl Distribution<Gaussian3d> for StandardUniform {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Gaussian3d {
Gaussian3d {
rotation: [
rng.random_range(-1.0..1.0),
rng.random_range(-1.0..1.0),
rng.random_range(-1.0..1.0),
rng.random_range(-1.0..1.0),
]
.into(),
position_visibility: [
rng.random_range(-20.0..20.0),
rng.random_range(-20.0..20.0),
rng.random_range(-20.0..20.0),
1.0,
]
.into(),
scale_opacity: [
rng.random_range(0.0..1.0),
rng.random_range(0.0..1.0),
rng.random_range(0.0..1.0),
rng.random_range(0.0..0.8),
]
.into(),
spherical_harmonic: SphericalHarmonicCoefficients {
coefficients: {
// #[cfg(feature = "f16")]
// {
// let mut coefficients: [u32; HALF_SH_COEFF_COUNT] = [0; HALF_SH_COEFF_COUNT];
// for coefficient in coefficients.iter_mut() {
// let upper = rng.gen_range(-1.0..1.0);
// let lower = rng.gen_range(-1.0..1.0);
// *coefficient = pack_f32s_to_u32(upper, lower);
// }
// coefficients
// }
{
let mut coefficients = [0.0; SH_COEFF_COUNT];
for coefficient in coefficients.iter_mut() {
*coefficient = rng.random_range(-1.0..1.0);
}
coefficients
}
},
},
}
}
}
pub fn random_gaussians_3d(n: usize) -> PlanarGaussian3d {
let mut rng = rng();
let mut gaussians: Vec<Gaussian3d> = Vec::with_capacity(n);
for _ in 0..n {
gaussians.push(rng.random());
}
PlanarGaussian3d::from_interleaved(gaussians)
}
pub fn random_gaussians_3d_seeded(n: usize, seed: u64) -> PlanarGaussian3d {
let mut rng = StdRng::seed_from_u64(seed);
let mut gaussians: Vec<Gaussian3d> = Vec::with_capacity(n);
for _ in 0..n {
gaussians.push(StandardUniform.sample(&mut rng));
}
PlanarGaussian3d::from_interleaved(gaussians)
}
impl TestCloud for PlanarGaussian3d {
fn test_model() -> Self {
let mut rng = rng();
let origin = Gaussian3d {
rotation: [1.0, 0.0, 0.0, 0.0].into(),
position_visibility: [0.0, 0.0, 0.0, 1.0].into(),
scale_opacity: [0.125, 0.125, 0.125, 0.125].into(),
spherical_harmonic: SphericalHarmonicCoefficients {
coefficients: {
// #[cfg(feature = "f16")]
// {
// let mut coefficients = [0_u32; HALF_SH_COEFF_COUNT];
// for coefficient in coefficients.iter_mut() {
// let upper = rng.gen_range(-1.0..1.0);
// let lower = rng.gen_range(-1.0..1.0);
// *coefficient = pack_f32s_to_u32(upper, lower);
// }
// coefficients
// }
{
let mut coefficients = [0.0; SH_COEFF_COUNT];
for coefficient in coefficients.iter_mut() {
*coefficient = rng.random_range(-1.0..1.0);
}
coefficients
}
},
},
};
let mut gaussians: Vec<Gaussian3d> = Vec::new();
for &x in [-0.5, 0.5].iter() {
for &y in [-0.5, 0.5].iter() {
for &z in [-0.5, 0.5].iter() {
let mut g = origin;
g.position_visibility = [x, y, z, 1.0].into();
gaussians.push(g);
gaussians
.last_mut()
.unwrap()
.spherical_harmonic
.coefficients
.shuffle(&mut rng);
}
}
}
gaussians.push(gaussians[0]);
gaussians.into()
}
}
// TODO: attempt iter() on the Planar trait
impl PlanarGaussian3d {
pub fn iter(&self) -> impl Iterator<Item = Gaussian3d> + '_ {
self.position_visibility
.iter()
.zip(self.spherical_harmonic.iter())
.zip(self.rotation.iter())
.zip(self.scale_opacity.iter())
.map(
|(((position_visibility, spherical_harmonic), rotation), scale_opacity)| {
Gaussian3d {
position_visibility: *position_visibility,
spherical_harmonic: *spherical_harmonic,
rotation: *rotation,
scale_opacity: *scale_opacity,
}
},
)
}
}
================================================
FILE: src/gaussian/formats/planar_3d_chunked.rs
================================================
================================================
FILE: src/gaussian/formats/planar_3d_lod.rs
================================================
// TODO: gaussian cloud 3d with level of detail
================================================
FILE: src/gaussian/formats/planar_3d_quantized.rs
================================================
// TODO: gaussian_3d and gaussian_3d_quantized conversions
// TODO: packed quantized gaussian 3d
================================================
FILE: src/gaussian/formats/planar_3d_spz.rs
================================================
// TODO: spz quantized format https://github.com/nianticlabs/spz
================================================
FILE: src/gaussian/formats/planar_4d.rs
================================================
use std::marker::Copy;
use bevy::prelude::*;
use bevy_interleave::prelude::*;
use bytemuck::{Pod, Zeroable};
use rand::{
Rng, SeedableRng,
distr::{Distribution, StandardUniform},
rng,
rngs::StdRng,
};
use serde::{Deserialize, Serialize};
use crate::{
gaussian::{
f32::{IsotropicRotations, PositionVisibility, ScaleOpacity, TimestampTimescale},
interface::{CommonCloud, TestCloud},
iter::PositionIter,
},
material::spherindrical_harmonics::{SH_4D_COEFF_COUNT, SpherindricalHarmonicCoefficients},
};
#[derive(
Clone,
Debug,
Default,
Copy,
PartialEq,
Planar,
ReflectInterleaved,
StorageBindings,
Reflect,
Pod,
Zeroable,
Serialize,
Deserialize,
)]
#[serde(default)]
#[repr(C)]
pub struct Gaussian4d {
#[serde(default)]
pub position_visibility: PositionVisibility,
#[serde(default)]
pub spherindrical_harmonic: SpherindricalHarmonicCoefficients,
#[serde(default)]
pub isotropic_rotations: IsotropicRotations,
#[serde(default)]
pub scale_opacity: ScaleOpacity,
#[serde(default)]
pub timestamp_timescale: TimestampTimescale,
}
// // TODO: GaussianSpacetime, determine temporal position/rotation structure
// pub struct GaussianSpacetime {
// pub position_visibility: PositionVisibility,
// pub color_mlp: ColorMlp,
// pub isotropic_rotations: IsotropicRotations,
// pub scale_opacity: ScaleOpacity,
// pub timestamp_timescale: TimestampTimescale,
// }
// TODO: quantize 4d representation
// #[derive(
// Debug,
// Default,
// PartialEq,
// Reflect,
// Serialize,
// Deserialize,
// )]
// pub struct HalfCloud4d {
// pub isotropic_rotations: Vec<IsotropicRotations>,
// pub position_visibility: Vec<PositionVisibility>,
// pub scale_opacity: Vec<ScaleOpacity>,
// pub spherindrical_harmonic: Vec<SpherindricalHarmonicCoefficients>,
// pub timestamp_timescale: Vec<TimestampTimescale>,
// }
// impl CommonCloud for HalfCloud4d {
// fn len(&self) -> usize {
// self.position_visibility.len()
// }
// fn position_iter(&self) -> impl Iterator<Item = &Position> {
// self.position_visibility.iter()
// .map(|position_visibility| &position_visibility.position)
// }
// #[cfg(feature = "sort_rayon")]
// fn position_par_iter(&self) -> impl IndexedParallelIterator<Item = &Position> + '_ {
// self.position_visibility.par_iter()
// .map(|position_visibility| &position_visibility.position)
// }
// fn subset(&self, indicies: &[usize]) -> Self {
// let mut isotropic_rotations = Vec::with_capacity(indicies.len());
// let mut position_visibility = Vec::with_capacity(indicies.len());
// let mut scale_opacity = Vec::with_capacity(indicies.len());
// let mut spherindrical_harmonic = Vec::with_capacity(indicies.len());
// let mut timestamp_timescale = Vec::with_capacity(indicies.len());
// for &index in indicies.iter() {
// position_visibility.push(self.position_visibility[index]);
// spherindrical_harmonic.push(self.spherindrical_harmonic[index]);
// rotation.push(self.rotation[index]);
// scale_opacity.push(self.scale_opacity[index]);
// timestamp_timescale.push(self.timestamp_timescale[index]);
// }
// Self {
// isotropic_rotations,
// position_visibility,
// spherindrical_harmonic,
// scale_opacity,
// timestamp_timescale,
// }
// }
// }
// impl TestCloud for HalfCloud4d {
// fn test_model() -> Self {
// let mut rng = rand::rng();
// let origin = Gaussian {
// isotropic_rotations: [
// 1.0,
// 0.0,
// 0.0,
// 0.0,
// 1.0,
// 0.0,
// 0.0,
// 0.0,
// ].into(),
// position_visibility: [
// 0.0,
// 0.0,
// 0.0,
// 1.0,
// ].into(),
// scale_opacity: [
// 0.5,
// 0.5,
// 0.5,
// 0.5,
// ].into(),
// spherindrical_harmonic: SpherindricalHarmonicCoefficients {
// coefficients: {
// let mut coefficients = [0.0; SH_4D_COEFF_COUNT];
// for coefficient in coefficients.iter_mut() {
// *coefficient = rng.gen_range(-1.0..1.0);
// }
// coefficients
// },
// },
// };
// let mut gaussians: Vec<Gaussian4d> = Vec::new();
// for &x in [-0.5, 0.5].iter() {
// for &y in [-0.5, 0.5].iter() {
// for &z in [-0.5, 0.5].iter() {
// let mut g = origin;
// g.position_visibility = [x, y, z, 0.5].into();
// gaussians.push(g);
// gaussians.last_mut().unwrap().spherindrical_harmonic.coefficients.shuffle(&mut rng);
// }
// }
// }
// gaussians.push(gaussians[0]);
// Cloud4d::from_packed(gaussians)
// }
// }
// impl HalfCloud4d {
// fn from_packed(gaussians: Vec<Gaussian4d>) -> Self {
// let mut isotropic_rotations = Vec::with_capacity(gaussians.len());
// let mut position_visibility = Vec::with_capacity(gaussians.len());
// let mut scale_opacity = Vec::with_capacity(gaussians.len());
// let mut spherindrical_harmonic = Vec::with_capacity(gaussians.len());
// let mut timestamp_timescale = Vec::with_capacity(gaussians.len());
// for gaussian in gaussians {
// isotropic_rotations.push(gaussian.isotropic_rotations);
// position_visibility.push(gaussian.position_visibility);
// scale_opacity.push(gaussian.scale_opacity);
// spherindrical_harmonic.push(gaussian.spherindrical_harmonic);
// timestamp_timescale.push(gaussian.timestamp_timescale);
// }
// Self {
// isotropic_rotations,
// position_visibility,
// scale_opacity,
// spherindrical_harmonic,
// timestamp_timescale,
// }
// }
// }
// impl FromIterator<Gaussian4d> for HalfCloud4d {
// fn from_iter<I: IntoIterator<Item=Gaussian4d>>(iter: I) -> Self {
// let gaussians = iter.into_iter().collect::<Vec<Gaussian4d>>();
// HalfCloud4d::from_packed(gaussians)
// }
// }
impl CommonCloud for PlanarGaussian4d {
type PackedType = Gaussian4d;
fn visibility(&self, index: usize) -> f32 {
self.position_visibility[index].visibility
}
fn visibility_mut(&mut self, index: usize) -> &mut f32 {
&mut self.position_visibility[index].visibility
}
fn position_iter(&self) -> PositionIter<'_> {
PositionIter::new(&self.position_visibility)
}
#[cfg(feature = "sort_rayon")]
fn position_par_iter(&self) -> crate::gaussian::iter::PositionParIter<'_> {
crate::gaussian::iter::PositionParIter::new(&self.position_visibility)
}
}
impl FromIterator<Gaussian4d> for PlanarGaussian4d {
fn from_iter<I: IntoIterator<Item = Gaussian4d>>(iter: I) -> Self {
iter.into_iter().collect::<Vec<Gaussian4d>>().into()
}
}
impl From<Vec<Gaussian4d>> for PlanarGaussian4d {
fn from(packed: Vec<Gaussian4d>) -> Self {
Self::from_interleaved(packed)
}
}
impl Distribution<Gaussian4d> for StandardUniform {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Gaussian4d {
let mut coefficients = [0.0; SH_4D_COEFF_COUNT];
for coefficient in coefficients.iter_mut() {
*coefficient = rng.random_range(-1.0..1.0);
}
Gaussian4d {
isotropic_rotations: [
rng.random_range(-1.0..1.0),
rng.random_range(-1.0..1.0),
rng.random_range(-1.0..1.0),
rng.random_range(-1.0..1.0),
rng.random_range(-1.0..1.0),
rng.random_range(-1.0..1.0),
rng.random_range(-1.0..1.0),
rng.random_range(-1.0..1.0),
]
.into(),
position_visibility: [
rng.random_range(-20.0..20.0),
rng.random_range(-20.0..20.0),
rng.random_range(-20.0..20.0),
1.0,
]
.into(),
scale_opacity: [
rng.random_range(0.0..1.0),
rng.random_range(0.0..1.0),
rng.random_range(0.0..1.0),
rng.random_range(0.0..0.8),
]
.into(),
spherindrical_harmonic: coefficients.into(),
timestamp_timescale: [
rng.random_range(0.0..1.0),
rng.random_range(-1.0..1.0),
0.0,
0.0,
]
.into(),
}
}
}
pub fn random_gaussians_4d(n: usize) -> PlanarGaussian4d {
let mut rng = rng();
let mut gaussians: Vec<Gaussian4d> = Vec::with_capacity(n);
for _ in 0..n {
gaussians.push(rng.random());
}
PlanarGaussian4d::from_interleaved(gaussians)
}
pub fn random_gaussians_4d_seeded(n: usize, seed: u64) -> PlanarGaussian4d {
let mut rng = StdRng::seed_from_u64(seed);
let mut gaussians: Vec<Gaussian4d> = Vec::with_capacity(n);
for _ in 0..n {
gaussians.push(StandardUniform.sample(&mut rng));
}
PlanarGaussian4d::from_interleaved(gaussians)
}
impl TestCloud for PlanarGaussian4d {
fn test_model() -> Self {
random_gaussians_4d(512)
}
}
================================================
FILE: src/gaussian/formats/planar_4d_hierarchy.rs
================================================
// TODO: gaussian cloud 4d with temporal hierarchy
use crate::gaussian::formats::planar_4d::PlanarGaussian4dHandle;
pub struct TemporalGaussianLevel {
pub instance_count: usize,
// TODO: swap buffer slicing
}
// TODO: make this an asset
pub struct TemporalGaussianHierarchy {
pub flat_cloud: PlanarGaussian4dHandle,
pub levels: Vec<TemporalGaussianLevel>,
// TODO: level descriptor validation
}
// TODO: implement level streaming utilities in src/stream/hierarchy.rs
// TODO: implement GPU slice utilities in src/stream/slice.rs
================================================
FILE: src/gaussian/formats/planar_4d_quantized.rs
================================================
================================================
FILE: src/gaussian/formats/spacetime.rs
================================================
// https://github.com/oppo-us-research/SpacetimeGaussians
// property float x
// property float y
// property float z
// property float trbf_center
// property float trbf_scale
// property float nx
// property float ny
// property float nz
// property float motion_0
// property float motion_2
// property float motion_3
// property float motion_4
// property float motion_5
// property float motion_6
// property float motion_7
// property float motion_8
// property float f_dc_0
// property float f_dc_1
// property float f_dc_2
// property float opacity
// property float scale_0
// property float scale_1
// property float scale_2
// property float rot_0
// property float rot_1
// property float rot_2
// property float rot_3
// property float omega_0
// property float omega_1
// property float omega_2
// property float omega_3
================================================
FILE: src/gaussian/interface.rs
================================================
use bevy::{math::bounding::Aabb3d, prelude::*};
use bevy_interleave::prelude::Planar;
#[cfg(feature = "sort_rayon")]
use rayon::prelude::*;
use crate::gaussian::iter::PositionIter;
pub trait CommonCloud
where
Self: Planar,
{
type PackedType;
fn len_sqrt_ceil(&self) -> usize {
(self.len() as f32).sqrt().ceil() as usize
}
fn square_len(&self) -> usize {
self.len_sqrt_ceil().pow(2)
}
fn compute_aabb(&self) -> Option<Aabb3d> {
if self.is_empty() {
return None;
}
let mut min = Vec3::splat(f32::INFINITY);
let mut max = Vec3::splat(f32::NEG_INFINITY);
// TODO: find a more correct aabb bound derived from scalar max gaussian scale
let max_scale = 0.1;
#[cfg(feature = "sort_rayon")]
{
(min, max) = self
.position_par_iter()
.fold(
|| (min, max),
|(curr_min, curr_max), position| {
let pos = Vec3::from(*position);
let offset = Vec3::splat(max_scale);
(curr_min.min(pos - offset), curr_max.max(pos + offset))
},
)
.reduce(
|| (min, max),
|(a_min, a_max), (b_min, b_max)| (a_min.min(b_min), a_max.max(b_max)),
);
}
#[cfg(not(feature = "sort_rayon"))]
{
for position in self.position_iter() {
min = min.min(Vec3::from(*position) - Vec3::splat(max_scale));
max = max.max(Vec3::from(*position) + Vec3::splat(max_scale));
}
}
Some(Aabb3d {
min: min.into(),
max: max.into(),
})
}
fn visibility(&self, index: usize) -> f32;
fn visibility_mut(&mut self, index: usize) -> &mut f32;
// TODO: type erasure for position iterators
fn position_iter(&self) -> PositionIter<'_>;
#[cfg(feature = "sort_rayon")]
fn position_par_iter(&self) -> crate::gaussian::iter::PositionParIter<'_>;
}
pub trait TestCloud {
fn test_model() -> Self;
}
// TODO: CloudSlice and CloudStream traits
================================================
FILE: src/gaussian/iter.rs
================================================
#[cfg(feature = "sort_rayon")]
use rayon::iter::plumbing::{Consumer, UnindexedConsumer};
#[cfg(feature = "sort_rayon")]
use rayon::prelude::*;
use crate::gaussian::f32::{Position, PositionVisibility};
pub struct PositionIter<'a> {
slice_iter: std::slice::Iter<'a, PositionVisibility>,
}
impl<'a> PositionIter<'a> {
pub fn new(slice: &'a [PositionVisibility]) -> Self {
Self {
slice_iter: slice.iter(),
}
}
}
impl<'a> Iterator for PositionIter<'a> {
type Item = &'a Position;
fn next(&mut self) -> Option<Self::Item> {
self.slice_iter.next().map(|pv| &pv.position)
}
}
#[cfg(feature = "sort_rayon")]
pub struct PositionParIter<'a> {
slice_par_iter: rayon::slice::Iter<'a, PositionVisibility>,
}
#[cfg(feature = "sort_rayon")]
impl<'a> PositionParIter<'a> {
pub fn new(slice: &'a [PositionVisibility]) -> Self {
Self {
slice_par_iter: slice.par_iter(),
}
}
}
#[cfg(feature = "sort_rayon")]
impl<'a> ParallelIterator for PositionParIter<'a> {
type Item = &'a Position;
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
self.slice_par_iter
.map(|pv| &pv.position)
.drive_unindexed(consumer)
}
}
#[cfg(feature = "sort_rayon")]
impl IndexedParallelIterator for PositionParIter<'_> {
fn len(&self) -> usize {
self.slice_par_iter.len()
}
fn drive<C>(self, consumer: C) -> <C as Consumer<Self::Item>>::Result
where
C: Consumer<Self::Item>,
{
self.slice_par_iter.map(|pv| &pv.position).drive(consumer)
}
fn with_producer<CB>(self, callback: CB) -> CB::Output
where
CB: rayon::iter::plumbing::ProducerCallback<Self::Item>,
{
self.slice_par_iter
.map(|pv| &pv.position)
.with_producer(callback)
}
}
================================================
FILE: src/gaussian/mod.rs
================================================
use static_assertions::assert_cfg;
pub mod cloud;
pub mod covariance;
pub mod f16;
pub mod f32;
pub mod formats;
pub mod interface;
pub mod iter;
pub mod settings;
assert_cfg!(
any(feature = "packed", feature = "planar",),
"specify one of the following features: packed, planar",
);
================================================
FILE: src/gaussian/settings.rs
================================================
use bevy::prelude::*;
use bevy_args::{Deserialize, Serialize, ValueEnum};
use crate::sort::SortMode;
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Reflect, Serialize, Deserialize)]
pub enum DrawMode {
#[default]
All,
Selected,
HighlightSelected,
}
#[derive(
Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Reflect, Serialize, Deserialize, ValueEnum,
)]
pub enum GaussianMode {
Gaussian2d,
#[default]
Gaussian3d,
Gaussian4d,
}
#[derive(
Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Reflect, Serialize, Deserialize, ValueEnum,
)]
pub enum PlaybackMode {
Loop,
Once,
Sin,
#[default]
Still,
}
#[derive(
Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Reflect, Serialize, Deserialize, ValueEnum,
)]
pub enum RasterizeMode {
Classification,
#[default]
Color,
Depth,
Normal,
OpticalFlow,
Position,
Velocity,
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Reflect, Serialize, Deserialize)]
pub enum GaussianColorSpace {
#[default]
SrgbRec709Display,
LinRec709Display,
}
// TODO: breakdown into components
#[derive(Component, Clone, Debug, Reflect, Serialize, Deserialize)]
#[reflect(Component)]
#[serde(default)]
pub struct CloudSettings {
pub aabb: bool,
pub global_opacity: f32,
pub global_scale: f32,
pub opacity_adaptive_radius: bool,
pub visualize_bounding_box: bool,
pub sort_mode: SortMode,
pub draw_mode: DrawMode,
pub gaussian_mode: GaussianMode,
pub playback_mode: PlaybackMode,
pub rasterize_mode: RasterizeMode,
pub color_space: GaussianColorSpace,
pub num_classes: usize,
pub time: f32,
pub time_scale: f32,
pub time_start: f32,
pub time_stop: f32,
}
impl Default for CloudSettings {
fn default() -> Self {
Self {
aabb: false,
global_opacity: 1.0,
global_scale: 1.0,
opacity_adaptive_radius: true,
visualize_bounding_box: false,
sort_mode: SortMode::default(),
draw_mode: DrawMode::default(),
gaussian_mode: GaussianMode::default(),
rasterize_mode: RasterizeMode::default(),
color_space: GaussianColorSpace::default(),
num_classes: 1,
playback_mode: PlaybackMode::default(),
time: 0.0,
time_scale: 1.0,
time_start: 0.0,
time_stop: 1.0,
}
}
}
#[derive(Default)]
pub struct SettingsPlugin;
impl Plugin for SettingsPlugin {
fn build(&self, app: &mut App) {
app.register_type::<CloudSettings>();
app.add_systems(Update, (playback_update,));
}
}
fn playback_update(time: Res<Time>, mut query: Query<(&mut CloudSettings,)>) {
for (mut settings,) in query.iter_mut() {
if settings.time_scale == 0.0 {
continue;
}
// bail condition
match settings.playback_mode {
PlaybackMode::Loop => {}
PlaybackMode::Once => {
if settings.time >= settings.time_stop {
continue;
}
}
PlaybackMode::Sin => {}
PlaybackMode::Still => {
continue;
}
}
// forward condition
match settings.playback_mode {
PlaybackMode::Loop | PlaybackMode::Once => {
settings.time += time.delta_secs() * settings.time_scale;
}
PlaybackMode::Sin => {
let theta = settings.time_scale * time.elapsed_secs();
let y = (theta * 2.0 * std::f32::consts::PI).sin();
settings.time = settings.time_start
+ (settings.time_stop - settings.time_start) * (y + 1.0) / 2.0;
}
PlaybackMode::Still => {}
}
// reset condition
match settings.playback_mode {
PlaybackMode::Loop => {
if settings.time > settings.time_stop {
settings.time = settings.time_start;
}
}
PlaybackMode::Once => {}
PlaybackMode::Sin => {}
PlaybackMode::Still => {}
}
}
}
================================================
FILE: src/io/codec.rs
================================================
use std::io::Write;
// TODO: support streamed codecs
pub trait CloudCodec {
fn encode(&self) -> Vec<u8>;
fn decode(data: &[u8]) -> Self;
fn write_to_file(&self, path: &str) {
let gcloud_file = std::fs::File::create(path).expect("failed to create file");
let mut gcloud_writer = std::io::BufWriter::new(gcloud_file);
let data = self.encode();
gcloud_writer
.write_all(data.as_slice())
.expect("failed to write to gcloud file");
}
}
================================================
FILE: src/io/gcloud/bincode2.rs
================================================
use bincode2::{deserialize_from, serialize_into};
use flate2::{Compression, read::GzDecoder, write::GzEncoder};
use serde::de::DeserializeOwned;
use std::io::Cursor;
use crate::{
gaussian::formats::{planar_3d::PlanarGaussian3d, planar_4d::PlanarGaussian4d},
io::codec::CloudCodec,
};
impl CloudCodec for PlanarGaussian3d {
fn encode(&self) -> Vec<u8> {
let mut output = Vec::new();
{
let mut gz_encoder = GzEncoder::new(&mut output, Compression::default());
serialize_into(&mut gz_encoder, &self).expect("failed to encode cloud");
}
output
}
fn decode(data: &[u8]) -> Self {
if let Ok(cloud) = decode_gzip(data) {
return cloud;
}
decode_raw(data)
}
}
impl CloudCodec for PlanarGaussian4d {
fn encode(&self) -> Vec<u8> {
let mut output = Vec::new();
{
let mut gz_encoder = GzEncoder::new(&mut output, Compression::default());
serialize_into(&mut gz_encoder, &self).expect("failed to encode cloud");
}
output
}
fn decode(data: &[u8]) -> Self {
if let Ok(cloud) = decode_gzip(data) {
return cloud;
}
decode_raw(data)
}
}
fn decode_gzip<T>(data: &[u8]) -> Result<T, bincode2::Error>
where
T: DeserializeOwned,
{
let decompressed = GzDecoder::new(data);
deserialize_from(decompressed)
}
fn decode_raw<T>(data: &[u8]) -> T
where
T: DeserializeOwned,
{
deserialize_from(Cursor::new(data)).expect("failed to decode cloud")
}
================================================
FILE: src/io/gcloud/flexbuffers.rs
================================================
use flexbuffers::{FlexbufferSerializer, Reader};
use serde::{Deserialize, Serialize};
use crate::{
gaussian::formats::{planar_3d::PlanarGaussian3d, planar_4d::PlanarGaussian4d},
io::codec::CloudCodec,
};
impl CloudCodec for PlanarGaussian3d {
fn encode(&self) -> Vec<u8> {
let mut serializer = FlexbufferSerializer::new();
self.serialize(&mut serializer)
.expect("failed to serialize cloud");
serializer.view().to_vec()
}
fn decode(data: &[u8]) -> Self {
let reader = Reader::get_root(data).expect("failed to read flexbuffer");
Self::deserialize(reader).expect("deserialization failed")
}
}
impl CloudCodec for PlanarGaussian4d {
fn encode(&self) -> Vec<u8> {
let mut serializer = FlexbufferSerializer::new();
self.serialize(&mut serializer)
.expect("failed to serialize cloud");
serializer.view().to_vec()
}
fn decode(data: &[u8]) -> Self {
let reader = Reader::get_root(data).expect("failed to read flexbuffer");
Self::deserialize(reader).expect("deserialization failed")
}
}
================================================
FILE: src/io/gcloud/mod.rs
================================================
use static_assertions::assert_cfg;
// If both codecs are enabled, prefer flexbuffers.
#[cfg(all(feature = "io_bincode2", not(feature = "io_flexbuffers")))]
pub mod bincode2;
#[cfg(feature = "io_flexbuffers")]
pub mod flexbuffers;
assert_cfg!(
any(feature = "io_bincode2", feature = "io_flexbuffers",),
"no gcloud io enabled",
);
================================================
FILE: src/io/gcloud/texture.rs
================================================
// TODO: convert gcloud to compressed texture format
================================================
FILE: src/io/loader.rs
================================================
#[allow(unused_imports)]
use std::io::{BufReader, Cursor, ErrorKind};
use bevy::{
asset::{AssetLoader, LoadContext, io::Reader},
reflect::TypePath,
};
use crate::{
gaussian::formats::planar_3d::PlanarGaussian3d, gaussian::formats::planar_4d::PlanarGaussian4d,
io::codec::CloudCodec,
};
#[derive(Default, TypePath)]
pub struct Gaussian3dLoader;
impl AssetLoader for Gaussian3dLoader {
type Asset = PlanarGaussian3d;
type Settings = ();
type Error = std::io::Error;
async fn load(
&self,
reader: &mut dyn Reader,
_: &Self::Settings,
load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let extension = load_context
.path()
.path()
.extension()
.and_then(|ext| ext.to_str());
match extension {
Some("ply") => {
#[cfg(feature = "io_ply")]
{
let cursor = Cursor::new(bytes);
let mut f = BufReader::new(cursor);
Ok(crate::io::ply::parse_ply_3d(&mut f)?)
}
#[cfg(not(feature = "io_ply"))]
{
Err(std::io::Error::other(
"ply support not enabled, enable with io_ply feature",
))
}
}
Some("gcloud") => {
let cloud = PlanarGaussian3d::decode(bytes.as_slice());
Ok(cloud)
}
_ => Err(std::io::Error::other("only .ply and .gcloud supported")),
}
}
fn extensions(&self) -> &[&str] {
&["ply", "gcloud"]
}
}
#[derive(Default, TypePath)]
pub struct Gaussian4dLoader;
impl AssetLoader for Gaussian4dLoader {
type Asset = PlanarGaussian4d;
type Settings = ();
type Error = std::io::Error;
async fn load(
&self,
reader: &mut dyn Reader,
_: &Self::Settings,
load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let extension = load_context
.path()
.path()
.extension()
.and_then(|ext| ext.to_str());
match extension {
Some("ply4d") => {
#[cfg(feature = "io_ply")]
{
let cursor = Cursor::new(bytes);
let mut f = BufReader::new(cursor);
Ok(crate::io::ply::parse_ply_4d(&mut f)?)
}
#[cfg(not(feature = "io_ply"))]
{
Err(std::io::Error::other(
"ply4d support not enabled, enable with io_ply feature",
))
}
}
Some("gc4d") => Ok(PlanarGaussian4d::decode(bytes.as_slice())),
_ => Err(std::io::Error::other("only .ply4d and .gc4d supported")),
}
}
fn extensions(&self) -> &[&str] {
&["ply4d", "gc4d"]
}
}
================================================
FILE: src/io/mod.rs
================================================
use bevy::prelude::*;
pub mod codec;
pub mod gcloud;
pub mod loader;
pub mod scene;
#[cfg(feature = "io_ply")]
pub mod ply;
#[derive(Default)]
pub struct IoPlugin;
impl Plugin for IoPlugin {
fn build(&self, app: &mut App) {
app.init_asset_loader::<loader::Gaussian3dLoader>();
app.init_asset_loader::<loader::Gaussian4dLoader>();
app.add_plugins(scene::GaussianScenePlugin);
}
}
================================================
FILE: src/io/ply.rs
================================================
use core::panic;
use std::io::BufRead;
use bevy_interleave::prelude::Planar;
use ply_rs::{
parser::Parser,
ply::{Property, PropertyAccess},
};
use crate::{
gaussian::formats::{
planar_3d::{Gaussian3d, PlanarGaussian3d},
planar_4d::{Gaussian4d, PlanarGaussian4d},
},
material::{
spherical_harmonics::{SH_CHANNELS, SH_COEFF_COUNT, SH_COEFF_COUNT_PER_CHANNEL},
spherindrical_harmonics::SH_4D_COEFF_COUNT,
},
};
pub const MAX_SIZE_VARIANCE: f32 = 4.0;
impl PropertyAccess for Gaussian3d {
fn new() -> Self {
Gaussian3d::default()
}
fn set_property(&mut self, key: String, property: Property) {
match (key.as_ref(), property) {
("x", Property::Float(v)) => self.position_visibility.position[0] = v,
("y", Property::Float(v)) => self.position_visibility.position[1] = v,
("z", Property::Float(v)) => self.position_visibility.position[2] = v,
("visibility", Property::Float(v)) => self.position_visibility.visibility = v,
("f_dc_0", Property::Float(v)) => self.spherical_harmonic.set(0, v),
("f_dc_1", Property::Float(v)) => self.spherical_harmonic.set(1, v),
("f_dc_2", Property::Float(v)) => self.spherical_harmonic.set(2, v),
("scale_0", Property::Float(v)) => self.scale_opacity.scale[0] = v,
("scale_1", Property::Float(v)) => self.scale_opacity.scale[1] = v,
("scale_2", Property::Float(v)) => self.scale_opacity.scale[2] = v,
("opacity", Property::Float(v)) => {
self.scale_opacity.opacity = 1.0 / (1.0 + (-v).exp())
}
("rot_0", Property::Float(v)) => self.rotation.rotation[0] = v,
("rot_1", Property::Float(v)) => self.rotation.rotation[1] = v,
("rot_2", Property::Float(v)) => self.rotation.rotation[2] = v,
("rot_3", Property::Float(v)) => self.rotation.rotation[3] = v,
(_, Property::Float(v)) if key.starts_with("f_rest_") => {
let i = key[7..].parse::<usize>().unwrap();
// interleaved
// if (i + 3) < SH_COEFF_COUNT {
// self.spherical_harmonic.coefficients[i + 3] = v;
// }
// planar
let channel = i / SH_COEFF_COUNT_PER_CHANNEL;
let coefficient = if SH_COEFF_COUNT_PER_CHANNEL == 1 {
1
} else {
(i % (SH_COEFF_COUNT_PER_CHANNEL - 1)) + 1
};
let interleaved_idx = coefficient * SH_CHANNELS + channel;
if interleaved_idx < SH_COEFF_COUNT {
self.spherical_harmonic.set(interleaved_idx, v);
} else {
// TODO: convert higher degree SH to lower degree SH
}
}
(_, _) => {}
}
}
}
pub fn parse_ply_3d(mut reader: &mut dyn BufRead) -> Result<PlanarGaussian3d, std::io::Error> {
let gaussian_parser = Parser::<Gaussian3d>::new();
let header = gaussian_parser.read_header(&mut reader)?;
let mut cloud = Vec::new();
let required_properties = vec![
"x", "y", "z", "f_dc_0", "f_dc_1", "f_dc_2", "scale_0", "scale_1", "opacity", "rot_0",
"rot_1", "rot_2", "rot_3",
];
let mut required_property_count = required_properties.len();
for (_key, element) in &header.elements {
if element.name == "vertex" {
for (key, _prop) in &element.properties {
required_property_count -= required_properties.contains(&key.as_str()) as usize;
}
if required_property_count > 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"missing required properties",
));
}
cloud = gaussian_parser.read_payload_for_element(&mut reader, element, &header)?;
}
}
for gaussian in &mut cloud {
// TODO: add automatic scaling normalization detection (e.g. don't normalize twice)
let mean_scale = (gaussian.scale_opacity.scale[0]
+ gaussian.scale_opacity.scale[1]
+ gaussian.scale_opacity.scale[2])
/ 3.0;
for i in 0..3 {
gaussian.scale_opacity.scale[i] = gaussian.scale_opacity.scale[i]
.max(mean_scale - MAX_SIZE_VARIANCE)
.min(mean_scale + MAX_SIZE_VARIANCE)
.exp();
}
let norm = (0..4)
.map(|i| gaussian.rotation.rotation[i].powf(2.0))
.sum::<f32>()
.sqrt();
for i in 0..4 {
gaussian.rotation.rotation[i] /= norm;
}
}
// pad with empty gaussians to multiple of 32
let pad = 32 - (cloud.len() % 32);
cloud.extend(std::iter::repeat_n(Gaussian3d::default(), pad));
Ok(PlanarGaussian3d::from_interleaved(cloud))
}
impl PropertyAccess for Gaussian4d {
fn new() -> Self {
Gaussian4d::default()
}
fn set_property(&mut self, key: String, property: Property) {
match (key.as_ref(), property) {
("x", Property::Float(v)) => self.position_visibility.position[0] = v,
("y", Property::Float(v)) => self.position_visibility.position[1] = v,
("z", Property::Float(v)) => self.position_visibility.position[2] = v,
("visibility", Property::Float(v)) => self.position_visibility.visibility = v,
("t", Property::Float(v)) => self.timestamp_timescale.timestamp = v,
("st", Property::Float(v)) => self.timestamp_timescale.timescale = v,
(_, Property::Float(v)) if key.starts_with("feat_") => {
let channel = match key.chars().nth(5).unwrap() {
'r' => 0,
'g' => 1,
'b' => 2,
_ => panic!("invalid feature channel, expected r, g, or b"),
};
let i = key[7..].parse::<usize>().unwrap();
let interleaved_idx = i * SH_CHANNELS + channel;
if interleaved_idx < SH_4D_COEFF_COUNT {
self.spherindrical_harmonic.set(interleaved_idx, v);
} else {
// TODO: handle higher-degree if needed
}
}
("sx", Property::Float(v)) => self.scale_opacity.scale[0] = v,
("sy", Property::Float(v)) => self.scale_opacity.scale[1] = v,
("sz", Property::Float(v)) => self.scale_opacity.scale[2] = v,
("opacity", Property::Float(v)) => self.scale_opacity.opacity = v,
("rot_x", Property::Float(v)) => self.isotropic_rotations.rotation[0] = v,
("rot_y", Property::Float(v)) => self.isotropic_rotations.rotation[1] = v,
("rot_z", Property::Float(v)) => self.isotropic_rotations.rotation[2] = v,
("rot_w", Property::Float(v)) => self.isotropic_rotations.rotation[3] = v,
("rot_r_x", Property::Float(v)) => self.isotropic_rotations.rotation_r[0] = v,
("rot_r_y", Property::Float(v)) => self.isotropic_rotations.rotation_r[1] = v,
("rot_r_z", Property::Float(v)) => self.isotropic_rotations.rotation_r[2] = v,
("rot_r_w", Property::Float(v)) => self.isotropic_rotations.rotation_r[3] = v,
_ => {}
}
}
}
pub fn parse_ply_4d(mut reader: &mut dyn BufRead) -> Result<PlanarGaussian4d, std::io::Error> {
let parser = Parser::<Gaussian4d>::new();
let header = parser.read_header(&mut reader)?;
let mut cloud = Vec::new();
let required_properties = vec![
"x", "y", "z", "t", "st", "sx", "sy", "sz", "opacity", "rot_x", "rot_y", "rot_z", "rot_w",
"rot_r_x", "rot_r_y", "rot_r_z", "rot_r_w",
];
let mut required_property_count = required_properties.len();
for (_key, element) in &header.elements {
if element.name == "vertex" {
for (key, _prop) in &element.properties {
required_property_count -= required_properties.contains(&key.as_str()) as usize;
}
if required_property_count > 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"missing required properties",
));
}
cloud = parser.read_payload_for_element(&mut reader, element, &header)?;
}
}
for g in &mut cloud {
let norm = g
.isotropic_rotations
.rotation
.iter()
.map(|v| v.powi(2))
.sum::<f32>()
.sqrt();
for v in &mut g.isotropic_rotations.rotation {
*v /= norm;
}
let norm = g
.isotropic_rotations
.rotation_r
.iter()
.map(|v| v.powi(2))
.sum::<f32>()
.sqrt();
for v in &mut g.isotropic_rotations.rotation_r {
*v /= norm;
}
// TODO: normalize timescale between 0 and 1
}
// pad to multiple of 32
let pad = 32 - (cloud.len() % 32);
cloud.extend(std::iter::repeat_n(Gaussian4d::default(), pad));
Ok(PlanarGaussian4d::from_interleaved(cloud))
}
================================================
FILE: src/io/scene.rs
================================================
use std::borrow::Cow;
use std::collections::{BTreeMap, HashMap};
use std::io::ErrorKind;
use std::path::Path;
use base64::Engine as _;
use bevy::reflect::TypePath;
use bevy::{
asset::{AssetLoader, LoadContext, io::Reader},
prelude::*,
};
use gltf::{
Accessor,
accessor::{DataType, Dimensions, Item, Iter},
buffer::Source,
};
use serde::Deserialize;
use serde_json::{Value, json};
use crate::gaussian::{
formats::planar_3d::{Gaussian3d, PlanarGaussian3d, PlanarGaussian3dHandle},
settings::{CloudSettings, GaussianColorSpace, GaussianMode},
};
use crate::material::spherical_harmonics::{
SH_CHANNELS, SH_COEFF_COUNT, SH_COEFF_COUNT_PER_CHANNEL,
};
const KHR_GAUSSIAN_SPLATTING_EXTENSION: &str = "KHR_gaussian_splatting";
const ATTR_POSITION: &str = "POSITION";
const ATTR_COLOR_0: &str = "COLOR_0";
const ATTR_ROTATION: &str = "KHR_gaussian_splatting:ROTATION";
const ATTR_SCALE: &str = "KHR_gaussian_splatting:SCALE";
const ATTR_OPACITY: &str = "KHR_gaussian_splatting:OPACITY";
const ATTR_SH_PREFIX: &str = "KHR_gaussian_splatting:SH_DEGREE_";
const SH_DEGREE_ZERO_BASIS: f32 = 0.282_095;
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Reflect)]
pub enum GaussianKernel {
#[default]
Ellipse,
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Reflect)]
pub enum GaussianProjection {
#[default]
Perspective,
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Reflect)]
pub enum GaussianSortingMethod {
#[default]
CameraDistance,
}
#[derive(Clone, Debug, Reflect)]
pub struct GaussianPrimitiveSpec {
pub kernel: String,
pub color_space: String,
pub projection: String,
pub sorting_method: String,
#[reflect(ignore)]
pub extension_object: Option<Value>,
}
impl Default for GaussianPrimitiveSpec {
fn default() -> Self {
Self {
kernel: "ellipse".to_owned(),
color_space: "srgb_rec709_display".to_owned(),
projection: "perspective".to_owned(),
sorting_method: "cameraDistance".to_owned(),
extension_object: None,
}
}
}
#[derive(Component, Clone, Debug, Default, Reflect)]
pub struct GaussianPrimitiveMetadata {
pub kernel: GaussianKernel,
pub projection: GaussianProjection,
pub sorting_method: GaussianSortingMethod,
pub spec: GaussianPrimitiveSpec,
}
#[derive(Clone, Debug, Default, Reflect)]
pub struct CloudBundle {
pub cloud: Handle<PlanarGaussian3d>,
pub name: String,
pub settings: CloudSettings,
pub transform: Transform,
pub metadata: GaussianPrimitiveMetadata,
}
#[derive(Clone, Debug, Default, Reflect)]
pub struct SceneCamera {
pub name: String,
pub transform: Transform,
}
#[derive(Asset, Clone, Debug, Default, Reflect)]
pub struct GaussianScene {
pub bundles: Vec<CloudBundle>,
pub cameras: Vec<SceneCamera>,
}
#[derive(Clone, Debug)]
pub struct SceneExportCloud {
pub cloud: PlanarGaussian3d,
pub name: String,
pub settings: CloudSettings,
pub transform: Transform,
pub metadata: GaussianPrimitiveMetadata,
}
#[derive(Clone, Debug)]
pub struct SceneExportCamera {
pub name: String,
pub transform: Transform,
pub yfov_radians: f32,
pub znear: f32,
pub zfar: Option<f32>,
}
impl Default for SceneExportCamera {
fn default() -> Self {
Self {
name: "camera".to_owned(),
transform: Transform::default(),
yfov_radians: std::f32::consts::FRAC_PI_4,
znear: 0.01,
zfar: Some(1000.0),
}
}
}
#[derive(Component, Clone, Debug, Default, Reflect)]
#[require(Transform, Visibility)]
pub struct GaussianSceneHandle(pub Handle<GaussianScene>);
#[derive(Component, Clone, Debug, Default, Reflect)]
pub struct GaussianSceneLoaded;
#[derive(Default)]
pub struct GaussianScenePlugin;
impl Plugin for GaussianScenePlugin {
fn build(&self, app: &mut App) {
app.register_type::<GaussianKernel>();
app.register_type::<GaussianProjection>();
app.register_type::<GaussianSortingMethod>();
app.register_type::<GaussianPrimitiveSpec>();
app.register_type::<GaussianPrimitiveMetadata>();
app.register_type::<CloudBundle>();
app.register_type::<SceneCamera>();
app.register_type::<GaussianScene>();
app.register_type::<GaussianSceneHandle>();
app.register_type::<GaussianSceneLoaded>();
app.init_asset::<GaussianScene>();
app.init_asset_loader::<GaussianSceneLoader>();
app.add_systems(Update, (spawn_scene,));
}
}
fn spawn_scene(
mut commands: Commands,
scene_handles: Query<(Entity, &GaussianSceneHandle), Without<GaussianSceneLoaded>>,
asset_server: Res<AssetServer>,
scenes: Res<Assets<GaussianScene>>,
) {
for (entity, scene_handle) in scene_handles.iter() {
if let Some(load_state) = asset_server.get_load_state(&scene_handle.0)
&& !load_state.is_loaded()
{
continue;
}
let Some(scene) = scenes.get(&scene_handle.0) else {
continue;
};
let bundles = scene.bundles.clone();
commands
.entity(entity)
.with_children(move |builder| {
for bundle in bundles {
builder.spawn((
PlanarGaussian3dHandle(bundle.cloud.clone()),
Name::new(bundle.name.clone()),
bundle.settings.clone(),
bundle.transform,
bundle.metadata.clone(),
));
}
})
.insert(GaussianSceneLoaded);
}
}
#[derive(Default, TypePath)]
pub struct GaussianSceneLoader;
impl AssetLoader for GaussianSceneLoader {
type Asset = GaussianScene;
type Settings = ();
type Error = std::io::Error;
async fn load(
&self,
reader: &mut dyn Reader,
_: &Self::Settings,
load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
load_gltf_scene(&bytes, load_context).await
}
fn extensions(&self) -> &[&str] {
&["gltf", "glb"]
}
}
#[derive(Clone, Debug)]
struct GaussianPrimitiveSource {
attributes: HashMap<String, usize>,
metadata: GaussianPrimitiveMetadata,
color_space: GaussianColorSpace,
}
#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawRoot {
#[serde(default, rename = "extensionsUsed")]
extensions_used: Vec<String>,
#[serde(default)]
meshes: Vec<RawMesh>,
#[serde(default)]
nodes: Vec<RawNode>,
}
#[derive(Debug, Default, Deserialize)]
struct RawMesh {
#[serde(default)]
primitives: Vec<RawPrimitive>,
}
#[derive(Debug, Default, Deserialize)]
struct RawNode {
#[serde(default)]
name: Option<String>,
}
#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawPrimitive {
#[serde(default)]
attributes: HashMap<String, usize>,
#[serde(default)]
mode: Option<u32>,
#[serde(default)]
extensions: HashMap<String, Value>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawGaussianExtension {
kernel: String,
color_space: String,
#[serde(default = "default_projection")]
projection: String,
#[serde(default = "default_sorting_method")]
sorting_method: String,
}
fn default_projection() -> String {
"perspective".to_owned()
}
fn default_sorting_method() -> String {
"cameraDistance".to_owned()
}
async fn load_gltf_scene(
bytes: &[u8],
load_context: &mut LoadContext<'_>,
) -> Result<GaussianScene, std::io::Error> {
let raw_root = parse_raw_root(bytes)?;
let gltf = gltf::Gltf::from_slice_without_validation(bytes).map_err(|err| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("failed to parse glTF document: {err}"),
)
})?;
let primitive_sources = collect_gaussian_primitives(&raw_root)?;
if primitive_sources.is_empty() {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
"no KHR_gaussian_splatting primitives found",
));
}
ensure_gaussian_extension_used(&raw_root.extensions_used)?;
let buffers = load_buffers(&gltf, load_context).await?;
let scene = gltf.default_scene().or_else(|| gltf.scenes().next());
let Some(scene) = scene else {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
"glTF does not contain any scenes",
));
};
let mut bundles = Vec::new();
let mut cameras = Vec::new();
let mut bundle_index = 0usize;
for node in scene.nodes() {
collect_node_bundles(
&node,
Mat4::IDENTITY,
&raw_root,
&gltf.document,
&buffers,
&primitive_sources,
load_context,
&mut bundle_index,
&mut bundles,
&mut cameras,
)?;
}
if bundles.is_empty() {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
"KHR_gaussian_splatting scene contained no loadable gaussian primitives",
));
}
Ok(GaussianScene { bundles, cameras })
}
fn ensure_gaussian_extension_used(extensions_used: &[String]) -> Result<(), std::io::Error> {
if extensions_used
.iter()
.any(|extension| extension == KHR_GAUSSIAN_SPLATTING_EXTENSION)
{
return Ok(());
}
Err(std::io::Error::new(
ErrorKind::InvalidData,
"KHR_gaussian_splatting primitives are present but the extension is missing from extensionsUsed",
))
}
fn parse_raw_root(bytes: &[u8]) -> Result<RawRoot, std::io::Error> {
if bytes.starts_with(b"glTF") {
let glb = gltf::binary::Glb::from_slice(bytes).map_err(|err| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("failed to parse GLB binary container: {err}"),
)
})?;
serde_json::from_slice(glb.json.as_ref()).map_err(|err| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("failed to parse GLB JSON chunk: {err}"),
)
})
} else {
serde_json::from_slice(bytes).map_err(|err| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("failed to parse glTF JSON: {err}"),
)
})
}
}
fn collect_gaussian_primitives(
raw_root: &RawRoot,
) -> Result<HashMap<(usize, usize), GaussianPrimitiveSource>, std::io::Error> {
let mut sources = HashMap::new();
for (mesh_index, mesh) in raw_root.meshes.iter().enumerate() {
for (primitive_index, primitive) in mesh.primitives.iter().enumerate() {
let Some(extension_value) = primitive.extensions.get(KHR_GAUSSIAN_SPLATTING_EXTENSION)
else {
continue;
};
let mode = primitive.mode.unwrap_or(4);
if mode != 0 {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"mesh {mesh_index} primitive {primitive_index} has KHR_gaussian_splatting but mode={mode}; mode must be POINTS (0)"
),
));
}
let extension: RawGaussianExtension =
serde_json::from_value(extension_value.clone()).map_err(|err| {
std::io::Error::new(
ErrorKind::InvalidData,
format!(
"mesh {mesh_index} primitive {primitive_index} has invalid KHR_gaussian_splatting extension payload: {err}"
),
)
})?;
let kernel = parse_kernel(&extension.kernel, mesh_index, primitive_index)?;
let color_space =
parse_color_space(&extension.color_space, mesh_index, primitive_index)?;
let projection = parse_projection(&extension.projection, mesh_index, primitive_index)?;
let sorting_method =
parse_sorting_method(&extension.sorting_method, mesh_index, primitive_index)?;
sources.insert(
(mesh_index, primitive_index),
GaussianPrimitiveSource {
attributes: primitive.attributes.clone(),
metadata: GaussianPrimitiveMetadata {
kernel,
projection,
sorting_method,
spec: GaussianPrimitiveSpec {
kernel: extension.kernel.clone(),
color_space: extension.color_space.clone(),
projection: extension.projection.clone(),
sorting_method: extension.sorting_method.clone(),
extension_object: Some(extension_value.clone()),
},
},
color_space,
},
);
}
}
Ok(sources)
}
fn parse_kernel(
value: &str,
mesh_index: usize,
primitive_index: usize,
) -> Result<GaussianKernel, std::io::Error> {
if value.trim().is_empty() {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"mesh {mesh_index} primitive {primitive_index} has an empty KHR_gaussian_splatting kernel value"
),
));
}
match value {
"ellipse" => Ok(GaussianKernel::Ellipse),
_ => {
warn!(
"mesh {} primitive {} uses extension kernel '{}'; falling back to base kernel 'ellipse'",
mesh_index, primitive_index, value
);
Ok(GaussianKernel::Ellipse)
}
}
}
fn parse_color_space(
value: &str,
mesh_index: usize,
primitive_index: usize,
) -> Result<GaussianColorSpace, std::io::Error> {
if value.trim().is_empty() {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"mesh {mesh_index} primitive {primitive_index} has an empty KHR_gaussian_splatting colorSpace value"
),
));
}
match value {
"srgb_rec709_display" => Ok(GaussianColorSpace::SrgbRec709Display),
"lin_rec709_display" => Ok(GaussianColorSpace::LinRec709Display),
_ => {
warn!(
"mesh {} primitive {} uses extension colorSpace '{}'; falling back to 'srgb_rec709_display'",
mesh_index, primitive_index, value
);
Ok(GaussianColorSpace::SrgbRec709Display)
}
}
}
fn parse_projection(
value: &str,
mesh_index: usize,
primitive_index: usize,
) -> Result<GaussianProjection, std::io::Error> {
if value.trim().is_empty() {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"mesh {mesh_index} primitive {primitive_index} has an empty KHR_gaussian_splatting projection value"
),
));
}
match value {
"perspective" => Ok(GaussianProjection::Perspective),
_ => {
warn!(
"mesh {} primitive {} uses extension projection '{}'; falling back to 'perspective'",
mesh_index, primitive_index, value
);
Ok(GaussianProjection::Perspective)
}
}
}
fn parse_sorting_method(
value: &str,
mesh_index: usize,
primitive_index: usize,
) -> Result<GaussianSortingMethod, std::io::Error> {
if value.trim().is_empty() {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"mesh {mesh_index} primitive {primitive_index} has an empty KHR_gaussian_splatting sortingMethod value"
),
));
}
match value {
"cameraDistance" => Ok(GaussianSortingMethod::CameraDistance),
_ => {
warn!(
"mesh {} primitive {} uses extension sortingMethod '{}'; falling back to 'cameraDistance'",
mesh_index, primitive_index, value
);
Ok(GaussianSortingMethod::CameraDistance)
}
}
}
async fn load_buffers(
gltf: &gltf::Gltf,
load_context: &mut LoadContext<'_>,
) -> Result<Vec<Vec<u8>>, std::io::Error> {
let mut buffers = Vec::new();
let mut blob = gltf.blob.clone();
for buffer in gltf.buffers() {
let mut data = match buffer.source() {
Source::Bin => blob.take().ok_or_else(|| {
std::io::Error::new(
ErrorKind::InvalidData,
"glTF buffer references BIN chunk but binary data is missing",
)
})?,
Source::Uri(uri) => {
if let Some(decoded) = decode_data_uri(uri) {
decoded?
} else {
let path = load_context.path().resolve_embed(uri).map_err(|err| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("failed to resolve external buffer URI '{uri}': {err}"),
)
})?;
load_context.read_asset_bytes(path).await.map_err(|err| {
std::io::Error::new(
ErrorKind::NotFound,
format!("failed to read external buffer '{uri}': {err}"),
)
})?
}
}
};
if data.len() < buffer.length() {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"buffer {} length mismatch: expected at least {} bytes, got {} bytes",
buffer.index(),
buffer.length(),
data.len()
),
));
}
while data.len() % 4 != 0 {
data.push(0);
}
buffers.push(data);
}
Ok(buffers)
}
fn decode_data_uri(uri: &str) -> Option<Result<Vec<u8>, std::io::Error>> {
let rest = uri.strip_prefix("data:")?;
let Some((metadata, payload)) = rest.split_once(',') else {
return Some(Err(std::io::Error::new(
ErrorKind::InvalidData,
"malformed data URI; expected a ',' separator",
)));
};
let is_base64 = metadata
.split(';')
.any(|part| part.eq_ignore_ascii_case("base64"));
if is_base64 {
return Some(
base64::engine::general_purpose::STANDARD
.decode(payload)
.map_err(|err| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("failed to decode base64 data URI: {err}"),
)
}),
);
}
Some(decode_percent_encoded_data_uri(payload))
}
fn decode_percent_encoded_data_uri(payload: &str) -> Result<Vec<u8>, std::io::Error> {
let bytes = payload.as_bytes();
let mut decoded = Vec::with_capacity(bytes.len());
let mut index = 0usize;
while index < bytes.len() {
if bytes[index] == b'%' {
if index + 2 >= bytes.len() {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
"malformed percent-encoded data URI payload",
));
}
let high = decode_hex(bytes[index + 1])?;
let low = decode_hex(bytes[index + 2])?;
decoded.push((high << 4) | low);
index += 3;
continue;
}
decoded.push(bytes[index]);
index += 1;
}
Ok(decoded)
}
fn decode_hex(value: u8) -> Result<u8, std::io::Error> {
match value {
b'0'..=b'9' => Ok(value - b'0'),
b'a'..=b'f' => Ok(value - b'a' + 10),
b'A'..=b'F' => Ok(value - b'A' + 10),
_ => Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"malformed percent-encoded data URI payload: invalid hex digit '{}'",
value as char
),
)),
}
}
#[allow(clippy::too_many_arguments)]
fn collect_node_bundles(
node: &gltf::Node<'_>,
parent_transform: Mat4,
raw_root: &RawRoot,
document: &gltf::Document,
buffers: &[Vec<u8>],
primitive_sources: &HashMap<(usize, usize), GaussianPrimitiveSource>,
load_context: &mut LoadContext<'_>,
bundle_index: &mut usize,
bundles: &mut Vec<CloudBundle>,
cameras: &mut Vec<SceneCamera>,
) -> Result<(), std::io::Error> {
let local_transform = Mat4::from_cols_array_2d(&node.transform().matrix());
let world_transform = parent_transform * local_transform;
let node_name = raw_root
.nodes
.get(node.index())
.and_then(|raw_node| raw_node.name.as_deref())
.unwrap_or("gaussian_node");
if node.camera().is_some() {
cameras.push(SceneCamera {
name: node_name.to_owned(),
transform: Transform::from_matrix(world_transform),
});
}
if let Some(mesh) = node.mesh() {
for primitive in mesh.primitives() {
let key = (mesh.index(), primitive.index());
let Some(source) = primitive_sources.get(&key) else {
continue;
};
let cloud = decode_gaussian_primitive(document, buffers, source)?;
let cloud_handle =
load_context.add_labeled_asset(format!("gltf_gaussian_{}", *bundle_index), cloud);
let settings = CloudSettings {
gaussian_mode: GaussianMode::Gaussian3d,
color_space: source.color_space,
..default()
};
bundles.push(CloudBundle {
cloud: cloud_handle,
name: format!(
"{node_name}_mesh{}_primitive{}",
mesh.index(),
primitive.index()
),
settings,
transform: Transform::from_matrix(world_transform),
metadata: source.metadata.clone(),
});
*bundle_index += 1;
}
}
for child in node.children() {
collect_node_bundles(
&child,
world_transform,
raw_root,
document,
buffers,
primitive_sources,
load_context,
bundle_index,
bundles,
cameras,
)?;
}
Ok(())
}
pub fn encode_khr_gaussian_scene_gltf_bytes(
clouds: &[SceneExportCloud],
camera: Option<&SceneExportCamera>,
) -> Result<Vec<u8>, std::io::Error> {
if clouds.is_empty() {
return Err(std::io::Error::new(
ErrorKind::InvalidInput,
"cannot export an empty KHR_gaussian_splatting scene",
));
}
let mut binary = Vec::<u8>::new();
let mut buffer_views = Vec::<Value>::new();
let mut accessors = Vec::<Value>::new();
let mut meshes = Vec::<Value>::new();
let mut nodes = Vec::<Value>::new();
let mut scene_nodes = Vec::<usize>::new();
let mut cameras_json = Vec::<Value>::new();
let export_sh_degree = max_export_sh_degree().min(3);
let export_coeff_count = (export_sh_degree + 1) * (export_sh_degree + 1);
for cloud in clouds {
let source_gaussian_count = cloud.cloud.position_visibility.len();
if source_gaussian_count == 0 {
continue;
}
let mut positions = Vec::<f32>::with_capacity(source_gaussian_count * 3);
let mut rotations = Vec::<f32>::with_capacity(source_gaussian_count * 4);
let mut scales = Vec::<f32>::with_capacity(source_gaussian_count * 3);
let mut opacities = Vec::<f32>::with_capacity(source_gaussian_count);
let mut sh_channels = (0..export_coeff_count)
.map(|_| Vec::<f32>::with_capacity(source_gaussian_count * 3))
.collect::<Vec<_>>();
let mut position_min = [f32::INFINITY; 3];
let mut position_max = [f32::NEG_INFINITY; 3];
let mut dropped_gaussians = 0usize;
for gaussian in cloud.cloud.iter() {
let rotation = gaussian.rotation.rotation;
let rotation_length_sq = rotation
.iter()
.map(|component| component * component)
.sum::<f32>();
if rotation_length_sq <= f32::EPSILON || !rotation_length_sq.is_finite() {
dropped_gaussians += 1;
continue;
}
let inv_rotation_length = rotation_length_sq.sqrt().recip();
let normalized_rotation = [
rotation[0] * inv_rotation_length,
rotation[1] * inv_rotation_length,
rotation[2] * inv_rotation_length,
rotation[3] * inv_rotation_length,
];
let position = gaussian.position_visibility.position;
positions.extend_from_slice(&position);
for axis in 0..3 {
position_min[axis] = position_min[axis].min(position[axis]);
position_max[axis] = position_max[axis].max(position[axis]);
}
rotations.extend_from_slice(&normalized_rotation);
scales.extend_from_slice(&[
gaussian.scale_opacity.scale[0].max(1e-6).ln(),
gaussian.scale_opacity.scale[1].max(1e-6).ln(),
gaussian.scale_opacity.scale[2].max(1e-6).ln(),
]);
opacities.push(gaussian.scale_opacity.opacity.clamp(0.0, 1.0));
for (coefficient_index, channel) in sh_channels.iter_mut().enumerate() {
let base = coefficient_index * SH_CHANNELS;
channel.extend_from_slice(&[
gaussian.spherical_harmonic.coefficients[base],
gaussian.spherical_harmonic.coefficients[base + 1],
gaussian.spherical_harmonic.coefficients[base + 2],
]);
}
}
let gaussian_count = positions.len() / 3;
if gaussian_count == 0 {
warn!(
"skipping cloud '{}' during KHR export because all gaussians had invalid rotations",
cloud.name
);
continue;
}
if dropped_gaussians > 0 {
warn!(
"dropped {} gaussians with invalid rotations while exporting cloud '{}'",
dropped_gaussians, cloud.name
);
}
let position_accessor = push_f32_accessor(
&mut binary,
&mut buffer_views,
&mut accessors,
AccessorSpec {
values: &positions,
count: gaussian_count,
accessor_type: "VEC3",
min: Some(position_min.to_vec()),
max: Some(position_max.to_vec()),
},
);
let rotation_accessor = push_f32_accessor(
&mut binary,
&mut buffer_views,
&mut accessors,
AccessorSpec {
values: &rotations,
count: gaussian_count,
accessor_type: "VEC4",
min: None,
max: None,
},
);
let scale_accessor = push_f32_accessor(
&mut binary,
&mut buffer_views,
&mut accessors,
AccessorSpec {
values: &scales,
count: gaussian_count,
accessor_type: "VEC3",
min: None,
max: None,
},
);
let opacity_accessor = push_f32_accessor(
&mut binary,
&mut buffer_views,
&mut accessors,
AccessorSpec {
values: &opacities,
count: gaussian_count,
accessor_type: "SCALAR",
min: None,
max: None,
},
);
let mut attributes = serde_json::Map::new();
attributes.insert(ATTR_POSITION.to_owned(), json!(position_accessor));
attributes.insert(ATTR_ROTATION.to_owned(), json!(rotation_accessor));
attributes.insert(ATTR_SCALE.to_owned(), json!(scale_accessor));
attributes.insert(ATTR_OPACITY.to_owned(), json!(opacity_accessor));
for (coefficient_index, values) in sh_channels.iter().enumerate() {
let sh_accessor = push_f32_accessor(
&mut binary,
&mut buffer_views,
&mut accessors,
AccessorSpec {
values,
count: gaussian_count,
accessor_type: "VEC3",
min: None,
max: None,
},
);
let (degree, coefficient) = sh_index_to_degree_coefficient(coefficient_index);
attributes.insert(
format!("{ATTR_SH_PREFIX}{degree}_COEF_{coefficient}"),
json!(sh_accessor),
);
}
let primitive_extension =
gaussian_extension_object(&cloud.metadata, cloud.settings.color_space);
meshes.push(json!({
"name": cloud.name,
"primitives": [{
"attributes": Value::Object(attributes),
"mode": 0,
"extensions": {
KHR_GAUSSIAN_SPLATTING_EXTENSION: primitive_extension
}
}]
}));
let node_index = nodes.len();
scene_nodes.push(node_index);
nodes.push(json!({
"name": cloud.name,
"mesh": meshes.len() - 1,
"matrix": transform_matrix_values(cloud.transform),
}));
}
if scene_nodes.is_empty() {
return Err(std::io::Error::new(
ErrorKind::InvalidInput,
"cannot export a KHR_gaussian_splatting scene with zero gaussians",
));
}
if let Some(camera) = camera {
let mut perspective = serde_json::Map::new();
perspective.insert("yfov".to_owned(), json!(camera.yfov_radians));
perspective.insert("znear".to_owned(), json!(camera.znear));
if let Some(zfar) = camera.zfar {
perspective.insert("zfar".to_owned(), json!(zfar));
}
cameras_json.push(json!({
"name": camera.name,
"type": "perspective",
"perspective": Value::Object(perspective),
}));
let camera_node_index = nodes.len();
scene_nodes.push(camera_node_index);
nodes.push(json!({
"name": camera.name,
"camera": cameras_json.len() - 1,
"matrix": transform_matrix_values(camera.transform),
}));
}
align_to_four_bytes(&mut binary);
let mut root = serde_json::Map::new();
root.insert("asset".to_owned(), json!({ "version": "2.0" }));
root.insert(
"extensionsUsed".to_owned(),
json!([KHR_GAUSSIAN_SPLATTING_EXTENSION]),
);
root.insert(
"extensionsRequired".to_owned(),
json!([KHR_GAUSSIAN_SPLATTING_EXTENSION]),
);
root.insert("scene".to_owned(), json!(0));
root.insert("scenes".to_owned(), json!([{ "nodes": scene_nodes }]));
root.insert("nodes".to_owned(), Value::Array(nodes));
root.insert("meshes".to_owned(), Value::Array(meshes));
root.insert(
"buffers".to_owned(),
json!([{
"byteLength": binary.len(),
"uri": format!(
"data:application/octet-stream;base64,{}",
base64::engine::general_purpose::STANDARD.encode(&binary)
),
}]),
);
root.insert("bufferViews".to_owned(), Value::Array(buffer_views));
root.insert("accessors".to_owned(), Value::Array(accessors));
if !cameras_json.is_empty() {
root.insert("cameras".to_owned(), Value::Array(cameras_json));
}
serde_json::to_vec_pretty(&Value::Object(root)).map_err(|err| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("failed to serialize KHR_gaussian_splatting scene: {err}"),
)
})
}
pub fn write_khr_gaussian_scene_gltf(
path: impl AsRef<Path>,
clouds: &[SceneExportCloud],
camera: Option<&SceneExportCamera>,
) -> Result<(), std::io::Error> {
let bytes = encode_khr_gaussian_scene_gltf_bytes(clouds, camera)?;
std::fs::write(path, bytes)
}
pub fn encode_khr_gaussian_scene_glb_bytes(
clouds: &[SceneExportCloud],
camera: Option<&SceneExportCamera>,
) -> Result<Vec<u8>, std::io::Error> {
let gltf_bytes = encode_khr_gaussian_scene_gltf_bytes(clouds, camera)?;
let mut root: Value = serde_json::from_slice(&gltf_bytes).map_err(|err| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("failed to parse generated KHR_gaussian_splatting glTF JSON: {err}"),
)
})?;
let binary = extract_embedded_binary_buffer(&mut root)?;
let json = serde_json::to_vec(&root).map_err(|err| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("failed to serialize KHR_gaussian_splatting GLB JSON chunk: {err}"),
)
})?;
let glb = gltf::binary::Glb {
header: gltf::binary::Header {
magic: *b"glTF",
version: 2,
length: 0,
},
json: Cow::Owned(json),
bin: Some(Cow::Owned(binary)),
};
glb.to_vec().map_err(|err| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("failed to serialize KHR_gaussian_splatting GLB: {err}"),
)
})
}
pub fn write_khr_gaussian_scene_glb(
path: impl AsRef<Path>,
clouds: &[SceneExportCloud],
camera: Option<&SceneExportCamera>,
) -> Result<(), std::io::Error> {
let bytes = encode_khr_gaussian_scene_glb_bytes(clouds, camera)?;
std::fs::write(path, bytes)
}
fn extract_embedded_binary_buffer(root: &mut Value) -> Result<Vec<u8>, std::io::Error> {
let buffers = root
.get_mut("buffers")
.and_then(Value::as_array_mut)
.ok_or_else(|| std::io::Error::new(ErrorKind::InvalidData, "missing glTF buffers array"))?;
if buffers.len() != 1 {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"KHR_gaussian_splatting export expects exactly one buffer, found {}",
buffers.len()
),
));
}
let buffer = buffers[0].as_object_mut().ok_or_else(|| {
std::io::Error::new(ErrorKind::InvalidData, "buffer entry must be a JSON object")
})?;
let uri = buffer
.remove("uri")
.and_then(|value| value.as_str().map(ToOwned::to_owned))
.ok_or_else(|| {
std::io::Error::new(
ErrorKind::InvalidData,
"buffer URI missing; expected embedded data URI for GLB conversion",
)
})?;
let binary = decode_data_uri(&uri).ok_or_else(|| {
std::io::Error::new(
ErrorKind::InvalidData,
"buffer URI must be an embedded data URI for GLB conversion",
)
})??;
buffer.insert("byteLength".to_owned(), json!(binary.len()));
Ok(binary)
}
fn align_to_four_bytes(bytes: &mut Vec<u8>) {
while !bytes.len().is_multiple_of(4) {
bytes.push(0);
}
}
struct AccessorSpec<'a> {
values: &'a [f32],
count: usize,
accessor_type: &'a str,
min: Option<Vec<f32>>,
max: Option<Vec<f32>>,
}
fn push_f32_accessor(
binary: &mut Vec<u8>,
buffer_views: &mut Vec<Value>,
accessors: &mut Vec<Value>,
spec: AccessorSpec<'_>,
) -> usize {
align_to_four_bytes(binary);
let byte_offset = binary.len();
for value in spec.values {
binary.extend_from_slice(&value.to_le_bytes());
}
let byte_length = std::mem::size_of_val(spec.values);
let buffer_view_index = buffer_views.len();
buffer_views.push(json!({
"buffer": 0,
"byteOffset": byte_offset,
"byteLength": byte_length,
}));
let mut accessor = serde_json::Map::new();
accessor.insert("bufferView".to_owned(), json!(buffer_view_index));
accessor.insert("componentType".to_owned(), json!(5126u32));
accessor.insert("count".to_owned(), json!(spec.count));
accessor.insert("type".to_owned(), json!(spec.accessor_type));
if let Some(min) = spec.min {
accessor.insert("min".to_owned(), json!(min));
}
if let Some(max) = spec.max {
accessor.insert("max".to_owned(), json!(max));
}
let accessor_index = accessors.len();
accessors.push(Value::Object(accessor));
accessor_index
}
fn transform_matrix_values(transform: Transform) -> [f32; 16] {
transform.to_matrix().to_cols_array()
}
fn gaussian_extension_object(
metadata: &GaussianPrimitiveMetadata,
color_space: GaussianColorSpace,
) -> Value {
let mut extension_object = metadata
.spec
.extension_object
.as_ref()
.and_then(Value::as_object)
.cloned()
.unwrap_or_default();
extension_object.insert(
"kernel".to_owned(),
Value::String(kernel_extension_identifier(metadata)),
);
extension_object.insert(
"colorSpace".to_owned(),
Value::String(color_space_extension_identifier(metadata, color_space)),
);
extension_object.insert(
"projection".to_owned(),
Value::String(projection_extension_identifier(metadata)),
);
extension_object.insert(
"sortingMethod".to_owned(),
Value::String(sorting_method_extension_identifier(metadata)),
);
Value::Object(extension_object)
}
fn kernel_extension_identifier(metadata: &GaussianPrimitiveMetadata) -> String {
extension_identifier(
&metadata.spec.kernel,
kernel_to_extension_value(metadata.kernel),
&["ellipse"],
)
}
fn color_space_extension_identifier(
metadata: &GaussianPrimitiveMetadata,
color_space: GaussianColorSpace,
) -> String {
extension_identifier(
&metadata.spec.color_space,
color_space_to_extension_value(color_space),
&["srgb_rec709_display", "lin_rec709_display"],
)
}
fn projection_extension_identifier(metadata: &GaussianPrimitiveMetadata) -> String {
extension_identifier(
&metadata.spec.projection,
projection_to_extension_value(metadata.projection),
&["perspective"],
)
}
fn sorting_method_extension_identifier(metadata: &GaussianPrimitiveMetadata) -> String {
extension_identifier(
&metadata.spec.sorting_method,
sorting_method_to_extension_value(metadata.sorting_method),
&["cameraDistance"],
)
}
fn extension_identifier(spec_value: &str, fallback_value: &str, known_values: &[&str]) -> String {
let spec_value = spec_value.trim();
if spec_value.is_empty() || known_values.contains(&spec_value) {
fallback_value.to_owned()
} else {
spec_value.to_owned()
}
}
fn kernel_to_extension_value(kernel: GaussianKernel) -> &'static str {
match kernel {
GaussianKernel::Ellipse => "ellipse",
}
}
fn projection_to_extension_value(projection: GaussianProjection) -> &'static str {
match projection {
GaussianProjection::Perspective => "perspective",
}
}
fn sorting_method_to_extension_value(method: GaussianSortingMethod) -> &'static str {
match method {
GaussianSortingMethod::CameraDistance => "cameraDistance",
}
}
fn color_space_to_extension_value(color_space: GaussianColorSpace) -> &'static str {
match color_space {
GaussianColorSpace::SrgbRec709Display => "srgb_rec709_display",
GaussianColorSpace::LinRec709Display => "lin_rec709_display",
}
}
fn sh_index_to_degree_coefficient(index: usize) -> (usize, usize) {
let mut degree = 0usize;
while (degree + 1) * (degree + 1) <= index {
degree += 1;
}
let coefficient = index - (degree * degree);
(degree, coefficient)
}
fn max_export_sh_degree() -> usize {
for degree in (0..=3).rev() {
if (degree + 1) * (degree + 1) <= SH_COEFF_COUNT_PER_CHANNEL {
return degree;
}
}
0
}
fn decode_gaussian_primitive(
document: &gltf::Document,
buffers: &[Vec<u8>],
source: &GaussianPrimitiveSource,
) -> Result<PlanarGaussian3d, std::io::Error> {
let position_accessor = required_accessor(document, &source.attributes, ATTR_POSITION)?;
let rotation_accessor = required_accessor(document, &source.attributes, ATTR_ROTATION)?;
let scale_accessor = required_accessor(document, &source.attributes, ATTR_SCALE)?;
let opacity_accessor = required_accessor(document, &source.attributes, ATTR_OPACITY)?;
let sh_accessors = collect_sh_accessors(document, &source.attributes)?;
let count = position_accessor.count();
ensure_count(&rotation_accessor, count, ATTR_ROTATION)?;
ensure_count(&scale_accessor, count, ATTR_SCALE)?;
ensure_count(&opacity_accessor, count, ATTR_OPACITY)?;
let positions = read_position_attribute(&position_accessor, buffers)?;
let rotations = read_rotation_attribute(&rotation_accessor, buffers)?;
let scales = read_scale_attribute(&scale_accessor, buffers)?;
let opacities = read_opacity_attribute(&opacity_accessor, buffers)?;
let color_fallback = if sh_accessors.is_empty() {
match optional_accessor(document, &source.attributes, ATTR_COLOR_0)? {
Some(color_accessor) => {
ensure_count(&color_accessor, count, ATTR_COLOR_0)?;
Some(read_color_attribute(&color_accessor, buffers)?)
}
None => None,
}
} else {
None
};
let mut sh_channels = Vec::with_capacity(sh_accessors.len());
for (coefficient_index, accessor) in sh_accessors {
ensure_count(
&accessor,
count,
&format!("{ATTR_SH_PREFIX}{coefficient_index}"),
)?;
sh_channels.push((
coefficient_index,
read_sh_coefficient_attribute(&accessor, buffers)?,
));
}
let mut gaussians = Vec::with_capacity(count);
for index in 0..count {
let mut spherical_harmonic =
crate::material::spherical_harmonics::SphericalHarmonicCoefficients::default();
if sh_channels.is_empty()
&& let Some(color_values) = &color_fallback
{
let color = color_values[index];
spherical_harmonic.set(0, color[0] / SH_DEGREE_ZERO_BASIS);
spherical_harmonic.set(1, color[1] / SH_DEGREE_ZERO_BASIS);
spherical_harmonic.set(2, color[2] / SH_DEGREE_ZERO_BASIS);
}
for (coefficient_index, values) in &sh_channels {
let rgb = values[index];
let base = coefficient_index * SH_CHANNELS;
if (base + 2) < SH_COEFF_COUNT {
spherical_harmonic.set(base, rgb[0]);
spherical_harmonic.set(base + 1, rgb[1]);
spherical_harmonic.set(base + 2, rgb[2]);
}
}
gaussians.push(Gaussian3d {
position_visibility: [
positions[index][0],
positions[index][1],
positions[index][2],
1.0,
]
.into(),
spherical_harmonic,
rotation: rotations[index].into(),
scale_opacity: [
scales[index][0],
scales[index][1],
scales[index][2],
opacities[index],
]
.into(),
});
}
Ok(gaussians.into())
}
fn required_accessor<'a>(
document: &'a gltf::Document,
attributes: &HashMap<String, usize>,
semantic: &str,
) -> Result<Accessor<'a>, std::io::Error> {
let index = attributes.get(semantic).ok_or_else(|| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("missing required attribute semantic '{semantic}'"),
)
})?;
document.accessors().nth(*index).ok_or_else(|| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("attribute semantic '{semantic}' references missing accessor index {index}"),
)
})
}
fn optional_accessor<'a>(
document: &'a gltf::Document,
attributes: &HashMap<String, usize>,
semantic: &str,
) -> Result<Option<Accessor<'a>>, std::io::Error> {
let Some(index) = attributes.get(semantic) else {
return Ok(None);
};
let accessor = document.accessors().nth(*index).ok_or_else(|| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("attribute semantic '{semantic}' references missing accessor index {index}"),
)
})?;
Ok(Some(accessor))
}
fn collect_sh_accessors<'a>(
document: &'a gltf::Document,
attributes: &HashMap<String, usize>,
) -> Result<Vec<(usize, Accessor<'a>)>, std::io::Error> {
let coefficient_map = collect_sh_coefficient_map(attributes)?;
let mut accessors = Vec::with_capacity(coefficient_map.len());
for (coefficient_index, accessor_index) in coefficient_map {
let accessor = document.accessors().nth(accessor_index).ok_or_else(|| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("SH attribute references missing accessor index {accessor_index}"),
)
})?;
accessors.push((coefficient_index, accessor));
}
Ok(accessors)
}
fn collect_sh_coefficient_map(
attributes: &HashMap<String, usize>,
) -> Result<Vec<(usize, usize)>, std::io::Error> {
let mut degrees: BTreeMap<usize, BTreeMap<usize, usize>> = BTreeMap::new();
for (semantic, accessor_index) in attributes {
let Some((degree, coefficient)) = parse_sh_semantic(semantic) else {
continue;
};
degrees
.entry(degree)
.or_default()
.insert(coefficient, *accessor_index);
}
if degrees.is_empty() {
return Ok(Vec::new());
}
let Some(degree_zero) = degrees.get(&0) else {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
"missing required spherical harmonics attribute 'KHR_gaussian_splatting:SH_DEGREE_0_COEF_0'",
));
};
if !degree_zero.contains_key(&0) {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
"missing required spherical harmonics attribute 'KHR_gaussian_splatting:SH_DEGREE_0_COEF_0'",
));
}
let max_degree = *degrees.keys().max().unwrap_or(&0);
if max_degree > 3 {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"unsupported spherical harmonics degree {max_degree}; KHR_gaussian_splatting supports degrees up to 3"
),
));
}
let supported_degree = max_supported_sh_degree();
if max_degree > supported_degree {
warn!(
"asset uses spherical harmonics degree {max_degree}, but this build supports up to degree {supported_degree}; higher-degree coefficients will be discarded"
);
}
for degree in 0..=max_degree {
let expected_count = 2 * degree + 1;
let Some(coefficients) = degrees.get(°ree) else {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"spherical harmonics degree {degree} is required because higher degrees are present, but its coefficients are missing"
),
));
};
if coefficients.len() != expected_count {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"spherical harmonics degree {degree} must define exactly {expected_count} coefficients"
),
));
}
for coefficient in 0..expected_count {
if !coefficients.contains_key(&coefficient) {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"spherical harmonics degree {degree} is partially defined; missing coefficient {coefficient}"
),
));
}
}
}
let mut coefficient_map = Vec::new();
let map_max_degree = max_degree.min(supported_degree);
for degree in 0..=map_max_degree {
let coefficients = °rees[°ree];
for coefficient in 0..(2 * degree + 1) {
let accessor_index = coefficients[&coefficient];
coefficient_map.push((sh_coefficient_index(degree, coefficient), accessor_index));
}
}
Ok(coefficient_map)
}
fn parse_sh_semantic(semantic: &str) -> Option<(usize, usize)> {
let rest = semantic.strip_prefix(ATTR_SH_PREFIX)?;
let (degree, coefficient) = rest.split_once("_COEF_")?;
Some((degree.parse().ok()?, coefficient.parse().ok()?))
}
fn sh_coefficient_index(degree: usize, coefficient: usize) -> usize {
degree * degree + coefficient
}
fn max_supported_sh_degree() -> usize {
let mut degree = 0usize;
loop {
let coefficient_count = (degree + 1) * (degree + 1);
if coefficient_count >= SH_COEFF_COUNT_PER_CHANNEL {
return degree;
}
degree += 1;
}
}
fn ensure_count(
accessor: &Accessor<'_>,
expected_count: usize,
semantic: &str,
) -> Result<(), std::io::Error> {
let count = accessor.count();
if count == expected_count {
return Ok(());
}
Err(std::io::Error::new(
ErrorKind::InvalidData,
format!("attribute semantic '{semantic}' has {count} entries; expected {expected_count}"),
))
}
fn read_position_attribute(
accessor: &Accessor<'_>,
buffers: &[Vec<u8>],
) -> Result<Vec<[f32; 3]>, std::io::Error> {
if accessor.dimensions() != Dimensions::Vec3 {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"attribute semantic '{ATTR_POSITION}' must use accessor type VEC3, got {:?}",
accessor.dimensions()
),
));
}
if accessor.data_type() != DataType::F32 {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"attribute semantic '{ATTR_POSITION}' must use float components, got {:?}",
accessor.data_type()
),
));
}
let values = read_items::<[f32; 3]>(accessor, buffers, ATTR_POSITION)?;
if values
.iter()
.flatten()
.any(|component| !component.is_finite())
{
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"attribute semantic '{ATTR_POSITION}' contains non-finite values, which are invalid"
),
));
}
Ok(values)
}
fn read_rotation_attribute(
accessor: &Accessor<'_>,
buffers: &[Vec<u8>],
) -> Result<Vec<[f32; 4]>, std::io::Error> {
if accessor.dimensions() != Dimensions::Vec4 {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"attribute semantic '{ATTR_ROTATION}' must use accessor type VEC4, got {:?}",
accessor.dimensions()
),
));
}
let normalized = accessor.normalized();
let mut values = match accessor.data_type() {
DataType::F32 => read_items::<[f32; 4]>(accessor, buffers, ATTR_ROTATION)?,
DataType::I8 if normalized => read_items::<[i8; 4]>(accessor, buffers, ATTR_ROTATION)?
.into_iter()
.map(|v| {
[
normalize_i8(v[0]),
normalize_i8(v[1]),
normalize_i8(v[2]),
normalize_i8(v[3]),
]
})
.collect(),
DataType::I16 if normalized => read_items::<[i16; 4]>(accessor, buffers, ATTR_ROTATION)?
.into_iter()
.map(|v| {
[
normalize_i16(v[0]),
normalize_i16(v[1]),
normalize_i16(v[2]),
normalize_i16(v[3]),
]
})
.collect(),
_ => {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"attribute semantic '{ATTR_ROTATION}' must use float or normalized signed integer components"
),
));
}
};
let mut replaced_zero_length_quaternions = 0usize;
for quaternion in &mut values {
if normalize_quaternion(quaternion) {
replaced_zero_length_quaternions += 1;
}
}
if replaced_zero_length_quaternions > 0 {
warn!(
"attribute semantic '{}' contained {} zero-length quaternions; replacing them with identity rotations",
ATTR_ROTATION, replaced_zero_length_quaternions
);
}
if values
.iter()
.flatten()
.any(|component| !component.is_finite())
{
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"attribute semantic '{ATTR_ROTATION}' contains non-finite values, which are invalid"
),
));
}
Ok(values)
}
fn read_scale_attribute(
accessor: &Accessor<'_>,
buffers: &[Vec<u8>],
) -> Result<Vec<[f32; 3]>, std::io::Error> {
if accessor.dimensions() != Dimensions::Vec3 {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"attribute semantic '{ATTR_SCALE}' must use accessor type VEC3, got {:?}",
accessor.dimensions()
),
));
}
let normalized = accessor.normalized();
let mut values = match accessor.data_type() {
DataType::F32 => read_items::<[f32; 3]>(accessor, buffers, ATTR_SCALE)?,
DataType::I8 => read_items::<[i8; 3]>(accessor, buffers, ATTR_SCALE)?
.into_iter()
.map(|v| {
if normalized {
[normalize_i8(v[0]), normalize_i8(v[1]), normalize_i8(v[2])]
} else {
[v[0] as f32, v[1] as f32, v[2] as f32]
}
})
.collect(),
DataType::I16 => read_items::<[i16; 3]>(accessor, buffers, ATTR_SCALE)?
.into_iter()
.map(|v| {
if normalized {
[
normalize_i16(v[0]),
normalize_i16(v[1]),
normalize_i16(v[2]),
]
} else {
[v[0] as f32, v[1] as f32, v[2] as f32]
}
})
.collect(),
_ => {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"attribute semantic '{ATTR_SCALE}' must use float or signed integer components"
),
));
}
};
for scale in &mut values {
scale[0] = scale[0].exp();
scale[1] = scale[1].exp();
scale[2] = scale[2].exp();
if !scale[0].is_finite() || !scale[1].is_finite() || !scale[2].is_finite() {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"attribute semantic '{ATTR_SCALE}' produces non-finite scale after exp(scale), which is invalid"
),
));
}
}
Ok(values)
}
fn read_opacity_attribute(
accessor: &Accessor<'_>,
buffers: &[Vec<u8>],
) -> Result<Vec<f32>, std::io::Error> {
if accessor.dimensions() != Dimensions::Scalar {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"attribute semantic '{ATTR_OPACITY}' must use accessor type SCALAR, got {:?}",
accessor.dimensions()
),
));
}
let normalized = accessor.normalized();
let values: Vec<f32> = match accessor.data_type() {
DataType::F32 => read_items::<f32>(accessor, buffers, ATTR_OPACITY)?,
DataType::U8 if normalized => read_items::<u8>(accessor, buffers, ATTR_OPACITY)?
.into_iter()
.map(normalize_u8)
.collect(),
DataType::U16 if normalized => read_items::<u16>(accessor, buffers, ATTR_OPACITY)?
.into_iter()
.map(normalize_u16)
.collect(),
_ => {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"attribute semantic '{ATTR_OPACITY}' must use float or normalized unsigned integer components"
),
));
}
};
if values
.iter()
.any(|opacity| !opacity.is_finite() || *opacity < 0.0 || *opacity > 1.0)
{
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"attribute semantic '{ATTR_OPACITY}' contains out-of-range values; opacity must be in [0, 1]"
),
));
}
Ok(values)
}
fn read_color_attribute(
accessor: &Accessor<'_>,
buffers: &[Vec<u8>],
) -> Result<Vec<[f32; 3]>, std::io::Error> {
let normalized = accessor.normalized();
match accessor.dimensions() {
Dimensions::Vec3 => {}
Dimensions::Vec4 => {}
_ => {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"attribute semantic '{ATTR_COLOR_0}' must use accessor type VEC3 or VEC4, got {:?}",
accessor.dimensions()
),
));
}
}
match (accessor.dimensions(), accessor.data_type()) {
(Dimensions::Vec3, DataType::F32) => {
read_items::<[f32; 3]>(accessor, buffers, ATTR_COLOR_0)
}
(Dimensions::Vec4, DataType::F32) => {
Ok(read_items::<[f32; 4]>(accessor, buffers, ATTR_COLOR_0)?
.into_iter()
.map(|v| [v[0], v[1], v[2]])
.collect())
}
(Dimensions::Vec3, DataType::U8) => {
if !normalized {
warn!(
"attribute semantic '{}' uses non-normalized U8 values; interpreting as normalized colors",
ATTR_COLOR_0
);
}
Ok(read_items::<[u8; 3]>(accessor, buffers, ATTR_COLOR_0)?
.into_iter()
.map(|v| [normalize_u8(v[0]), normalize_u8(v[1]), normalize_u8(v[2])])
.collect())
}
(Dimensions::Vec4, DataType::U8) => {
if !normalized {
warn!(
"attribute semantic '{}' uses non-normalized U8 values; interpreting as normalized colors",
ATTR_COLOR_0
);
}
Ok(read_items::<[u8; 4]>(accessor, buffers, ATTR_COLOR_0)?
.into_iter()
.map(|v| [normalize_u8(v[0]), normalize_u8(v[1]), normalize_u8(v[2])])
.collect())
}
(Dimensions::Vec3, DataType::U16) => {
if !normalized {
warn!(
"attribute semantic '{}' uses non-normalized U16 values; interpreting as normalized colors",
ATTR_COLOR_0
);
}
Ok(read_items::<[u16; 3]>(accessor, buffers, ATTR_COLOR_0)?
.into_iter()
.map(|v| {
[
normalize_u16(v[0]),
normalize_u16(v[1]),
normalize_u16(v[2]),
]
})
.collect())
}
(Dimensions::Vec4, DataType::U16) => {
if !normalized {
warn!(
"attribute semantic '{}' uses non-normalized U16 values; interpreting as normalized colors",
ATTR_COLOR_0
);
}
Ok(read_items::<[u16; 4]>(accessor, buffers, ATTR_COLOR_0)?
.into_iter()
.map(|v| {
[
normalize_u16(v[0]),
normalize_u16(v[1]),
normalize_u16(v[2]),
]
})
.collect())
}
_ => Err(std::io::Error::new(
ErrorKind::InvalidData,
format!(
"attribute semantic '{ATTR_COLOR_0}' must use float, normalized unsigned byte, or normalized unsigned short components"
),
)),
}
}
fn read_sh_coefficient_attribute(
accessor: &Accessor<'_>,
buffers: &[Vec<u8>],
) -> Result<Vec<[f32; 3]>, std::io::Error> {
if accessor.dimensions() != Dimensions::Vec3 {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
"spherical harmonics attributes must use accessor type VEC3",
));
}
if accessor.data_type() != DataType::F32 {
return Err(std::io::Error::new(
ErrorKind::InvalidData,
"spherical harmonics attributes must use float components",
));
}
let values = read_items::<[f32; 3]>(accessor, buffers, "KHR_gaussian_splatting:SH")?;
if values
.iter()
.flatten()
.any(|component| !component.is_finite())
{
return Err(std::io::Error::new(
ErrorKind::InvalidData,
"spherical harmonics attributes contain non-finite values, which are invalid",
));
}
Ok(values)
}
fn read_items<T: Item + Copy>(
accessor: &Accessor<'_>,
buffers: &[Vec<u8>],
semantic: &str,
) -> Result<Vec<T>, std::io::Error> {
let iter = Iter::<T>::new(accessor.clone(), |buffer| {
buffers.get(buffer.index()).map(Vec::as_slice)
})
.ok_or_else(|| {
std::io::Error::new(
ErrorKind::InvalidData,
format!(
"failed to decode accessor data for attribute semantic '{semantic}' (accessor index {})",
accessor.index()
),
)
})?;
Ok(iter.collect())
}
fn normalize_quaternion(quaternion: &mut [f32; 4]) -> bool {
let length_sq = quaternion
.iter()
.map(|component| component * component)
.sum::<f32>();
if length_sq <= f32::EPSILON {
quaternion[0] = 1.0;
quaternion[1] = 0.0;
quaternion[2] = 0.0;
quaternion[3] = 0.0;
return true;
}
let inv_length = length_sq.sqrt().recip();
quaternion[0] *= inv_length;
quaternion[1] *= inv_length;
quaternion[2] *= inv_length;
quaternion[3] *= inv_length;
false
}
fn normalize_i8(value: i8) -> f32 {
(value as f32 / 127.0).max(-1.0)
}
fn normalize_i16(value: i16) -> f32 {
(value as f32 / 32767.0).max(-1.0)
}
fn normalize_u8(value: u8) -> f32 {
value as f32 / 255.0
}
fn normalize_u16(value: u16) -> f32 {
value as f32 / 65535.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validates_complete_sh_coefficients_for_supported_degree() {
let mut attributes = HashMap::new();
attributes.insert(
"KHR_gaussian_splatting:SH_DEGREE_0_COEF_0".to_owned(),
0usize,
);
let supported_degree = max_supported_sh_degree().min(3);
let mut index = 1usize;
for degree in 1..=supported_degree {
for coefficient in 0..(2 * degree + 1) {
attributes.insert(
format!("KHR_gaussian_splatting:SH_DEGREE_{degree}_COEF_{coefficient}"),
index,
);
index += 1;
}
}
let result = collect_sh_coefficient_map(&attributes).unwrap();
assert_eq!(
result.len(),
(supported_degree + 1) * (supported_degree + 1)
);
assert_eq!(result[0].0, 0);
assert_eq!(result.last().unwrap().0, result.len() - 1);
}
#[test]
fn allows_no_sh_coefficients_for_extension_defined_lighting() {
let attributes = HashMap::new();
let result = collect_sh_coefficient_map(&attributes).unwrap();
assert!(result.is_empty());
}
#[test]
fn rejects_partial_sh_degree() {
let mut attributes = HashMap::new();
attributes.insert(
"KHR_gaussian_splatting:SH_DEGREE_0_COEF_0".to_owned(),
0usize,
);
attributes.insert(
"KHR_gaussian_splatting:SH_DEGREE_1_COEF_0".to_owned(),
1usize,
);
attributes.insert(
"KHR_gaussian_splatting:SH_DEGREE_1_COEF_2".to_owned(),
2usize,
);
let err = collect_sh_coefficient_map(&attributes).unwrap_err();
let message = err.to_string();
assert!(message.contains("must define exactly"));
}
#[test]
fn clips_sh_coefficients_above_supported_degree() {
let supported_degree = max_supported_sh_degree();
if supported_degree >= 3 {
return;
}
let mut attributes = HashMap::new();
attributes.insert(
"KHR_gaussian_splatting:SH_DEGREE_0_COEF_0".to_owned(),
0usize,
);
let mut index = 1usize;
for degree in 1..=3 {
for coefficient in 0..(2 * degree + 1) {
attributes.insert(
format!("KHR_gaussian_splatting:SH_DEGREE_{degree}_COEF_{coefficient}"),
index,
);
index += 1;
}
}
let result = collect_sh_coefficient_map(&attributes).unwrap();
assert_eq!(
result.len(),
(supported_degree + 1) * (supported_degree + 1)
);
assert!(result
.iter()
.all(|(index, _)| *index < (supported_degree + 1) * (supported_degree + 1)));
}
#[test]
fn preserves_unknown_extension_identifiers_on_export() {
let mut metadata = GaussianPrimitiveMetadata::default();
metadata.spec.kernel = "customShape".to_owned();
metadata.spec.color_space = "custom_space_display".to_owned();
metadata.spec.projection = "customProjection".to_owned();
metadata.spec.sorting_method = "customSort".to_owned();
assert_eq!(kernel_extension_identifier(&metadata), "customShape");
assert_eq!(
color_space_extension_identifier(&metadata, GaussianColorSpace::SrgbRec709Display),
"custom_space_display"
);
assert_eq!(
projection_extension_identifier(&metadata),
"customProjection"
);
assert_eq!(sorting_method_extension_identifier(&metadata), "customSort");
}
#[test]
fn uses_runtime_color_space_for_known_identifiers() {
let mut metadata = GaussianPrimitiveMetadata::default();
metadata.spec.color_space = "lin_rec709_display".to_owned();
assert_eq!(
color_space_extension_identifier(&metadata, GaussianColorSpace::SrgbRec709Display),
"srgb_rec709_display"
);
assert_eq!(
color_space_extension_identifier(&metadata, GaussianColorSpace::LinRec709Display),
"lin_rec709_display"
);
}
#[test]
fn decodes_base64_data_uri() {
let uri = "data:application/octet-stream;base64,AAECAwQF";
let data = decode_data_uri(uri).unwrap().unwrap();
assert_eq!(data, vec![0, 1, 2, 3, 4, 5]);
}
#[test]
fn decodes_percent_encoded_data_uri() {
let uri = "data:application/octet-stream,%00%01%02%03";
let data = decode_data_uri(uri).unwrap().unwrap();
assert_eq!(data, vec![0, 1, 2, 3]);
}
#[test]
fn requires_khr_extension_in_extensions_used() {
let err = ensure_gaussian_extension_used(&[]).unwrap_err();
assert!(err.to_string().contains("extensionsUsed"));
ensure_gaussian_extension_used(&[KHR_GAUSSIAN_SPLATTING_EXTENSION.to_owned()]).unwrap();
}
#[test]
fn encodes_khr_scene_with_camera_node() {
let cloud: PlanarGaussian3d = vec![Gaussian3d {
position_visibility: [1.0, 2.0, 3.0, 1.0].into(),
spherical_harmonic:
crate::material::spherical_harmonics::SphericalHarmonicCoefficients::default(),
rotation: [1.0, 0.0, 0.0, 0.0].into(),
scale_opacity: [1.0, 1.0, 1.0, 0.5].into(),
}]
.into();
let export_cloud = SceneExportCloud {
cloud,
name: "cloud".to_owned(),
settings: CloudSettings::default(),
transform: Transform::default(),
metadata: GaussianPrimitiveMetadata::default(),
};
let export_camera = SceneExportCamera {
name: "camera".to_owned(),
transform: Transform::from_xyz(4.0, 5.0, 6.0),
..default()
};
let bytes =
encode_khr_gaussian_scene_gltf_bytes(&[export_cloud], Some(&export_camera)).unwrap();
let root: Value = serde_json::from_slice(&bytes).unwrap();
assert_eq!(
root["extensionsUsed"]
.as_array()
.unwrap()
.iter()
.filter_map(Value::as_str)
.next(),
Some(KHR_GAUSSIAN_SPLATTING_EXTENSION)
);
assert_eq!(root["meshes"].as_array().unwrap().len(), 1);
assert_eq!(root["cameras"].as_array().unwrap().len(), 1);
assert_eq!(root["nodes"].as_array().unwrap().len(), 2);
}
#[test]
fn encodes_khr_scene_as_glb_with_bin_chunk() {
let cloud: PlanarGaussian3d = vec![Gaussian3d {
position_visibility: [1.0, 2.0, 3.0, 1.0].into(),
spherical_harmonic:
crate::material::spherical_harmonics::SphericalHarmonicCoefficients::default(),
rotation: [1.0, 0.0, 0.0, 0.0].into(),
scale_opacity: [1.0, 1.0, 1.0, 0.5].into(),
}]
.into();
let export_cloud = SceneExportCloud {
cloud,
name: "cloud".to_owned(),
settings: CloudSettings::default(),
transform: Transform::default(),
metadata: GaussianPrimitiveMetadata::default(),
};
let export_camera = SceneExportCamera {
name: "camera".to_owned(),
transform: Transform::from_xyz(4.0, 5.0, 6.0),
..default()
};
let glb_bytes =
encode_khr_gaussian_scene_glb_bytes(&[export_cloud], Some(&export_camera)).unwrap();
let glb = gltf::binary::Glb::from_slice(&glb_bytes).unwrap();
assert!(glb.bin.is_some());
let root: Value = serde_json::from_slice(glb.json.as_ref()).unwrap();
assert_eq!(root["meshes"].as_array().unwrap().len(), 1);
assert_eq!(root["cameras"].as_array().unwrap().len(), 1);
assert_eq!(root["nodes"].as_array().unwrap().len(), 2);
let buffer = root["buffers"][0].as_object().unwrap();
assert!(buffer.get("uri").is_none());
assert!(buffer.get("byteLength").and_then(Value::as_u64).unwrap() > 0);
}
#[test]
fn normalizes_zero_length_quaternion_to_identity() {
let mut quaternion = [0.0, 0.0, 0.0, 0.0];
let replaced = normalize_quaternion(&mut quaternion);
assert!(replaced);
assert_eq!(quaternion, [1.0, 0.0, 0.0, 0.0]);
}
fn build_gltf_with_accessors(
buffer: Vec<u8>,
buffer_views: Vec<(usize, usize)>,
access
gitextract_ni3w1v_t/
├── .cargo/
│ └── config.toml
├── .devcontainer/
│ ├── Dockerfile
│ └── docker-compose.yaml
├── .gitattributes
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── bench.yml
│ ├── build.yml
│ ├── clippy.yml
│ ├── deploy-pages.yml
│ ├── test.yml
│ └── todo_tracker.yml
├── .gitignore
├── .vscode/
│ └── bevy_gaussian_splatting.code-workspace
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── benches/
│ └── io.rs
├── docs/
│ └── credits.md
├── examples/
│ ├── headless.rs
│ ├── minimal.rs
│ └── multi_camera.rs
├── src/
│ ├── camera.rs
│ ├── gaussian/
│ │ ├── cloud.rs
│ │ ├── covariance.rs
│ │ ├── f16.rs
│ │ ├── f32.rs
│ │ ├── formats/
│ │ │ ├── mod.rs
│ │ │ ├── planar_3d.rs
│ │ │ ├── planar_3d_chunked.rs
│ │ │ ├── planar_3d_lod.rs
│ │ │ ├── planar_3d_quantized.rs
│ │ │ ├── planar_3d_spz.rs
│ │ │ ├── planar_4d.rs
│ │ │ ├── planar_4d_hierarchy.rs
│ │ │ ├── planar_4d_quantized.rs
│ │ │ └── spacetime.rs
│ │ ├── interface.rs
│ │ ├── iter.rs
│ │ ├── mod.rs
│ │ └── settings.rs
│ ├── io/
│ │ ├── codec.rs
│ │ ├── gcloud/
│ │ │ ├── bincode2.rs
│ │ │ ├── flexbuffers.rs
│ │ │ ├── mod.rs
│ │ │ └── texture.rs
│ │ ├── loader.rs
│ │ ├── mod.rs
│ │ ├── ply.rs
│ │ └── scene.rs
│ ├── lib.rs
│ ├── lighting/
│ │ ├── environmental.rs
│ │ └── mod.rs
│ ├── material/
│ │ ├── classification.rs
│ │ ├── classification.wgsl
│ │ ├── depth.rs
│ │ ├── depth.wgsl
│ │ ├── mod.rs
│ │ ├── noise.rs
│ │ ├── noise.wgsl
│ │ ├── optical_flow.rs
│ │ ├── optical_flow.wgsl
│ │ ├── pbr.rs
│ │ ├── pbr.wgsl
│ │ ├── position.rs
│ │ ├── position.wgsl
│ │ ├── spherical_harmonics.rs
│ │ ├── spherical_harmonics.wgsl
│ │ ├── spherindrical_harmonics.rs
│ │ └── spherindrical_harmonics.wgsl
│ ├── math/
│ │ └── mod.rs
│ ├── morph/
│ │ ├── interpolate.rs
│ │ ├── interpolate.wgsl
│ │ ├── mod.rs
│ │ ├── particle.rs
│ │ └── particle.wgsl
│ ├── noise/
│ │ ├── mod.rs
│ │ └── noise.wgsl
│ ├── query/
│ │ ├── mod.rs
│ │ ├── raycast.rs
│ │ ├── select.rs
│ │ └── sparse.rs
│ ├── render/
│ │ ├── bindings.wgsl
│ │ ├── gaussian.wgsl
│ │ ├── gaussian_2d.wgsl
│ │ ├── gaussian_3d.wgsl
│ │ ├── gaussian_4d.wgsl
│ │ ├── helpers.wgsl
│ │ ├── mod.rs
│ │ ├── packed.rs
│ │ ├── packed.wgsl
│ │ ├── planar.rs
│ │ ├── planar.wgsl
│ │ ├── texture.rs
│ │ ├── texture.wgsl
│ │ └── transform.wgsl
│ ├── sort/
│ │ ├── bitonic.rs
│ │ ├── bitonic.wgsl
│ │ ├── mod.rs
│ │ ├── radix.rs
│ │ ├── radix.wgsl
│ │ ├── rayon.rs
│ │ ├── std_sort.rs
│ │ └── temporal.wgsl
│ ├── stream/
│ │ ├── hierarchy.rs
│ │ ├── mod.rs
│ │ └── slice.rs
│ └── utils.rs
├── tests/
│ ├── fixtures/
│ │ └── khr_gaussian_splatting/
│ │ ├── khr_conformance_matrix.glb
│ │ ├── khr_conformance_matrix.gltf
│ │ └── khr_extensible_fallback.gltf
│ ├── gaussian.rs
│ ├── gpu/
│ │ ├── _harness.rs
│ │ ├── gaussian.rs
│ │ └── radix.rs
│ ├── headless_examples.rs
│ ├── io.rs
│ ├── khr_loader_conformance.rs
│ └── radix.rs
├── tools/
│ ├── README.md
│ ├── build_www.ps1
│ ├── build_www.sh
│ ├── compare_aabb_obb.rs
│ ├── ply_to_gcloud.rs
│ ├── render_trellis_thumbnails.rs
│ └── surfel_plane.rs
├── viewer/
│ └── viewer.rs
└── www/
├── README.md
├── examples/
│ ├── examples.css
│ ├── examples.js
│ └── index.html
└── index.html
SYMBOL INDEX (696 symbols across 60 files)
FILE: benches/io.rs
constant GAUSSIAN_COUNTS (line 10) | const GAUSSIAN_COUNTS: [usize; 4] = [
function gaussian_cloud_3d_decode_benchmark (line 15) | fn gaussian_cloud_3d_decode_benchmark(c: &mut Criterion) {
function gaussian_cloud_4d_decode_benchmark (line 30) | fn gaussian_cloud_4d_decode_benchmark(c: &mut Criterion) {
function khr_gltf_scene_encode_benchmark (line 45) | fn khr_gltf_scene_encode_benchmark(c: &mut Criterion) {
FILE: examples/headless.rs
type MainWorldReceiver (line 46) | struct MainWorldReceiver(Receiver<Vec<u8>>);
type RenderWorldSender (line 49) | struct RenderWorldSender(Sender<Vec<u8>>);
type CaptureController (line 52) | struct CaptureController {
method new (line 59) | pub fn new(width: u32, height: u32) -> Self {
function main (line 68) | fn main() {
function setup_gaussian_cloud (line 95) | fn setup_gaussian_cloud(
type ImageCopyPlugin (line 194) | pub struct ImageCopyPlugin;
method build (line 197) | fn build(&self, app: &mut App) {
type CaptureFramePlugin (line 218) | pub struct CaptureFramePlugin;
method build (line 221) | fn build(&self, app: &mut App) {
type ImageCopier (line 227) | struct ImageCopier {
method new (line 234) | pub fn new(src_image: Handle<Image>, size: Extent3d, render_device: &R...
method enabled (line 251) | pub fn enabled(&self) -> bool {
type ImageCopiers (line 257) | struct ImageCopiers(Vec<ImageCopier>);
function extract_image_copiers (line 259) | fn extract_image_copiers(mut commands: Commands, image_copiers: Extract<...
type ImageCopy (line 265) | struct ImageCopy;
type ImageCopyDriver (line 268) | struct ImageCopyDriver;
method run (line 271) | fn run(
function receive_image_from_buffer (line 325) | fn receive_image_from_buffer(
type ImageToSave (line 355) | struct ImageToSave(Handle<Image>);
function save_captured_frame (line 357) | fn save_captured_frame(
FILE: examples/minimal.rs
function main (line 3) | fn main() {
FILE: examples/multi_camera.rs
function multi_camera_app (line 16) | fn multi_camera_app() {
function setup_multi_camera (line 59) | pub fn setup_multi_camera(
function press_s_to_spawn_camera (line 189) | fn press_s_to_spawn_camera(
type CameraPosition (line 224) | struct CameraPosition {
function set_camera_viewports (line 228) | fn set_camera_viewports(
function esc_close (line 247) | fn esc_close(keys: Res<ButtonInput<KeyCode>>, mut exit: MessageWriter<Ap...
function main (line 253) | pub fn main() {
FILE: src/camera.rs
type GaussianCamera (line 7) | pub struct GaussianCamera {
type GaussianCameraPlugin (line 12) | pub struct GaussianCameraPlugin;
method build (line 15) | fn build(&self, app: &mut App) {
function apply_camera_warmup (line 23) | fn apply_camera_warmup(mut cameras: Query<&mut GaussianCamera>) {
FILE: src/gaussian/cloud.rs
type CloudPlugin (line 15) | pub struct CloudPlugin<R: PlanarSync> {
type CloudVisibilityClass (line 19) | pub struct CloudVisibilityClass;
function add_planar_class (line 21) | fn add_planar_class(world: DeferredWorld, ctx: HookContext) {
method build (line 30) | fn build(&self, app: &mut App) {
function calculate_bounds (line 45) | pub fn calculate_bounds<R: PlanarSync>(
FILE: src/gaussian/covariance.rs
function compute_covariance_3d (line 4) | pub fn compute_covariance_3d(rotation: Vec4, scale: Vec3) -> [f32; 6] {
FILE: src/gaussian/f16.rs
type RotationScaleOpacityPacked128 (line 30) | pub struct RotationScaleOpacityPacked128 {
method from_gaussian (line 38) | pub fn from_gaussian(gaussian: &Gaussian3d) -> Self {
method rotation (line 57) | pub fn rotation(&self) -> Rotation {
method scale_opacity (line 66) | pub fn scale_opacity(&self) -> ScaleOpacity {
method from (line 78) | fn from(rotation_scale_opacity: [f32; 8]) -> Self {
method from (line 93) | fn from(rotation_scale_opacity: [f16; 8]) -> Self {
method from (line 108) | fn from(rotation_scale_opacity: [u32; 4]) -> Self {
type Covariance3dOpacityPacked128 (line 131) | pub struct Covariance3dOpacityPacked128 {
method from_gaussian (line 138) | pub fn from_gaussian(gaussian: &Gaussian3d) -> Self {
method covariance_3d_opacity (line 154) | pub fn covariance_3d_opacity(&self) -> Covariance3dOpacity {
method from (line 172) | fn from(cov3d_opacity: [u32; 4]) -> Self {
type IsotropicRotations (line 195) | pub struct IsotropicRotations {
method from_gaussian (line 201) | pub fn from_gaussian(gaussian: &Gaussian4d) -> Self {
method rotations (line 217) | pub fn rotations(&self) -> [Rotation; 2] {
method from (line 236) | fn from(rotations: [u32; 4]) -> Self {
function pack_f32s_to_u32 (line 244) | pub fn pack_f32s_to_u32(upper: f32, lower: f32) -> u32 {
function pack_f16s_to_u32 (line 248) | pub fn pack_f16s_to_u32(upper: f16, lower: f16) -> u32 {
function unpack_u32_to_f16s (line 254) | pub fn unpack_u32_to_f16s(value: u32) -> (f16, f16) {
function unpack_u32_to_f32s (line 260) | pub fn unpack_u32_to_f32s(value: u32) -> (f32, f32) {
FILE: src/gaussian/f32.rs
type Position (line 13) | pub type Position = [f32; 3];
type PositionTimestamp (line 30) | pub struct PositionTimestamp {
method from (line 36) | fn from(position_timestamp: [f32; 4]) -> Self {
type PositionVisibility (line 53) | pub struct PositionVisibility {
method from (line 68) | fn from(position_visibility: [f32; 4]) -> Self {
method default (line 59) | fn default() -> Self {
type Rotation (line 95) | pub struct Rotation {
method from (line 100) | fn from(rotation: [f32; 4]) -> Self {
type IsotropicRotations (line 120) | pub struct IsotropicRotations {
method from_gaussian (line 126) | pub fn from_gaussian(gaussian: &Gaussian4d) -> Self {
method rotations (line 136) | pub fn rotations(&self) -> [Rotation; 2] {
method from (line 149) | fn from(rotations: [f32; 8]) -> Self {
type ScaleOpacity (line 172) | pub struct ScaleOpacity {
method from (line 178) | fn from(scale_opacity: [f32; 4]) -> Self {
type TimestampTimescale (line 201) | pub struct TimestampTimescale {
method from (line 208) | fn from(timestamp_timescale: [f32; 4]) -> Self {
type Covariance3dOpacity (line 232) | pub struct Covariance3dOpacity {
method from (line 239) | fn from(gaussian: &Gaussian3d) -> Self {
FILE: src/gaussian/formats/planar_3d.rs
type Gaussian3d (line 45) | pub struct Gaussian3d {
type Gaussian2d (line 56) | pub type Gaussian2d = Gaussian3d;
type PackedType (line 88) | type PackedType = Gaussian3d;
method visibility (line 90) | fn visibility(&self, index: usize) -> f32 {
method visibility_mut (line 94) | fn visibility_mut(&mut self, index: usize) -> &mut f32 {
method position_iter (line 98) | fn position_iter(&self) -> PositionIter<'_> {
method position_par_iter (line 103) | fn position_par_iter(&self) -> crate::gaussian::iter::PositionParIter<'_> {
method from_iter (line 109) | fn from_iter<I: IntoIterator<Item = Gaussian3d>>(iter: I) -> Self {
method from (line 115) | fn from(packed: Vec<Gaussian3d>) -> Self {
method sample (line 121) | fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Gaussian3d {
function random_gaussians_3d (line 171) | pub fn random_gaussians_3d(n: usize) -> PlanarGaussian3d {
function random_gaussians_3d_seeded (line 182) | pub fn random_gaussians_3d_seeded(n: usize, seed: u64) -> PlanarGaussian...
method test_model (line 194) | fn test_model() -> Self {
method iter (line 255) | pub fn iter(&self) -> impl Iterator<Item = Gaussian3d> + '_ {
FILE: src/gaussian/formats/planar_4d.rs
type Gaussian4d (line 40) | pub struct Gaussian4d {
type PackedType (line 213) | type PackedType = Gaussian4d;
method visibility (line 215) | fn visibility(&self, index: usize) -> f32 {
method visibility_mut (line 219) | fn visibility_mut(&mut self, index: usize) -> &mut f32 {
method position_iter (line 223) | fn position_iter(&self) -> PositionIter<'_> {
method position_par_iter (line 228) | fn position_par_iter(&self) -> crate::gaussian::iter::PositionParIter<'_> {
method from_iter (line 234) | fn from_iter<I: IntoIterator<Item = Gaussian4d>>(iter: I) -> Self {
method from (line 240) | fn from(packed: Vec<Gaussian4d>) -> Self {
method sample (line 246) | fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Gaussian4d {
function random_gaussians_4d (line 290) | pub fn random_gaussians_4d(n: usize) -> PlanarGaussian4d {
function random_gaussians_4d_seeded (line 301) | pub fn random_gaussians_4d_seeded(n: usize, seed: u64) -> PlanarGaussian...
method test_model (line 313) | fn test_model() -> Self {
FILE: src/gaussian/formats/planar_4d_hierarchy.rs
type TemporalGaussianLevel (line 4) | pub struct TemporalGaussianLevel {
type TemporalGaussianHierarchy (line 10) | pub struct TemporalGaussianHierarchy {
FILE: src/gaussian/interface.rs
type CommonCloud (line 9) | pub trait CommonCloud
method len_sqrt_ceil (line 15) | fn len_sqrt_ceil(&self) -> usize {
method square_len (line 18) | fn square_len(&self) -> usize {
method compute_aabb (line 22) | fn compute_aabb(&self) -> Option<Aabb3d> {
method visibility (line 65) | fn visibility(&self, index: usize) -> f32;
method visibility_mut (line 66) | fn visibility_mut(&mut self, index: usize) -> &mut f32;
method position_iter (line 69) | fn position_iter(&self) -> PositionIter<'_>;
method position_par_iter (line 72) | fn position_par_iter(&self) -> crate::gaussian::iter::PositionParIter<...
type TestCloud (line 75) | pub trait TestCloud {
method test_model (line 76) | fn test_model() -> Self;
FILE: src/gaussian/iter.rs
type PositionIter (line 8) | pub struct PositionIter<'a> {
function new (line 13) | pub fn new(slice: &'a [PositionVisibility]) -> Self {
type Item (line 21) | type Item = &'a Position;
method next (line 23) | fn next(&mut self) -> Option<Self::Item> {
type PositionParIter (line 29) | pub struct PositionParIter<'a> {
function new (line 35) | pub fn new(slice: &'a [PositionVisibility]) -> Self {
type Item (line 44) | type Item = &'a Position;
method drive_unindexed (line 46) | fn drive_unindexed<C>(self, consumer: C) -> C::Result
method len (line 58) | fn len(&self) -> usize {
method drive (line 62) | fn drive<C>(self, consumer: C) -> <C as Consumer<Self::Item>>::Result
method with_producer (line 69) | fn with_producer<CB>(self, callback: CB) -> CB::Output
FILE: src/gaussian/settings.rs
type DrawMode (line 7) | pub enum DrawMode {
type GaussianMode (line 17) | pub enum GaussianMode {
type PlaybackMode (line 27) | pub enum PlaybackMode {
type RasterizeMode (line 38) | pub enum RasterizeMode {
type GaussianColorSpace (line 50) | pub enum GaussianColorSpace {
type CloudSettings (line 60) | pub struct CloudSettings {
method default (line 80) | fn default() -> Self {
type SettingsPlugin (line 103) | pub struct SettingsPlugin;
method build (line 105) | fn build(&self, app: &mut App) {
function playback_update (line 112) | fn playback_update(time: Res<Time>, mut query: Query<(&mut CloudSettings...
FILE: src/io/codec.rs
type CloudCodec (line 4) | pub trait CloudCodec {
method encode (line 5) | fn encode(&self) -> Vec<u8>;
method decode (line 6) | fn decode(data: &[u8]) -> Self;
method write_to_file (line 8) | fn write_to_file(&self, path: &str) {
FILE: src/io/gcloud/bincode2.rs
method encode (line 12) | fn encode(&self) -> Vec<u8> {
method decode (line 23) | fn decode(data: &[u8]) -> Self {
method encode (line 33) | fn encode(&self) -> Vec<u8> {
method decode (line 44) | fn decode(data: &[u8]) -> Self {
function decode_gzip (line 53) | fn decode_gzip<T>(data: &[u8]) -> Result<T, bincode2::Error>
function decode_raw (line 61) | fn decode_raw<T>(data: &[u8]) -> T
FILE: src/io/gcloud/flexbuffers.rs
method encode (line 10) | fn encode(&self) -> Vec<u8> {
method decode (line 18) | fn decode(data: &[u8]) -> Self {
method encode (line 25) | fn encode(&self) -> Vec<u8> {
method decode (line 33) | fn decode(data: &[u8]) -> Self {
FILE: src/io/loader.rs
type Gaussian3dLoader (line 15) | pub struct Gaussian3dLoader;
type Asset (line 18) | type Asset = PlanarGaussian3d;
type Settings (line 19) | type Settings = ();
type Error (line 20) | type Error = std::io::Error;
method load (line 22) | async fn load(
method extensions (line 63) | fn extensions(&self) -> &[&str] {
type Gaussian4dLoader (line 69) | pub struct Gaussian4dLoader;
type Asset (line 72) | type Asset = PlanarGaussian4d;
type Settings (line 73) | type Settings = ();
type Error (line 74) | type Error = std::io::Error;
method load (line 76) | async fn load(
method extensions (line 113) | fn extensions(&self) -> &[&str] {
FILE: src/io/mod.rs
type IoPlugin (line 12) | pub struct IoPlugin;
method build (line 14) | fn build(&self, app: &mut App) {
FILE: src/io/ply.rs
constant MAX_SIZE_VARIANCE (line 21) | pub const MAX_SIZE_VARIANCE: f32 = 4.0;
method new (line 24) | fn new() -> Self {
method set_property (line 28) | fn set_property(&mut self, key: String, property: Property) {
function parse_ply_3d (line 76) | pub fn parse_ply_3d(mut reader: &mut dyn BufRead) -> Result<PlanarGaussi...
method new (line 135) | fn new() -> Self {
method set_property (line 139) | fn set_property(&mut self, key: String, property: Property) {
function parse_ply_4d (line 185) | pub fn parse_ply_4d(mut reader: &mut dyn BufRead) -> Result<PlanarGaussi...
FILE: src/io/scene.rs
constant KHR_GAUSSIAN_SPLATTING_EXTENSION (line 28) | const KHR_GAUSSIAN_SPLATTING_EXTENSION: &str = "KHR_gaussian_splatting";
constant ATTR_POSITION (line 30) | const ATTR_POSITION: &str = "POSITION";
constant ATTR_COLOR_0 (line 31) | const ATTR_COLOR_0: &str = "COLOR_0";
constant ATTR_ROTATION (line 32) | const ATTR_ROTATION: &str = "KHR_gaussian_splatting:ROTATION";
constant ATTR_SCALE (line 33) | const ATTR_SCALE: &str = "KHR_gaussian_splatting:SCALE";
constant ATTR_OPACITY (line 34) | const ATTR_OPACITY: &str = "KHR_gaussian_splatting:OPACITY";
constant ATTR_SH_PREFIX (line 35) | const ATTR_SH_PREFIX: &str = "KHR_gaussian_splatting:SH_DEGREE_";
constant SH_DEGREE_ZERO_BASIS (line 36) | const SH_DEGREE_ZERO_BASIS: f32 = 0.282_095;
type GaussianKernel (line 39) | pub enum GaussianKernel {
type GaussianProjection (line 45) | pub enum GaussianProjection {
type GaussianSortingMethod (line 51) | pub enum GaussianSortingMethod {
type GaussianPrimitiveSpec (line 57) | pub struct GaussianPrimitiveSpec {
method default (line 67) | fn default() -> Self {
type GaussianPrimitiveMetadata (line 79) | pub struct GaussianPrimitiveMetadata {
type CloudBundle (line 87) | pub struct CloudBundle {
type SceneCamera (line 96) | pub struct SceneCamera {
type GaussianScene (line 102) | pub struct GaussianScene {
type SceneExportCloud (line 108) | pub struct SceneExportCloud {
type SceneExportCamera (line 117) | pub struct SceneExportCamera {
method default (line 126) | fn default() -> Self {
type GaussianSceneHandle (line 139) | pub struct GaussianSceneHandle(pub Handle<GaussianScene>);
type GaussianSceneLoaded (line 142) | pub struct GaussianSceneLoaded;
type GaussianScenePlugin (line 145) | pub struct GaussianScenePlugin;
method build (line 148) | fn build(&self, app: &mut App) {
function spawn_scene (line 167) | fn spawn_scene(
type GaussianSceneLoader (line 204) | pub struct GaussianSceneLoader;
type Asset (line 207) | type Asset = GaussianScene;
type Settings (line 208) | type Settings = ();
type Error (line 209) | type Error = std::io::Error;
method load (line 211) | async fn load(
method extensions (line 223) | fn extensions(&self) -> &[&str] {
type GaussianPrimitiveSource (line 229) | struct GaussianPrimitiveSource {
type RawRoot (line 237) | struct RawRoot {
type RawMesh (line 247) | struct RawMesh {
type RawNode (line 253) | struct RawNode {
type RawPrimitive (line 260) | struct RawPrimitive {
type RawGaussianExtension (line 271) | struct RawGaussianExtension {
function default_projection (line 280) | fn default_projection() -> String {
function default_sorting_method (line 284) | fn default_sorting_method() -> String {
function load_gltf_scene (line 288) | async fn load_gltf_scene(
function ensure_gaussian_extension_used (line 348) | fn ensure_gaussian_extension_used(extensions_used: &[String]) -> Result<...
function parse_raw_root (line 362) | fn parse_raw_root(bytes: &[u8]) -> Result<RawRoot, std::io::Error> {
function collect_gaussian_primitives (line 387) | fn collect_gaussian_primitives(
function parse_kernel (line 451) | fn parse_kernel(
function parse_color_space (line 477) | fn parse_color_space(
function parse_projection (line 504) | fn parse_projection(
function parse_sorting_method (line 530) | fn parse_sorting_method(
function load_buffers (line 556) | async fn load_buffers(
function decode_data_uri (line 614) | fn decode_data_uri(uri: &str) -> Option<Result<Vec<u8>, std::io::Error>> {
function decode_percent_encoded_data_uri (line 644) | fn decode_percent_encoded_data_uri(payload: &str) -> Result<Vec<u8>, std...
function decode_hex (line 672) | fn decode_hex(value: u8) -> Result<u8, std::io::Error> {
function collect_node_bundles (line 688) | fn collect_node_bundles(
function encode_khr_gaussian_scene_gltf_bytes (line 765) | pub fn encode_khr_gaussian_scene_gltf_bytes(
function write_khr_gaussian_scene_gltf (line 1031) | pub fn write_khr_gaussian_scene_gltf(
function encode_khr_gaussian_scene_glb_bytes (line 1040) | pub fn encode_khr_gaussian_scene_glb_bytes(
function write_khr_gaussian_scene_glb (line 1079) | pub fn write_khr_gaussian_scene_glb(
function extract_embedded_binary_buffer (line 1088) | fn extract_embedded_binary_buffer(root: &mut Value) -> Result<Vec<u8>, s...
function align_to_four_bytes (line 1129) | fn align_to_four_bytes(bytes: &mut Vec<u8>) {
type AccessorSpec (line 1135) | struct AccessorSpec<'a> {
function push_f32_accessor (line 1143) | fn push_f32_accessor(
function transform_matrix_values (line 1180) | fn transform_matrix_values(transform: Transform) -> [f32; 16] {
function gaussian_extension_object (line 1184) | fn gaussian_extension_object(
function kernel_extension_identifier (line 1216) | fn kernel_extension_identifier(metadata: &GaussianPrimitiveMetadata) -> ...
function color_space_extension_identifier (line 1224) | fn color_space_extension_identifier(
function projection_extension_identifier (line 1235) | fn projection_extension_identifier(metadata: &GaussianPrimitiveMetadata)...
function sorting_method_extension_identifier (line 1243) | fn sorting_method_extension_identifier(metadata: &GaussianPrimitiveMetad...
function extension_identifier (line 1251) | fn extension_identifier(spec_value: &str, fallback_value: &str, known_va...
function kernel_to_extension_value (line 1260) | fn kernel_to_extension_value(kernel: GaussianKernel) -> &'static str {
function projection_to_extension_value (line 1266) | fn projection_to_extension_value(projection: GaussianProjection) -> &'st...
function sorting_method_to_extension_value (line 1272) | fn sorting_method_to_extension_value(method: GaussianSortingMethod) -> &...
function color_space_to_extension_value (line 1278) | fn color_space_to_extension_value(color_space: GaussianColorSpace) -> &'...
function sh_index_to_degree_coefficient (line 1285) | fn sh_index_to_degree_coefficient(index: usize) -> (usize, usize) {
function max_export_sh_degree (line 1295) | fn max_export_sh_degree() -> usize {
function decode_gaussian_primitive (line 1304) | fn decode_gaussian_primitive(
function required_accessor (line 1396) | fn required_accessor<'a>(
function optional_accessor (line 1416) | fn optional_accessor<'a>(
function collect_sh_accessors (line 1435) | fn collect_sh_accessors<'a>(
function collect_sh_coefficient_map (line 1456) | fn collect_sh_coefficient_map(
function parse_sh_semantic (line 1551) | fn parse_sh_semantic(semantic: &str) -> Option<(usize, usize)> {
function sh_coefficient_index (line 1558) | fn sh_coefficient_index(degree: usize, coefficient: usize) -> usize {
function max_supported_sh_degree (line 1562) | fn max_supported_sh_degree() -> usize {
function ensure_count (line 1573) | fn ensure_count(
function read_position_attribute (line 1589) | fn read_position_attribute(
function read_rotation_attribute (line 1630) | fn read_rotation_attribute(
function read_scale_attribute (line 1708) | fn read_scale_attribute(
function read_opacity_attribute (line 1777) | fn read_opacity_attribute(
function read_color_attribute (line 1827) | fn read_color_attribute(
function read_sh_coefficient_attribute (line 1925) | fn read_sh_coefficient_attribute(
function read_items (line 1957) | fn read_items<T: Item + Copy>(
function normalize_quaternion (line 1978) | fn normalize_quaternion(quaternion: &mut [f32; 4]) -> bool {
function normalize_i8 (line 2000) | fn normalize_i8(value: i8) -> f32 {
function normalize_i16 (line 2004) | fn normalize_i16(value: i16) -> f32 {
function normalize_u8 (line 2008) | fn normalize_u8(value: u8) -> f32 {
function normalize_u16 (line 2012) | fn normalize_u16(value: u16) -> f32 {
function validates_complete_sh_coefficients_for_supported_degree (line 2021) | fn validates_complete_sh_coefficients_for_supported_degree() {
function allows_no_sh_coefficients_for_extension_defined_lighting (line 2050) | fn allows_no_sh_coefficients_for_extension_defined_lighting() {
function rejects_partial_sh_degree (line 2057) | fn rejects_partial_sh_degree() {
function clips_sh_coefficients_above_supported_degree (line 2078) | fn clips_sh_coefficients_above_supported_degree() {
function preserves_unknown_extension_identifiers_on_export (line 2112) | fn preserves_unknown_extension_identifiers_on_export() {
function uses_runtime_color_space_for_known_identifiers (line 2132) | fn uses_runtime_color_space_for_known_identifiers() {
function decodes_base64_data_uri (line 2147) | fn decodes_base64_data_uri() {
function decodes_percent_encoded_data_uri (line 2154) | fn decodes_percent_encoded_data_uri() {
function requires_khr_extension_in_extensions_used (line 2161) | fn requires_khr_extension_in_extensions_used() {
function encodes_khr_scene_with_camera_node (line 2169) | fn encodes_khr_scene_with_camera_node() {
function encodes_khr_scene_as_glb_with_bin_chunk (line 2211) | fn encodes_khr_scene_as_glb_with_bin_chunk() {
function normalizes_zero_length_quaternion_to_identity (line 2251) | fn normalizes_zero_length_quaternion_to_identity() {
function build_gltf_with_accessors (line 2258) | fn build_gltf_with_accessors(
function reads_quantized_rotation_scale_opacity (line 2292) | fn reads_quantized_rotation_scale_opacity() {
function reads_unnormalized_scale_i16 (line 2360) | fn reads_unnormalized_scale_i16() {
function skips_invalid_rotation_gaussians_during_export (line 2387) | fn skips_invalid_rotation_gaussians_during_export() {
FILE: src/lib.rs
type GaussianSplattingPlugin (line 48) | pub struct GaussianSplattingPlugin;
method build (line 51) | fn build(&self, app: &mut App) {
FILE: src/material/classification.rs
constant CLASSIFICATION_SHADER_HANDLE (line 6) | const CLASSIFICATION_SHADER_HANDLE: Handle<Shader> =
type ClassificationMaterialPlugin (line 9) | pub struct ClassificationMaterialPlugin;
method build (line 12) | fn build(&self, app: &mut App) {
FILE: src/material/depth.rs
constant DEPTH_SHADER_HANDLE (line 6) | const DEPTH_SHADER_HANDLE: Handle<Shader> = uuid_handle!("72e596c7-6226-...
type DepthMaterialPlugin (line 8) | pub struct DepthMaterialPlugin;
method build (line 11) | fn build(&self, app: &mut App) {
FILE: src/material/mod.rs
type MaterialPlugin (line 14) | pub struct MaterialPlugin;
method build (line 18) | fn build(&self, app: &mut App) {
FILE: src/material/noise.rs
type NoiseMaterial (line 8) | pub struct NoiseMaterial {
method default (line 13) | fn default() -> Self {
type NoiseMaterialPlugin (line 19) | pub struct NoiseMaterialPlugin;
method build (line 22) | fn build(&self, app: &mut App) {
function apply_noise_cpu (line 28) | fn apply_noise_cpu(
FILE: src/material/optical_flow.rs
constant OPTICAL_FLOW_SHADER_HANDLE (line 6) | const OPTICAL_FLOW_SHADER_HANDLE: Handle<Shader> =
type OpticalFlowMaterialPlugin (line 9) | pub struct OpticalFlowMaterialPlugin;
method build (line 12) | fn build(&self, app: &mut App) {
FILE: src/material/position.rs
constant POSITION_SHADER_HANDLE (line 6) | const POSITION_SHADER_HANDLE: Handle<Shader> = uuid_handle!("91ad4ad8-5e...
type PositionMaterialPlugin (line 8) | pub struct PositionMaterialPlugin;
method build (line 11) | fn build(&self, app: &mut App) {
FILE: src/material/spherical_harmonics.rs
constant SPHERICAL_HARMONICS_SHADER_HANDLE (line 17) | const SPHERICAL_HARMONICS_SHADER_HANDLE: Handle<Shader> =
type SphericalHarmonicCoefficientsPlugin (line 20) | pub struct SphericalHarmonicCoefficientsPlugin;
method build (line 23) | fn build(&self, app: &mut App) {
function num_sh_coefficients (line 33) | const fn num_sh_coefficients(degree: usize) -> usize {
constant SH_DEGREE (line 44) | pub const SH_DEGREE: usize = 4;
constant SH_DEGREE (line 47) | pub const SH_DEGREE: usize = 3;
constant SH_DEGREE (line 50) | pub const SH_DEGREE: usize = 2;
constant SH_DEGREE (line 58) | pub const SH_DEGREE: usize = 1;
constant SH_DEGREE (line 67) | pub const SH_DEGREE: usize = 0;
constant SH_DEGREE (line 76) | pub const SH_DEGREE: usize = 0;
constant SH_CHANNELS (line 78) | pub const SH_CHANNELS: usize = 3;
constant SH_COEFF_COUNT_PER_CHANNEL (line 79) | pub const SH_COEFF_COUNT_PER_CHANNEL: usize = num_sh_coefficients(SH_DEG...
constant SH_COEFF_COUNT (line 80) | pub const SH_COEFF_COUNT: usize = pad_4(SH_COEFF_COUNT_PER_CHANNEL * SH_...
constant HALF_SH_COEFF_COUNT (line 82) | pub const HALF_SH_COEFF_COUNT: usize = SH_COEFF_COUNT / 2;
constant PADDED_HALF_SH_COEFF_COUNT (line 83) | pub const PADDED_HALF_SH_COEFF_COUNT: usize = pad_4(HALF_SH_COEFF_COUNT);
constant SH_VEC4_PLANES (line 87) | pub const SH_VEC4_PLANES: usize = SH_COEFF_COUNT / 4;
type SphericalHarmonicCoefficients (line 114) | pub struct SphericalHarmonicCoefficients {
method set (line 150) | pub fn set(&mut self, index: usize, value: f32) {
method default (line 132) | fn default() -> Self {
function coefficients_serializer (line 200) | fn coefficients_serializer<S>(n: &[f32; SH_COEFF_COUNT], s: S) -> Result...
function coefficients_deserializer (line 212) | fn coefficients_deserializer<'de, D>(d: D) -> Result<[f32; SH_COEFF_COUN...
FILE: src/material/spherindrical_harmonics.rs
constant SH_4D_DEGREE_TIME (line 20) | pub const SH_4D_DEGREE_TIME: usize = 2;
constant SH_4D_COEFF_COUNT_PER_CHANNEL (line 22) | pub const SH_4D_COEFF_COUNT_PER_CHANNEL: usize = (SH_DEGREE + 1).pow(2) ...
constant SH_4D_COEFF_COUNT (line 23) | pub const SH_4D_COEFF_COUNT: usize = pad_4(SH_4D_COEFF_COUNT_PER_CHANNEL...
constant HALF_SH_4D_COEFF_COUNT (line 25) | pub const HALF_SH_4D_COEFF_COUNT: usize = pad_4(SH_4D_COEFF_COUNT / 2);
constant MAX_POD_U32_ARRAY_SIZE (line 28) | pub const MAX_POD_U32_ARRAY_SIZE: usize = 32;
constant POD_ARRAY_SIZE (line 29) | pub const POD_ARRAY_SIZE: usize = gcd(SH_4D_COEFF_COUNT, MAX_POD_U32_ARR...
constant POD_PLANE_COUNT (line 30) | pub const POD_PLANE_COUNT: usize = SH_4D_COEFF_COUNT / POD_ARRAY_SIZE;
constant WASTE (line 32) | pub const WASTE: usize = POD_PLANE_COUNT * POD_ARRAY_SIZE - SH_4D_COEFF_...
constant SH_4D_VEC4_PLANES (line 37) | pub const SH_4D_VEC4_PLANES: usize = SH_4D_COEFF_COUNT / 4;
constant SPHERINDRICAL_HARMONICS_SHADER_HANDLE (line 39) | const SPHERINDRICAL_HARMONICS_SHADER_HANDLE: Handle<Shader> =
type SpherindricalHarmonicCoefficientsPlugin (line 42) | pub struct SpherindricalHarmonicCoefficientsPlugin;
method build (line 44) | fn build(&self, app: &mut App) {
type SpherindricalHarmonicCoefficients (line 79) | pub struct SpherindricalHarmonicCoefficients {
method from (line 105) | fn from(flat_coefficients: [f32; SH_4D_COEFF_COUNT]) -> Self {
method set (line 131) | pub fn set(&mut self, index: usize, value: f32) {
method default (line 97) | fn default() -> Self {
function coefficients_serializer (line 184) | fn coefficients_serializer<S>(
function coefficients_deserializer (line 199) | fn coefficients_deserializer<'de, D>(
FILE: src/math/mod.rs
function gcd (line 1) | pub const fn gcd(a: usize, b: usize) -> usize {
function pad_4 (line 5) | pub const fn pad_4(x: usize) -> usize {
FILE: src/morph/interpolate.rs
constant INTERPOLATE_SHADER_HANDLE (line 36) | const INTERPOLATE_SHADER_HANDLE: Handle<Shader> =
constant WORKGROUP_SIZE (line 38) | const WORKGROUP_SIZE: u32 = 256;
type InterpolateLabel (line 41) | pub struct InterpolateLabel;
type InterpolatePlugin (line 43) | pub struct InterpolatePlugin<R: PlanarSync> {
method default (line 48) | fn default() -> Self {
method build (line 61) | fn build(&self, app: &mut App) {
method finish (line 88) | fn finish(&self, app: &mut App) {
function ensure_gaussian_interpolate_synced (line 95) | fn ensure_gaussian_interpolate_synced<R: PlanarSync>(
function ensure_gaussian_interpolate_output_gaussian3d (line 110) | fn ensure_gaussian_interpolate_output_gaussian3d(
type GaussianInterpolate (line 149) | pub struct GaussianInterpolate<R: PlanarSync> {
method clone (line 158) | fn clone(&self) -> Self {
type GaussianInterpolateBindGroups (line 166) | pub struct GaussianInterpolateBindGroups<R: PlanarSync> {
type GaussianInterpolatePipeline (line 174) | pub struct GaussianInterpolatePipeline<R: PlanarSync> {
method from_world (line 185) | fn from_world(render_world: &mut World) -> Self {
function extract_gaussian_interpolate (line 226) | pub fn extract_gaussian_interpolate<R>(
function queue_gaussian_interpolate_bind_groups (line 265) | pub fn queue_gaussian_interpolate_bind_groups<R: PlanarSync>(
type GaussianInterpolateNode (line 388) | pub struct GaussianInterpolateNode<R: PlanarSync> {
method from_world (line 406) | fn from_world(world: &mut World) -> Self {
method update (line 420) | fn update(&mut self, world: &mut World) {
method run (line 445) | fn run(
FILE: src/morph/mod.rs
type MorphPlugin (line 10) | pub struct MorphPlugin<R: PlanarSync> {
method default (line 14) | fn default() -> Self {
method build (line 27) | fn build(&self, app: &mut App) {
FILE: src/morph/particle.rs
constant PARTICLE_SHADER_HANDLE (line 42) | const PARTICLE_SHADER_HANDLE: Handle<Shader> = uuid_handle!("00000000-00...
type MorphLabel (line 45) | pub struct MorphLabel;
type ParticleBehaviorPlugin (line 47) | pub struct ParticleBehaviorPlugin<R: PlanarSync> {
method default (line 51) | fn default() -> Self {
method build (line 59) | fn build(&self, app: &mut App) {
method finish (line 97) | fn finish(&self, app: &mut App) {
function extract_particle_behaviors (line 104) | pub fn extract_particle_behaviors(
type GpuParticleBehaviorBuffers (line 119) | pub struct GpuParticleBehaviorBuffers {
type SourceAsset (line 125) | type SourceAsset = ParticleBehaviors;
type Param (line 126) | type Param = SRes<RenderDevice>;
method prepare_asset (line 128) | fn prepare_asset(
method asset_usage (line 149) | fn asset_usage(_: &Self::SourceAsset) -> RenderAssetUsages {
type ParticleBehaviorPipeline (line 155) | pub struct ParticleBehaviorPipeline<R: PlanarSync> {
method from_world (line 162) | fn from_world(render_world: &mut World) -> Self {
type ParticleBehaviorBindGroup (line 213) | pub struct ParticleBehaviorBindGroup {
function queue_particle_behavior_bind_group (line 217) | pub fn queue_particle_behavior_bind_group<R: PlanarSync>(
type ParticleBehaviorNode (line 257) | pub struct ParticleBehaviorNode<R: PlanarSync> {
method from_world (line 273) | fn from_world(world: &mut World) -> Self {
method update (line 284) | fn update(&mut self, world: &mut World) {
method run (line 304) | fn run(
type ParticleBehaviorsHandle (line 364) | pub struct ParticleBehaviorsHandle(pub Handle<ParticleBehaviors>);
method from (line 367) | fn from(handle: Handle<ParticleBehaviors>) -> Self {
function from (line 373) | fn from(handle: ParticleBehaviorsHandle) -> Self {
function from (line 379) | fn from(handle: &ParticleBehaviorsHandle) -> Self {
type ParticleBehavior (line 389) | pub struct ParticleBehavior {
method default (line 397) | fn default() -> Self {
type ParticleBehaviors (line 408) | pub struct ParticleBehaviors(pub Vec<ParticleBehavior>);
method sample (line 411) | fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> ParticleBehavior {
function random_particle_behaviors (line 436) | pub fn random_particle_behaviors(n: usize) -> ParticleBehaviors {
FILE: src/noise/mod.rs
constant NOISE_SHADER_HANDLE (line 6) | const NOISE_SHADER_HANDLE: Handle<Shader> = uuid_handle!("4f73e89b-30f9-...
type NoisePlugin (line 9) | pub struct NoisePlugin;
method build (line 12) | fn build(&self, app: &mut App) {
FILE: src/query/mod.rs
type QueryPlugin (line 13) | pub struct QueryPlugin;
method build (line 17) | fn build(&self, app: &mut App) {
FILE: src/query/raycast.rs
type Point (line 8) | pub struct Point {
type InsideMesh (line 14) | pub struct InsideMesh;
type RaycastSelectionPlugin (line 17) | pub struct RaycastSelectionPlugin;
method build (line 20) | fn build(&self, app: &mut App) {
type Triangle (line 27) | struct Triangle {
function point_in_mesh_system (line 31) | fn point_in_mesh_system(
function is_point_in_mesh (line 54) | fn is_point_in_mesh(point: Vec3, mesh: &Mesh) -> bool {
function ray_intersects_triangle (line 92) | fn ray_intersects_triangle(ray_origin: Vec3, ray_direction: Vec3, triang...
FILE: src/query/select.rs
type Select (line 13) | pub struct Select {
method from_iter (line 19) | fn from_iter<I: IntoIterator<Item = usize>>(iter: I) -> Self {
method invert (line 29) | pub fn invert(&mut self, cloud_size: usize) -> Select {
type SelectPlugin (line 42) | pub struct SelectPlugin;
method build (line 45) | fn build(&self, app: &mut App) {
type CommonCloudSelectPlugin (line 57) | pub struct CommonCloudSelectPlugin<R: PlanarSync>
method build (line 69) | fn build(&self, app: &mut App) {
function apply_selection (line 81) | fn apply_selection<R: PlanarSync>(
type InvertSelectionEvent (line 114) | pub struct InvertSelectionEvent;
function invert_selection (line 116) | fn invert_selection<R: PlanarSync>(
type SaveSelectionEvent (line 154) | pub struct SaveSelectionEvent;
function save_selection (line 156) | pub fn save_selection<R: PlanarSync>(
FILE: src/query/sparse.rs
type PositionPoint (line 12) | struct PositionPoint([f32; 3]);
type Scalar (line 15) | type Scalar = f32;
type Dim (line 16) | type Dim = U3;
method at (line 18) | fn at(&self, i: usize) -> Self::Scalar {
type SparseSelect (line 24) | pub struct SparseSelect {
method select (line 41) | pub fn select(&self, cloud: &PlanarGaussian3d) -> Select {
method default (line 31) | fn default() -> Self {
type SparsePlugin (line 57) | pub struct SparsePlugin;
method build (line 60) | fn build(&self, app: &mut App) {
function collect_points (line 66) | fn collect_points(cloud: &PlanarGaussian3d) -> Vec<PositionPoint> {
function select_sparse_handler (line 73) | fn select_sparse_handler(
FILE: src/render/mod.rs
constant BINDINGS_SHADER_HANDLE (line 67) | const BINDINGS_SHADER_HANDLE: Handle<Shader> = uuid_handle!("cfd9a3d9-a0...
constant GAUSSIAN_SHADER_HANDLE (line 68) | const GAUSSIAN_SHADER_HANDLE: Handle<Shader> = uuid_handle!("9a18d83b-13...
constant GAUSSIAN_2D_SHADER_HANDLE (line 69) | const GAUSSIAN_2D_SHADER_HANDLE: Handle<Shader> =
constant GAUSSIAN_3D_SHADER_HANDLE (line 71) | const GAUSSIAN_3D_SHADER_HANDLE: Handle<Shader> =
constant GAUSSIAN_4D_SHADER_HANDLE (line 73) | const GAUSSIAN_4D_SHADER_HANDLE: Handle<Shader> =
constant HELPERS_SHADER_HANDLE (line 75) | const HELPERS_SHADER_HANDLE: Handle<Shader> = uuid_handle!("9ca57ab0-07d...
constant PACKED_SHADER_HANDLE (line 76) | const PACKED_SHADER_HANDLE: Handle<Shader> = uuid_handle!("5bb62086-7004...
constant PLANAR_SHADER_HANDLE (line 77) | const PLANAR_SHADER_HANDLE: Handle<Shader> = uuid_handle!("d6a3f978-f795...
constant TEXTURE_SHADER_HANDLE (line 78) | const TEXTURE_SHADER_HANDLE: Handle<Shader> = uuid_handle!("500e2ebf-51a...
constant TRANSFORM_SHADER_HANDLE (line 79) | const TRANSFORM_SHADER_HANDLE: Handle<Shader> =
type RenderPipelinePlugin (line 84) | pub struct RenderPipelinePlugin<R: PlanarSync> {
method default (line 89) | fn default() -> Self {
method build (line 102) | fn build(&self, app: &mut App) {
method finish (line 209) | fn finish(&self, app: &mut App) {
type PlanarStorageRebindQueue (line 219) | pub struct PlanarStorageRebindQueue<R: PlanarSync> {
method default (line 225) | fn default() -> Self {
method clone (line 234) | fn clone(&self) -> Self {
function push_unique (line 243) | pub fn push_unique(&mut self, id: AssetId<R::PlanarType>) {
function queue_planar_storage_rebinds (line 250) | fn queue_planar_storage_rebinds<R: PlanarSync>(
function extract_planar_storage_rebind_queue (line 267) | fn extract_planar_storage_rebind_queue<R: PlanarSync>(
function refresh_planar_storage_bind_groups (line 276) | fn refresh_planar_storage_bind_groups<R: PlanarSync>(
type GpuCloudBundle (line 313) | pub struct GpuCloudBundle<R: PlanarSync> {
type GpuCloudBundleQuery (line 323) | type GpuCloudBundleQuery<R: bevy_interleave::prelude::PlanarSync> = (
type GpuCloudBindGroupQuery (line 333) | type GpuCloudBindGroupQuery<R: bevy_interleave::prelude::PlanarSync> = (
function queue_gaussians (line 341) | fn queue_gaussians<R: PlanarSync>(
type CloudPipeline (line 446) | pub struct CloudPipeline<R: PlanarSync> {
function buffer_layout (line 461) | fn buffer_layout(
function storage_layout_descriptor (line 480) | pub(crate) fn storage_layout_descriptor<P: ReflectInterleaved>(
method from_world (line 507) | fn from_world(render_world: &mut World) -> Self {
type ShaderDefines (line 686) | pub struct ShaderDefines {
method max_tile_count (line 702) | pub fn max_tile_count(&self, count: usize) -> u32 {
method sorting_status_counters_buffer_size (line 706) | pub fn sorting_status_counters_buffer_size(&self, count: usize) -> usi...
method default (line 712) | fn default() -> Self {
function shader_defs (line 743) | pub fn shader_defs(key: CloudPipelineKey) -> Vec<ShaderDefVal> {
type CloudPipelineKey (line 869) | pub struct CloudPipelineKey {
type Key (line 882) | type Key = CloudPipelineKey;
method specialize (line 884) | fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
type DrawGaussians (line 956) | type DrawGaussians<R: bevy_interleave::prelude::PlanarSync> = (
type CloudUniform (line 966) | pub struct CloudUniform {
function extract_gaussians (line 982) | pub fn extract_gaussians<R: PlanarSync>(
type GaussianUniformBindGroups (line 1061) | pub struct GaussianUniformBindGroups {
type SortBindGroup (line 1066) | pub struct SortBindGroup {
function queue_gaussian_bind_group (line 1071) | fn queue_gaussian_bind_group<R: PlanarSync>(
type GaussianViewBindGroup (line 1189) | pub struct GaussianViewBindGroup {
type GaussianComputeViewBindGroup (line 1194) | pub struct GaussianComputeViewBindGroup {
function queue_gaussian_view_bind_groups (line 1202) | pub fn queue_gaussian_view_bind_groups<R: PlanarSync>(
function queue_gaussian_compute_view_bind_groups (line 1278) | pub fn queue_gaussian_compute_view_bind_groups<R: PlanarSync>(
type SetViewBindGroup (line 1354) | pub struct SetViewBindGroup<const I: usize>;
type Param (line 1356) | type Param = ();
type ViewQuery (line 1357) | type ViewQuery = (Read<GaussianViewBindGroup>, Read<ViewUniformOffset>);
type ItemQuery (line 1358) | type ItemQuery = ();
function render (line 1361) | fn render<'w>(
type SetPreviousViewBindGroup (line 1376) | pub struct SetPreviousViewBindGroup<const I: usize>;
type Param (line 1378) | type Param = SRes<PrepassViewBindGroup>;
type ViewQuery (line 1379) | type ViewQuery = (
type ItemQuery (line 1384) | type ItemQuery = ();
function render (line 1387) | fn render<'w>(
type SetGaussianUniformBindGroup (line 1423) | pub struct SetGaussianUniformBindGroup<const I: usize>;
type Param (line 1425) | type Param = SRes<GaussianUniformBindGroups>;
type ViewQuery (line 1426) | type ViewQuery = ();
type ItemQuery (line 1427) | type ItemQuery = Read<DynamicUniformIndex<CloudUniform>>;
function render (line 1430) | fn render<'w>(
type DrawGaussianInstanced (line 1459) | pub struct DrawGaussianInstanced<R: PlanarSync> {
method default (line 1464) | fn default() -> Self {
type Param (line 1475) | type Param = SRes<RenderAssets<R::GpuPlanarType>>;
type ViewQuery (line 1476) | type ViewQuery = Read<SortTrigger>;
type ItemQuery (line 1477) | type ItemQuery = (
function render (line 1484) | fn render<'w>(
FILE: src/render/packed.rs
type PackedBuffers (line 17) | pub struct PackedBuffers {
function prepare_cloud (line 21) | pub fn prepare_cloud(render_device: &RenderDevice, cloud: &PlanarGaussia...
function get_bind_group_layout (line 32) | pub fn get_bind_group_layout(render_device: &RenderDevice, read_only: bo...
function get_bind_group (line 49) | pub fn get_bind_group<R: PlanarSync>(
FILE: src/render/texture.rs
type BufferTexturePlugin (line 23) | pub struct BufferTexturePlugin;
method build (line 26) | fn build(&self, _app: &mut App) {}
function get_sorted_bind_group_layout (line 29) | pub fn get_sorted_bind_group_layout(render_device: &RenderDevice) -> Bin...
FILE: src/sort/mod.rs
type SortMode (line 47) | pub enum SortMode {
method default (line 62) | fn default() -> Self {
type SortConfig (line 78) | pub struct SortConfig {
method default (line 83) | fn default() -> Self {
type SortPluginFlag (line 89) | pub struct SortPluginFlag;
method build (line 91) | fn build(&self, _app: &mut App) {}
type SortPlugin (line 96) | pub struct SortPlugin<R: PlanarSync> {
method build (line 105) | fn build(&self, app: &mut App) {
type SortTrigger (line 145) | pub struct SortTrigger {
function update_sort_trigger (line 153) | fn update_sort_trigger(
function update_textures_on_change (line 197) | fn update_textures_on_change(
function auto_insert_sorted_entries (line 219) | fn auto_insert_sorted_entries<R: PlanarSync>(
function update_sorted_entries_sizes (line 273) | fn update_sorted_entries_sizes(
type SortedEntriesHandle (line 303) | pub struct SortedEntriesHandle(pub Handle<SortedEntries>);
method from (line 306) | fn from(handle: Handle<SortedEntries>) -> Self {
function from (line 312) | fn from(handle: SortedEntriesHandle) -> Self {
function from (line 318) | fn from(handle: &SortedEntriesHandle) -> Self {
type SortEntry (line 326) | pub struct SortEntry {
type SortedEntries (line 332) | pub struct SortedEntries {
method new (line 342) | pub fn new(
type SourceAsset (line 396) | type SourceAsset = SortedEntries;
type Param (line 397) | type Param = SRes<RenderDevice>;
method prepare_asset (line 399) | fn prepare_asset(
method asset_usage (line 422) | fn asset_usage(_: &Self::SourceAsset) -> RenderAssetUsages {
type GpuSortedEntry (line 430) | pub struct GpuSortedEntry {
FILE: src/sort/radix.rs
constant RADIX_SHADER_HANDLE (line 46) | const RADIX_SHADER_HANDLE: Handle<Shader> = uuid_handle!("dedb3ddf-f254-...
constant TEMPORAL_SORT_SHADER_HANDLE (line 47) | const TEMPORAL_SORT_SHADER_HANDLE: Handle<Shader> =
constant RADIX_PIPELINE_RESET (line 49) | const RADIX_PIPELINE_RESET: usize = 0;
constant RADIX_PIPELINE_A (line 50) | const RADIX_PIPELINE_A: usize = 1;
constant RADIX_PIPELINE_B (line 51) | const RADIX_PIPELINE_B: usize = 2;
constant RADIX_PIPELINE_C_COUNT (line 52) | const RADIX_PIPELINE_C_COUNT: usize = 3;
constant RADIX_PIPELINE_C_SCAN (line 53) | const RADIX_PIPELINE_C_SCAN: usize = 4;
constant RADIX_PIPELINE_C_SCATTER (line 54) | const RADIX_PIPELINE_C_SCATTER: usize = 5;
constant RADIX_PIPELINE_COUNT (line 55) | const RADIX_PIPELINE_COUNT: usize = 6;
type RadixSortLabel (line 58) | pub struct RadixSortLabel;
type RadixSortPlugin (line 61) | pub struct RadixSortPlugin<R: PlanarSync> {
method build (line 69) | fn build(&self, app: &mut App) {
method finish (line 107) | fn finish(&self, app: &mut App) {
type RadixSortBuffers (line 115) | pub struct RadixSortBuffers<R: PlanarSync> {
method default (line 121) | fn default() -> Self {
type GpuRadixBuffers (line 129) | pub struct GpuRadixBuffers {
method new (line 137) | pub fn new(count: usize, render_device: &RenderDevice) -> Self {
function update_sort_buffers (line 180) | fn update_sort_buffers<R: PlanarSync>(
type RadixSortPipeline (line 197) | pub struct RadixSortPipeline<R: PlanarSync> {
method from_world (line 204) | fn from_world(render_world: &mut World) -> Self {
type RadixBindGroup (line 373) | pub struct RadixBindGroup {
function queue_radix_bind_group (line 380) | pub fn queue_radix_bind_group<R: PlanarSync>(
type RadixSortNode (line 530) | pub struct RadixSortNode<R: PlanarSync> {
method from_world (line 546) | fn from_world(world: &mut World) -> Self {
method update (line 559) | fn update(&mut self, world: &mut World) {
method run (line 586) | fn run(
FILE: src/sort/rayon.rs
type RayonSortPlugin (line 13) | pub struct RayonSortPlugin<R: PlanarSync> {
method build (line 21) | fn build(&self, app: &mut App) {
function rayon_sort (line 27) | pub fn rayon_sort<R: PlanarSync>(
FILE: src/sort/std_sort.rs
type StdSortPlugin (line 12) | pub struct StdSortPlugin<R: PlanarSync> {
method build (line 20) | fn build(&self, app: &mut App) {
function std_sort (line 27) | pub fn std_sort<R: PlanarSync>(
FILE: src/utils.rs
type GaussianSplattingViewer (line 8) | pub struct GaussianSplattingViewer {
method cloud_transform (line 106) | pub fn cloud_transform(&self) -> Transform {
method default (line 78) | fn default() -> GaussianSplattingViewer {
function parse_vec3 (line 130) | fn parse_vec3(value: &str) -> Option<Vec3> {
function parse_scale (line 146) | fn parse_scale(value: &str) -> Option<Vec3> {
function setup_hooks (line 171) | pub fn setup_hooks() {
function log (line 179) | pub fn log(_msg: &str) {
FILE: tests/gaussian.rs
function test_codec_3d (line 7) | fn test_codec_3d() {
function test_codec_4d (line 18) | fn test_codec_4d() {
FILE: tests/gpu/_harness.rs
type TestHarness (line 10) | pub struct TestHarness {
function test_harness_app (line 14) | pub fn test_harness_app(harness: TestHarness) -> App {
FILE: tests/gpu/gaussian.rs
function main (line 14) | fn main() {
function setup (line 25) | fn setup(mut commands: Commands, mut gaussian_assets: ResMut<Assets<Plan...
function exit_after_warmup (line 42) | fn exit_after_warmup(mut exit: MessageWriter<AppExit>, frame_count: Res<...
FILE: tests/gpu/radix.rs
function main (line 14) | fn main() {
function setup (line 25) | fn setup(mut commands: Commands, mut gaussian_assets: ResMut<Assets<Plan...
function exit_after_warmup (line 42) | fn exit_after_warmup(mut exit: MessageWriter<AppExit>, frame_count: Res<...
FILE: tests/headless_examples.rs
constant MANIFEST_PATH (line 54) | const MANIFEST_PATH: &str = "www/examples/examples.json";
constant THUMB_WIDTH (line 55) | const THUMB_WIDTH: u32 = 960;
constant THUMB_HEIGHT (line 56) | const THUMB_HEIGHT: u32 = 540;
type ExamplesManifest (line 59) | struct ExamplesManifest {
type ExampleEntry (line 66) | struct ExampleEntry {
type MainWorldReceiver (line 86) | struct MainWorldReceiver(Receiver<Vec<u8>>);
type RenderWorldSender (line 89) | struct RenderWorldSender(Sender<Vec<u8>>);
type CaptureController (line 92) | struct CaptureController {
method new (line 105) | pub fn new(width: u32, height: u32) -> Self {
type OutputTarget (line 121) | struct OutputTarget {
type ThumbnailRenderConfig (line 126) | struct ThumbnailRenderConfig {
type CaptureRenderTarget (line 131) | struct CaptureRenderTarget(Handle<Image>);
type AutoFrameState (line 134) | struct AutoFrameState {
type SceneCameraApplied (line 139) | struct SceneCameraApplied;
type SceneRenderModeApplied (line 142) | struct SceneRenderModeApplied;
type SceneRenderModeQuery (line 144) | type SceneRenderModeQuery = (Entity, &'static Children);
type SceneRenderModeFilter (line 145) | type SceneRenderModeFilter = (With<GaussianSceneLoaded>, Without<SceneRe...
type SceneReadyQuery (line 146) | type SceneReadyQuery = (
type SceneReadyFilter (line 153) | type SceneReadyFilter = With<GaussianSceneLoaded>;
function render_example_thumbnails (line 156) | fn render_example_thumbnails() {
function load_manifest (line 189) | fn load_manifest() -> ExamplesManifest {
function apply_args (line 194) | fn apply_args(
function resolve_thumbnail_scene_input (line 244) | fn resolve_thumbnail_scene_input(input_scene: &str) -> String {
function supported_thumbnail_sort_modes (line 295) | fn supported_thumbnail_sort_modes() -> String {
function preferred_thumbnail_sort_mode (line 306) | fn preferred_thumbnail_sort_mode() -> SortMode {
function render_example (line 354) | fn render_example(args: GaussianSplattingViewer, output_path: PathBuf) {
function setup_gaussian_cloud (line 403) | fn setup_gaussian_cloud(
function apply_scene_camera_spawn (line 507) | fn apply_scene_camera_spawn(
function apply_scene_render_mode_override (line 539) | fn apply_scene_render_mode_override(
function mark_capture_ready (line 564) | fn mark_capture_ready(
function request_screenshot_capture (line 685) | fn request_screenshot_capture(
function on_screenshot_captured (line 798) | fn on_screenshot_captured(
function load_ply_cloud (line 819) | fn load_ply_cloud(input_cloud: &str) -> PlanarGaussian3d {
type ImageCopyPlugin (line 835) | pub struct ImageCopyPlugin;
method build (line 838) | fn build(&self, app: &mut App) {
type CaptureFramePlugin (line 859) | pub struct CaptureFramePlugin;
method build (line 862) | fn build(&self, app: &mut App) {
type ImageCopier (line 868) | struct ImageCopier {
method new (line 875) | pub fn new(src_image: Handle<Image>, size: Extent3d, render_device: &R...
method enabled (line 892) | pub fn enabled(&self) -> bool {
type ImageCopiers (line 898) | struct ImageCopiers(Vec<ImageCopier>);
function extract_image_copiers (line 900) | fn extract_image_copiers(mut commands: Commands, image_copiers: Extract<...
type ImageCopy (line 905) | struct ImageCopy;
type ImageCopyDriver (line 908) | struct ImageCopyDriver;
method run (line 911) | fn run(
function receive_image_from_buffer (line 965) | fn receive_image_from_buffer(
type ImageToSave (line 995) | struct ImageToSave(Handle<Image>);
function save_captured_frame (line 998) | fn save_captured_frame(
FILE: tests/io.rs
function test_codec_3d (line 7) | fn test_codec_3d() {
function test_codec_4d (line 18) | fn test_codec_4d() {
FILE: tests/khr_loader_conformance.rs
constant FIXTURE_ROOT (line 24) | const FIXTURE_ROOT: &str = "tests/fixtures/khr_gaussian_splatting";
type ExpectedCase (line 27) | struct ExpectedCase {
function approx_eq (line 34) | fn approx_eq(actual: f32, expected: f32, epsilon: f32) {
function max_supported_test_sh_degree (line 41) | fn max_supported_test_sh_degree() -> usize {
function try_load_fixture_scene (line 55) | fn try_load_fixture_scene(path: &str) -> Result<(GaussianScene, HashMap<...
function load_fixture_scene (line 150) | fn load_fixture_scene(path: &str) -> (GaussianScene, HashMap<String, Pla...
function expected_cases (line 154) | fn expected_cases() -> HashMap<&'static str, ExpectedCase> {
function assert_case_cloud (line 288) | fn assert_case_cloud(
function assert_scene_cases (line 328) | fn assert_scene_cases(
function khr_loader_conformance_matrix_gltf_and_glb (line 363) | fn khr_loader_conformance_matrix_gltf_and_glb() {
function khr_loader_extensibility_and_color0_fallback (line 372) | fn khr_loader_extensibility_and_color0_fallback() {
FILE: tools/compare_aabb_obb.rs
function setup_aabb_obb_compare (line 13) | pub fn setup_aabb_obb_compare(
function compare_aabb_obb_app (line 75) | fn compare_aabb_obb_app() {
function esc_close (line 118) | pub fn esc_close(keys: Res<ButtonInput<KeyCode>>, mut exit: MessageWrite...
function main (line 124) | pub fn main() {
FILE: tools/ply_to_gcloud.rs
function is_point_in_transformed_sphere (line 13) | fn is_point_in_transformed_sphere(pos: &[f32; 3]) -> bool {
function main (line 31) | fn main() {
FILE: tools/render_trellis_thumbnails.rs
constant WIDTH (line 15) | const WIDTH: u32 = 960;
constant HEIGHT (line 16) | const HEIGHT: u32 = 540;
constant INPUT_PLY (line 17) | const INPUT_PLY: &str = "assets/trellis.ply";
type ThumbnailMode (line 20) | enum ThumbnailMode {
type ProjectedPoint (line 27) | struct ProjectedPoint {
function main (line 34) | fn main() {
function load_cloud (line 70) | fn load_cloud(path: &str) -> PlanarGaussian3d {
function project_points (line 78) | fn project_points(
function render_and_save (line 154) | fn render_and_save(
FILE: tools/surfel_plane.rs
function setup_surfel_compare (line 14) | pub fn setup_surfel_compare(
function compare_surfel_app (line 124) | fn compare_surfel_app() {
function esc_close (line 167) | pub fn esc_close(keys: Res<ButtonInput<KeyCode>>, mut exit: MessageWrite...
function main (line 173) | pub fn main() {
FILE: viewer/viewer.rs
type ViewerMainCamera (line 63) | struct ViewerMainCamera;
type SceneCameraApplied (line 66) | struct SceneCameraApplied;
type SceneRenderModeApplied (line 69) | struct SceneRenderModeApplied;
type ExportCloudQuery (line 72) | type ExportCloudQuery = (
type ExportCameraQuery (line 81) | type ExportCameraQuery = (&'static GlobalTransform, Option<&'static Name>);
type SceneCameraApplyQuery (line 82) | type SceneCameraApplyQuery = (Entity, &'static mut Transform, &'static m...
type SceneRenderModeQuery (line 83) | type SceneRenderModeQuery = (Entity, &'static Children);
type SceneRenderModeFilter (line 84) | type SceneRenderModeFilter = (With<GaussianSceneLoaded>, Without<SceneRe...
function parse_input_file (line 86) | fn parse_input_file(input_file: &str) -> String {
function decode_percent_encoded (line 111) | fn decode_percent_encoded(value: &str) -> Option<String> {
function decode_hex (line 143) | fn decode_hex(value: u8) -> Option<u8> {
function setup_gaussian_cloud (line 152) | fn setup_gaussian_cloud(
function apply_scene_camera_spawn (line 293) | fn apply_scene_camera_spawn(
function apply_scene_render_mode_override (line 363) | fn apply_scene_render_mode_override(
function orbit_from_translation_and_focus (line 385) | fn orbit_from_translation_and_focus(
function setup_particle_behavior (line 406) | fn setup_particle_behavior(
function setup_noise_material (line 439) | fn setup_noise_material(
function setup_sparse_select (line 460) | fn setup_sparse_select(
function viewer_app (line 476) | fn viewer_app() {
function press_s_screenshot (line 598) | pub fn press_s_screenshot(
function press_g_save_gltf_scene (line 615) | fn press_g_save_gltf_scene(
function press_g_save_gltf_scene (line 684) | fn press_g_save_gltf_scene(keys: Res<ButtonInput<KeyCode>>) {
type ShowAxes (line 691) | pub struct ShowAxes;
function draw_axes (line 693) | fn draw_axes(mut gizmos: Gizmos, query: Query<(&Transform, &Aabb), With<...
function press_esc_close (line 700) | pub fn press_esc_close(keys: Res<ButtonInput<KeyCode>>, mut exit: Messag...
function press_i_invert_selection (line 707) | fn press_i_invert_selection(
function press_o_save_selection (line 718) | fn press_o_save_selection(
function fps_display_setup (line 728) | fn fps_display_setup(mut commands: Commands, asset_server: Res<AssetServ...
type FpsText (line 759) | struct FpsText;
function fps_update_system (line 761) | fn fps_update_system(
function decodes_percent_encoded_input_url (line 779) | fn decodes_percent_encoded_input_url() {
function keeps_plain_relative_path (line 786) | fn keeps_plain_relative_path() {
function main (line 793) | pub fn main() {
Condensed preview — 133 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (656K chars).
[
{
"path": ".cargo/config.toml",
"chars": 644,
"preview": "# alternatively, `export CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER=wasm-server-runner`\n\n[target.wasm32-unknown-unknown]"
},
{
"path": ".devcontainer/Dockerfile",
"chars": 460,
"preview": "FROM mcr.microsoft.com/devcontainers/rust:0-1\n\nWORKDIR /workspace\n\nRUN apt-get update && DEBIAN_FRONTEND=\"noninteractive"
},
{
"path": ".devcontainer/docker-compose.yaml",
"chars": 191,
"preview": "services:\n devcontainer:\n build:\n context: .\n dockerfile: ./Dockerfile\n volumes:\n - type: bind\n "
},
{
"path": ".gitattributes",
"chars": 168,
"preview": "* text=auto eol=lf\n*.{cmd,[cC][mM][dD]} text eol=crlf\n*.{bat,[bB][aA][tT]} text eol=crlf\n*.sh text eol=lf\n*.conf text eo"
},
{
"path": ".github/dependabot.yml",
"chars": 474,
"preview": "# Please see the documentation for all configuration options:\n# https://help.github.com/github/administering-a-repositor"
},
{
"path": ".github/workflows/bench.yml",
"chars": 914,
"preview": "name: bench\n\non:\n pull_request:\n types: [ labeled, synchronize ]\n branches: [ \"main\" ]\n\nenv:\n CARGO_TERM_COLOR: "
},
{
"path": ".github/workflows/build.yml",
"chars": 1268,
"preview": "name: build\n\non:\n push:\n branches: [ \"main\" ]\n pull_request:\n branches: [ \"main\" ]\n\nenv:\n CARGO_TERM_COLOR: alw"
},
{
"path": ".github/workflows/clippy.yml",
"chars": 740,
"preview": "name: clippy\n\non:\n push:\n branches: [ \"main\" ]\n pull_request:\n branches: [ \"main\" ]\n\nenv:\n CARGO_TERM_COLOR: al"
},
{
"path": ".github/workflows/deploy-pages.yml",
"chars": 1259,
"preview": "name: deploy github pages\n\non:\n push:\n branches:\n - main\n workflow_dispatch:\n\npermissions:\n contents: write\n\n"
},
{
"path": ".github/workflows/test.yml",
"chars": 1305,
"preview": "name: test\n\non:\n push:\n branches: [ \"main\" ]\n pull_request:\n branches: [ \"main\" ]\n\nenv:\n CARGO_TERM_COLOR: alwa"
},
{
"path": ".github/workflows/todo_tracker.yml",
"chars": 362,
"preview": "\nname: 'todo tracker'\n\non:\n push:\n branches: [ main ]\n\njobs:\n build:\n permissions:\n issues: write\n\n name"
},
{
"path": ".gitignore",
"chars": 279,
"preview": "debug/\ntarget/\nvendor/\nout/\n**/*.rs.bk\n*.pdb\n\n*.py\n\n*.gc4d\n*.gcloud\n*.glb\n*.ply\n*.ply4d\n*.json\n\n.DS_Store\n\nexports/\nscre"
},
{
"path": ".vscode/bevy_gaussian_splatting.code-workspace",
"chars": 171,
"preview": "{\n\t\"folders\": [\n\t\t{\n\t\t\t\"path\": \"../\"\n\t\t},\n\t\t{\n\t\t\t\"path\": \"../../bevy\"\n\t\t}\n\t],\n\t\"settings\": {\n\t\t\"liveServer.settings.mult"
},
{
"path": "CONTRIBUTING.md",
"chars": 1925,
"preview": "# contributing to bevy_gaussian_splatting\n\n\n\nthank you for your interest in contributin"
},
{
"path": "Cargo.toml",
"chars": 5792,
"preview": "[package]\nname = \"bevy_gaussian_splatting\"\ndescription = \"bevy gaussian splatting render pipeline plugin\"\nversion = \"7.0"
},
{
"path": "LICENSE-APACHE",
"chars": 10165,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "LICENSE-MIT",
"chars": 1036,
"preview": "MIT License\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associate"
},
{
"path": "README.md",
"chars": 5355,
"preview": "# bevy_gaussian_splatting 🌌\n\n[](https"
},
{
"path": "benches/io.rs",
"chars": 3005,
"preview": "use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};\n\nuse bevy::prelude::Transform;\nuse"
},
{
"path": "docs/credits.md",
"chars": 2164,
"preview": "- [2d-gaussian-splatting](https://github.com/hbb1/2d-gaussian-splatting)\n- [4d gaussians](https://github.com/hustvl/4DGa"
},
{
"path": "examples/headless.rs",
"chars": 14053,
"preview": "//! Headless rendering for gaussian splatting\n//!\n//! Renders gaussian splatting to images without creating a window.\n//"
},
{
"path": "examples/minimal.rs",
"chars": 67,
"preview": "// TODO: minimal app\n\nfn main() {\n println!(\"Hello, world!\");\n}\n"
},
{
"path": "examples/multi_camera.rs",
"chars": 8151,
"preview": "use bevy::{\n app::AppExit, camera::Viewport, core_pipeline::tonemapping::Tonemapping, prelude::*,\n window::WindowR"
},
{
"path": "src/camera.rs",
"chars": 789,
"preview": "use bevy::{\n prelude::*,\n render::extract_component::{ExtractComponent, ExtractComponentPlugin},\n};\n\n#[derive(Clon"
},
{
"path": "src/gaussian/cloud.rs",
"chars": 1905,
"preview": "use bevy::{\n camera::{\n primitives::Aabb,\n visibility::{NoFrustumCulling, VisibilityClass, VisibilitySy"
},
{
"path": "src/gaussian/covariance.rs",
"chars": 945,
"preview": "use bevy::math::{Mat3, Vec3, Vec4};\n\n#[allow(non_snake_case)]\npub fn compute_covariance_3d(rotation: Vec4, scale: Vec3) "
},
{
"path": "src/gaussian/f16.rs",
"chars": 7254,
"preview": "#![allow(dead_code)] // ShaderType derives emit unused check helpers\nuse std::marker::Copy;\n\nuse half::f16;\n\nuse bevy::{"
},
{
"path": "src/gaussian/f32.rs",
"chars": 4938,
"preview": "#![allow(dead_code)] // ShaderType derives emit unused check helpers\nuse std::marker::Copy;\n\nuse bevy::{prelude::*, rend"
},
{
"path": "src/gaussian/formats/mod.rs",
"chars": 282,
"preview": "// TODO: move all format specific code here (e.g. rand, packed)\n\npub mod planar_3d;\npub mod planar_3d_chunked;\npub mod p"
},
{
"path": "src/gaussian/formats/planar_3d.rs",
"chars": 8188,
"preview": "use rand::{\n Rng, SeedableRng,\n distr::{Distribution, StandardUniform},\n rng,\n rngs::StdRng,\n seq::SliceR"
},
{
"path": "src/gaussian/formats/planar_3d_chunked.rs",
"chars": 1,
"preview": "\n"
},
{
"path": "src/gaussian/formats/planar_3d_lod.rs",
"chars": 48,
"preview": "// TODO: gaussian cloud 3d with level of detail\n"
},
{
"path": "src/gaussian/formats/planar_3d_quantized.rs",
"chars": 97,
"preview": "// TODO: gaussian_3d and gaussian_3d_quantized conversions\n// TODO: packed quantized gaussian 3d\n"
},
{
"path": "src/gaussian/formats/planar_3d_spz.rs",
"chars": 65,
"preview": "// TODO: spz quantized format https://github.com/nianticlabs/spz\n"
},
{
"path": "src/gaussian/formats/planar_4d.rs",
"chars": 9903,
"preview": "use std::marker::Copy;\n\nuse bevy::prelude::*;\nuse bevy_interleave::prelude::*;\nuse bytemuck::{Pod, Zeroable};\nuse rand::"
},
{
"path": "src/gaussian/formats/planar_4d_hierarchy.rs",
"chars": 552,
"preview": "// TODO: gaussian cloud 4d with temporal hierarchy\nuse crate::gaussian::formats::planar_4d::PlanarGaussian4dHandle;\n\npub"
},
{
"path": "src/gaussian/formats/planar_4d_quantized.rs",
"chars": 1,
"preview": "\n"
},
{
"path": "src/gaussian/formats/spacetime.rs",
"chars": 837,
"preview": "// https://github.com/oppo-us-research/SpacetimeGaussians\n\n// property float x\n// property float y\n// property float z\n/"
},
{
"path": "src/gaussian/interface.rs",
"chars": 2220,
"preview": "use bevy::{math::bounding::Aabb3d, prelude::*};\nuse bevy_interleave::prelude::Planar;\n\n#[cfg(feature = \"sort_rayon\")]\nus"
},
{
"path": "src/gaussian/iter.rs",
"chars": 1909,
"preview": "#[cfg(feature = \"sort_rayon\")]\nuse rayon::iter::plumbing::{Consumer, UnindexedConsumer};\n#[cfg(feature = \"sort_rayon\")]\n"
},
{
"path": "src/gaussian/mod.rs",
"chars": 293,
"preview": "use static_assertions::assert_cfg;\n\npub mod cloud;\npub mod covariance;\npub mod f16;\npub mod f32;\npub mod formats;\npub mo"
},
{
"path": "src/gaussian/settings.rs",
"chars": 4246,
"preview": "use bevy::prelude::*;\nuse bevy_args::{Deserialize, Serialize, ValueEnum};\n\nuse crate::sort::SortMode;\n\n#[derive(Clone, C"
},
{
"path": "src/io/codec.rs",
"chars": 506,
"preview": "use std::io::Write;\n\n// TODO: support streamed codecs\npub trait CloudCodec {\n fn encode(&self) -> Vec<u8>;\n fn dec"
},
{
"path": "src/io/gcloud/bincode2.rs",
"chars": 1574,
"preview": "use bincode2::{deserialize_from, serialize_into};\nuse flate2::{Compression, read::GzDecoder, write::GzEncoder};\nuse serd"
},
{
"path": "src/io/gcloud/flexbuffers.rs",
"chars": 1129,
"preview": "use flexbuffers::{FlexbufferSerializer, Reader};\nuse serde::{Deserialize, Serialize};\n\nuse crate::{\n gaussian::format"
},
{
"path": "src/io/gcloud/mod.rs",
"chars": 340,
"preview": "use static_assertions::assert_cfg;\n\n// If both codecs are enabled, prefer flexbuffers.\n#[cfg(all(feature = \"io_bincode2\""
},
{
"path": "src/io/gcloud/texture.rs",
"chars": 53,
"preview": "// TODO: convert gcloud to compressed texture format\n"
},
{
"path": "src/io/loader.rs",
"chars": 3189,
"preview": "#[allow(unused_imports)]\nuse std::io::{BufReader, Cursor, ErrorKind};\n\nuse bevy::{\n asset::{AssetLoader, LoadContext,"
},
{
"path": "src/io/mod.rs",
"chars": 415,
"preview": "use bevy::prelude::*;\n\npub mod codec;\npub mod gcloud;\npub mod loader;\npub mod scene;\n\n#[cfg(feature = \"io_ply\")]\npub mod"
},
{
"path": "src/io/ply.rs",
"chars": 9338,
"preview": "use core::panic;\nuse std::io::BufRead;\n\nuse bevy_interleave::prelude::Planar;\nuse ply_rs::{\n parser::Parser,\n ply:"
},
{
"path": "src/io/scene.rs",
"chars": 78593,
"preview": "use std::borrow::Cow;\nuse std::collections::{BTreeMap, HashMap};\nuse std::io::ErrorKind;\nuse std::path::Path;\n\nuse base6"
},
{
"path": "src/lib.rs",
"chars": 2291,
"preview": "#![allow(incomplete_features)]\n#![cfg_attr(feature = \"nightly_generic_alias\", feature(lazy_type_alias))]\n\nuse bevy::prel"
},
{
"path": "src/lighting/environmental.rs",
"chars": 0,
"preview": ""
},
{
"path": "src/lighting/mod.rs",
"chars": 0,
"preview": ""
},
{
"path": "src/material/classification.rs",
"chars": 490,
"preview": "use bevy::{\n asset::{load_internal_asset, uuid_handle},\n prelude::*,\n};\n\nconst CLASSIFICATION_SHADER_HANDLE: Handl"
},
{
"path": "src/material/classification.wgsl",
"chars": 585,
"preview": "#define_import_path bevy_gaussian_splatting::classification\n\n#import bevy_render::color_operations::{\n hsv_to_rgb,\n "
},
{
"path": "src/material/depth.rs",
"chars": 383,
"preview": "use bevy::{\n asset::{load_internal_asset, uuid_handle},\n prelude::*,\n};\n\nconst DEPTH_SHADER_HANDLE: Handle<Shader>"
},
{
"path": "src/material/depth.wgsl",
"chars": 416,
"preview": "#define_import_path bevy_gaussian_splatting::depth\n\nfn depth_to_rgb(depth: f32, min_depth: f32, max_depth: f32) -> vec3<"
},
{
"path": "src/material/mod.rs",
"chars": 842,
"preview": "use bevy::prelude::*;\n\npub mod classification;\npub mod depth;\npub mod optical_flow;\npub mod position;\npub mod spherical_"
},
{
"path": "src/material/noise.rs",
"chars": 1688,
"preview": "use bevy::prelude::*;\nuse bevy_interleave::prelude::{Planar, PlanarHandle};\nuse noise::{NoiseFn, RidgedMulti, Simplex};\n"
},
{
"path": "src/material/noise.wgsl",
"chars": 1,
"preview": "\n"
},
{
"path": "src/material/optical_flow.rs",
"chars": 478,
"preview": "use bevy::{\n asset::{load_internal_asset, uuid_handle},\n prelude::*,\n};\n\nconst OPTICAL_FLOW_SHADER_HANDLE: Handle<"
},
{
"path": "src/material/optical_flow.wgsl",
"chars": 2094,
"preview": "#define_import_path bevy_gaussian_splatting::optical_flow\n\n#import bevy_pbr::{\n forward_io::VertexOutput,\n prepass"
},
{
"path": "src/material/pbr.rs",
"chars": 0,
"preview": ""
},
{
"path": "src/material/pbr.wgsl",
"chars": 1,
"preview": "\n"
},
{
"path": "src/material/position.rs",
"chars": 456,
"preview": "use bevy::{\n asset::{load_internal_asset, uuid_handle},\n prelude::*,\n};\n\nconst POSITION_SHADER_HANDLE: Handle<Shad"
},
{
"path": "src/material/position.wgsl",
"chars": 1,
"preview": "\n"
},
{
"path": "src/material/spherical_harmonics.rs",
"chars": 6832,
"preview": "#![allow(dead_code)] // ShaderType derives emit unused check helpers\nuse std::marker::Copy;\n\nuse bevy::{\n asset::{loa"
},
{
"path": "src/material/spherical_harmonics.wgsl",
"chars": 2634,
"preview": "#define_import_path bevy_gaussian_splatting::spherical_harmonics\n\nconst shc = array<f32, 16>(\n 0.28209479177387814,\n "
},
{
"path": "src/material/spherindrical_harmonics.rs",
"chars": 7292,
"preview": "#![allow(dead_code)] // ShaderType derives emit unused check helpers\nuse std::marker::Copy;\n\nuse bevy::{\n asset::{loa"
},
{
"path": "src/material/spherindrical_harmonics.wgsl",
"chars": 4373,
"preview": "#define_import_path bevy_gaussian_splatting::spherindrical_harmonics\n\n#import bevy_gaussian_splatting::bindings::gaussia"
},
{
"path": "src/math/mod.rs",
"chars": 153,
"preview": "pub const fn gcd(a: usize, b: usize) -> usize {\n if b == 0 { a } else { gcd(b, a % b) }\n}\n\npub const fn pad_4(x: usiz"
},
{
"path": "src/morph/interpolate.rs",
"chars": 18231,
"preview": "use std::{any::TypeId, marker::PhantomData};\n\nuse bevy::{\n asset::{Assets, LoadState, load_internal_asset, uuid_handl"
},
{
"path": "src/morph/interpolate.wgsl",
"chars": 3549,
"preview": "#define_import_path bevy_gaussian_splatting::morph::interpolate\n\n#import bevy_gaussian_splatting::bindings::gaussian_uni"
},
{
"path": "src/morph/mod.rs",
"chars": 1023,
"preview": "use bevy::prelude::*;\nuse bevy_interleave::prelude::*;\n\n#[cfg(feature = \"morph_interpolate\")]\npub mod interpolate;\n\n#[cf"
},
{
"path": "src/morph/particle.rs",
"chars": 15410,
"preview": "use rand::{\n Rng,\n distr::{Distribution, StandardUniform},\n rng,\n};\nuse std::marker::Copy;\n\n#[allow(unused_impo"
},
{
"path": "src/morph/particle.wgsl",
"chars": 1985,
"preview": "#define_import_path bevy_gaussian_splatting::morph::particle\n\n#import bevy_gaussian_splatting::bindings::{\n gaussian_"
},
{
"path": "src/noise/mod.rs",
"chars": 386,
"preview": "use bevy::{\n asset::{load_internal_asset, uuid_handle},\n prelude::*,\n};\n\nconst NOISE_SHADER_HANDLE: Handle<Shader>"
},
{
"path": "src/noise/noise.wgsl",
"chars": 7000,
"preview": "#define_import_path bevy_gaussian_splatting::noise\n\n// MIT License. © Ian McEwan, Stefan Gustavson, Munrocket, Johan He"
},
{
"path": "src/query/mod.rs",
"chars": 593,
"preview": "use bevy::prelude::*;\n\n#[cfg(feature = \"query_raycast\")]\npub mod raycast;\n\n#[cfg(feature = \"query_select\")]\npub mod sele"
},
{
"path": "src/query/raycast.rs",
"chars": 3153,
"preview": "use bevy::{\n mesh::{Indices, Mesh3d, PrimitiveTopology},\n prelude::*,\n};\n\n#[derive(Component, Clone, Copy, Debug, "
},
{
"path": "src/query/select.rs",
"chars": 4575,
"preview": "use bevy::prelude::*;\nuse bevy_interleave::prelude::*;\n\nuse crate::{\n gaussian::{\n formats::{planar_3d::Gaussi"
},
{
"path": "src/query/sparse.rs",
"chars": 2896,
"preview": "use bevy::prelude::*;\nuse bevy_interleave::prelude::PlanarHandle;\nuse kd_tree::{KdPoint, KdTree};\nuse typenum::consts::U"
},
{
"path": "src/render/bindings.wgsl",
"chars": 9397,
"preview": "#define_import_path bevy_gaussian_splatting::bindings\n\n#import bevy_pbr::prepass_bindings::PreviousViewUniforms\n#import "
},
{
"path": "src/render/gaussian.wgsl",
"chars": 14615,
"preview": "#import bevy_gaussian_splatting::bindings::{\n view,\n gaussian_uniforms,\n Entry,\n}\n#import bevy_gaussian_splatti"
},
{
"path": "src/render/gaussian_2d.wgsl",
"chars": 3542,
"preview": "#define_import_path bevy_gaussian_splatting::gaussian_2d\n\n#ifdef GAUSSIAN_2D\n#import bevy_gaussian_splatting::bindings::"
},
{
"path": "src/render/gaussian_3d.wgsl",
"chars": 1997,
"preview": "#define_import_path bevy_gaussian_splatting::gaussian_3d\n\n#ifdef GAUSSIAN_3D\n#import bevy_gaussian_splatting::bindings::"
},
{
"path": "src/render/gaussian_4d.wgsl",
"chars": 3227,
"preview": "#define_import_path bevy_gaussian_splatting::gaussian_4d\n\n#import bevy_gaussian_splatting::bindings::{\n view,\n glo"
},
{
"path": "src/render/helpers.wgsl",
"chars": 3944,
"preview": "#define_import_path bevy_gaussian_splatting::helpers\n\n#import bevy_gaussian_splatting::bindings::{\n view,\n gaussia"
},
{
"path": "src/render/mod.rs",
"chars": 53343,
"preview": "#![allow(dead_code)] // ShaderType derives emit unused check helpers\nuse std::{borrow::Cow, hash::Hash, num::NonZero};\n\n"
},
{
"path": "src/render/packed.rs",
"chars": 2246,
"preview": "use bevy::render::{\n render_resource::{\n BindGroup, BindGroupEntry, BindGroupLayout, BindGroupLayoutEntry, Bin"
},
{
"path": "src/render/packed.wgsl",
"chars": 3959,
"preview": "#define_import_path bevy_gaussian_splatting::packed\n\n#import bevy_gaussian_splatting::bindings::{gaussian_uniforms, poin"
},
{
"path": "src/render/planar.rs",
"chars": 1,
"preview": "\n"
},
{
"path": "src/render/planar.wgsl",
"chars": 18934,
"preview": "#define_import_path bevy_gaussian_splatting::planar\n\n#import bevy_gaussian_splatting::bindings::gaussian_uniforms\n\n#ifde"
},
{
"path": "src/render/texture.rs",
"chars": 1303,
"preview": "use bevy::{\n prelude::*,\n render::{\n render_resource::{\n BindGroupLayout, BindGroupLayoutEntry, "
},
{
"path": "src/render/texture.wgsl",
"chars": 8491,
"preview": "#define_import_path bevy_gaussian_splatting::texture\n\n#ifdef PRECOMPUTE_COVARIANCE_3D\n#import bevy_gaussian_splatting::b"
},
{
"path": "src/render/transform.wgsl",
"chars": 487,
"preview": "#define_import_path bevy_gaussian_splatting::transform\n\n#import bevy_gaussian_splatting::bindings::view\n\nfn world_to_cli"
},
{
"path": "src/sort/bitonic.rs",
"chars": 1,
"preview": "\n"
},
{
"path": "src/sort/bitonic.wgsl",
"chars": 1,
"preview": "\n"
},
{
"path": "src/sort/mod.rs",
"chars": 13177,
"preview": "#![allow(dead_code)] // ShaderType derives emit unused check helpers\nuse core::time::Duration;\nuse std::marker::PhantomD"
},
{
"path": "src/sort/radix.rs",
"chars": 29452,
"preview": "#[cfg(feature = \"morph_interpolate\")]\nuse std::any::TypeId;\nuse std::collections::HashMap;\n\nuse bevy::{\n asset::{load"
},
{
"path": "src/sort/radix.wgsl",
"chars": 9464,
"preview": "#import bevy_gaussian_splatting::bindings::{\n view,\n globals,\n gaussian_uniforms,\n sorting_pass_index,\n s"
},
{
"path": "src/sort/rayon.rs",
"chars": 4274,
"preview": "use bevy::{math::Vec3A, platform::time::Instant, prelude::*};\nuse bevy_interleave::prelude::*;\nuse rayon::prelude::*;\n\nu"
},
{
"path": "src/sort/std_sort.rs",
"chars": 4292,
"preview": "use bevy::{math::Vec3A, platform::time::Instant, prelude::*};\nuse bevy_interleave::prelude::*;\n\nuse crate::{\n CloudSe"
},
{
"path": "src/sort/temporal.wgsl",
"chars": 1071,
"preview": "\n@compute @workgroup_size(#{TEMPORAL_SORT_WINDOW_SIZE})\nfn temporal_sort_flip(\n @builtin(local_invocation_id) gl_Loca"
},
{
"path": "src/stream/hierarchy.rs",
"chars": 1,
"preview": "\n"
},
{
"path": "src/stream/mod.rs",
"chars": 34,
"preview": "pub mod hierarchy;\npub mod slice;\n"
},
{
"path": "src/stream/slice.rs",
"chars": 3793,
"preview": "// use bevy::prelude::*;\n// use bevy::render::{\n// render_graph::{Node, NodeRunError, RenderGraphContext, RenderLabe"
},
{
"path": "src/utils.rs",
"chars": 5426,
"preview": "use bevy::prelude::*;\nuse bevy_args::{Deserialize, Parser, Serialize};\n\nuse crate::gaussian::settings::{GaussianMode, Pl"
},
{
"path": "tests/fixtures/khr_gaussian_splatting/khr_conformance_matrix.gltf",
"chars": 20281,
"preview": "{\"asset\":{\"version\":\"2.0\"},\"extensionsUsed\":[\"KHR_gaussian_splatting\"],\"scene\":0,\"scenes\":[{\"nodes\":[0,1,2,3,4,5,6,7,8,9"
},
{
"path": "tests/fixtures/khr_gaussian_splatting/khr_extensible_fallback.gltf",
"chars": 1277,
"preview": "{\"asset\":{\"version\":\"2.0\"},\"extensionsUsed\":[\"KHR_gaussian_splatting\",\"EXT_gaussian_splatting_kernel_customShape\"],\"scen"
},
{
"path": "tests/gaussian.rs",
"chars": 625,
"preview": "use bevy_gaussian_splatting::{\n PlanarGaussian3d, PlanarGaussian4d, io::codec::CloudCodec, random_gaussians_3d,\n r"
},
{
"path": "tests/gpu/_harness.rs",
"chars": 1109,
"preview": "use bevy::prelude::*;\n\nuse bevy_gaussian_splatting::GaussianSplattingPlugin;\n\n// scraping this in CI for now until bevy "
},
{
"path": "tests/gpu/gaussian.rs",
"chars": 1267,
"preview": "use bevy::{\n app::AppExit, core_pipeline::tonemapping::Tonemapping, diagnostic::FrameCount, prelude::*,\n};\n\nuse bevy_"
},
{
"path": "tests/gpu/radix.rs",
"chars": 1287,
"preview": "use bevy::{\n app::AppExit, core_pipeline::tonemapping::Tonemapping, diagnostic::FrameCount, prelude::*,\n};\n\nuse bevy_"
},
{
"path": "tests/headless_examples.rs",
"chars": 32320,
"preview": "#![allow(dead_code, unused_imports)]\n\nuse bevy::{\n app::{AppExit, ScheduleRunnerPlugin},\n asset::LoadState,\n ca"
},
{
"path": "tests/io.rs",
"chars": 629,
"preview": "use bevy_gaussian_splatting::{\n PlanarGaussian3d, PlanarGaussian4d, io::codec::CloudCodec, random_gaussians_3d,\n r"
},
{
"path": "tests/khr_loader_conformance.rs",
"chars": 14091,
"preview": "use std::{\n collections::HashMap,\n path::PathBuf,\n thread,\n time::{Duration, Instant},\n};\n\nuse bevy::{\n a"
},
{
"path": "tests/radix.rs",
"chars": 1,
"preview": "\n"
},
{
"path": "tools/README.md",
"chars": 592,
"preview": "# bevy_gaussian_splatting tools\n\n## ply to gcloud converter\n\nconvert ply files into bevy_gaussian_splatting gcloud file "
},
{
"path": "tools/build_www.ps1",
"chars": 1711,
"preview": "$ErrorActionPreference = 'Stop'\n\nfunction Invoke-Step {\n param(\n [Parameter(Mandatory = $true)][string]$Exe,\n [Pa"
},
{
"path": "tools/build_www.sh",
"chars": 2905,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nscript_dir=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nrepo_root=\"$(cd \"${scr"
},
{
"path": "tools/compare_aabb_obb.rs",
"chars": 4000,
"preview": "use bevy::{app::AppExit, core_pipeline::tonemapping::Tonemapping, prelude::*};\nuse bevy_args::{BevyArgsPlugin, parse_arg"
},
{
"path": "tools/ply_to_gcloud.rs",
"chars": 2760,
"preview": "use byte_unit::{Byte, UnitType};\n\nuse bevy_gaussian_splatting::io::{codec::CloudCodec, ply::parse_ply_3d};\n\n#[cfg(featur"
},
{
"path": "tools/render_trellis_thumbnails.rs",
"chars": 6891,
"preview": "use std::{f32::consts::FRAC_PI_4, fs::File, io::BufReader, path::Path};\n\nuse bevy::{\n asset::RenderAssetUsages,\n i"
},
{
"path": "tools/surfel_plane.rs",
"chars": 5882,
"preview": "use bevy::{app::AppExit, core_pipeline::tonemapping::Tonemapping, prelude::*};\nuse bevy_args::{BevyArgsPlugin, parse_arg"
},
{
"path": "viewer/viewer.rs",
"chars": 26527,
"preview": "// TODO: move to editor crate\nuse std::path::PathBuf;\n\nuse bevy::{\n app::AppExit,\n camera::primitives::Aabb,\n c"
},
{
"path": "www/README.md",
"chars": 1083,
"preview": "# bevy_gaussian_splatting for web\n\n## wasm support\n\nto build wasm run:\n\n```bash\ncargo build --target wasm32-unknown-unkn"
},
{
"path": "www/examples/examples.css",
"chars": 5953,
"preview": "@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Space+Mono:wght@400;600&"
},
{
"path": "www/examples/examples.js",
"chars": 3994,
"preview": "const grid = document.querySelector('[data-grid]');\nconst searchInput = document.querySelector('[data-search]');\nconst m"
},
{
"path": "www/examples/index.html",
"chars": 2507,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "www/index.html",
"chars": 796,
"preview": "<html>\n <head>\n <meta charset=\"UTF-8\" />\n <title>bevy_gaussian_splatting</title>\n <style>\n body {\n "
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the mosure/bevy_gaussian_splatting GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 133 files (609.7 KB), approximately 159.2k tokens, and a symbol index with 696 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.