bevy/examples/math/render_primitives.rs
Joona Aalto 54006b107b
Migrate meshes and materials to required components (#15524)
# 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:

![Näyttökuva 2024-09-29
181746](https://github.com/user-attachments/assets/5c8be029-d2fe-4b8c-ae89-17a72ff82c9a)

![Näyttökuva 2024-09-29
181918](https://github.com/user-attachments/assets/58adbc55-5a1e-4c7d-a2c7-ed456227b909)

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>
2024-10-01 21:33:17 +00:00

709 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(&REGULAR_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 => {}
}
}