Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions rust/spark-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,33 @@ license.workspace = true
authors.workspace = true
repository.workspace = true

[features]
default = ["antisplat", "ksplat", "ply", "rad", "sogs", "spz", "csplat", "gsplat", "tiny_lod", "bhatt_lod"]
antisplat = []
ksplat = []
ply = []
rad = ["dep:miniz_oxide"]
sogs = ["dep:zip", "dep:image"]
spz = ["dep:miniz_oxide"]
csplat = []
gsplat = []
quick_lod = ["gsplat"]
tiny_lod = []
bhatt_lod = []

[dependencies]
ahash.workspace = true
anyhow.workspace = true
glam.workspace = true
half.workspace = true
ordered-float.workspace = true
miniz_oxide.workspace = true
miniz_oxide = { workspace = true, optional = true }
serde.workspace = true
smallvec.workspace = true
itertools.workspace = true
serde_json.workspace = true
zip.workspace = true
image.workspace = true
zip = { workspace = true, optional = true }
image = { workspace = true, optional = true }
hnsw.workspace = true
rand_pcg.workspace = true
space.workspace = true
113 changes: 83 additions & 30 deletions rust/spark-lib/src/decoder.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
use std::any::Any;

#[cfg(feature = "spz")]
use miniz_oxide::inflate::{core::{decompress, inflate_flags::{TINFL_FLAG_HAS_MORE_INPUT, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF}, DecompressorOxide}, TINFLStatus};
use serde::{Deserialize, Serialize};

use crate::{
antisplat::AntiSplatDecoder,
ksplat::KsplatDecoder,
ply::{PLY_MAGIC, PlyDecoder},
rad::{RAD_CHUNK_MAGIC, RAD_MAGIC, RadDecoder},
sogs::SogsDecoder,
spz::{SPZ_MAGIC, SpzDecoder}
};
#[cfg(feature = "antisplat")]
use crate::antisplat::AntiSplatDecoder;
#[cfg(feature = "ksplat")]
use crate::ksplat::KsplatDecoder;
#[cfg(feature = "ply")]
use crate::ply::{PLY_MAGIC, PlyDecoder};
#[cfg(feature = "rad")]
use crate::rad::{RAD_CHUNK_MAGIC, RAD_MAGIC, RadDecoder};
#[cfg(feature = "sogs")]
use crate::sogs::{PK_MAGIC, SogsDecoder};
#[cfg(feature = "spz")]
use crate::spz::{SPZ_MAGIC, SpzDecoder};

pub trait ChunkReceiver: Any {
fn push(&mut self, bytes: &[u8]) -> anyhow::Result<()>;
Expand Down Expand Up @@ -329,47 +334,73 @@ pub trait SplatGetter: 'static {

#[derive(Debug, Clone, Copy)]
pub enum SplatFileType {
#[cfg(feature = "ply")]
PLY,
#[cfg(feature = "spz")]
SPZ,
#[cfg(feature = "antisplat")]
ANTISPLAT,
#[cfg(feature = "ksplat")]
KSPLAT,
#[cfg(feature = "sogs")]
SOGS,
#[cfg(feature = "rad")]
RAD,
}

impl SplatFileType {
pub fn to_enum_str(self) -> &'static str {
match self {
#[cfg(feature = "ply")]
Self::PLY => "ply",
#[cfg(feature = "spz")]
Self::SPZ => "spz",
#[cfg(feature = "antisplat")]
Self::ANTISPLAT => "splat",
#[cfg(feature = "ksplat")]
Self::KSPLAT => "ksplat",
#[cfg(feature = "sogs")]
Self::SOGS => "pcsogszip",
#[cfg(feature = "rad")]
Self::RAD => "rad",
}
}

pub fn from_enum_str(enum_str: &str) -> anyhow::Result<Self> {
match enum_str {
#[cfg(feature = "ply")]
"ply" => Ok(Self::PLY),
#[cfg(feature = "spz")]
"spz" => Ok(Self::SPZ),
#[cfg(feature = "antisplat")]
"splat" => Ok(Self::ANTISPLAT),
#[cfg(feature = "ksplat")]
"ksplat" => Ok(Self::KSPLAT),
#[cfg(feature = "sogs")]
"pcsogszip" => Ok(Self::SOGS),
#[cfg(feature = "rad")]
"rad" => Ok(Self::RAD),
_ => Err(anyhow::anyhow!("Invalid file type: {}", enum_str)),
}
}

pub fn from_extension(extension: &str) -> Option<Self> {
match extension.to_lowercase().as_str() {
#[cfg(feature = "ply")]
"ply" => Some(Self::PLY),
#[cfg(feature = "spz")]
"spz" => Some(Self::SPZ),
#[cfg(feature = "antisplat")]
"splat" => Some(Self::ANTISPLAT),
#[cfg(feature = "ksplat")]
"ksplat" => Some(Self::KSPLAT),
#[cfg(feature = "sogs")]
"sog" => Some(Self::SOGS),
#[cfg(feature = "sogs")]
"sogs" => Some(Self::SOGS),
#[cfg(feature = "sogs")]
"zip" => Some(Self::SOGS),
#[cfg(feature = "rad")]
"rad" => Some(Self::RAD),
_ => None,
}
Expand Down Expand Up @@ -418,26 +449,32 @@ impl<T: SplatReceiver> MultiDecoder<T> {

pub fn into_splats(self) -> T {
let inner_any = self.inner.unwrap().into_any();
#[cfg(feature = "ply")]
let inner_any = match inner_any.downcast::<PlyDecoder<T>>() {
Ok(ply) => { return ply.into_splats(); },
Err(inner_any) => inner_any,
};
#[cfg(feature = "spz")]
let inner_any = match inner_any.downcast::<SpzDecoder<T>>() {
Ok(spz) => { return spz.into_splats(); },
Err(inner_any) => inner_any,
};
#[cfg(feature = "antisplat")]
let inner_any = match inner_any.downcast::<AntiSplatDecoder<T>>() {
Ok(antisplat) => { return antisplat.into_splats(); },
Err(inner_any) => inner_any,
};
#[cfg(feature = "ksplat")]
let inner_any = match inner_any.downcast::<KsplatDecoder<T>>() {
Ok(ksplat) => { return ksplat.into_splats(); },
Err(inner_any) => inner_any,
};
#[cfg(feature = "sogs")]
let inner_any = match inner_any.downcast::<SogsDecoder<T>>() {
Ok(sogs) => { return sogs.into_splats(); },
Err(inner_any) => inner_any,
};
#[cfg(feature = "rad")]
let inner_any = match inner_any.downcast::<RadDecoder<T>>() {
Ok(rad) => { return rad.into_splats(); },
Err(inner_any) => inner_any,
Expand Down Expand Up @@ -471,34 +508,43 @@ impl<T: SplatReceiver> ChunkReceiver for MultiDecoder<T> {
let mut detection_complete = false;

let magic = u32::from_le_bytes([self.buffer[0], self.buffer[1], self.buffer[2], self.buffer[3]]);
if (magic & 0x00ffffff) == PLY_MAGIC {
return self.init_file_type(SplatFileType::PLY);
}
if (magic & 0x00ffffff) == GZIP_MAGIC {
// Gzipped file, unpack beginning to check magic number
if self.buffer_gz.is_none() {
self.buffer_gz = try_gunzip(&self.buffer, 4)?;
match (magic, magic & 0x00ffffff) {
#[cfg(feature = "ply")]
(_, PLY_MAGIC) => {
return self.init_file_type(SplatFileType::PLY);
}
if let Some(buffer_gz) = self.buffer_gz.as_ref() {
detection_complete = true;
if buffer_gz.len() >= 4 {
let magic = u32::from_le_bytes([buffer_gz[0], buffer_gz[1], buffer_gz[2], buffer_gz[3]]);
if magic == SPZ_MAGIC {
return self.init_file_type(SplatFileType::SPZ);
#[cfg(feature = "spz")]
(_, GZIP_MAGIC) => {
// Gzipped file, unpack beginning to check magic number
if self.buffer_gz.is_none() {
self.buffer_gz = try_gunzip(&self.buffer, 4)?;
}
if let Some(buffer_gz) = self.buffer_gz.as_ref() {
detection_complete = true;
if buffer_gz.len() >= 4 {
let magic = u32::from_le_bytes([buffer_gz[0], buffer_gz[1], buffer_gz[2], buffer_gz[3]]);
if magic == SPZ_MAGIC {
return self.init_file_type(SplatFileType::SPZ);
}
}
}
}
} else if magic == 0x04034b50 {
detection_complete = true;
if let Some(pathname) = &self.pathname {
if let Some(SplatFileType::SOGS) = SplatFileType::from_pathname(pathname) {
return self.init_file_type(SplatFileType::SOGS);
#[cfg(feature = "sogs")]
(PK_MAGIC, _) => {
detection_complete = true;
if let Some(pathname) = &self.pathname {
if let Some(SplatFileType::SOGS) = SplatFileType::from_pathname(pathname) {
return self.init_file_type(SplatFileType::SOGS);
}
}
}
} else if magic == RAD_MAGIC || magic == RAD_CHUNK_MAGIC {
return self.init_file_type(SplatFileType::RAD);
} else {
detection_complete = true;
#[cfg(feature = "rad")]
(RAD_MAGIC, _) | (RAD_CHUNK_MAGIC, _) => {
return self.init_file_type(SplatFileType::RAD);
}
_ => {
detection_complete = true;
}
}

if detection_complete {
Expand Down Expand Up @@ -528,15 +574,22 @@ impl<T: SplatReceiver> ChunkReceiver for MultiDecoder<T> {

fn new_decoder<T: SplatReceiver>(file_type: SplatFileType, splats: T) -> Box<dyn ChunkReceiver> {
match file_type {
#[cfg(feature = "ply")]
SplatFileType::PLY => Box::new(PlyDecoder::new(splats)),
#[cfg(feature = "spz")]
SplatFileType::SPZ => Box::new(SpzDecoder::new(splats)),
#[cfg(feature = "antisplat")]
SplatFileType::ANTISPLAT => Box::new(AntiSplatDecoder::new(splats)),
#[cfg(feature = "ksplat")]
SplatFileType::KSPLAT => Box::new(KsplatDecoder::new(splats)),
#[cfg(feature = "sogs")]
SplatFileType::SOGS => Box::new(SogsDecoder::new(splats, None)),
#[cfg(feature = "rad")]
SplatFileType::RAD => Box::new(RadDecoder::new(splats)),
}
}

#[cfg(feature = "spz")]
fn try_gunzip(buffer: &[u8], max_bytes: usize) -> anyhow::Result<Option<Vec<u8>>> {
if buffer.len() < 10 {
return Ok(None);
Expand Down
14 changes: 14 additions & 0 deletions rust/spark-lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@

pub mod tsplat;
#[cfg(feature = "gsplat")]
pub mod gsplat;
#[cfg(feature = "csplat")]
pub mod csplat;
pub mod symmat3;
#[cfg(feature = "quick_lod")]
pub mod quick_lod;
#[cfg(feature = "tiny_lod")]
pub mod tiny_lod;
#[cfg(feature = "bhatt_lod")]
pub mod bhatt_lod;
#[cfg(feature = "ply")]
pub mod ply;
#[cfg(feature = "spz")]
pub mod spz;
#[cfg(feature = "antisplat")]
pub mod antisplat;
#[cfg(feature = "ksplat")]
pub mod ksplat;
#[cfg(feature = "sogs")]
pub mod sogs;
#[cfg(feature = "rad")]
pub mod rad;
pub mod decoder;
pub mod splat_encode;
pub mod ordering;
pub mod chunk_tree;
pub mod sh_clustering;

#[cfg(not(any(feature = "gsplat", feature = "csplat")))]
compile_error!("at least one of \"gsplat\" and \"csplat\" must be enabled");

#[cfg(test)]
mod tests {
use super::{
Expand Down
2 changes: 1 addition & 1 deletion rust/spark-lib/src/sogs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use zip::ZipArchive;

use crate::decoder::{ChunkReceiver, SplatInit, SplatProps, SplatReceiver};

const PK_MAGIC: u32 = 0x04034b50;
pub const PK_MAGIC: u32 = 0x04034b50;
const SH_C0: f32 = 0.28209479177387814;
const MAX_SPLAT_CHUNK: usize = 65536;

Expand Down
15 changes: 14 additions & 1 deletion rust/spark-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ crate-type = ["cdylib"]
[package.metadata.wasm-pack.profile.release.wasm-bindgen]
omit-default-module-path = true

[features]
default = ["antisplat", "ksplat", "ply", "rad", "sogs", "spz", "csplat", "gsplat", "tiny_lod", "bhatt_lod"]
antisplat = ["spark-lib/antisplat"]
ksplat = ["spark-lib/ksplat"]
ply = ["spark-lib/ply"]
rad = ["spark-lib/rad"]
sogs = ["spark-lib/sogs"]
spz = ["spark-lib/spz"]
csplat = ["spark-lib/csplat"]
gsplat = ["spark-lib/gsplat"]
tiny_lod = ["spark-lib/tiny_lod"]
bhatt_lod = ["spark-lib/bhatt_lod"]

[dependencies]
ahash.workspace = true
anyhow.workspace = true
Expand All @@ -23,7 +36,7 @@ ordered-float.workspace = true
smallvec.workspace = true
wasm-bindgen.workspace = true
web-sys = { workspace = true, features = ["Window", "Performance"] }
spark-lib = { path = "../spark-lib" }
spark-lib = { path = "../spark-lib", default-features = false, features = [] }
serde-wasm-bindgen.workspace = true
serde_json.workspace = true
serde.workspace = true
Expand Down
4 changes: 3 additions & 1 deletion rust/spark-rs/src/ext_splats.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::array;

use js_sys::{Array, Object, Reflect, Uint32Array};
#[cfg(feature = "csplat")]
use spark_lib::csplat::CsplatArray;
use spark_lib::{
decoder::{SetSplatEncoding, SplatEncoding, SplatGetter, SplatInit, SplatProps, SplatPropsMut, SplatReceiver, copy_getter_to_receiver},
gsplat::GsplatArray,
csplat::CsplatArray,
tsplat::{TsplatArray, Tsplat},
splat_encode::{
decode_ext_rgb, decode_ext_splat_center, decode_ext_splat_opacity, decode_ext_splat_quat, decode_ext_splat_rgb, decode_ext_splat_scale, encode_ext_rgb, encode_ext_splat, encode_ext_splat_center, encode_ext_splat_opacity, encode_ext_splat_quat, encode_ext_splat_rgb, encode_ext_splat_rgba, encode_ext_splat_scale, encode_lod_tree, get_splat_tex_size
Expand Down Expand Up @@ -324,6 +325,7 @@ impl ExtSplatsData {
}

#[allow(dead_code)]
#[cfg(feature = "csplat")]
pub fn to_csplat_array(&mut self) -> anyhow::Result<CsplatArray> {
let mut out = CsplatArray::new();
copy_getter_to_receiver(self, &mut out)?;
Expand Down
Loading
Loading