Skip to content

Commit b3285ae

Browse files
committed
Add UnitOffset to VoxLoaderSettings
1 parent 2145249 commit b3285ae

File tree

10 files changed

+181
-56
lines changed

10 files changed

+181
-56
lines changed

examples/recentering-model.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use bevy::{
2+
core_pipeline::{bloom::BloomSettings, tonemapping::Tonemapping},
3+
prelude::*,
4+
};
5+
use bevy_vox_scene::{UnitOffset, VoxLoaderSettings, VoxScenePlugin};
6+
use utilities::{PanOrbitCamera, PanOrbitCameraPlugin};
7+
8+
fn main() {
9+
App::new()
10+
.add_plugins((
11+
DefaultPlugins,
12+
VoxScenePlugin {
13+
// Using global settings because Bevy's `load_with_settings` is broken:
14+
// https://github.com/bevyengine/bevy/issues/12320
15+
// https://github.com/bevyengine/bevy/issues/11111
16+
global_settings: Some(VoxLoaderSettings {
17+
voxel_size: 0.1,
18+
mesh_offset: UnitOffset::CENTER_BASE, // centre the model at its base
19+
..default()
20+
}),
21+
},
22+
PanOrbitCameraPlugin,
23+
))
24+
.add_systems(Startup, setup)
25+
.run();
26+
}
27+
28+
fn setup(
29+
mut commands: Commands,
30+
assets: Res<AssetServer>,
31+
mut meshes: ResMut<Assets<Mesh>>,
32+
mut materials: ResMut<Assets<StandardMaterial>>,
33+
) {
34+
commands.spawn((
35+
Camera3dBundle {
36+
camera: Camera {
37+
hdr: true,
38+
..Default::default()
39+
},
40+
transform: Transform::from_xyz(8.0, 1.5, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
41+
tonemapping: Tonemapping::SomewhatBoringDisplayTransform,
42+
..Default::default()
43+
},
44+
PanOrbitCamera::default(),
45+
BloomSettings {
46+
intensity: 0.3,
47+
..default()
48+
},
49+
EnvironmentMapLight {
50+
diffuse_map: assets.load("pisa_diffuse.ktx2"),
51+
specular_map: assets.load("pisa_specular.ktx2"),
52+
intensity: 500.0,
53+
},
54+
Name::new("camera"),
55+
));
56+
57+
commands.spawn(DirectionalLightBundle {
58+
directional_light: DirectionalLight {
59+
illuminance: 5000.0,
60+
shadows_enabled: true,
61+
..Default::default()
62+
},
63+
transform: Transform::IDENTITY.looking_to(Vec3::new(2.5, -1., 0.85), Vec3::Y),
64+
..default()
65+
});
66+
67+
commands.spawn(SceneBundle {
68+
// Only load a single model when using `UnitOffset::CENTER_BASE`
69+
// If you attempt to load a scene containing several models using a setting other than the default of `UnitOffset::CENTER`,
70+
// their transforms will be messed up
71+
scene: assets.load("study.vox#workstation/desk"),
72+
..default()
73+
});
74+
75+
// Add a ground plane for the voxel desk to stand on
76+
commands.spawn(PbrBundle {
77+
mesh: meshes.add(Plane3d::new(Vec3::Y, Vec2::new(30., 30.))),
78+
material: materials.add(StandardMaterial {
79+
base_color: Color::LinearRgba(LinearRgba::GREEN),
80+
..default()
81+
}),
82+
..default()
83+
});
84+
}

examples/voxel-generation.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use bevy::{core_pipeline::bloom::BloomSettings, prelude::*};
22
use bevy_vox_scene::{
3-
VoxScenePlugin, Voxel, VoxelContext, VoxelModel, VoxelModelInstance, VoxelPalette, SDF,
3+
VoxLoaderSettings, VoxScenePlugin, Voxel, VoxelContext, VoxelModel, VoxelModelInstance,
4+
VoxelPalette, SDF,
45
};
56
use utilities::{PanOrbitCamera, PanOrbitCameraPlugin};
67

@@ -46,12 +47,16 @@ fn setup(world: &mut World) {
4647
]);
4748
let data = SDF::cuboid(Vec3::splat(13.0))
4849
.subtract(SDF::sphere(16.0))
49-
.map_to_voxels(UVec3::splat(32), 1.0, |d, _| match d {
50-
x if x < -1.0 => Voxel(2),
51-
x if x < 0.0 => Voxel(1),
52-
x if x >= 0.0 => Voxel::EMPTY,
53-
_ => Voxel::EMPTY,
54-
});
50+
.map_to_voxels(
51+
UVec3::splat(32),
52+
VoxLoaderSettings::default(),
53+
|d, _| match d {
54+
x if x < -1.0 => Voxel(2),
55+
x if x < 0.0 => Voxel(1),
56+
x if x >= 0.0 => Voxel::EMPTY,
57+
_ => Voxel::EMPTY,
58+
},
59+
);
5560
let context = VoxelContext::new(world, palette);
5661
let model_name = "my sdf model";
5762
let Some((model_handle, model)) =

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ mod tests;
5959

6060
#[doc(inline)]
6161
use load::VoxSceneLoader;
62-
pub use load::{VoxLoaderSettings, VoxelLayer, VoxelModelInstance};
62+
pub use load::{UnitOffset, VoxLoaderSettings, VoxelLayer, VoxelModelInstance};
6363
#[cfg(feature = "generate_voxels")]
6464
pub use model::sdf::SDF;
6565
#[cfg(feature = "modify_voxels")]

src/load/mod.rs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use bevy::{
77
asset::{io::Reader, AssetLoader, AsyncReadExt, Handle, LoadContext},
88
color::LinearRgba,
99
log::info,
10+
math::Vec3,
1011
pbr::StandardMaterial,
1112
scene::Scene,
1213
utils::HashSet,
@@ -33,17 +34,18 @@ pub(super) struct VoxSceneLoader {
3334
}
3435

3536
/// Settings for the VoxSceneLoader.
36-
#[derive(Serialize, Deserialize, Clone)]
37+
#[derive(Serialize, Deserialize, Clone, Debug)]
3738
pub struct VoxLoaderSettings {
3839
/// The length of each side of a single voxel. Defaults to 1.0.
3940
pub voxel_size: f32,
4041
/// Whether the outer-most faces of the model should be meshed. Defaults to true. Set this to false if the outer faces of a
4142
/// model will never be visible, for instance if the model id part of a 3D tileset.
4243
pub mesh_outer_faces: bool,
43-
/// Multiplier for emissive strength. Defaults to 2.0.
44+
/// Amount that the vertex positions of the mesh will be offset described as unit of their size.
45+
/// Defaults to [`UnitOffset::CENTER`] the center of the model, as this is where MagicaVoxel places the origin in its world editor
46+
pub mesh_offset: UnitOffset,
47+
/// Multiplier for emissive strength. Defaults to 10.0.
4448
pub emission_strength: f32,
45-
/// Defaults to `true` to more accurately reflect the colours in Magica Voxel.
46-
pub uses_srgb: bool,
4749
/// Magica Voxel doesn't let you adjust the roughness for the default "diffuse" block type, so it can be adjusted with this setting. Defaults to 0.8.
4850
pub diffuse_roughness: f32,
4951
}
@@ -53,13 +55,30 @@ impl Default for VoxLoaderSettings {
5355
Self {
5456
voxel_size: 1.0,
5557
mesh_outer_faces: true,
58+
mesh_offset: UnitOffset::CENTER,
5659
emission_strength: 10.0,
57-
uses_srgb: true,
5860
diffuse_roughness: 0.8,
5961
}
6062
}
6163
}
6264

65+
/// An offset applied to the vertex positions of the mesh expressed as a unit of the mesh's size.
66+
/// For a fully centered mesh, use [`UnitOffset::CENTER`]
67+
/// For a mesh centred around it's base, use [`UnitOffset::CENTER_BASE`]
68+
#[derive(Serialize, Deserialize, Clone, Debug)]
69+
pub struct UnitOffset(pub(crate) Vec3);
70+
71+
impl UnitOffset {
72+
/// Vertex positions will not be offset at all, so that the origin will be the minimum of the model's AABB
73+
pub const ZERO: Self = UnitOffset(Vec3::ZERO);
74+
75+
/// Offset representing the center of a model
76+
pub const CENTER: Self = UnitOffset(Vec3::splat(0.5));
77+
78+
/// Offset representing the center base of a model
79+
pub const CENTER_BASE: Self = UnitOffset(Vec3::new(0.5, 0.0, 0.5));
80+
}
81+
6382
#[derive(Error, Debug)]
6483
pub enum VoxLoaderError {
6584
#[error(transparent)]
@@ -160,8 +179,7 @@ impl VoxSceneLoader {
160179
.enumerate()
161180
.for_each(|(index, (maybe_name, model))| {
162181
let name = maybe_name.clone().unwrap_or(format!("model-{}", index));
163-
let data =
164-
VoxelData::from_model(&model, settings.mesh_outer_faces, settings.voxel_size);
182+
let data = VoxelData::from_model(&model, settings.clone());
165183
let (visible_voxels, ior) = data.visible_voxels(&indices_of_refraction);
166184
let mesh = load_context.labeled_asset_scope(format!("{}@mesh", name), |_| {
167185
crate::model::mesh::mesh_model(&visible_voxels, &data)

src/load/parse_model.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ use dot_vox::Model;
33

44
use crate::model::{RawVoxel, VoxelData};
55

6+
use super::VoxLoaderSettings;
7+
68
impl VoxelData {
79
/// Ingest Magica Voxel data and perform coordinate conversion from MV's left-handed Z-up to bevy's right-handed Y-up
8-
pub(super) fn from_model(model: &Model, mesh_outer_faces: bool, voxel_size: f32) -> VoxelData {
10+
pub(super) fn from_model(model: &Model, settings: VoxLoaderSettings) -> VoxelData {
911
let mut data = VoxelData::new(
1012
UVec3::new(model.size.x, model.size.z, model.size.y),
11-
mesh_outer_faces,
12-
voxel_size,
13+
settings,
1314
);
1415
model.voxels.iter().for_each(|voxel| {
1516
let raw_voxel = RawVoxel(voxel.i);

src/model/data.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,24 @@ use block_mesh::VoxelVisibility;
66
use ndshape::{RuntimeShape, Shape};
77
use std::fmt::Debug;
88

9+
use crate::VoxLoaderSettings;
10+
911
use super::{voxel::VisibleVoxel, RawVoxel};
1012

1113
/// The voxel data used to create a mesh and a material.
1214
#[derive(Clone)]
1315
pub struct VoxelData {
1416
pub(crate) shape: RuntimeShape<u32, 3>,
1517
pub(crate) voxels: Vec<RawVoxel>,
16-
pub(crate) mesh_outer_faces: bool,
17-
pub(crate) voxel_size: f32,
18+
pub(crate) settings: VoxLoaderSettings,
1819
}
1920

2021
impl Default for VoxelData {
2122
fn default() -> Self {
2223
Self {
2324
shape: RuntimeShape::<u32, 3>::new([0, 0, 0]),
2425
voxels: Default::default(),
25-
mesh_outer_faces: true,
26-
voxel_size: 1.0,
26+
settings: VoxLoaderSettings::default(),
2727
}
2828
}
2929
}
@@ -33,15 +33,15 @@ impl Debug for VoxelData {
3333
f.debug_struct("VoxelData")
3434
.field("shape", &self.shape.as_array())
3535
.field("voxels", &self.voxels.len())
36-
.field("mesh_outer_faces", &self.mesh_outer_faces)
36+
.field("settings", &self.settings)
3737
.finish()
3838
}
3939
}
4040

4141
impl VoxelData {
4242
/// Returns a new, empty VoxelData model
43-
pub fn new(size: UVec3, mesh_outer_faces: bool, voxel_size: f32) -> Self {
44-
let padding = if mesh_outer_faces {
43+
pub fn new(size: UVec3, settings: VoxLoaderSettings) -> Self {
44+
let padding = if settings.mesh_outer_faces {
4545
UVec3::splat(2)
4646
} else {
4747
UVec3::ZERO
@@ -51,8 +51,7 @@ impl VoxelData {
5151
Self {
5252
shape,
5353
voxels: vec![RawVoxel::EMPTY; size],
54-
mesh_outer_faces,
55-
voxel_size,
54+
settings,
5655
}
5756
}
5857
/// The size of the voxel model, not including the padding that may have been added if the outer faces are being meshed.
@@ -64,7 +63,7 @@ impl VoxelData {
6463

6564
/// If the outer faces are to be meshed, the mesher requires 1 voxel of padding around the edge of the model
6665
pub(crate) fn padding(&self) -> u32 {
67-
if self.mesh_outer_faces {
66+
if self.settings.mesh_outer_faces {
6867
2
6968
} else {
7069
0

src/model/mesh.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ pub(crate) fn mesh_model(voxels: &[VisibleVoxel], data: &VoxelData) -> Mesh {
2222
&quads_config.faces,
2323
&mut greedy_quads_buffer,
2424
);
25-
let half_extents = data.model_size() * 0.5; // center the mesh
26-
let leading_padding = (data.padding() / 2) as f32 * data.voxel_size; // corrects the 1 offset introduced by the meshing.
27-
let position_offset = half_extents + Vec3::splat(leading_padding);
25+
let offset = data.model_size() * data.settings.mesh_offset.0; // center the mesh
26+
let leading_padding = (data.padding() / 2) as f32 * data.settings.voxel_size; // corrects the 1 offset introduced by the meshing.
27+
let position_offset = offset + Vec3::splat(leading_padding);
2828

2929
let num_indices = greedy_quads_buffer.quads.num_quads() * 6;
3030
let num_vertices = greedy_quads_buffer.quads.num_quads() * 4;
@@ -48,15 +48,17 @@ pub(crate) fn mesh_model(voxels: &[VisibleVoxel], data: &VoxelData) -> Mesh {
4848
for quad in group.iter() {
4949
let palette_index = voxels[data.shape.linearize(quad.minimum) as usize].index;
5050
indices.extend_from_slice(&face.quad_mesh_indices(positions.len() as u32));
51-
positions.extend_from_slice(&face.quad_mesh_positions(quad, data.voxel_size).map(
52-
|position| {
53-
[
54-
position[0] - position_offset.x,
55-
position[1] - position_offset.y,
56-
position[2] - position_offset.z,
57-
]
58-
},
59-
));
51+
positions.extend_from_slice(
52+
&face
53+
.quad_mesh_positions(quad, data.settings.voxel_size)
54+
.map(|position| {
55+
[
56+
position[0] - position_offset.x,
57+
position[1] - position_offset.y,
58+
position[2] - position_offset.z,
59+
]
60+
}),
61+
);
6062
let u = ((palette_index % 16) as f32 + 0.5) / 16.0;
6163
let v = ((palette_index / 16) as f32 + 0.5) / 16.0;
6264
uvs.extend_from_slice(&[[u, v], [u, v], [u, v], [u, v]]);

src/model/queryable.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,18 +100,18 @@ impl VoxelQueryable for VoxelData {
100100
}
101101

102102
fn model_size(&self) -> Vec3 {
103-
self._size().as_vec3() * self.voxel_size
103+
self._size().as_vec3() * self.settings.voxel_size
104104
}
105105

106106
fn local_point_to_voxel_space(&self, local_point: Vec3) -> IVec3 {
107107
let half_extents = self.size().as_vec3() * 0.5;
108-
let voxel_postition = (local_point / self.voxel_size) + half_extents;
108+
let voxel_postition = (local_point / self.settings.voxel_size) + half_extents;
109109
voxel_postition.as_ivec3()
110110
}
111111

112112
fn voxel_coord_to_local_space(&self, voxel_coord: IVec3) -> Vec3 {
113113
let half_extents = self.size().as_vec3() * 0.5;
114-
(voxel_coord.as_vec3() - half_extents) * self.voxel_size
114+
(voxel_coord.as_vec3() - half_extents) * self.settings.voxel_size
115115
}
116116

117117
fn get_voxel_at_point(&self, position: IVec3) -> Result<Voxel, OutOfBoundsError> {

src/model/sdf.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use bevy::math::{Quat, UVec3, Vec3};
22

3-
use crate::{Voxel, VoxelData};
3+
use crate::{VoxLoaderSettings, Voxel, VoxelData};
44

55
/// A 3d signed distance field
66
pub struct SDF {
@@ -72,10 +72,10 @@ impl SDF {
7272
pub fn map_to_voxels<F: Fn(f32, Vec3) -> Voxel>(
7373
self,
7474
size: UVec3,
75-
voxel_size: f32,
75+
settings: VoxLoaderSettings,
7676
map: F,
7777
) -> VoxelData {
78-
let mut data = VoxelData::new(size, true, voxel_size);
78+
let mut data = VoxelData::new(size, settings);
7979
let half_extent = Vec3::new(size.x as f32, size.y as f32, size.z as f32) * 0.5;
8080
for x in 0..size.x {
8181
for y in 0..size.y {
@@ -92,8 +92,8 @@ impl SDF {
9292
}
9393

9494
/// Converts the SDF to [`VoxelData`] by filling every cell that is less than 0 with `fill`.
95-
pub fn voxelize(self, size: UVec3, voxel_size: f32, fill: Voxel) -> VoxelData {
96-
self.map_to_voxels(size, voxel_size, |distance, _| {
95+
pub fn voxelize(self, size: UVec3, settings: VoxLoaderSettings, fill: Voxel) -> VoxelData {
96+
self.map_to_voxels(size, settings, |distance, _| {
9797
if distance < 0.0 {
9898
fill.clone()
9999
} else {

0 commit comments

Comments
 (0)