mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 14:10:19 +00:00
Dedicated primitive example (#11697)
I just implemented this to record a video for the new blog post, but I figured it would also make a good dedicated example. This also allows us to remove a lot of code from the 2d/3d gizmo examples since it supersedes this portion of code. Depends on: https://github.com/bevyengine/bevy/pull/11699
This commit is contained in:
parent
8cf3447343
commit
b446374392
5 changed files with 681 additions and 374 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -2644,6 +2644,17 @@ description = "Demonstrates creating and using custom Ui materials"
|
|||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "render_primitives"
|
||||
path = "examples/math/render_primitives.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.render_primitives]
|
||||
name = "Rendering Primitives"
|
||||
description = "Shows off rendering for all math primitives as both Meshes and Gizmos"
|
||||
category = "Math"
|
||||
wasm = true
|
||||
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
|
|
|
@ -6,12 +6,10 @@ use bevy::prelude::*;
|
|||
|
||||
fn main() {
|
||||
App::new()
|
||||
.init_state::<PrimitiveState>()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.init_gizmo_group::<MyRoundGizmos>()
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, (draw_example_collection, update_config))
|
||||
.add_systems(Update, (draw_primitives, update_primitives))
|
||||
.run();
|
||||
}
|
||||
|
||||
|
@ -19,61 +17,13 @@ fn main() {
|
|||
#[derive(Default, Reflect, GizmoConfigGroup)]
|
||||
struct MyRoundGizmos {}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States, Default)]
|
||||
enum PrimitiveState {
|
||||
#[default]
|
||||
Nothing,
|
||||
Circle,
|
||||
Ellipse,
|
||||
Capsule,
|
||||
Line,
|
||||
Plane,
|
||||
Segment,
|
||||
Triangle,
|
||||
Rectangle,
|
||||
RegularPolygon,
|
||||
}
|
||||
|
||||
impl PrimitiveState {
|
||||
const ALL: [Self; 10] = [
|
||||
Self::Nothing,
|
||||
Self::Circle,
|
||||
Self::Ellipse,
|
||||
Self::Capsule,
|
||||
Self::Line,
|
||||
Self::Plane,
|
||||
Self::Segment,
|
||||
Self::Triangle,
|
||||
Self::Rectangle,
|
||||
Self::RegularPolygon,
|
||||
];
|
||||
fn next(self) -> Self {
|
||||
Self::ALL
|
||||
.into_iter()
|
||||
.cycle()
|
||||
.skip_while(|&x| x != self)
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
}
|
||||
fn last(self) -> Self {
|
||||
Self::ALL
|
||||
.into_iter()
|
||||
.rev()
|
||||
.cycle()
|
||||
.skip_while(|&x| x != self)
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
// text
|
||||
commands.spawn(TextBundle::from_section(
|
||||
"Hold 'Left' or 'Right' to change the line width of straight gizmos\n\
|
||||
Hold 'Up' or 'Down' to change the line width of round gizmos\n\
|
||||
Press '1' or '2' to toggle the visibility of straight gizmos or round gizmos\n\
|
||||
Press 'K' or 'J' to cycle through primitives rendered with gizmos",
|
||||
Press '1' or '2' to toggle the visibility of straight gizmos or round gizmos",
|
||||
TextStyle {
|
||||
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
||||
font_size: 24.,
|
||||
|
@ -130,92 +80,6 @@ fn draw_example_collection(
|
|||
);
|
||||
}
|
||||
|
||||
fn draw_primitives(
|
||||
mut gizmos: Gizmos,
|
||||
time: Res<Time>,
|
||||
primitive_state: Res<State<PrimitiveState>>,
|
||||
) {
|
||||
let angle = time.elapsed_seconds();
|
||||
let rotation = Mat2::from_angle(angle);
|
||||
let position = rotation * Vec2::X;
|
||||
let color = Color::WHITE;
|
||||
|
||||
const SIZE: f32 = 50.0;
|
||||
match primitive_state.get() {
|
||||
PrimitiveState::Nothing => {}
|
||||
PrimitiveState::Circle => {
|
||||
gizmos.primitive_2d(Circle { radius: SIZE }, position, angle, color);
|
||||
}
|
||||
PrimitiveState::Ellipse => gizmos.primitive_2d(
|
||||
Ellipse {
|
||||
half_size: Vec2::new(SIZE, SIZE * 0.5),
|
||||
},
|
||||
position,
|
||||
angle,
|
||||
color,
|
||||
),
|
||||
PrimitiveState::Capsule => gizmos.primitive_2d(
|
||||
Capsule2d {
|
||||
radius: SIZE * 0.5,
|
||||
half_length: SIZE,
|
||||
},
|
||||
position,
|
||||
angle,
|
||||
color,
|
||||
),
|
||||
PrimitiveState::Line => drop(gizmos.primitive_2d(
|
||||
Line2d {
|
||||
direction: Direction2d::X,
|
||||
},
|
||||
position,
|
||||
angle,
|
||||
color,
|
||||
)),
|
||||
PrimitiveState::Plane => gizmos.primitive_2d(
|
||||
Plane2d {
|
||||
normal: Direction2d::Y,
|
||||
},
|
||||
position,
|
||||
angle,
|
||||
color,
|
||||
),
|
||||
PrimitiveState::Segment => drop(gizmos.primitive_2d(
|
||||
Segment2d {
|
||||
direction: Direction2d::X,
|
||||
half_length: SIZE * 0.5,
|
||||
},
|
||||
position,
|
||||
angle,
|
||||
color,
|
||||
)),
|
||||
PrimitiveState::Triangle => gizmos.primitive_2d(
|
||||
Triangle2d {
|
||||
vertices: [Vec2::ZERO, Vec2::Y, Vec2::X].map(|p| p * SIZE * 0.5),
|
||||
},
|
||||
position,
|
||||
angle,
|
||||
color,
|
||||
),
|
||||
PrimitiveState::Rectangle => gizmos.primitive_2d(
|
||||
Rectangle {
|
||||
half_size: Vec2::splat(SIZE * 0.5),
|
||||
},
|
||||
position,
|
||||
angle,
|
||||
color,
|
||||
),
|
||||
PrimitiveState::RegularPolygon => gizmos.primitive_2d(
|
||||
RegularPolygon {
|
||||
circumcircle: Circle { radius: SIZE * 0.5 },
|
||||
sides: 5,
|
||||
},
|
||||
position,
|
||||
angle,
|
||||
color,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_config(
|
||||
mut config_store: ResMut<GizmoConfigStore>,
|
||||
keyboard: Res<ButtonInput<KeyCode>>,
|
||||
|
@ -247,16 +111,3 @@ fn update_config(
|
|||
my_config.enabled ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
fn update_primitives(
|
||||
keyboard: Res<ButtonInput<KeyCode>>,
|
||||
mut next_primitive_state: ResMut<NextState<PrimitiveState>>,
|
||||
primitive_state: Res<State<PrimitiveState>>,
|
||||
) {
|
||||
if keyboard.just_pressed(KeyCode::KeyJ) {
|
||||
next_primitive_state.set(primitive_state.get().last());
|
||||
}
|
||||
if keyboard.just_pressed(KeyCode::KeyK) {
|
||||
next_primitive_state.set(primitive_state.get().next());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,21 +2,15 @@
|
|||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use bevy::math::primitives::{
|
||||
Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, Segment3d, Sphere, Torus,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.insert_state(PrimitiveState::Nothing)
|
||||
.init_resource::<PrimitiveSegments>()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.init_gizmo_group::<MyRoundGizmos>()
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, rotate_camera)
|
||||
.add_systems(Update, (draw_example_collection, update_config))
|
||||
.add_systems(Update, (draw_primitives, update_primitives))
|
||||
.run();
|
||||
}
|
||||
|
||||
|
@ -24,62 +18,6 @@ fn main() {
|
|||
#[derive(Default, Reflect, GizmoConfigGroup)]
|
||||
struct MyRoundGizmos {}
|
||||
|
||||
#[derive(Debug, Clone, Resource)]
|
||||
struct PrimitiveSegments(usize);
|
||||
impl Default for PrimitiveSegments {
|
||||
fn default() -> Self {
|
||||
Self(10)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States)]
|
||||
enum PrimitiveState {
|
||||
Nothing,
|
||||
Sphere,
|
||||
Plane,
|
||||
Line,
|
||||
LineSegment,
|
||||
Cuboid,
|
||||
Cylinder,
|
||||
Capsule,
|
||||
Cone,
|
||||
ConicalFrustum,
|
||||
Torus,
|
||||
}
|
||||
|
||||
impl PrimitiveState {
|
||||
const ALL: [Self; 11] = [
|
||||
Self::Sphere,
|
||||
Self::Plane,
|
||||
Self::Line,
|
||||
Self::LineSegment,
|
||||
Self::Cuboid,
|
||||
Self::Cylinder,
|
||||
Self::Capsule,
|
||||
Self::Cone,
|
||||
Self::ConicalFrustum,
|
||||
Self::Torus,
|
||||
Self::Nothing,
|
||||
];
|
||||
fn next(self) -> Self {
|
||||
Self::ALL
|
||||
.into_iter()
|
||||
.cycle()
|
||||
.skip_while(|&x| x != self)
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
}
|
||||
fn last(self) -> Self {
|
||||
Self::ALL
|
||||
.into_iter()
|
||||
.rev()
|
||||
.cycle()
|
||||
.skip_while(|&x| x != self)
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
|
@ -193,145 +131,6 @@ fn draw_example_collection(
|
|||
gizmos.arrow(Vec3::ZERO, Vec3::ONE * 1.5, Color::YELLOW);
|
||||
}
|
||||
|
||||
fn draw_primitives(
|
||||
mut gizmos: Gizmos,
|
||||
time: Res<Time>,
|
||||
primitive_state: Res<State<PrimitiveState>>,
|
||||
segments: Res<PrimitiveSegments>,
|
||||
) {
|
||||
let normal = Vec3::new(
|
||||
time.elapsed_seconds().sin(),
|
||||
time.elapsed_seconds().cos(),
|
||||
time.elapsed_seconds().sin().cos(),
|
||||
)
|
||||
.try_normalize()
|
||||
.unwrap_or(Vec3::X);
|
||||
let angle = time.elapsed_seconds().to_radians() * 10.0;
|
||||
let center = Quat::from_axis_angle(Vec3::Z, angle) * Vec3::X;
|
||||
let rotation = Quat::from_rotation_arc(Vec3::Y, normal);
|
||||
let segments = segments.0;
|
||||
match primitive_state.get() {
|
||||
PrimitiveState::Nothing => {}
|
||||
PrimitiveState::Sphere => {
|
||||
gizmos
|
||||
.primitive_3d(Sphere { radius: 1.0 }, center, rotation, Color::default())
|
||||
.segments(segments);
|
||||
}
|
||||
PrimitiveState::Plane => {
|
||||
gizmos
|
||||
.primitive_3d(
|
||||
Plane3d {
|
||||
normal: Direction3d::Y,
|
||||
},
|
||||
center,
|
||||
rotation,
|
||||
Color::default(),
|
||||
)
|
||||
.axis_count((segments / 5).max(4))
|
||||
.segment_count(segments)
|
||||
.segment_length(1.0 / segments as f32);
|
||||
}
|
||||
PrimitiveState::Line => {
|
||||
gizmos.primitive_3d(
|
||||
Line3d {
|
||||
direction: Direction3d::X,
|
||||
},
|
||||
center,
|
||||
rotation,
|
||||
Color::default(),
|
||||
);
|
||||
}
|
||||
PrimitiveState::LineSegment => {
|
||||
gizmos.primitive_3d(
|
||||
Segment3d {
|
||||
direction: Direction3d::X,
|
||||
half_length: 1.0,
|
||||
},
|
||||
center,
|
||||
rotation,
|
||||
Color::default(),
|
||||
);
|
||||
}
|
||||
PrimitiveState::Cuboid => {
|
||||
gizmos.primitive_3d(
|
||||
Cuboid {
|
||||
half_size: Vec3::new(1.0, 0.5, 2.0),
|
||||
},
|
||||
center,
|
||||
rotation,
|
||||
Color::default(),
|
||||
);
|
||||
}
|
||||
PrimitiveState::Cylinder => {
|
||||
gizmos
|
||||
.primitive_3d(
|
||||
Cylinder {
|
||||
radius: 1.0,
|
||||
half_height: 1.0,
|
||||
},
|
||||
center,
|
||||
rotation,
|
||||
Color::default(),
|
||||
)
|
||||
.segments(segments);
|
||||
}
|
||||
PrimitiveState::Capsule => {
|
||||
gizmos
|
||||
.primitive_3d(
|
||||
Capsule3d {
|
||||
radius: 1.0,
|
||||
half_length: 1.0,
|
||||
},
|
||||
center,
|
||||
rotation,
|
||||
Color::default(),
|
||||
)
|
||||
.segments(segments);
|
||||
}
|
||||
PrimitiveState::Cone => {
|
||||
gizmos
|
||||
.primitive_3d(
|
||||
Cone {
|
||||
radius: 1.0,
|
||||
height: 1.0,
|
||||
},
|
||||
center,
|
||||
rotation,
|
||||
Color::default(),
|
||||
)
|
||||
.segments(segments);
|
||||
}
|
||||
PrimitiveState::ConicalFrustum => {
|
||||
gizmos
|
||||
.primitive_3d(
|
||||
ConicalFrustum {
|
||||
radius_top: 0.5,
|
||||
radius_bottom: 1.0,
|
||||
height: 1.0,
|
||||
},
|
||||
center,
|
||||
rotation,
|
||||
Color::default(),
|
||||
)
|
||||
.segments(segments);
|
||||
}
|
||||
PrimitiveState::Torus => {
|
||||
gizmos
|
||||
.primitive_3d(
|
||||
Torus {
|
||||
minor_radius: 0.3,
|
||||
major_radius: 1.0,
|
||||
},
|
||||
center,
|
||||
rotation,
|
||||
Color::default(),
|
||||
)
|
||||
.major_segments(segments)
|
||||
.minor_segments((segments / 4).max(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_config(
|
||||
mut config_store: ResMut<GizmoConfigStore>,
|
||||
keyboard: Res<ButtonInput<KeyCode>>,
|
||||
|
@ -383,26 +182,3 @@ fn update_config(
|
|||
config_store.config_mut::<AabbGizmoConfigGroup>().1.draw_all ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
fn update_primitives(
|
||||
keyboard: Res<ButtonInput<KeyCode>>,
|
||||
primitive_state: Res<State<PrimitiveState>>,
|
||||
mut next_primitive_state: ResMut<NextState<PrimitiveState>>,
|
||||
mut segments: ResMut<PrimitiveSegments>,
|
||||
mut segments_f: Local<f32>,
|
||||
) {
|
||||
if keyboard.just_pressed(KeyCode::KeyK) {
|
||||
next_primitive_state.set(primitive_state.get().next());
|
||||
}
|
||||
if keyboard.just_pressed(KeyCode::KeyJ) {
|
||||
next_primitive_state.set(primitive_state.get().last());
|
||||
}
|
||||
if keyboard.pressed(KeyCode::KeyL) {
|
||||
*segments_f = (*segments_f + 0.05).max(2.0);
|
||||
segments.0 = segments_f.floor() as usize;
|
||||
}
|
||||
if keyboard.pressed(KeyCode::KeyH) {
|
||||
*segments_f = (*segments_f - 0.05).max(2.0);
|
||||
segments.0 = segments_f.floor() as usize;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ git checkout v0.4.0
|
|||
- [ECS (Entity Component System)](#ecs-entity-component-system)
|
||||
- [Games](#games)
|
||||
- [Input](#input)
|
||||
- [Math](#math)
|
||||
- [Reflection](#reflection)
|
||||
- [Scene](#scene)
|
||||
- [Shaders](#shaders)
|
||||
|
@ -277,6 +278,12 @@ Example | Description
|
|||
[Touch Input](../examples/input/touch_input.rs) | Displays touch presses, releases, and cancels
|
||||
[Touch Input Events](../examples/input/touch_input_events.rs) | Prints out all touch inputs
|
||||
|
||||
## Math
|
||||
|
||||
Example | Description
|
||||
--- | ---
|
||||
[Rendering Primitives](../examples/math/render_primitives.rs) | Shows off rendering for all math primitives as both Meshes and Gizmos
|
||||
|
||||
## Reflection
|
||||
|
||||
Example | Description
|
||||
|
|
662
examples/math/render_primitives.rs
Normal file
662
examples/math/render_primitives.rs
Normal file
|
@ -0,0 +1,662 @@
|
|||
//! 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, prelude::*, sprite::MaterialMesh2dBundle,
|
||||
};
|
||||
|
||||
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_else(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,
|
||||
ConicalFrustrum,
|
||||
Torus,
|
||||
}
|
||||
|
||||
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; 15] = [
|
||||
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::ConicalFrustrum,
|
||||
Self::Torus,
|
||||
];
|
||||
|
||||
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: Triangle2d = Triangle2d {
|
||||
vertices: [
|
||||
Vec2::new(SMALL_2D, 0.0),
|
||||
Vec2::new(0.0, SMALL_2D),
|
||||
Vec2::new(-SMALL_2D, 0.0),
|
||||
],
|
||||
};
|
||||
|
||||
const PLANE_2D: Plane2d = Plane2d {
|
||||
normal: Direction2d::Y,
|
||||
};
|
||||
const PLANE_3D: Plane3d = Plane3d {
|
||||
normal: Direction3d::Y,
|
||||
};
|
||||
|
||||
const LINE2D: Line2d = Line2d {
|
||||
direction: Direction2d::X,
|
||||
};
|
||||
const LINE3D: Line3d = Line3d {
|
||||
direction: Direction3d::X,
|
||||
};
|
||||
|
||||
const SEGMENT_2D: Segment2d = Segment2d {
|
||||
direction: Direction2d::X,
|
||||
half_length: BIG_2D,
|
||||
};
|
||||
const SEGMENT_3D: Segment3d = Segment3d {
|
||||
direction: Direction3d::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_FRUSTRUM: ConicalFrustum = ConicalFrustum {
|
||||
radius_top: BIG_3D,
|
||||
radius_bottom: SMALL_3D,
|
||||
height: BIG_3D,
|
||||
};
|
||||
|
||||
const TORUS: Torus = Torus {
|
||||
minor_radius: SMALL_3D / 2.0,
|
||||
major_radius: SMALL_3D * 1.5,
|
||||
};
|
||||
|
||||
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(PointLightBundle {
|
||||
point_light: PointLight {
|
||||
intensity: 5000.0,
|
||||
..default()
|
||||
},
|
||||
transform: 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),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
/// 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,
|
||||
asset_server: Res<AssetServer>,
|
||||
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 font_size = 24.0;
|
||||
let font: Handle<Font> = asset_server.load("fonts/FiraMono-Medium.ttf");
|
||||
let style = TextStyle {
|
||||
font,
|
||||
font_size,
|
||||
color: Color::WHITE,
|
||||
};
|
||||
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 = new_text.clone();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
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 color = Color::WHITE;
|
||||
|
||||
match state.get() {
|
||||
PrimitiveSelected::RectangleAndCuboid => {
|
||||
gizmos.primitive_2d(RECTANGLE, POSITION, angle, color);
|
||||
}
|
||||
PrimitiveSelected::CircleAndSphere => gizmos.primitive_2d(CIRCLE, POSITION, angle, color),
|
||||
PrimitiveSelected::Ellipse => gizmos.primitive_2d(ELLIPSE, POSITION, angle, color),
|
||||
PrimitiveSelected::Triangle => gizmos.primitive_2d(TRIANGLE, POSITION, angle, color),
|
||||
PrimitiveSelected::Plane => gizmos.primitive_2d(PLANE_2D, POSITION, angle, color),
|
||||
PrimitiveSelected::Line => drop(gizmos.primitive_2d(LINE2D, POSITION, angle, color)),
|
||||
PrimitiveSelected::Segment => drop(gizmos.primitive_2d(SEGMENT_2D, POSITION, angle, color)),
|
||||
PrimitiveSelected::Polyline => gizmos.primitive_2d(POLYLINE_2D, POSITION, angle, color),
|
||||
PrimitiveSelected::Polygon => gizmos.primitive_2d(POLYGON_2D, POSITION, angle, color),
|
||||
PrimitiveSelected::RegularPolygon => {
|
||||
gizmos.primitive_2d(REGULAR_POLYGON, POSITION, angle, color);
|
||||
}
|
||||
PrimitiveSelected::Capsule => gizmos.primitive_2d(CAPSULE_2D, POSITION, angle, color),
|
||||
PrimitiveSelected::Cylinder => {}
|
||||
PrimitiveSelected::Cone => {}
|
||||
PrimitiveSelected::ConicalFrustrum => {}
|
||||
PrimitiveSelected::Torus => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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()),
|
||||
Some(CIRCLE.mesh().build()),
|
||||
Some(ELLIPSE.mesh().build()),
|
||||
Some(TRIANGLE.mesh()),
|
||||
None, // plane
|
||||
None, // line
|
||||
None, // segment
|
||||
None, // polyline
|
||||
None, // polygon
|
||||
Some(REGULAR_POLYGON.mesh()),
|
||||
Some(CAPSULE_2D.mesh().build()),
|
||||
None, // cylinder
|
||||
None, // cone
|
||||
None, // conical frustrum
|
||||
None, // torus
|
||||
]
|
||||
.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,
|
||||
},
|
||||
MaterialMesh2dBundle {
|
||||
mesh: meshes.add(mesh).into(),
|
||||
material: material.clone(),
|
||||
transform: Transform::from_translation(POSITION),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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()),
|
||||
Some(SPHERE.mesh().build()),
|
||||
None, // ellipse
|
||||
None, // triangle
|
||||
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 frustrum
|
||||
Some(TORUS.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,
|
||||
},
|
||||
PbrBundle {
|
||||
mesh: meshes.add(mesh),
|
||||
material: material.clone(),
|
||||
transform: Transform::from_translation(POSITION),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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(
|
||||
time.elapsed_seconds().sin(),
|
||||
time.elapsed_seconds().cos(),
|
||||
time.elapsed_seconds().sin() * 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(
|
||||
time.elapsed_seconds().sin(),
|
||||
time.elapsed_seconds().cos(),
|
||||
time.elapsed_seconds().sin() * 0.5,
|
||||
)
|
||||
.try_normalize()
|
||||
.unwrap_or(Vec3::Z),
|
||||
);
|
||||
let color = Color::WHITE;
|
||||
let segments = 10;
|
||||
|
||||
match state.get() {
|
||||
PrimitiveSelected::RectangleAndCuboid => {
|
||||
gizmos.primitive_3d(CUBOID, POSITION, rotation, color);
|
||||
}
|
||||
PrimitiveSelected::CircleAndSphere => drop(
|
||||
gizmos
|
||||
.primitive_3d(SPHERE, POSITION, rotation, color)
|
||||
.segments(segments),
|
||||
),
|
||||
PrimitiveSelected::Ellipse => {}
|
||||
PrimitiveSelected::Triangle => {}
|
||||
PrimitiveSelected::Plane => drop(gizmos.primitive_3d(PLANE_3D, POSITION, rotation, color)),
|
||||
PrimitiveSelected::Line => gizmos.primitive_3d(LINE3D, POSITION, rotation, color),
|
||||
PrimitiveSelected::Segment => gizmos.primitive_3d(SEGMENT_3D, POSITION, rotation, color),
|
||||
PrimitiveSelected::Polyline => gizmos.primitive_3d(POLYLINE_3D, POSITION, rotation, color),
|
||||
PrimitiveSelected::Polygon => {}
|
||||
PrimitiveSelected::RegularPolygon => {}
|
||||
PrimitiveSelected::Capsule => drop(
|
||||
gizmos
|
||||
.primitive_3d(CAPSULE_3D, POSITION, rotation, color)
|
||||
.segments(segments),
|
||||
),
|
||||
PrimitiveSelected::Cylinder => drop(
|
||||
gizmos
|
||||
.primitive_3d(CYLINDER, POSITION, rotation, color)
|
||||
.segments(segments),
|
||||
),
|
||||
PrimitiveSelected::Cone => drop(
|
||||
gizmos
|
||||
.primitive_3d(CONE, POSITION, rotation, color)
|
||||
.segments(segments),
|
||||
),
|
||||
PrimitiveSelected::ConicalFrustrum => {
|
||||
gizmos.primitive_3d(CONICAL_FRUSTRUM, POSITION, rotation, color);
|
||||
}
|
||||
|
||||
PrimitiveSelected::Torus => drop(
|
||||
gizmos
|
||||
.primitive_3d(TORUS, POSITION, rotation, color)
|
||||
.minor_segments(segments)
|
||||
.major_segments(segments),
|
||||
),
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue