Skip to content

Commit 8e315a8

Browse files
authored
feat: gaussian scene asset loader (#179)
* feat: gaussian scene asset loader * fix: .gscene -> .json * fix: packed descriptors
1 parent a1a214e commit 8e315a8

File tree

14 files changed

+300
-37
lines changed

14 files changed

+300
-37
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ out/
1111
*.gcloud
1212
*.ply
1313
*.ply4d
14+
*.json
1415

1516
.DS_Store
1617

Cargo.lock

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "bevy_gaussian_splatting"
33
description = "bevy gaussian splatting render pipeline plugin"
4-
version = "4.2.0"
4+
version = "4.3.0"
55
edition = "2021"
66
authors = ["mosure <[email protected]>"]
77
license = "MIT OR Apache-2.0"
@@ -170,6 +170,7 @@ ply-rs = { version = "0.1", optional = true }
170170
rand = "0.8"
171171
rayon = { version = "1.8", optional = true }
172172
serde = "1.0"
173+
serde_json = "1.0"
173174
static_assertions = "1.1"
174175
typenum = "1.17"
175176
wgpu = "23.0.1"
@@ -189,6 +190,7 @@ features = [
189190
"bevy_pbr",
190191
"bevy_render",
191192
"bevy_winit",
193+
"serialize",
192194
"x11",
193195
]
194196

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ bevy_gaussian_splatting --input-file {gaussian_splat_ply_file}
3737
- [X] 2dgs
3838
- [X] 3dgs
3939
- [x] 4dgs
40+
- [x] multi-cloud scene format
41+
- [ ] gltf gaussian extensions
4042
- [ ] 4dgs motion blur
4143
- [ ] implicit mlp node (isotropic rotation, color)
4244
- [ ] temporal gaussian hierarchy
@@ -82,7 +84,7 @@ fn setup_gaussian_cloud(
8284
CloudSettings::default(),
8385
));
8486

85-
commands.spawn(Camera3dBundle::default());
87+
commands.spawn(Camera3d::default());
8688
}
8789
```
8890

assets/scene.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"bundles": [
3+
{
4+
"asset_path": "./trellis.ply",
5+
"name": "trellis_0",
6+
"settings": {},
7+
"transform": {
8+
"translation": [0.0, 0.0, 0.0],
9+
"rotation": [0.0, 0.0, 0.0, 1.0],
10+
"scale": [1.0, -1.0, 1.0]
11+
}
12+
},
13+
{
14+
"asset_path": "./trellis.ply",
15+
"name": "trellis_1",
16+
"settings": {},
17+
"transform": {
18+
"translation": [5.0, 0.0, 0.0],
19+
"rotation": [0.0, 0.0, 0.0, 1.0],
20+
"scale": [1.0, -1.0, 1.0]
21+
}
22+
}
23+
]
24+
}

examples/headless.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,9 @@ fn setup_gaussian_cloud(
378378
if args.gaussian_count > 0 {
379379
println!("generating {} gaussians", args.gaussian_count);
380380
cloud = gaussian_assets.add(random_gaussians_3d(args.gaussian_count));
381-
} else if !args.input_file.is_empty() {
382-
println!("loading {}", args.input_file);
383-
cloud = asset_server.load(&args.input_file);
381+
} else if args.input_cloud.is_some() && !args.input_cloud.as_ref().unwrap().is_empty() {
382+
println!("loading {:?}", args.input_cloud);
383+
cloud = asset_server.load(&args.input_cloud.as_ref().unwrap().clone());
384384
} else {
385385
cloud = gaussian_assets.add(PlanarGaussian3d::test_model());
386386
}

src/gaussian/settings.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ use crate::sort::SortMode;
1717
Hash,
1818
PartialEq,
1919
Reflect,
20+
Serialize,
21+
Deserialize,
2022
)]
2123
pub enum DrawMode {
2224
#[default]
@@ -93,8 +95,16 @@ pub enum RasterizeMode {
9395

9496

9597
// TODO: breakdown into components
96-
#[derive(Component, Reflect, Clone)]
98+
#[derive(
99+
Component,
100+
Clone,
101+
Debug,
102+
Reflect,
103+
Serialize,
104+
Deserialize,
105+
)]
97106
#[reflect(Component)]
107+
#[serde(default)]
98108
pub struct CloudSettings {
99109
pub aabb: bool,
100110
pub global_opacity: f32,

src/io/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
1+
use bevy::prelude::*;
2+
13
pub mod codec;
24
pub mod gcloud;
35
pub mod loader;
6+
pub mod scene;
47

58
#[cfg(feature = "io_ply")]
69
pub mod ply;
10+
11+
12+
#[derive(Default)]
13+
pub struct IoPlugin;
14+
impl Plugin for IoPlugin {
15+
fn build(&self, app: &mut App) {
16+
app.init_asset_loader::<loader::Gaussian3dLoader>();
17+
app.init_asset_loader::<loader::Gaussian4dLoader>();
18+
19+
app.add_plugins(scene::GaussianScenePlugin);
20+
}
21+
}

src/io/scene.rs

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
2+
#[allow(unused_imports)]
3+
use std::io::{
4+
BufReader,
5+
Cursor,
6+
ErrorKind,
7+
};
8+
9+
use bevy::{
10+
prelude::*,
11+
asset::{
12+
AssetLoader,
13+
LoadContext,
14+
io::Reader,
15+
},
16+
};
17+
use serde::{
18+
Deserialize,
19+
Serialize,
20+
};
21+
22+
use crate::gaussian::{
23+
formats::planar_3d::{
24+
PlanarGaussian3d,
25+
PlanarGaussian3dHandle,
26+
},
27+
settings::CloudSettings,
28+
};
29+
30+
31+
#[derive(Default)]
32+
pub struct GaussianScenePlugin;
33+
impl Plugin for GaussianScenePlugin {
34+
fn build(&self, app: &mut App) {
35+
app.register_type::<GaussianScene>();
36+
app.init_asset::<GaussianScene>();
37+
38+
app.init_asset_loader::<GaussianSceneLoader>();
39+
40+
app.add_systems(
41+
Update,
42+
(
43+
spawn_scene,
44+
)
45+
);
46+
}
47+
}
48+
49+
50+
51+
#[derive(
52+
Clone,
53+
Debug,
54+
Default,
55+
Reflect,
56+
Serialize,
57+
Deserialize,
58+
)]
59+
pub struct CloudBundle {
60+
pub asset_path: String,
61+
pub name: String,
62+
pub settings: CloudSettings,
63+
pub transform: Transform,
64+
}
65+
66+
// TODO: support scene hierarchy with gaussian gltf extension
67+
#[derive(
68+
Asset,
69+
Clone,
70+
Debug,
71+
Default,
72+
Reflect,
73+
Serialize,
74+
Deserialize,
75+
)]
76+
pub struct GaussianScene {
77+
pub bundles: Vec<CloudBundle>,
78+
}
79+
80+
#[derive(Component, Clone, Debug, Default, Reflect)]
81+
#[require(Transform, Visibility)]
82+
pub struct GaussianSceneHandle(pub Handle<GaussianScene>);
83+
84+
#[derive(Component, Clone, Debug, Default, Reflect)]
85+
pub struct GaussianSceneLoaded;
86+
87+
88+
fn spawn_scene(
89+
mut commands: Commands,
90+
scene_handles: Query<
91+
(
92+
Entity,
93+
&GaussianSceneHandle,
94+
),
95+
Without<GaussianSceneLoaded>,
96+
>,
97+
asset_server: Res<AssetServer>,
98+
scenes: Res<Assets<GaussianScene>>,
99+
) {
100+
for (entity, scene_handle) in scene_handles.iter() {
101+
if let Some(load_state) = &asset_server.get_load_state(&scene_handle.0) {
102+
if !load_state.is_loaded() {
103+
continue;
104+
}
105+
}
106+
107+
if scenes.get(&scene_handle.0).is_none() {
108+
continue;
109+
}
110+
111+
let scene = scenes.get(&scene_handle.0).unwrap();
112+
113+
let bundles = scene.bundles
114+
.iter()
115+
.map(|bundle|(
116+
// TODO: switch between 3d and 4d clouds based on settings
117+
PlanarGaussian3dHandle(
118+
asset_server.load::<PlanarGaussian3d>(bundle.asset_path.clone())
119+
),
120+
Name::new(bundle.name.clone()),
121+
bundle.settings.clone(),
122+
bundle.transform,
123+
)
124+
)
125+
.collect::<Vec<_>>();
126+
127+
commands
128+
.entity(entity)
129+
.with_children(move |builder| {
130+
for bundle in bundles {
131+
builder.spawn(bundle);
132+
}
133+
})
134+
.insert(GaussianSceneLoaded);
135+
}
136+
}
137+
138+
139+
#[derive(Default)]
140+
pub struct GaussianSceneLoader;
141+
142+
impl AssetLoader for GaussianSceneLoader {
143+
type Asset = GaussianScene;
144+
type Settings = ();
145+
type Error = std::io::Error;
146+
147+
async fn load(
148+
&self,
149+
reader: &mut dyn Reader,
150+
_: &Self::Settings,
151+
load_context: &mut LoadContext<'_>,
152+
) -> Result<Self::Asset, Self::Error> {
153+
let mut bytes = Vec::new();
154+
reader.read_to_end(&mut bytes).await?;
155+
156+
match load_context.path().extension() {
157+
Some(ext) if ext == "json" => {
158+
let scene: GaussianScene = serde_json::from_slice(&bytes)
159+
.map_err(|err| std::io::Error::new(ErrorKind::InvalidData, err))?;
160+
Ok(scene)
161+
},
162+
_ => Err(std::io::Error::new(ErrorKind::Other, "only .json supported")),
163+
}
164+
}
165+
166+
fn extensions(&self) -> &[&str] {
167+
&["json"]
168+
}
169+
}

0 commit comments

Comments
 (0)