 54006b107b
			
		
	
	
		54006b107b
		
			
		
	
	
	
	
		
			
			# Objective A big step in the migration to required components: meshes and materials! ## Solution As per the [selected proposal](https://hackmd.io/@bevy/required_components/%2Fj9-PnF-2QKK0on1KQ29UWQ): - Deprecate `MaterialMesh2dBundle`, `MaterialMeshBundle`, and `PbrBundle`. - Add `Mesh2d` and `Mesh3d` components, which wrap a `Handle<Mesh>`. - Add `MeshMaterial2d<M: Material2d>` and `MeshMaterial3d<M: Material>`, which wrap a `Handle<M>`. - Meshes *without* a mesh material should be rendered with a default material. The existence of a material is determined by `HasMaterial2d`/`HasMaterial3d`, which is required by `MeshMaterial2d`/`MeshMaterial3d`. This gets around problems with the generics. Previously: ```rust commands.spawn(MaterialMesh2dBundle { mesh: meshes.add(Circle::new(100.0)).into(), material: materials.add(Color::srgb(7.5, 0.0, 7.5)), transform: Transform::from_translation(Vec3::new(-200., 0., 0.)), ..default() }); ``` Now: ```rust commands.spawn(( Mesh2d(meshes.add(Circle::new(100.0))), MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))), Transform::from_translation(Vec3::new(-200., 0., 0.)), )); ``` If the mesh material is missing, previously nothing was rendered. Now, it renders a white default `ColorMaterial` in 2D and a `StandardMaterial` in 3D (this can be overridden). Below, only every other entity has a material:   Why white? This is still open for discussion, but I think white makes sense for a *default* material, while *invalid* asset handles pointing to nothing should have something like a pink material to indicate that something is broken (I don't handle that in this PR yet). This is kind of a mix of Godot and Unity: Godot just renders a white material for non-existent materials, while Unity renders nothing when no materials exist, but renders pink for invalid materials. I can also change the default material to pink if that is preferable though. ## Testing I ran some 2D and 3D examples to test if anything changed visually. I have not tested all examples or features yet however. If anyone wants to test more extensively, it would be appreciated! ## Implementation Notes - The relationship between `bevy_render` and `bevy_pbr` is weird here. `bevy_render` needs `Mesh3d` for its own systems, but `bevy_pbr` has all of the material logic, and `bevy_render` doesn't depend on it. I feel like the two crates should be refactored in some way, but I think that's out of scope for this PR. - I didn't migrate meshlets to required components yet. That can probably be done in a follow-up, as this is already a huge PR. - It is becoming increasingly clear to me that we really, *really* want to disallow raw asset handles as components. They caused me a *ton* of headache here already, and it took me a long time to find every place that queried for them or inserted them directly on entities, since there were no compiler errors for it. If we don't remove the `Component` derive, I expect raw asset handles to be a *huge* footgun for users as we transition to wrapper components, especially as handles as components have been the norm so far. I personally consider this to be a blocker for 0.15: we need to migrate to wrapper components for asset handles everywhere, and remove the `Component` derive. Also see https://github.com/bevyengine/bevy/issues/14124. --- ## Migration Guide Asset handles for meshes and mesh materials must now be wrapped in the `Mesh2d` and `MeshMaterial2d` or `Mesh3d` and `MeshMaterial3d` components for 2D and 3D respectively. Raw handles as components no longer render meshes. Additionally, `MaterialMesh2dBundle`, `MaterialMeshBundle`, and `PbrBundle` have been deprecated. Instead, use the mesh and material components directly. Previously: ```rust commands.spawn(MaterialMesh2dBundle { mesh: meshes.add(Circle::new(100.0)).into(), material: materials.add(Color::srgb(7.5, 0.0, 7.5)), transform: Transform::from_translation(Vec3::new(-200., 0., 0.)), ..default() }); ``` Now: ```rust commands.spawn(( Mesh2d(meshes.add(Circle::new(100.0))), MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))), Transform::from_translation(Vec3::new(-200., 0., 0.)), )); ``` If the mesh material is missing, a white default material is now used. Previously, nothing was rendered if the material was missing. The `WithMesh2d` and `WithMesh3d` query filter type aliases have also been removed. Simply use `With<Mesh2d>` or `With<Mesh3d>`. --------- Co-authored-by: Tim Blackbird <justthecooldude@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
		
			
				
	
	
		
			710 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			710 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! This example demonstrates how each of Bevy's math primitives look like in 2D and 3D with meshes
 | |
| //! and with gizmos
 | |
| #![allow(clippy::match_same_arms)]
 | |
| 
 | |
| use bevy::{input::common_conditions::input_just_pressed, math::Isometry2d, prelude::*};
 | |
| 
 | |
| const LEFT_RIGHT_OFFSET_2D: f32 = 200.0;
 | |
| const LEFT_RIGHT_OFFSET_3D: f32 = 2.0;
 | |
| 
 | |
| fn main() {
 | |
|     let mut app = App::new();
 | |
| 
 | |
|     app.add_plugins(DefaultPlugins)
 | |
|         .init_state::<PrimitiveSelected>()
 | |
|         .init_state::<CameraActive>();
 | |
| 
 | |
|     // cameras
 | |
|     app.add_systems(Startup, (setup_cameras, setup_lights, setup_ambient_light))
 | |
|         .add_systems(
 | |
|             Update,
 | |
|             (
 | |
|                 update_active_cameras.run_if(state_changed::<CameraActive>),
 | |
|                 switch_cameras.run_if(input_just_pressed(KeyCode::KeyC)),
 | |
|             ),
 | |
|         );
 | |
| 
 | |
|     // text
 | |
| 
 | |
|     // PostStartup since we need the cameras to exist
 | |
|     app.add_systems(PostStartup, setup_text);
 | |
|     app.add_systems(
 | |
|         Update,
 | |
|         (update_text.run_if(state_changed::<PrimitiveSelected>),),
 | |
|     );
 | |
| 
 | |
|     // primitives
 | |
|     app.add_systems(Startup, (spawn_primitive_2d, spawn_primitive_3d))
 | |
|         .add_systems(
 | |
|             Update,
 | |
|             (
 | |
|                 switch_to_next_primitive.run_if(input_just_pressed(KeyCode::ArrowUp)),
 | |
|                 switch_to_previous_primitive.run_if(input_just_pressed(KeyCode::ArrowDown)),
 | |
|                 draw_gizmos_2d.run_if(in_mode(CameraActive::Dim2)),
 | |
|                 draw_gizmos_3d.run_if(in_mode(CameraActive::Dim3)),
 | |
|                 update_primitive_meshes
 | |
|                     .run_if(state_changed::<PrimitiveSelected>.or(state_changed::<CameraActive>)),
 | |
|                 rotate_primitive_2d_meshes,
 | |
|                 rotate_primitive_3d_meshes,
 | |
|             ),
 | |
|         );
 | |
| 
 | |
|     app.run();
 | |
| }
 | |
| 
 | |
| /// State for tracking which of the two cameras (2D & 3D) is currently active
 | |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States, Default, Reflect)]
 | |
| enum CameraActive {
 | |
|     #[default]
 | |
|     /// 2D Camera is active
 | |
|     Dim2,
 | |
|     /// 3D Camera is active
 | |
|     Dim3,
 | |
| }
 | |
| 
 | |
| /// State for tracking which primitives are currently displayed
 | |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States, Default, Reflect)]
 | |
| enum PrimitiveSelected {
 | |
|     #[default]
 | |
|     RectangleAndCuboid,
 | |
|     CircleAndSphere,
 | |
|     Ellipse,
 | |
|     Triangle,
 | |
|     Plane,
 | |
|     Line,
 | |
|     Segment,
 | |
|     Polyline,
 | |
|     Polygon,
 | |
|     RegularPolygon,
 | |
|     Capsule,
 | |
|     Cylinder,
 | |
|     Cone,
 | |
|     ConicalFrustum,
 | |
|     Torus,
 | |
|     Tetrahedron,
 | |
|     Arc,
 | |
|     CircularSector,
 | |
|     CircularSegment,
 | |
| }
 | |
| 
 | |
| impl std::fmt::Display for PrimitiveSelected {
 | |
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | |
|         let name = match self {
 | |
|             PrimitiveSelected::RectangleAndCuboid => String::from("Rectangle/Cuboid"),
 | |
|             PrimitiveSelected::CircleAndSphere => String::from("Circle/Sphere"),
 | |
|             other => format!("{other:?}"),
 | |
|         };
 | |
|         write!(f, "{name}")
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl PrimitiveSelected {
 | |
|     const ALL: [Self; 19] = [
 | |
|         Self::RectangleAndCuboid,
 | |
|         Self::CircleAndSphere,
 | |
|         Self::Ellipse,
 | |
|         Self::Triangle,
 | |
|         Self::Plane,
 | |
|         Self::Line,
 | |
|         Self::Segment,
 | |
|         Self::Polyline,
 | |
|         Self::Polygon,
 | |
|         Self::RegularPolygon,
 | |
|         Self::Capsule,
 | |
|         Self::Cylinder,
 | |
|         Self::Cone,
 | |
|         Self::ConicalFrustum,
 | |
|         Self::Torus,
 | |
|         Self::Tetrahedron,
 | |
|         Self::Arc,
 | |
|         Self::CircularSector,
 | |
|         Self::CircularSegment,
 | |
|     ];
 | |
| 
 | |
|     fn next(self) -> Self {
 | |
|         Self::ALL
 | |
|             .into_iter()
 | |
|             .cycle()
 | |
|             .skip_while(|&x| x != self)
 | |
|             .nth(1)
 | |
|             .unwrap()
 | |
|     }
 | |
| 
 | |
|     fn previous(self) -> Self {
 | |
|         Self::ALL
 | |
|             .into_iter()
 | |
|             .rev()
 | |
|             .cycle()
 | |
|             .skip_while(|&x| x != self)
 | |
|             .nth(1)
 | |
|             .unwrap()
 | |
|     }
 | |
| }
 | |
| 
 | |
| const SMALL_2D: f32 = 50.0;
 | |
| const BIG_2D: f32 = 100.0;
 | |
| 
 | |
| const SMALL_3D: f32 = 0.5;
 | |
| const BIG_3D: f32 = 1.0;
 | |
| 
 | |
| // primitives
 | |
| const RECTANGLE: Rectangle = Rectangle {
 | |
|     half_size: Vec2::new(SMALL_2D, BIG_2D),
 | |
| };
 | |
| const CUBOID: Cuboid = Cuboid {
 | |
|     half_size: Vec3::new(BIG_3D, SMALL_3D, BIG_3D),
 | |
| };
 | |
| 
 | |
| const CIRCLE: Circle = Circle { radius: BIG_2D };
 | |
| const SPHERE: Sphere = Sphere { radius: BIG_3D };
 | |
| 
 | |
| const ELLIPSE: Ellipse = Ellipse {
 | |
|     half_size: Vec2::new(BIG_2D, SMALL_2D),
 | |
| };
 | |
| 
 | |
| const TRIANGLE_2D: Triangle2d = Triangle2d {
 | |
|     vertices: [
 | |
|         Vec2::new(BIG_2D, 0.0),
 | |
|         Vec2::new(0.0, BIG_2D),
 | |
|         Vec2::new(-BIG_2D, 0.0),
 | |
|     ],
 | |
| };
 | |
| 
 | |
| const TRIANGLE_3D: Triangle3d = Triangle3d {
 | |
|     vertices: [
 | |
|         Vec3::new(BIG_3D, 0.0, 0.0),
 | |
|         Vec3::new(0.0, BIG_3D, 0.0),
 | |
|         Vec3::new(-BIG_3D, 0.0, 0.0),
 | |
|     ],
 | |
| };
 | |
| 
 | |
| const PLANE_2D: Plane2d = Plane2d { normal: Dir2::Y };
 | |
| const PLANE_3D: Plane3d = Plane3d {
 | |
|     normal: Dir3::Y,
 | |
|     half_size: Vec2::new(BIG_3D, BIG_3D),
 | |
| };
 | |
| 
 | |
| const LINE2D: Line2d = Line2d { direction: Dir2::X };
 | |
| const LINE3D: Line3d = Line3d { direction: Dir3::X };
 | |
| 
 | |
| const SEGMENT_2D: Segment2d = Segment2d {
 | |
|     direction: Dir2::X,
 | |
|     half_length: BIG_2D,
 | |
| };
 | |
| const SEGMENT_3D: Segment3d = Segment3d {
 | |
|     direction: Dir3::X,
 | |
|     half_length: BIG_3D,
 | |
| };
 | |
| 
 | |
| const POLYLINE_2D: Polyline2d<4> = Polyline2d {
 | |
|     vertices: [
 | |
|         Vec2::new(-BIG_2D, -SMALL_2D),
 | |
|         Vec2::new(-SMALL_2D, SMALL_2D),
 | |
|         Vec2::new(SMALL_2D, -SMALL_2D),
 | |
|         Vec2::new(BIG_2D, SMALL_2D),
 | |
|     ],
 | |
| };
 | |
| const POLYLINE_3D: Polyline3d<4> = Polyline3d {
 | |
|     vertices: [
 | |
|         Vec3::new(-BIG_3D, -SMALL_3D, -SMALL_3D),
 | |
|         Vec3::new(SMALL_3D, SMALL_3D, 0.0),
 | |
|         Vec3::new(-SMALL_3D, -SMALL_3D, 0.0),
 | |
|         Vec3::new(BIG_3D, SMALL_3D, SMALL_3D),
 | |
|     ],
 | |
| };
 | |
| 
 | |
| const POLYGON_2D: Polygon<5> = Polygon {
 | |
|     vertices: [
 | |
|         Vec2::new(-BIG_2D, -SMALL_2D),
 | |
|         Vec2::new(BIG_2D, -SMALL_2D),
 | |
|         Vec2::new(BIG_2D, SMALL_2D),
 | |
|         Vec2::new(0.0, 0.0),
 | |
|         Vec2::new(-BIG_2D, SMALL_2D),
 | |
|     ],
 | |
| };
 | |
| 
 | |
| const REGULAR_POLYGON: RegularPolygon = RegularPolygon {
 | |
|     circumcircle: Circle { radius: BIG_2D },
 | |
|     sides: 5,
 | |
| };
 | |
| 
 | |
| const CAPSULE_2D: Capsule2d = Capsule2d {
 | |
|     radius: SMALL_2D,
 | |
|     half_length: SMALL_2D,
 | |
| };
 | |
| const CAPSULE_3D: Capsule3d = Capsule3d {
 | |
|     radius: SMALL_3D,
 | |
|     half_length: SMALL_3D,
 | |
| };
 | |
| 
 | |
| const CYLINDER: Cylinder = Cylinder {
 | |
|     radius: SMALL_3D,
 | |
|     half_height: SMALL_3D,
 | |
| };
 | |
| 
 | |
| const CONE: Cone = Cone {
 | |
|     radius: BIG_3D,
 | |
|     height: BIG_3D,
 | |
| };
 | |
| 
 | |
| const CONICAL_FRUSTUM: ConicalFrustum = ConicalFrustum {
 | |
|     radius_top: BIG_3D,
 | |
|     radius_bottom: SMALL_3D,
 | |
|     height: BIG_3D,
 | |
| };
 | |
| 
 | |
| const ANNULUS: Annulus = Annulus {
 | |
|     inner_circle: Circle { radius: SMALL_2D },
 | |
|     outer_circle: Circle { radius: BIG_2D },
 | |
| };
 | |
| 
 | |
| const TORUS: Torus = Torus {
 | |
|     minor_radius: SMALL_3D / 2.0,
 | |
|     major_radius: SMALL_3D * 1.5,
 | |
| };
 | |
| 
 | |
| const TETRAHEDRON: Tetrahedron = Tetrahedron {
 | |
|     vertices: [
 | |
|         Vec3::new(-BIG_3D, 0.0, 0.0),
 | |
|         Vec3::new(BIG_3D, 0.0, 0.0),
 | |
|         Vec3::new(0.0, 0.0, -BIG_3D * 1.67),
 | |
|         Vec3::new(0.0, BIG_3D * 1.67, -BIG_3D * 0.5),
 | |
|     ],
 | |
| };
 | |
| 
 | |
| const ARC: Arc2d = Arc2d {
 | |
|     radius: BIG_2D,
 | |
|     half_angle: std::f32::consts::FRAC_PI_4,
 | |
| };
 | |
| 
 | |
| const CIRCULAR_SECTOR: CircularSector = CircularSector {
 | |
|     arc: Arc2d {
 | |
|         radius: BIG_2D,
 | |
|         half_angle: std::f32::consts::FRAC_PI_4,
 | |
|     },
 | |
| };
 | |
| 
 | |
| const CIRCULAR_SEGMENT: CircularSegment = CircularSegment {
 | |
|     arc: Arc2d {
 | |
|         radius: BIG_2D,
 | |
|         half_angle: std::f32::consts::FRAC_PI_4,
 | |
|     },
 | |
| };
 | |
| 
 | |
| fn setup_cameras(mut commands: Commands) {
 | |
|     let start_in_2d = true;
 | |
|     let make_camera = |is_active| Camera {
 | |
|         is_active,
 | |
|         ..Default::default()
 | |
|     };
 | |
| 
 | |
|     commands.spawn(Camera2dBundle {
 | |
|         camera: make_camera(start_in_2d),
 | |
|         ..Default::default()
 | |
|     });
 | |
| 
 | |
|     commands.spawn(Camera3dBundle {
 | |
|         camera: make_camera(!start_in_2d),
 | |
|         transform: Transform::from_xyz(0.0, 10.0, 0.0).looking_at(Vec3::ZERO, Vec3::Z),
 | |
|         ..Default::default()
 | |
|     });
 | |
| }
 | |
| 
 | |
| fn setup_ambient_light(mut ambient_light: ResMut<AmbientLight>) {
 | |
|     ambient_light.brightness = 50.0;
 | |
| }
 | |
| 
 | |
| fn setup_lights(mut commands: Commands) {
 | |
|     commands.spawn((
 | |
|         PointLight {
 | |
|             intensity: 5000.0,
 | |
|             ..default()
 | |
|         },
 | |
|         Transform::from_translation(Vec3::new(-LEFT_RIGHT_OFFSET_3D, 2.0, 0.0))
 | |
|             .looking_at(Vec3::new(-LEFT_RIGHT_OFFSET_3D, 0.0, 0.0), Vec3::Y),
 | |
|     ));
 | |
| }
 | |
| 
 | |
| /// Marker component for header text
 | |
| #[derive(Debug, Clone, Component, Default, Reflect)]
 | |
| pub struct HeaderText;
 | |
| 
 | |
| /// Marker component for header node
 | |
| #[derive(Debug, Clone, Component, Default, Reflect)]
 | |
| pub struct HeaderNode;
 | |
| 
 | |
| fn update_active_cameras(
 | |
|     state: Res<State<CameraActive>>,
 | |
|     mut camera_2d: Query<(Entity, &mut Camera), With<Camera2d>>,
 | |
|     mut camera_3d: Query<(Entity, &mut Camera), (With<Camera3d>, Without<Camera2d>)>,
 | |
|     mut text: Query<&mut TargetCamera, With<HeaderNode>>,
 | |
| ) {
 | |
|     let (entity_2d, mut cam_2d) = camera_2d.single_mut();
 | |
|     let (entity_3d, mut cam_3d) = camera_3d.single_mut();
 | |
|     let is_camera_2d_active = matches!(*state.get(), CameraActive::Dim2);
 | |
| 
 | |
|     cam_2d.is_active = is_camera_2d_active;
 | |
|     cam_3d.is_active = !is_camera_2d_active;
 | |
| 
 | |
|     let active_camera = if is_camera_2d_active {
 | |
|         entity_2d
 | |
|     } else {
 | |
|         entity_3d
 | |
|     };
 | |
| 
 | |
|     text.iter_mut().for_each(|mut target_camera| {
 | |
|         *target_camera = TargetCamera(active_camera);
 | |
|     });
 | |
| }
 | |
| 
 | |
| fn switch_cameras(current: Res<State<CameraActive>>, mut next: ResMut<NextState<CameraActive>>) {
 | |
|     let next_state = match current.get() {
 | |
|         CameraActive::Dim2 => CameraActive::Dim3,
 | |
|         CameraActive::Dim3 => CameraActive::Dim2,
 | |
|     };
 | |
|     next.set(next_state);
 | |
| }
 | |
| 
 | |
| fn setup_text(mut commands: Commands, cameras: Query<(Entity, &Camera)>) {
 | |
|     let active_camera = cameras
 | |
|         .iter()
 | |
|         .find_map(|(entity, camera)| camera.is_active.then_some(entity))
 | |
|         .expect("run condition ensures existence");
 | |
|     let text = format!("{text}", text = PrimitiveSelected::default());
 | |
|     let style = TextStyle::default();
 | |
|     let instructions = "Press 'C' to switch between 2D and 3D mode\n\
 | |
|         Press 'Up' or 'Down' to switch to the next/previous primitive";
 | |
|     let text = [
 | |
|         TextSection::new("Primitive: ", style.clone()),
 | |
|         TextSection::new(text, style.clone()),
 | |
|         TextSection::new("\n\n", style.clone()),
 | |
|         TextSection::new(instructions, style.clone()),
 | |
|         TextSection::new("\n\n", style.clone()),
 | |
|         TextSection::new(
 | |
|             "(If nothing is displayed, there's no rendering support yet)",
 | |
|             style.clone(),
 | |
|         ),
 | |
|     ];
 | |
| 
 | |
|     commands
 | |
|         .spawn((
 | |
|             HeaderNode,
 | |
|             NodeBundle {
 | |
|                 style: Style {
 | |
|                     justify_self: JustifySelf::Center,
 | |
|                     top: Val::Px(5.0),
 | |
|                     ..Default::default()
 | |
|                 },
 | |
|                 ..Default::default()
 | |
|             },
 | |
|             TargetCamera(active_camera),
 | |
|         ))
 | |
|         .with_children(|parent| {
 | |
|             parent.spawn((
 | |
|                 HeaderText,
 | |
|                 TextBundle::from_sections(text).with_text_justify(JustifyText::Center),
 | |
|             ));
 | |
|         });
 | |
| }
 | |
| 
 | |
| fn update_text(
 | |
|     primitive_state: Res<State<PrimitiveSelected>>,
 | |
|     mut header: Query<&mut Text, With<HeaderText>>,
 | |
| ) {
 | |
|     let new_text = format!("{text}", text = primitive_state.get());
 | |
|     header.iter_mut().for_each(|mut header_text| {
 | |
|         if let Some(kind) = header_text.sections.get_mut(1) {
 | |
|             kind.value.clone_from(&new_text);
 | |
|         };
 | |
|     });
 | |
| }
 | |
| 
 | |
| fn switch_to_next_primitive(
 | |
|     current: Res<State<PrimitiveSelected>>,
 | |
|     mut next: ResMut<NextState<PrimitiveSelected>>,
 | |
| ) {
 | |
|     let next_state = current.get().next();
 | |
|     next.set(next_state);
 | |
| }
 | |
| 
 | |
| fn switch_to_previous_primitive(
 | |
|     current: Res<State<PrimitiveSelected>>,
 | |
|     mut next: ResMut<NextState<PrimitiveSelected>>,
 | |
| ) {
 | |
|     let next_state = current.get().previous();
 | |
|     next.set(next_state);
 | |
| }
 | |
| 
 | |
| fn in_mode(active: CameraActive) -> impl Fn(Res<State<CameraActive>>) -> bool {
 | |
|     move |state| *state.get() == active
 | |
| }
 | |
| 
 | |
| fn draw_gizmos_2d(mut gizmos: Gizmos, state: Res<State<PrimitiveSelected>>, time: Res<Time>) {
 | |
|     const POSITION: Vec2 = Vec2::new(-LEFT_RIGHT_OFFSET_2D, 0.0);
 | |
|     let angle = time.elapsed_seconds();
 | |
|     let isometry = Isometry2d::new(POSITION, Rot2::radians(angle));
 | |
|     let color = Color::WHITE;
 | |
| 
 | |
|     match state.get() {
 | |
|         PrimitiveSelected::RectangleAndCuboid => {
 | |
|             gizmos.primitive_2d(&RECTANGLE, isometry, color);
 | |
|         }
 | |
|         PrimitiveSelected::CircleAndSphere => {
 | |
|             gizmos.primitive_2d(&CIRCLE, isometry, color);
 | |
|         }
 | |
|         PrimitiveSelected::Ellipse => drop(gizmos.primitive_2d(&ELLIPSE, isometry, color)),
 | |
|         PrimitiveSelected::Triangle => gizmos.primitive_2d(&TRIANGLE_2D, isometry, color),
 | |
|         PrimitiveSelected::Plane => gizmos.primitive_2d(&PLANE_2D, isometry, color),
 | |
|         PrimitiveSelected::Line => drop(gizmos.primitive_2d(&LINE2D, isometry, color)),
 | |
|         PrimitiveSelected::Segment => {
 | |
|             drop(gizmos.primitive_2d(&SEGMENT_2D, isometry, color));
 | |
|         }
 | |
|         PrimitiveSelected::Polyline => gizmos.primitive_2d(&POLYLINE_2D, isometry, color),
 | |
|         PrimitiveSelected::Polygon => gizmos.primitive_2d(&POLYGON_2D, isometry, color),
 | |
|         PrimitiveSelected::RegularPolygon => {
 | |
|             gizmos.primitive_2d(®ULAR_POLYGON, isometry, color);
 | |
|         }
 | |
|         PrimitiveSelected::Capsule => gizmos.primitive_2d(&CAPSULE_2D, isometry, color),
 | |
|         PrimitiveSelected::Cylinder => {}
 | |
|         PrimitiveSelected::Cone => {}
 | |
|         PrimitiveSelected::ConicalFrustum => {}
 | |
|         PrimitiveSelected::Torus => drop(gizmos.primitive_2d(&ANNULUS, isometry, color)),
 | |
|         PrimitiveSelected::Tetrahedron => {}
 | |
|         PrimitiveSelected::Arc => gizmos.primitive_2d(&ARC, isometry, color),
 | |
|         PrimitiveSelected::CircularSector => {
 | |
|             gizmos.primitive_2d(&CIRCULAR_SECTOR, isometry, color);
 | |
|         }
 | |
|         PrimitiveSelected::CircularSegment => {
 | |
|             gizmos.primitive_2d(&CIRCULAR_SEGMENT, isometry, color);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Marker for primitive meshes to record in which state they should be visible in
 | |
| #[derive(Debug, Clone, Component, Default, Reflect)]
 | |
| pub struct PrimitiveData {
 | |
|     camera_mode: CameraActive,
 | |
|     primitive_state: PrimitiveSelected,
 | |
| }
 | |
| 
 | |
| /// Marker for meshes of 2D primitives
 | |
| #[derive(Debug, Clone, Component, Default)]
 | |
| pub struct MeshDim2;
 | |
| 
 | |
| /// Marker for meshes of 3D primitives
 | |
| #[derive(Debug, Clone, Component, Default)]
 | |
| pub struct MeshDim3;
 | |
| 
 | |
| fn spawn_primitive_2d(
 | |
|     mut commands: Commands,
 | |
|     mut materials: ResMut<Assets<ColorMaterial>>,
 | |
|     mut meshes: ResMut<Assets<Mesh>>,
 | |
| ) {
 | |
|     const POSITION: Vec3 = Vec3::new(LEFT_RIGHT_OFFSET_2D, 0.0, 0.0);
 | |
|     let material: Handle<ColorMaterial> = materials.add(Color::WHITE);
 | |
|     let camera_mode = CameraActive::Dim2;
 | |
|     [
 | |
|         Some(RECTANGLE.mesh().build()),
 | |
|         Some(CIRCLE.mesh().build()),
 | |
|         Some(ELLIPSE.mesh().build()),
 | |
|         Some(TRIANGLE_2D.mesh().build()),
 | |
|         None, // plane
 | |
|         None, // line
 | |
|         None, // segment
 | |
|         None, // polyline
 | |
|         None, // polygon
 | |
|         Some(REGULAR_POLYGON.mesh().build()),
 | |
|         Some(CAPSULE_2D.mesh().build()),
 | |
|         None, // cylinder
 | |
|         None, // cone
 | |
|         None, // conical frustum
 | |
|         Some(ANNULUS.mesh().build()),
 | |
|         None, // tetrahedron
 | |
|     ]
 | |
|     .into_iter()
 | |
|     .zip(PrimitiveSelected::ALL)
 | |
|     .for_each(|(maybe_mesh, state)| {
 | |
|         if let Some(mesh) = maybe_mesh {
 | |
|             commands.spawn((
 | |
|                 MeshDim2,
 | |
|                 PrimitiveData {
 | |
|                     camera_mode,
 | |
|                     primitive_state: state,
 | |
|                 },
 | |
|                 Mesh2d(meshes.add(mesh)),
 | |
|                 MeshMaterial2d(material.clone()),
 | |
|                 Transform::from_translation(POSITION),
 | |
|             ));
 | |
|         }
 | |
|     });
 | |
| }
 | |
| 
 | |
| fn spawn_primitive_3d(
 | |
|     mut commands: Commands,
 | |
|     mut materials: ResMut<Assets<StandardMaterial>>,
 | |
|     mut meshes: ResMut<Assets<Mesh>>,
 | |
| ) {
 | |
|     const POSITION: Vec3 = Vec3::new(-LEFT_RIGHT_OFFSET_3D, 0.0, 0.0);
 | |
|     let material: Handle<StandardMaterial> = materials.add(Color::WHITE);
 | |
|     let camera_mode = CameraActive::Dim3;
 | |
|     [
 | |
|         Some(CUBOID.mesh().build()),
 | |
|         Some(SPHERE.mesh().build()),
 | |
|         None, // ellipse
 | |
|         Some(TRIANGLE_3D.mesh().build()),
 | |
|         Some(PLANE_3D.mesh().build()),
 | |
|         None, // line
 | |
|         None, // segment
 | |
|         None, // polyline
 | |
|         None, // polygon
 | |
|         None, // regular polygon
 | |
|         Some(CAPSULE_3D.mesh().build()),
 | |
|         Some(CYLINDER.mesh().build()),
 | |
|         None, // cone
 | |
|         None, // conical frustum
 | |
|         Some(TORUS.mesh().build()),
 | |
|         Some(TETRAHEDRON.mesh().build()),
 | |
|     ]
 | |
|     .into_iter()
 | |
|     .zip(PrimitiveSelected::ALL)
 | |
|     .for_each(|(maybe_mesh, state)| {
 | |
|         if let Some(mesh) = maybe_mesh {
 | |
|             commands.spawn((
 | |
|                 MeshDim3,
 | |
|                 PrimitiveData {
 | |
|                     camera_mode,
 | |
|                     primitive_state: state,
 | |
|                 },
 | |
|                 Mesh3d(meshes.add(mesh)),
 | |
|                 MeshMaterial3d(material.clone()),
 | |
|                 Transform::from_translation(POSITION),
 | |
|             ));
 | |
|         }
 | |
|     });
 | |
| }
 | |
| 
 | |
| fn update_primitive_meshes(
 | |
|     camera_state: Res<State<CameraActive>>,
 | |
|     primitive_state: Res<State<PrimitiveSelected>>,
 | |
|     mut primitives: Query<(&mut Visibility, &PrimitiveData)>,
 | |
| ) {
 | |
|     primitives.iter_mut().for_each(|(mut vis, primitive)| {
 | |
|         let visible = primitive.camera_mode == *camera_state.get()
 | |
|             && primitive.primitive_state == *primitive_state.get();
 | |
|         *vis = if visible {
 | |
|             Visibility::Inherited
 | |
|         } else {
 | |
|             Visibility::Hidden
 | |
|         };
 | |
|     });
 | |
| }
 | |
| 
 | |
| fn rotate_primitive_2d_meshes(
 | |
|     mut primitives_2d: Query<
 | |
|         (&mut Transform, &ViewVisibility),
 | |
|         (With<PrimitiveData>, With<MeshDim2>),
 | |
|     >,
 | |
|     time: Res<Time>,
 | |
| ) {
 | |
|     let rotation_2d = Quat::from_mat3(&Mat3::from_angle(time.elapsed_seconds()));
 | |
|     primitives_2d
 | |
|         .iter_mut()
 | |
|         .filter(|(_, vis)| vis.get())
 | |
|         .for_each(|(mut transform, _)| {
 | |
|             transform.rotation = rotation_2d;
 | |
|         });
 | |
| }
 | |
| 
 | |
| fn rotate_primitive_3d_meshes(
 | |
|     mut primitives_3d: Query<
 | |
|         (&mut Transform, &ViewVisibility),
 | |
|         (With<PrimitiveData>, With<MeshDim3>),
 | |
|     >,
 | |
|     time: Res<Time>,
 | |
| ) {
 | |
|     let rotation_3d = Quat::from_rotation_arc(
 | |
|         Vec3::Z,
 | |
|         Vec3::new(
 | |
|             ops::sin(time.elapsed_seconds()),
 | |
|             ops::cos(time.elapsed_seconds()),
 | |
|             ops::sin(time.elapsed_seconds()) * 0.5,
 | |
|         )
 | |
|         .try_normalize()
 | |
|         .unwrap_or(Vec3::Z),
 | |
|     );
 | |
|     primitives_3d
 | |
|         .iter_mut()
 | |
|         .filter(|(_, vis)| vis.get())
 | |
|         .for_each(|(mut transform, _)| {
 | |
|             transform.rotation = rotation_3d;
 | |
|         });
 | |
| }
 | |
| 
 | |
| fn draw_gizmos_3d(mut gizmos: Gizmos, state: Res<State<PrimitiveSelected>>, time: Res<Time>) {
 | |
|     const POSITION: Vec3 = Vec3::new(LEFT_RIGHT_OFFSET_3D, 0.0, 0.0);
 | |
|     let rotation = Quat::from_rotation_arc(
 | |
|         Vec3::Z,
 | |
|         Vec3::new(
 | |
|             ops::sin(time.elapsed_seconds()),
 | |
|             ops::cos(time.elapsed_seconds()),
 | |
|             ops::sin(time.elapsed_seconds()) * 0.5,
 | |
|         )
 | |
|         .try_normalize()
 | |
|         .unwrap_or(Vec3::Z),
 | |
|     );
 | |
|     let isometry = Isometry3d::new(POSITION, rotation);
 | |
|     let color = Color::WHITE;
 | |
|     let resolution = 10;
 | |
| 
 | |
|     match state.get() {
 | |
|         PrimitiveSelected::RectangleAndCuboid => {
 | |
|             gizmos.primitive_3d(&CUBOID, isometry, color);
 | |
|         }
 | |
|         PrimitiveSelected::CircleAndSphere => drop(
 | |
|             gizmos
 | |
|                 .primitive_3d(&SPHERE, isometry, color)
 | |
|                 .resolution(resolution),
 | |
|         ),
 | |
|         PrimitiveSelected::Ellipse => {}
 | |
|         PrimitiveSelected::Triangle => gizmos.primitive_3d(&TRIANGLE_3D, isometry, color),
 | |
|         PrimitiveSelected::Plane => drop(gizmos.primitive_3d(&PLANE_3D, isometry, color)),
 | |
|         PrimitiveSelected::Line => gizmos.primitive_3d(&LINE3D, isometry, color),
 | |
|         PrimitiveSelected::Segment => gizmos.primitive_3d(&SEGMENT_3D, isometry, color),
 | |
|         PrimitiveSelected::Polyline => gizmos.primitive_3d(&POLYLINE_3D, isometry, color),
 | |
|         PrimitiveSelected::Polygon => {}
 | |
|         PrimitiveSelected::RegularPolygon => {}
 | |
|         PrimitiveSelected::Capsule => drop(
 | |
|             gizmos
 | |
|                 .primitive_3d(&CAPSULE_3D, isometry, color)
 | |
|                 .resolution(resolution),
 | |
|         ),
 | |
|         PrimitiveSelected::Cylinder => drop(
 | |
|             gizmos
 | |
|                 .primitive_3d(&CYLINDER, isometry, color)
 | |
|                 .resolution(resolution),
 | |
|         ),
 | |
|         PrimitiveSelected::Cone => drop(
 | |
|             gizmos
 | |
|                 .primitive_3d(&CONE, isometry, color)
 | |
|                 .resolution(resolution),
 | |
|         ),
 | |
|         PrimitiveSelected::ConicalFrustum => {
 | |
|             gizmos.primitive_3d(&CONICAL_FRUSTUM, isometry, color);
 | |
|         }
 | |
| 
 | |
|         PrimitiveSelected::Torus => drop(
 | |
|             gizmos
 | |
|                 .primitive_3d(&TORUS, isometry, color)
 | |
|                 .minor_resolution(resolution)
 | |
|                 .major_resolution(resolution),
 | |
|         ),
 | |
|         PrimitiveSelected::Tetrahedron => {
 | |
|             gizmos.primitive_3d(&TETRAHEDRON, isometry, color);
 | |
|         }
 | |
| 
 | |
|         PrimitiveSelected::Arc => {}
 | |
|         PrimitiveSelected::CircularSector => {}
 | |
|         PrimitiveSelected::CircularSegment => {}
 | |
|     }
 | |
| }
 |