Last active
February 14, 2024 19:54
-
-
Save rparrett/8835a47d65f372763983e4eb7e783e25 to your computer and use it in GitHub Desktop.
Bevy 0.13 `3d_shapes.rs` but with normal debugging gizmos and wireframes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! This example demonstrates the built-in 3d shapes in Bevy. | |
//! The scene includes a patterned texture and a rotation for visualizing the normals and UVs. | |
use std::f32::consts::PI; | |
use bevy::{ | |
input::common_conditions::input_just_pressed, | |
pbr::wireframe::{Wireframe, WireframePlugin}, | |
prelude::*, | |
render::{ | |
mesh::VertexAttributeValues, | |
render_asset::RenderAssetUsages, | |
render_resource::{Extent3d, TextureDimension, TextureFormat}, | |
}, | |
}; | |
fn main() { | |
App::new() | |
.add_plugins(( | |
DefaultPlugins.set(ImagePlugin::default_nearest()), | |
WireframePlugin, | |
)) | |
.init_resource::<Debug>() | |
.add_systems(Startup, setup) | |
.add_systems( | |
Update, | |
( | |
rotate, | |
debug_normals.run_if(resource_equals::<Debug>(Debug(true))), | |
toggle_debug.run_if(input_just_pressed(KeyCode::Space)), | |
), | |
) | |
.run(); | |
} | |
/// A marker component for our shapes so we can query them separately from the ground plane | |
#[derive(Component)] | |
struct Shape; | |
#[derive(Resource, Deref, DerefMut, Default, PartialEq)] | |
struct Debug(bool); | |
const X_EXTENT: f32 = 12.0; | |
const NORMAL_SCALE: f32 = 0.1; | |
fn setup( | |
mut commands: Commands, | |
mut meshes: ResMut<Assets<Mesh>>, | |
mut images: ResMut<Assets<Image>>, | |
mut materials: ResMut<Assets<StandardMaterial>>, | |
) { | |
let debug_material = materials.add(StandardMaterial { | |
base_color_texture: Some(images.add(uv_debug_texture())), | |
..default() | |
}); | |
let shapes = [ | |
meshes.add(Cuboid::default()), | |
meshes.add(Capsule3d::default()), | |
meshes.add(Torus::default()), | |
meshes.add(Cylinder::default()), | |
meshes.add(Sphere::default().mesh().ico(5).unwrap()), | |
meshes.add(Sphere::default().mesh().uv(32, 18)), | |
]; | |
let num_shapes = shapes.len(); | |
for (i, shape) in shapes.into_iter().enumerate() { | |
commands.spawn(( | |
PbrBundle { | |
mesh: shape, | |
material: debug_material.clone(), | |
transform: Transform::from_xyz( | |
-X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * X_EXTENT, | |
2.0, | |
0.0, | |
) | |
.with_rotation(Quat::from_rotation_x(-PI / 4.)), | |
..default() | |
}, | |
Shape, | |
)); | |
} | |
commands.spawn(PointLightBundle { | |
point_light: PointLight { | |
intensity: 1500000.0, | |
range: 100., | |
shadows_enabled: true, | |
..default() | |
}, | |
transform: Transform::from_xyz(8.0, 16.0, 8.0), | |
..default() | |
}); | |
// ground plane | |
commands.spawn(PbrBundle { | |
mesh: meshes.add(Plane3d::default().mesh().size(50.0, 50.0)), | |
material: materials.add(Color::SILVER), | |
..default() | |
}); | |
commands.spawn(Camera3dBundle { | |
transform: Transform::from_xyz(0.0, 6., 12.0).looking_at(Vec3::new(0., 1., 0.), Vec3::Y), | |
..default() | |
}); | |
} | |
fn rotate(mut query: Query<&mut Transform, With<Shape>>, time: Res<Time>) { | |
for mut transform in &mut query { | |
transform.rotate_y(time.delta_seconds() / 2.); | |
} | |
} | |
/// Creates a colorful test pattern | |
fn uv_debug_texture() -> Image { | |
const TEXTURE_SIZE: usize = 8; | |
let mut palette: [u8; 32] = [ | |
255, 102, 159, 255, 255, 159, 102, 255, 236, 255, 102, 255, 121, 255, 102, 255, 102, 255, | |
198, 255, 102, 198, 255, 255, 121, 102, 255, 255, 236, 102, 255, 255, | |
]; | |
let mut texture_data = [0; TEXTURE_SIZE * TEXTURE_SIZE * 4]; | |
for y in 0..TEXTURE_SIZE { | |
let offset = TEXTURE_SIZE * y * 4; | |
texture_data[offset..(offset + TEXTURE_SIZE * 4)].copy_from_slice(&palette); | |
palette.rotate_right(4); | |
} | |
Image::new_fill( | |
Extent3d { | |
width: TEXTURE_SIZE as u32, | |
height: TEXTURE_SIZE as u32, | |
depth_or_array_layers: 1, | |
}, | |
TextureDimension::D2, | |
&texture_data, | |
TextureFormat::Rgba8UnormSrgb, | |
RenderAssetUsages::RENDER_WORLD, | |
) | |
} | |
fn toggle_debug( | |
mut commands: Commands, | |
mut debug: ResMut<Debug>, | |
shapes: Query<Entity, With<Shape>>, | |
) { | |
**debug = !**debug; | |
if **debug { | |
for entity in &shapes { | |
commands.entity(entity).insert(Wireframe); | |
} | |
} else { | |
for entity in &shapes { | |
commands.entity(entity).remove::<Wireframe>(); | |
} | |
} | |
} | |
fn debug_normals( | |
handles: Query<(&GlobalTransform, &Handle<Mesh>), With<Shape>>, | |
meshes: Res<Assets<Mesh>>, | |
mut gizmos: Gizmos, | |
) { | |
for (transform, handle) in &handles { | |
let Some(mesh) = meshes.get(handle) else { | |
continue; | |
}; | |
let Some(VertexAttributeValues::Float32x3(position_vals)) = | |
mesh.attribute(Mesh::ATTRIBUTE_POSITION) | |
else { | |
continue; | |
}; | |
let Some(VertexAttributeValues::Float32x3(normal_vals)) = | |
mesh.attribute(Mesh::ATTRIBUTE_NORMAL) | |
else { | |
continue; | |
}; | |
for (position, normal) in position_vals.iter().zip(normal_vals.iter()) { | |
let (_, rotation, _) = transform.to_scale_rotation_translation(); | |
let position = transform.transform_point(Vec3::from(*position)); | |
let normal = Vec3::from(*normal); | |
let color = if normal.is_normalized() { | |
Color::WHITE | |
} else { | |
Color::TOMATO | |
}; | |
let normal = (rotation * normal * NORMAL_SCALE) + position; | |
gizmos.arrow(position, normal, color); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment