Skip to content

Commit

Permalink
Add UnitOffset to VoxLoaderSettings
Browse files Browse the repository at this point in the history
  • Loading branch information
Utsira committed Oct 15, 2024
1 parent 2145249 commit b3285ae
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 56 deletions.
84 changes: 84 additions & 0 deletions examples/recentering-model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use bevy::{
core_pipeline::{bloom::BloomSettings, tonemapping::Tonemapping},
prelude::*,
};
use bevy_vox_scene::{UnitOffset, VoxLoaderSettings, VoxScenePlugin};
use utilities::{PanOrbitCamera, PanOrbitCameraPlugin};

fn main() {
App::new()
.add_plugins((
DefaultPlugins,
VoxScenePlugin {
// Using global settings because Bevy's `load_with_settings` is broken:
// https://github.com/bevyengine/bevy/issues/12320
// https://github.com/bevyengine/bevy/issues/11111
global_settings: Some(VoxLoaderSettings {
voxel_size: 0.1,
mesh_offset: UnitOffset::CENTER_BASE, // centre the model at its base
..default()
}),
},
PanOrbitCameraPlugin,
))
.add_systems(Startup, setup)
.run();
}

fn setup(
mut commands: Commands,
assets: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn((
Camera3dBundle {
camera: Camera {
hdr: true,
..Default::default()
},
transform: Transform::from_xyz(8.0, 1.5, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
tonemapping: Tonemapping::SomewhatBoringDisplayTransform,
..Default::default()
},
PanOrbitCamera::default(),
BloomSettings {
intensity: 0.3,
..default()
},
EnvironmentMapLight {
diffuse_map: assets.load("pisa_diffuse.ktx2"),
specular_map: assets.load("pisa_specular.ktx2"),
intensity: 500.0,
},
Name::new("camera"),
));

commands.spawn(DirectionalLightBundle {
directional_light: DirectionalLight {
illuminance: 5000.0,
shadows_enabled: true,
..Default::default()
},
transform: Transform::IDENTITY.looking_to(Vec3::new(2.5, -1., 0.85), Vec3::Y),
..default()
});

commands.spawn(SceneBundle {
// Only load a single model when using `UnitOffset::CENTER_BASE`
// If you attempt to load a scene containing several models using a setting other than the default of `UnitOffset::CENTER`,
// their transforms will be messed up
scene: assets.load("study.vox#workstation/desk"),
..default()
});

// Add a ground plane for the voxel desk to stand on
commands.spawn(PbrBundle {
mesh: meshes.add(Plane3d::new(Vec3::Y, Vec2::new(30., 30.))),
material: materials.add(StandardMaterial {
base_color: Color::LinearRgba(LinearRgba::GREEN),
..default()
}),
..default()
});
}
19 changes: 12 additions & 7 deletions examples/voxel-generation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use bevy::{core_pipeline::bloom::BloomSettings, prelude::*};
use bevy_vox_scene::{
VoxScenePlugin, Voxel, VoxelContext, VoxelModel, VoxelModelInstance, VoxelPalette, SDF,
VoxLoaderSettings, VoxScenePlugin, Voxel, VoxelContext, VoxelModel, VoxelModelInstance,
VoxelPalette, SDF,
};
use utilities::{PanOrbitCamera, PanOrbitCameraPlugin};

Expand Down Expand Up @@ -46,12 +47,16 @@ fn setup(world: &mut World) {
]);
let data = SDF::cuboid(Vec3::splat(13.0))
.subtract(SDF::sphere(16.0))
.map_to_voxels(UVec3::splat(32), 1.0, |d, _| match d {
x if x < -1.0 => Voxel(2),
x if x < 0.0 => Voxel(1),
x if x >= 0.0 => Voxel::EMPTY,
_ => Voxel::EMPTY,
});
.map_to_voxels(
UVec3::splat(32),
VoxLoaderSettings::default(),
|d, _| match d {
x if x < -1.0 => Voxel(2),
x if x < 0.0 => Voxel(1),
x if x >= 0.0 => Voxel::EMPTY,
_ => Voxel::EMPTY,
},
);
let context = VoxelContext::new(world, palette);
let model_name = "my sdf model";
let Some((model_handle, model)) =
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ mod tests;

#[doc(inline)]
use load::VoxSceneLoader;
pub use load::{VoxLoaderSettings, VoxelLayer, VoxelModelInstance};
pub use load::{UnitOffset, VoxLoaderSettings, VoxelLayer, VoxelModelInstance};
#[cfg(feature = "generate_voxels")]
pub use model::sdf::SDF;
#[cfg(feature = "modify_voxels")]
Expand Down
32 changes: 25 additions & 7 deletions src/load/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use bevy::{
asset::{io::Reader, AssetLoader, AsyncReadExt, Handle, LoadContext},
color::LinearRgba,
log::info,
math::Vec3,
pbr::StandardMaterial,
scene::Scene,
utils::HashSet,
Expand All @@ -33,17 +34,18 @@ pub(super) struct VoxSceneLoader {
}

/// Settings for the VoxSceneLoader.
#[derive(Serialize, Deserialize, Clone)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct VoxLoaderSettings {
/// The length of each side of a single voxel. Defaults to 1.0.
pub voxel_size: f32,
/// Whether the outer-most faces of the model should be meshed. Defaults to true. Set this to false if the outer faces of a
/// model will never be visible, for instance if the model id part of a 3D tileset.
pub mesh_outer_faces: bool,
/// Multiplier for emissive strength. Defaults to 2.0.
/// Amount that the vertex positions of the mesh will be offset described as unit of their size.
/// Defaults to [`UnitOffset::CENTER`] the center of the model, as this is where MagicaVoxel places the origin in its world editor
pub mesh_offset: UnitOffset,
/// Multiplier for emissive strength. Defaults to 10.0.
pub emission_strength: f32,
/// Defaults to `true` to more accurately reflect the colours in Magica Voxel.
pub uses_srgb: bool,
/// 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.
pub diffuse_roughness: f32,
}
Expand All @@ -53,13 +55,30 @@ impl Default for VoxLoaderSettings {
Self {
voxel_size: 1.0,
mesh_outer_faces: true,
mesh_offset: UnitOffset::CENTER,
emission_strength: 10.0,
uses_srgb: true,
diffuse_roughness: 0.8,
}
}
}

/// An offset applied to the vertex positions of the mesh expressed as a unit of the mesh's size.
/// For a fully centered mesh, use [`UnitOffset::CENTER`]
/// For a mesh centred around it's base, use [`UnitOffset::CENTER_BASE`]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct UnitOffset(pub(crate) Vec3);

impl UnitOffset {
/// Vertex positions will not be offset at all, so that the origin will be the minimum of the model's AABB
pub const ZERO: Self = UnitOffset(Vec3::ZERO);

/// Offset representing the center of a model
pub const CENTER: Self = UnitOffset(Vec3::splat(0.5));

/// Offset representing the center base of a model
pub const CENTER_BASE: Self = UnitOffset(Vec3::new(0.5, 0.0, 0.5));
}

#[derive(Error, Debug)]
pub enum VoxLoaderError {
#[error(transparent)]
Expand Down Expand Up @@ -160,8 +179,7 @@ impl VoxSceneLoader {
.enumerate()
.for_each(|(index, (maybe_name, model))| {
let name = maybe_name.clone().unwrap_or(format!("model-{}", index));
let data =
VoxelData::from_model(&model, settings.mesh_outer_faces, settings.voxel_size);
let data = VoxelData::from_model(&model, settings.clone());
let (visible_voxels, ior) = data.visible_voxels(&indices_of_refraction);
let mesh = load_context.labeled_asset_scope(format!("{}@mesh", name), |_| {
crate::model::mesh::mesh_model(&visible_voxels, &data)
Expand Down
7 changes: 4 additions & 3 deletions src/load/parse_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ use dot_vox::Model;

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

use super::VoxLoaderSettings;

impl VoxelData {
/// Ingest Magica Voxel data and perform coordinate conversion from MV's left-handed Z-up to bevy's right-handed Y-up
pub(super) fn from_model(model: &Model, mesh_outer_faces: bool, voxel_size: f32) -> VoxelData {
pub(super) fn from_model(model: &Model, settings: VoxLoaderSettings) -> VoxelData {
let mut data = VoxelData::new(
UVec3::new(model.size.x, model.size.z, model.size.y),
mesh_outer_faces,
voxel_size,
settings,
);
model.voxels.iter().for_each(|voxel| {
let raw_voxel = RawVoxel(voxel.i);
Expand Down
19 changes: 9 additions & 10 deletions src/model/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@ use block_mesh::VoxelVisibility;
use ndshape::{RuntimeShape, Shape};
use std::fmt::Debug;

use crate::VoxLoaderSettings;

use super::{voxel::VisibleVoxel, RawVoxel};

/// The voxel data used to create a mesh and a material.
#[derive(Clone)]
pub struct VoxelData {
pub(crate) shape: RuntimeShape<u32, 3>,
pub(crate) voxels: Vec<RawVoxel>,
pub(crate) mesh_outer_faces: bool,
pub(crate) voxel_size: f32,
pub(crate) settings: VoxLoaderSettings,
}

impl Default for VoxelData {
fn default() -> Self {
Self {
shape: RuntimeShape::<u32, 3>::new([0, 0, 0]),
voxels: Default::default(),
mesh_outer_faces: true,
voxel_size: 1.0,
settings: VoxLoaderSettings::default(),
}
}
}
Expand All @@ -33,15 +33,15 @@ impl Debug for VoxelData {
f.debug_struct("VoxelData")
.field("shape", &self.shape.as_array())
.field("voxels", &self.voxels.len())
.field("mesh_outer_faces", &self.mesh_outer_faces)
.field("settings", &self.settings)
.finish()
}
}

impl VoxelData {
/// Returns a new, empty VoxelData model
pub fn new(size: UVec3, mesh_outer_faces: bool, voxel_size: f32) -> Self {
let padding = if mesh_outer_faces {
pub fn new(size: UVec3, settings: VoxLoaderSettings) -> Self {
let padding = if settings.mesh_outer_faces {
UVec3::splat(2)
} else {
UVec3::ZERO
Expand All @@ -51,8 +51,7 @@ impl VoxelData {
Self {
shape,
voxels: vec![RawVoxel::EMPTY; size],
mesh_outer_faces,
voxel_size,
settings,
}
}
/// The size of the voxel model, not including the padding that may have been added if the outer faces are being meshed.
Expand All @@ -64,7 +63,7 @@ impl VoxelData {

/// If the outer faces are to be meshed, the mesher requires 1 voxel of padding around the edge of the model
pub(crate) fn padding(&self) -> u32 {
if self.mesh_outer_faces {
if self.settings.mesh_outer_faces {
2
} else {
0
Expand Down
26 changes: 14 additions & 12 deletions src/model/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ pub(crate) fn mesh_model(voxels: &[VisibleVoxel], data: &VoxelData) -> Mesh {
&quads_config.faces,
&mut greedy_quads_buffer,
);
let half_extents = data.model_size() * 0.5; // center the mesh
let leading_padding = (data.padding() / 2) as f32 * data.voxel_size; // corrects the 1 offset introduced by the meshing.
let position_offset = half_extents + Vec3::splat(leading_padding);
let offset = data.model_size() * data.settings.mesh_offset.0; // center the mesh
let leading_padding = (data.padding() / 2) as f32 * data.settings.voxel_size; // corrects the 1 offset introduced by the meshing.
let position_offset = offset + Vec3::splat(leading_padding);

let num_indices = greedy_quads_buffer.quads.num_quads() * 6;
let num_vertices = greedy_quads_buffer.quads.num_quads() * 4;
Expand All @@ -48,15 +48,17 @@ pub(crate) fn mesh_model(voxels: &[VisibleVoxel], data: &VoxelData) -> Mesh {
for quad in group.iter() {
let palette_index = voxels[data.shape.linearize(quad.minimum) as usize].index;
indices.extend_from_slice(&face.quad_mesh_indices(positions.len() as u32));
positions.extend_from_slice(&face.quad_mesh_positions(quad, data.voxel_size).map(
|position| {
[
position[0] - position_offset.x,
position[1] - position_offset.y,
position[2] - position_offset.z,
]
},
));
positions.extend_from_slice(
&face
.quad_mesh_positions(quad, data.settings.voxel_size)
.map(|position| {
[
position[0] - position_offset.x,
position[1] - position_offset.y,
position[2] - position_offset.z,
]
}),
);
let u = ((palette_index % 16) as f32 + 0.5) / 16.0;
let v = ((palette_index / 16) as f32 + 0.5) / 16.0;
uvs.extend_from_slice(&[[u, v], [u, v], [u, v], [u, v]]);
Expand Down
6 changes: 3 additions & 3 deletions src/model/queryable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,18 @@ impl VoxelQueryable for VoxelData {
}

fn model_size(&self) -> Vec3 {
self._size().as_vec3() * self.voxel_size
self._size().as_vec3() * self.settings.voxel_size
}

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

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

fn get_voxel_at_point(&self, position: IVec3) -> Result<Voxel, OutOfBoundsError> {
Expand Down
10 changes: 5 additions & 5 deletions src/model/sdf.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bevy::math::{Quat, UVec3, Vec3};

use crate::{Voxel, VoxelData};
use crate::{VoxLoaderSettings, Voxel, VoxelData};

/// A 3d signed distance field
pub struct SDF {
Expand Down Expand Up @@ -72,10 +72,10 @@ impl SDF {
pub fn map_to_voxels<F: Fn(f32, Vec3) -> Voxel>(
self,
size: UVec3,
voxel_size: f32,
settings: VoxLoaderSettings,
map: F,
) -> VoxelData {
let mut data = VoxelData::new(size, true, voxel_size);
let mut data = VoxelData::new(size, settings);
let half_extent = Vec3::new(size.x as f32, size.y as f32, size.z as f32) * 0.5;
for x in 0..size.x {
for y in 0..size.y {
Expand All @@ -92,8 +92,8 @@ impl SDF {
}

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

0 comments on commit b3285ae

Please sign in to comment.