Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raycast fn using fast traversal #25

Merged
merged 11 commits into from
Jun 5, 2024
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 18 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ futures-lite = "2.0.0"
rand = "0.8.5"
ahash = "0.7.8"
weak-table = { version = "0.3.2", features = ["ahash"] }
noise = { version = "0.8.2", optional = true }
smooth-bevy-cameras = { version = "0.11.0", optional = true }

[dev-dependencies]
noise = "0.8.2"

[[example]]
name = "fast_traversal_ray"
required-features = ["smooth-bevy-cameras"]

[[example]]
name = "bombs"
required-features = ["noise"]

[[example]]
name = "noise_terrain"
required-features = ["noise"]

[[example]]
name = "multiple_worlds"
required-features = ["noise"]
239 changes: 239 additions & 0 deletions examples/fast_traversal_ray.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
use std::sync::Arc;

use bevy::prelude::*;
use smooth_bevy_cameras::{
controllers::unreal::{UnrealCameraBundle, UnrealCameraController, UnrealCameraPlugin},
LookTransformPlugin,
};

use bevy_voxel_world::{prelude::*, traversal_alg::*};

// Declare materials as consts for convenience
const SNOWY_BRICK: u8 = 0;
const FULL_BRICK: u8 = 1;
const GRASS: u8 = 2;

#[derive(Resource, Clone, Default)]
struct MyMainWorld;

#[derive(Resource, Clone, Default)]
struct VoxelTrace {
start: Option<Vec3>,
end: Vec3,
}

impl VoxelWorldConfig for MyMainWorld {
fn texture_index_mapper(&self) -> Arc<dyn Fn(u8) -> [u32; 3] + Send + Sync> {
Arc::new(|vox_mat: u8| match vox_mat {
SNOWY_BRICK => [0, 1, 2],
FULL_BRICK => [2, 2, 2],
_ => [3, 3, 3],
})
}

fn voxel_texture(&self) -> Option<(String, u32)> {
Some(("example_voxel_texture.png".into(), 4))
}
}

/// Controls:
/// - Left-click to place a voxel somewhere
/// - Ctrl + Left click to place the source of a voxel line trace
/// - Hold Ctrl to see the trace end follow the cursor
/// - Hold Ctrl and hit E to erase all solid voxels intersecting the trace
/// - Release Ctrl to stop tracing
/// - Unreal controls to move the camera around
fn main() {
App::new()
.add_plugins(DefaultPlugins)
// We can specify a custom texture when initializing the plugin.
// This should just be a path to an image in your assets folder.
.add_plugins((
VoxelWorldPlugin::with_config(MyMainWorld),
LookTransformPlugin,
UnrealCameraPlugin::default(),
))
.init_resource::<VoxelTrace>()
.add_systems(Startup, (setup, create_voxel_scene))
.add_systems(Update, (inputs, update_cursor_cube, draw_trace))
.run();
}

#[derive(Component)]
struct CursorCube {
voxel_pos: IVec3,
voxel_mat: u8,
}

fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Cursor cube
commands.spawn((
PbrBundle {
mesh: meshes.add(Mesh::from(Cuboid {
half_size: Vec3::splat(0.5),
})),
material: materials.add(Color::rgba_u8(124, 144, 255, 128)),
transform: Transform::from_xyz(0.0, -10.0, 0.0),
..default()
},
CursorCube {
voxel_pos: IVec3::new(0, -10, 0),
voxel_mat: FULL_BRICK,
},
));

// Camera
commands
.spawn((
Camera3dBundle::default(),
// This tells bevy_voxel_world to use this cameras transform to calculate spawning area
VoxelWorldCamera::<MyMainWorld>::default(),
))
.insert(UnrealCameraBundle::new(
UnrealCameraController::default(),
Vec3::new(10.0, 10.0, 10.0),
Vec3::ZERO,
Vec3::Y,
));

// light
commands.spawn(PointLightBundle {
point_light: PointLight {
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
}

fn create_voxel_scene(mut voxel_world: VoxelWorld<MyMainWorld>) {
// Then we can use the `u8` consts to specify the type of voxel

// 20 by 20 floor
for x in -10..10 {
for z in -10..10 {
voxel_world.set_voxel(IVec3::new(x, -1, z), WorldVoxel::Solid(GRASS));
// Grassy floor
}
}

// Some bricks
voxel_world.set_voxel(IVec3::new(0, 0, 0), WorldVoxel::Solid(SNOWY_BRICK));
voxel_world.set_voxel(IVec3::new(1, 0, 0), WorldVoxel::Solid(SNOWY_BRICK));
voxel_world.set_voxel(IVec3::new(0, 0, 1), WorldVoxel::Solid(SNOWY_BRICK));
voxel_world.set_voxel(IVec3::new(0, 0, -1), WorldVoxel::Solid(SNOWY_BRICK));
voxel_world.set_voxel(IVec3::new(-1, 0, 0), WorldVoxel::Solid(FULL_BRICK));
voxel_world.set_voxel(IVec3::new(-2, 0, 0), WorldVoxel::Solid(FULL_BRICK));
voxel_world.set_voxel(IVec3::new(-1, 1, 0), WorldVoxel::Solid(SNOWY_BRICK));
voxel_world.set_voxel(IVec3::new(-2, 1, 0), WorldVoxel::Solid(SNOWY_BRICK));
voxel_world.set_voxel(IVec3::new(0, 1, 0), WorldVoxel::Solid(SNOWY_BRICK));
}

fn update_cursor_cube(
voxel_world_raycast: VoxelWorld<MyMainWorld>,
mut trace: ResMut<VoxelTrace>,
camera_info: Query<(&Camera, &GlobalTransform), With<VoxelWorldCamera<MyMainWorld>>>,
mut cursor_evr: EventReader<CursorMoved>,
mut cursor_cube: Query<(&mut Transform, &mut CursorCube)>,
) {
for ev in cursor_evr.read() {
// Get a ray from the cursor position into the world
let (camera, cam_gtf) = camera_info.single();
let Some(ray) = camera.viewport_to_world(cam_gtf, ev.position) else {
return;
};

if let Some(result) = voxel_world_raycast.raycast(ray, &|(_pos, _vox)| true) {
let (mut transform, mut cursor_cube) = cursor_cube.single_mut();

// Camera could end up inside geometry - in that case just ignore the trace
if let Some(normal) = result.normal {
// Move the cursor cube to the position of the voxel we hit
let voxel_pos = result.position + normal;
transform.translation = voxel_pos + Vec3::splat(VOXEL_SIZE / 2.);
cursor_cube.voxel_pos = voxel_pos.as_ivec3();

// Update current trace end to the cursor cube position
trace.end = transform.translation;
}
}
}
}

fn draw_trace(trace: Res<VoxelTrace>, mut gizmos: Gizmos) {
if let Some(trace_start) = trace.start {
gizmos.line(trace_start, trace.end, Color::RED);

voxel_line_traversal(trace_start, trace.end, |voxel_coord, time, face| {
let voxel_center = voxel_coord.as_vec3() + Vec3::splat(VOXEL_SIZE / 2.);

gizmos.cuboid(
Transform::from_translation(voxel_center).with_scale(Vec3::splat(VOXEL_SIZE)),
Color::PINK,
);

if let Ok(normal) = face.try_into() {
gizmos.circle(
voxel_center + (normal * VOXEL_SIZE / 2.),
Direction3d::new(normal).unwrap(),
0.8 * VOXEL_SIZE / 2.,
Color::RED.with_a(0.5),
);

gizmos.sphere(
trace_start + (trace.end - trace_start) * time,
Quat::IDENTITY,
0.1,
Color::BLACK,
);
}

// Keep drawing until trace has finished visiting all voxels along the way
const NEVER_STOP: bool = true;
NEVER_STOP
});
}
}

fn inputs(
buttons: Res<ButtonInput<MouseButton>>,
keys: Res<ButtonInput<KeyCode>>,
mut voxel_world: VoxelWorld<MyMainWorld>,
mut trace: ResMut<VoxelTrace>,
cursor_cube: Query<&CursorCube>,
) {
if keys.just_released(KeyCode::ControlLeft) {
trace.start = None;
} else if keys.pressed(KeyCode::ControlLeft) && keys.just_pressed(KeyCode::KeyE) {
let cursor = cursor_cube.single();
let trace_end = cursor.voxel_pos.as_vec3() + Vec3::splat(VOXEL_SIZE / 2.);

voxel_line_traversal(
trace.start.unwrap(),
trace_end,
|voxel_coords, _time, _face| {
voxel_world.set_voxel(voxel_coords, WorldVoxel::Air);

// Keep erasing until trace has finished visiting all voxels along the way
const NEVER_STOP: bool = true;
NEVER_STOP
},
);
}

if buttons.just_pressed(MouseButton::Left) {
let cursor = cursor_cube.single();

if keys.pressed(KeyCode::ControlLeft) {
trace.start = Some(cursor.voxel_pos.as_vec3() + Vec3::splat(VOXEL_SIZE / 2.))
} else {
let vox_pos = cursor.voxel_pos;
voxel_world.set_voxel(vox_pos, WorldVoxel::Solid(cursor.voxel_mat));
}
}
}
7 changes: 5 additions & 2 deletions examples/ray_cast.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::sync::Arc;

use bevy::prelude::*;

use bevy_voxel_world::prelude::*;
use std::sync::Arc;

// Declare materials as consts for convenience
const SNOWY_BRICK: u8 = 0;
Expand Down Expand Up @@ -120,7 +122,8 @@ fn update_cursor_cube(
if let Some(result) = voxel_world_raycast.raycast(ray, &|(_pos, _vox)| true) {
let (mut transform, mut cursor_cube) = cursor_cube.single_mut();
// Move the cursor cube to the position of the voxel we hit
let voxel_pos = result.position + result.normal;
// Camera is by construction not in a solid voxel, so result.normal must be Some(...)
let voxel_pos = result.position + result.normal.unwrap();
transform.translation = voxel_pos + Vec3::new(0.5, 0.5, 0.5);
cursor_cube.voxel_pos = voxel_pos.as_ivec3();
}
Expand Down
Loading
Loading