mirror of
https://github.com/bevyengine/bevy
synced 2024-12-18 17:13:10 +00:00
015f2c69ca
# Objective Continue improving the user experience of our UI Node API in the direction specified by [Bevy's Next Generation Scene / UI System](https://github.com/bevyengine/bevy/discussions/14437) ## Solution As specified in the document above, merge `Style` fields into `Node`, and move "computed Node fields" into `ComputedNode` (I chose this name over something like `ComputedNodeLayout` because it currently contains more than just layout info. If we want to break this up / rename these concepts, lets do that in a separate PR). `Style` has been removed. This accomplishes a number of goals: ## Ergonomics wins Specifying both `Node` and `Style` is now no longer required for non-default styles Before: ```rust commands.spawn(( Node::default(), Style { width: Val::Px(100.), ..default() }, )); ``` After: ```rust commands.spawn(Node { width: Val::Px(100.), ..default() }); ``` ## Conceptual clarity `Style` was never a comprehensive "style sheet". It only defined "core" style properties that all `Nodes` shared. Any "styled property" that couldn't fit that mold had to be in a separate component. A "real" style system would style properties _across_ components (`Node`, `Button`, etc). We have plans to build a true style system (see the doc linked above). By moving the `Style` fields to `Node`, we fully embrace `Node` as the driving concept and remove the "style system" confusion. ## Next Steps * Consider identifying and splitting out "style properties that aren't core to Node". This should not happen for Bevy 0.15. --- ## Migration Guide Move any fields set on `Style` into `Node` and replace all `Style` component usage with `Node`. Before: ```rust commands.spawn(( Node::default(), Style { width: Val::Px(100.), ..default() }, )); ``` After: ```rust commands.spawn(Node { width: Val::Px(100.), ..default() }); ``` For any usage of the "computed node properties" that used to live on `Node`, use `ComputedNode` instead: Before: ```rust fn system(nodes: Query<&Node>) { for node in &nodes { let computed_size = node.size(); } } ``` After: ```rust fn system(computed_nodes: Query<&ComputedNode>) { for computed_node in &computed_nodes { let computed_size = computed_node.size(); } } ```
705 lines
21 KiB
Rust
705 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((Camera2d, make_camera(start_in_2d)));
|
|
|
|
commands.spawn((
|
|
Camera3d::default(),
|
|
make_camera(!start_in_2d),
|
|
Transform::from_xyz(0.0, 10.0, 0.0).looking_at(Vec3::ZERO, Vec3::Z),
|
|
));
|
|
}
|
|
|
|
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>>,
|
|
camera_2d: Single<(Entity, &mut Camera), With<Camera2d>>,
|
|
camera_3d: Single<(Entity, &mut Camera), (With<Camera3d>, Without<Camera2d>)>,
|
|
mut text: Query<&mut TargetCamera, With<HeaderNode>>,
|
|
) {
|
|
let (entity_2d, mut cam_2d) = camera_2d.into_inner();
|
|
let (entity_3d, mut cam_3d) = camera_3d.into_inner();
|
|
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");
|
|
commands
|
|
.spawn((
|
|
HeaderNode,
|
|
Node {
|
|
justify_self: JustifySelf::Center,
|
|
top: Val::Px(5.0),
|
|
..Default::default()
|
|
},
|
|
TargetCamera(active_camera),
|
|
))
|
|
.with_children(|p| {
|
|
p.spawn((
|
|
Text::default(),
|
|
HeaderText,
|
|
TextLayout::new_with_justify(JustifyText::Center),
|
|
))
|
|
.with_children(|p| {
|
|
p.spawn(TextSpan::new("Primitive: "));
|
|
p.spawn(TextSpan(format!(
|
|
"{text}",
|
|
text = PrimitiveSelected::default()
|
|
)));
|
|
p.spawn(TextSpan::new("\n\n"));
|
|
p.spawn(TextSpan::new(
|
|
"Press 'C' to switch between 2D and 3D mode\n\
|
|
Press 'Up' or 'Down' to switch to the next/previous primitive",
|
|
));
|
|
p.spawn(TextSpan::new("\n\n"));
|
|
p.spawn(TextSpan::new(
|
|
"(If nothing is displayed, there's no rendering support yet)",
|
|
));
|
|
});
|
|
});
|
|
}
|
|
|
|
fn update_text(
|
|
primitive_state: Res<State<PrimitiveSelected>>,
|
|
header: Query<Entity, With<HeaderText>>,
|
|
mut writer: TextUiWriter,
|
|
) {
|
|
let new_text = format!("{text}", text = primitive_state.get());
|
|
header.iter().for_each(|header_text| {
|
|
if let Some(mut text) = writer.get_text(header_text, 2) {
|
|
(*text).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_secs();
|
|
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_secs()));
|
|
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_secs()),
|
|
ops::cos(time.elapsed_secs()),
|
|
ops::sin(time.elapsed_secs()) * 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_secs()),
|
|
ops::cos(time.elapsed_secs()),
|
|
ops::sin(time.elapsed_secs()) * 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 => {}
|
|
}
|
|
}
|