Skip to content

Commit

Permalink
Add VoxelQueryable
Browse files Browse the repository at this point in the history
  • Loading branch information
Utsira committed Jan 18, 2024
1 parent 98dc623 commit 1c2a89c
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 134 deletions.
Binary file modified assets/study.vox
Binary file not shown.
46 changes: 35 additions & 11 deletions examples/modify-voxels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ use bevy::{
use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin};
use bevy_vox_scene::{
BoxRegion, ModifyVoxelModel, VoxScenePlugin, Voxel, VoxelModel, VoxelModelInstance,
VoxelRegion, VoxelScene, VoxelSceneBundle, VoxelSceneHook, VoxelSceneHookBundle,
VoxelQueryable, VoxelRegion, VoxelScene, VoxelSceneBundle, VoxelSceneHook,
VoxelSceneHookBundle,
};
use rand::Rng;

// When a snowflake lands on the scenery, it is added to scenery's voxel data, so that snow gradually builds up
fn main() {
let mut app = App::new();
// Making this frequency not cleanly divisible by the snowflake speed ensures that expensive collision checks
// Making this frequency not cleanly divisible by the snowflake speed ensures that expensive collisions
// don't all happen on the same frame
let snow_spawn_freq = Duration::from_secs_f32(0.213);
app.add_plugins((DefaultPlugins, PanOrbitCameraPlugin, VoxScenePlugin))
Expand Down Expand Up @@ -93,7 +94,7 @@ fn setup(mut commands: Commands, assets: Res<AssetServer>) {
}

#[derive(Component)]
struct Snowflake;
struct Snowflake(Quat);

#[derive(Component)]
struct Scenery;
Expand All @@ -105,8 +106,10 @@ fn spawn_snow(mut commands: Commands, scenes: Res<Scenes>) {
let mut rng = rand::thread_rng();
let position = Vec3::new(rng.gen_range(-30.0..30.0), 80.0, rng.gen_range(-20.0..20.0)).round()
+ Vec3::splat(0.5);
let rotation_axis = Vec3::new(rng.gen_range(-0.5..0.5), 1.0, rng.gen_range(-0.5..0.5)).normalize();
let angular_velocity = Quat::from_axis_angle(rotation_axis, 0.01);
commands.spawn((
Snowflake,
Snowflake(angular_velocity),
VoxelSceneBundle {
scene: scenes.snowflake.clone(),
transform: Transform::from_translation(position),
Expand All @@ -117,33 +120,54 @@ fn spawn_snow(mut commands: Commands, scenes: Res<Scenes>) {

fn update_snow(
mut commands: Commands,
mut snowflakes: Query<(Entity, &mut Transform), (With<Snowflake>, Without<Scenery>)>,
mut snowflakes: Query<(Entity, &Snowflake, &mut Transform), Without<Scenery>>,
scenery: Query<
(Entity, &GlobalTransform, &VoxelModelInstance),
(With<Scenery>, Without<Snowflake>),
>,
models: Res<Assets<VoxelModel>>,
) {
for (snowflake, mut snowflake_xform) in snowflakes.iter_mut() {
for (snowflake, snowflake_angular_vel, mut snowflake_xform) in snowflakes.iter_mut() {
let old_ypos = snowflake_xform.translation.y;
snowflake_xform.translation.y -= 0.1;
snowflake_xform.rotation *= snowflake_angular_vel.0;
// don't check collisions unless crossing boundary to next voxel
if old_ypos.trunc() == snowflake_xform.translation.y.trunc() {
continue;
}
for (item, item_xform, item_instance) in scenery.iter() {
let Some(model) = models.get(item_instance.0.id()) else { continue };
let Some(vox_pos_below_snowflake) = model.data.global_point_to_voxel_space(snowflake_xform.translation - Vec3::Y, item_xform) else { continue };
if model.data.get_voxel_at_point(vox_pos_below_snowflake) == Voxel::EMPTY {
let vox_pos =
model.global_point_to_voxel_space(snowflake_xform.translation, item_xform);
// check whether snowflake has landed on something solid
let pos_below_snowflake = vox_pos - IVec3::Y;
let Some(voxel) = model.get_voxel_at_point(pos_below_snowflake) else { continue };
if voxel == Voxel::EMPTY {
continue;
};
let flake_radius = 2;
let radius_squared = flake_radius * flake_radius;
let flake_region = BoxRegion {
origin: vox_pos_below_snowflake + UVec3::Y,
size: UVec3::ONE,
origin: vox_pos - IVec3::splat(flake_radius),
size: IVec3::splat(1 + (flake_radius * 2)),
};
commands.entity(item).insert(ModifyVoxelModel::new(
VoxelRegion::Box(flake_region),
|_, _| Voxel(234),
move |pos, voxel, model| {
// a signed distance field for a sphere, but _only_ drawing it on empty cells directly above solid voxels
if *voxel == Voxel::EMPTY && pos.distance_squared(vox_pos) <= radius_squared {
if let Some(voxel_below) = model.get_voxel_at_point(pos - IVec3::Y) {
if voxel_below == Voxel::EMPTY {
return Voxel::EMPTY;
} else {
// draw our snow material
return Voxel(234);
}
}
}
// else we return the underlying voxel, unmodified
voxel.clone()
},
));
commands.entity(snowflake).insert(ToBeDespawned);
}
Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,11 @@ mod tests;
use load::RawVoxel;
#[doc(inline)]
use load::VoxSceneLoader;
pub use load::{VoxLoaderSettings, Voxel, VoxelData};
pub use scene::modify::{BoxRegion, ModifyVoxelModel, VoxelRegion};
pub use load::{VoxLoaderSettings, Voxel};

pub use scene::{
modify::{BoxRegion, ModifyVoxelModel, VoxelRegion},
queryable::VoxelQueryable,
VoxelLayer, VoxelModel, VoxelModelInstance, VoxelScene, VoxelSceneBundle, VoxelSceneHook,
VoxelSceneHookBundle,
};
Expand Down
5 changes: 3 additions & 2 deletions src/load/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::scene::{LayerInfo, VoxelModel, VoxelNode, VoxelScene};
pub(crate) use voxel::RawVoxel;
pub use voxel::{Voxel, VoxelData};
pub use voxel::Voxel;
pub(crate) use voxel::{RawVoxel, VoxelData};

/// An asset loader capable of loading models in `.vox` files as usable [`bevy::render::mesh::Mesh`]es.
///
/// It converts Magica Voxel's left-handed Z-up space to bevy's right-handed Y-up space.
Expand Down
107 changes: 14 additions & 93 deletions src/load/voxel.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
use bevy::{
math::{BVec3, UVec3, Vec3},
render::mesh::Mesh,
transform::components::GlobalTransform,
utils::HashMap,
};
use bevy::{math::UVec3, render::mesh::Mesh, utils::HashMap};
use block_mesh::{MergeVoxel, Voxel as BlockyVoxel, VoxelVisibility};
use dot_vox::Model;
use ndshape::{RuntimeShape, Shape};

/// A Voxel. The value is its index in the Magica Voxel palette (1-255), with 0 reserved fpr [`Voxel::EMPTY`].
/// A Voxel. The value is its index in the Magica Voxel palette (1-255), with 0 reserved for [`Voxel::EMPTY`].
#[derive(Clone, PartialEq, Debug)]
pub struct Voxel(pub u8);

Expand Down Expand Up @@ -61,14 +56,21 @@ impl MergeVoxel for VisibleVoxel {
/// The voxel data used to create a mesh and a material.
///
/// Note that all coordinates are in Bevy's right-handed Y-up space
pub struct VoxelData {
pub(crate) shape: RuntimeShape<u32, 3>,
pub(crate) voxels: Vec<RawVoxel>,
pub(crate) ior_for_voxel: HashMap<u8, f32>,
pub(crate) struct VoxelData {
pub shape: RuntimeShape<u32, 3>,
pub voxels: Vec<RawVoxel>,
pub ior_for_voxel: HashMap<u8, f32>,
mesh_outer_faces: bool,
}

impl VoxelData {
/// The size of the voxel model.
pub(crate) fn size(&self) -> UVec3 {
let raw_size: UVec3 = self.shape.as_array().into();
raw_size - UVec3::splat(self.padding())
}

/// 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 {
2
Expand All @@ -77,72 +79,6 @@ impl VoxelData {
}
}

/// The size of the voxel model.
pub fn size(&self) -> UVec3 {
let raw_size: UVec3 = self.shape.as_array().into();
raw_size - UVec3::splat(self.padding())
}

/// Converts a global point to a point in voxel coordinates
///
/// ### Arguments
/// * `global_point` - the point in global space to convert
/// * `global_xform` - the [`bevy::transform::components::GlobalTransform`] of the entity that owns this [`crate::VoxelModelInstance`]
///
/// ### Returns
/// A voxel coordinate if the `global_point` lies within the voxel model's bounds, or None if it is outside
pub fn global_point_to_voxel_space(
&self,
global_point: Vec3,
global_xform: &GlobalTransform,
) -> Option<UVec3> {
let local_position = global_xform
.affine()
.inverse()
.transform_point3(global_point);
self.local_point_to_voxel_space(local_position)
}

/// Converts a local point to a point in voxel coordinates
///
/// ### Arguments
/// * `local_point` - the point in the local space of the entity that owns this [`crate::VoxelModelInstance`]
///
/// ### Returns
/// A voxel coordinate if the `local_point` lies within the voxel model's bounds, or None if it is outside
pub fn local_point_to_voxel_space(&self, local_point: Vec3) -> Option<UVec3> {
let size = self.size();
let half_extents = Vec3::new(size.x as f32, size.y as f32, size.z as f32) * 0.5;
if local_point.abs().greater_than_or_equal(half_extents).any() {
return None;
};
let voxel_postition = local_point + half_extents;
Some(UVec3::new(
voxel_postition.x as u32,
voxel_postition.y as u32,
voxel_postition.z as u32,
))
}

/// Returns the [`Voxel`] at the point (given in voxel space)
///
/// ### Arguments
/// * `position` - the position in voxel space
///
/// ### Returns
/// the voxel at this point. If the point lies outside the bounds of the model, it will return [`Voxel::EMPTY`].
pub fn get_voxel_at_point(&self, position: UVec3) -> Voxel {
let leading_padding = UVec3::splat(self.padding() / 2);
let index = self.shape.linearize((position + leading_padding).into()) as usize;
let voxel: Voxel = self
.voxels
.get(index)
.unwrap_or(&RawVoxel::EMPTY)
.clone()
.into();
voxel
}

pub(crate) fn remesh(&self) -> Mesh {
let (visible_voxels, _) = self.visible_voxels();
super::mesh::mesh_model(&visible_voxels, &self)
Expand Down Expand Up @@ -183,6 +119,7 @@ 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(crate) fn load_from_model(
model: &Model,
ior_for_voxel: &HashMap<u8, f32>,
Expand Down Expand Up @@ -214,19 +151,3 @@ pub(crate) fn load_from_model(
mesh_outer_faces,
}
}

trait BitwiseComparable {
fn less_than(&self, other: Vec3) -> BVec3;

fn greater_than_or_equal(&self, other: Vec3) -> BVec3;
}

impl BitwiseComparable for Vec3 {
fn less_than(&self, other: Vec3) -> BVec3 {
BVec3::new(self.x < other.x, self.y < other.y, self.z < other.z)
}

fn greater_than_or_equal(&self, other: Vec3) -> BVec3 {
!self.less_than(other)
}
}
7 changes: 4 additions & 3 deletions src/scene/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod hook;
pub(super) mod modify;
pub(super) mod queryable;
pub(super) mod systems;

use bevy::{
Expand Down Expand Up @@ -124,11 +125,11 @@ pub(crate) struct VoxelNode {
#[derive(Asset, TypePath)]
pub struct VoxelModel {
/// The voxel data used to generate the mesh
pub data: VoxelData,
pub(crate) data: VoxelData,
/// Handle to the model's mesh
pub mesh: Handle<Mesh>,
pub(crate) mesh: Handle<Mesh>,
/// Handle to the model's material
pub material: Handle<StandardMaterial>,
pub(crate) material: Handle<StandardMaterial>,
}

#[derive(Debug, Clone)]
Expand Down
Loading

0 comments on commit 1c2a89c

Please sign in to comment.