use bevy::asset::{AssetLoader, LoadContext, io::Reader};
use bevy::reflect::TypePath;
use serde::{Deserialize, Serialize};
use crate::splats::{SplatCoordinateConvention, Splats};
use crate::spz::{DEFAULT_MAX_SPLATS, SpzError, parse_spz_with_max_splats};
#[derive(Default, TypePath)]
pub struct SpzLoader;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct SpzLoaderSettings {
pub max_splats: u32,
pub coordinate_convention: SplatCoordinateConvention,
pub generate_lod: bool,
pub generated_lod_leaf_size: usize,
pub generated_lod_branch_factor: usize,
}
impl Default for SpzLoaderSettings {
fn default() -> Self {
Self {
max_splats: env_u32("BEVY_SPARK_MAX_SPLATS", DEFAULT_MAX_SPLATS),
coordinate_convention: env_coordinate_convention(
"BEVY_SPARK_COORDINATE_CONVENTION",
SplatCoordinateConvention::BevyYUp,
),
generate_lod: env_bool("BEVY_SPARK_GENERATE_LOD", false),
generated_lod_leaf_size: env_usize("BEVY_SPARK_GENERATED_LOD_LEAF_SIZE", 32),
generated_lod_branch_factor: env_usize("BEVY_SPARK_GENERATED_LOD_BRANCH_FACTOR", 8),
}
}
}
impl AssetLoader for SpzLoader {
type Asset = Splats;
type Settings = SpzLoaderSettings;
type Error = SpzError;
async fn load(
&self,
reader: &mut dyn Reader,
settings: &SpzLoaderSettings,
_load_context: &mut LoadContext<'_>,
) -> Result<Splats, SpzError> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await.map_err(SpzError::Io)?;
let mut splats = parse_spz_with_max_splats(&bytes, settings.max_splats)?;
splats.coordinate_convention = settings.coordinate_convention;
let generated_lod = settings.generate_lod
&& !splats.lod
&& splats.generate_quick_lod(
settings.generated_lod_leaf_size,
settings.generated_lod_branch_factor,
);
bevy::log::info!(
"loaded SPZ: {} splats (anti_aliased={}, sh={}, lod={}, generated_lod={}, v{})",
splats.len(),
splats.anti_aliased,
splats.sh_degree,
splats.lod,
generated_lod,
splats.header_version
);
Ok(splats)
}
fn extensions(&self) -> &[&str] {
&["spz"]
}
}
fn env_bool(name: &str, default: bool) -> bool {
std::env::var(name)
.ok()
.map(|value| {
matches!(
value.to_ascii_lowercase().as_str(),
"1" | "true" | "yes" | "on"
)
})
.unwrap_or(default)
}
fn env_usize(name: &str, default: usize) -> usize {
std::env::var(name)
.ok()
.and_then(|value| value.parse().ok())
.unwrap_or(default)
}
fn env_u32(name: &str, default: u32) -> u32 {
std::env::var(name)
.ok()
.and_then(|value| value.parse().ok())
.unwrap_or(default)
}
fn env_coordinate_convention(
name: &str,
default: SplatCoordinateConvention,
) -> SplatCoordinateConvention {
std::env::var(name)
.ok()
.map(|value| match value.to_ascii_lowercase().as_str() {
"ydown" | "y-down" | "y_down" | "sparkjs" | "3dgs" => SplatCoordinateConvention::YDown,
_ => SplatCoordinateConvention::BevyYUp,
})
.unwrap_or(default)
}