From c172c3c4b556109a04714fe5ed8bdc39759365a5 Mon Sep 17 00:00:00 2001 From: Lynn <62256001+lynn-lumen@users.noreply.github.com> Date: Mon, 10 Jun 2024 23:15:21 +0200 Subject: [PATCH] Custom primitives example (#13795) # Objective - Add a new example showcasing how to add custom primitives and what you can do with them. ## Solution - Added a new example `custom_primitives` with a 2D heart shape primitive highlighting - `Bounded2d` by implementing and visualising bounding shapes, - `Measured2d` by implementing it, - `Meshable` to show the shape on the screen - The example also includes an `Extrusion` implementing - `Measured3d`, - `Bounded3d` using the `BoundedExtrusion` trait and - meshing using the `Extrudable` trait. ## Additional information Here are two images of the heart and its extrusion: ![image_2024-06-10_194631194](https://github.com/bevyengine/bevy/assets/62256001/53f1836c-df74-4ba6-85e9-fabdafa94c66) ![Screenshot 2024-06-10 194609](https://github.com/bevyengine/bevy/assets/62256001/b1630e71-6e94-4293-b7b5-da8d9cc98faf) --------- Co-authored-by: Jakub Marcowski <37378746+Chubercik@users.noreply.github.com> --- Cargo.toml | 11 + examples/README.md | 1 + examples/math/custom_primitives.rs | 496 +++++++++++++++++++++++++++++ 3 files changed, 508 insertions(+) create mode 100644 examples/math/custom_primitives.rs diff --git a/Cargo.toml b/Cargo.toml index 5926ad497c..fc1e10fc7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3024,6 +3024,17 @@ description = "Demonstrates all the primitives which can be sampled." category = "Math" wasm = true +[[example]] +name = "custom_primitives" +path = "examples/math/custom_primitives.rs" +doc-scrape-examples = true + +[package.metadata.example.custom_primitives] +name = "Custom Primitives" +description = "Demonstrates how to add custom primitives and useful traits for them." +category = "Math" +wasm = true + [[example]] name = "random_sampling" path = "examples/math/random_sampling.rs" diff --git a/examples/README.md b/examples/README.md index c528898e06..95cf4b465b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -329,6 +329,7 @@ Example | Description Example | Description --- | --- +[Custom Primitives](../examples/math/custom_primitives.rs) | Demonstrates how to add custom primitives and useful traits for them. [Random Sampling](../examples/math/random_sampling.rs) | Demonstrates how to sample random points from mathematical primitives [Rendering Primitives](../examples/math/render_primitives.rs) | Shows off rendering for all math primitives as both Meshes and Gizmos [Sampling Primitives](../examples/math/sampling_primitives.rs) | Demonstrates all the primitives which can be sampled. diff --git a/examples/math/custom_primitives.rs b/examples/math/custom_primitives.rs new file mode 100644 index 0000000000..9196714da9 --- /dev/null +++ b/examples/math/custom_primitives.rs @@ -0,0 +1,496 @@ +//! This example demonstrates how you can add your own custom primitives to bevy highlighting +//! traits you may want to implement for your primitives to achieve different functionalities. + +use std::f32::consts::{PI, SQRT_2}; + +use bevy::{ + color::palettes::css::{RED, WHITE}, + input::common_conditions::input_just_pressed, + math::bounding::{ + Aabb2d, Bounded2d, Bounded3d, BoundedExtrusion, BoundingCircle, BoundingVolume, + }, + prelude::*, + render::{ + camera::ScalingMode, + mesh::{Extrudable, ExtrusionBuilder, PerimeterSegment}, + render_asset::RenderAssetUsages, + }, +}; + +const HEART: Heart = Heart::new(0.5); +const EXTRUSION: Extrusion = Extrusion { + base_shape: Heart::new(0.5), + half_depth: 0.5, +}; + +// The transform of the camera in 2D +const TRANSFORM_2D: Transform = Transform { + translation: Vec3::ZERO, + rotation: Quat::IDENTITY, + scale: Vec3::ONE, +}; +// The projection used for the camera in 2D +const PROJECTION_2D: Projection = Projection::Orthographic(OrthographicProjection { + near: -1.0, + far: 10.0, + scale: 1.0, + viewport_origin: Vec2::new(0.5, 0.5), + scaling_mode: ScalingMode::AutoMax { + max_width: 8.0, + max_height: 20.0, + }, + area: Rect { + min: Vec2::NEG_ONE, + max: Vec2::ONE, + }, +}); + +// The transform of the camera in 3D +const TRANSFORM_3D: Transform = Transform { + translation: Vec3::ZERO, + // The camera is pointing at the 3D shape + rotation: Quat::from_xyzw(-0.14521316, -0.0, -0.0, 0.98940045), + scale: Vec3::ONE, +}; +// The projection used for the camera in 3D +const PROJECTION_3D: Projection = Projection::Perspective(PerspectiveProjection { + fov: PI / 4.0, + near: 0.1, + far: 1000.0, + aspect_ratio: 1.0, +}); + +/// State for tracking the currently displayed shape +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States, Default, Reflect)] +enum CameraActive { + #[default] + /// The 2D shape is displayed + Dim2, + /// The 3D shape is displayed + Dim3, +} + +/// State for tracking the currently displayed shape +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States, Default, Reflect)] +enum BoundingShape { + #[default] + /// No bounding shapes + None, + /// The bounding sphere or circle of the shape + BoundingSphere, + /// The Axis Aligned Bounding Box (AABB) of the shape + BoundingBox, +} + +/// A marker component for our 2D shapes so we can query them separately from the camera +#[derive(Component)] +struct Shape2d; + +/// A marker component for our 3D shapes so we can query them separately from the camera +#[derive(Component)] +struct Shape3d; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .init_state::() + .init_state::() + .add_systems(Startup, setup) + .add_systems( + Update, + ( + (rotate_2d_shapes, bounding_shapes_2d).run_if(in_state(CameraActive::Dim2)), + (rotate_3d_shapes, bounding_shapes_3d).run_if(in_state(CameraActive::Dim3)), + update_bounding_shape.run_if(input_just_pressed(KeyCode::KeyB)), + switch_cameras.run_if(input_just_pressed(KeyCode::Space)), + ), + ) + .run(); +} + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // Spawn the camera + commands.spawn(Camera3dBundle { + transform: TRANSFORM_2D, + projection: PROJECTION_2D, + ..Default::default() + }); + + // Spawn the 2D heart + commands.spawn(( + PbrBundle { + // We can use the methods defined on the meshbuilder to customize the mesh. + mesh: meshes.add(HEART.mesh().resolution(50)), + material: materials.add(StandardMaterial { + emissive: RED.into(), + base_color: RED.into(), + ..Default::default() + }), + transform: Transform::from_xyz(0.0, 0.0, 0.0), + ..default() + }, + Shape2d, + )); + + // Spawn an extrusion of the heart. + commands.spawn(( + PbrBundle { + transform: Transform::from_xyz(0., -3., -10.) + .with_rotation(Quat::from_rotation_x(-PI / 4.)), + // We can set a custom resolution for the round parts of the extrusion aswell. + mesh: meshes.add(EXTRUSION.mesh().resolution(50)), + material: materials.add(StandardMaterial { + base_color: RED.into(), + ..Default::default() + }), + ..Default::default() + }, + Shape3d, + )); + + // Point light for 3D + commands.spawn(PointLightBundle { + point_light: PointLight { + shadows_enabled: true, + intensity: 10_000_000., + range: 100.0, + shadow_depth_bias: 0.2, + ..default() + }, + transform: Transform::from_xyz(8.0, 12.0, 1.0), + ..default() + }); + + // Example instructions + commands.spawn( + TextBundle::from_section( + "Press 'B' to toggle between no bounding shapes, bounding boxes (AABBs) and bounding spheres / circles\n\ + Press 'Space' to switch between 3D and 2D", + TextStyle::default(), + ) + .with_style(Style { + position_type: PositionType::Absolute, + top: Val::Px(12.0), + left: Val::Px(12.0), + ..default() + }), + ); +} + +// Rotate the 2D shapes. +fn rotate_2d_shapes(mut shapes: Query<&mut Transform, With>, time: Res