mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
Mesh picking fixes (#16110)
# Objective - Mesh picking is noisy when a non triangle list is used - Mesh picking runs even when users don't need it - Resolve #16065 ## Solution - Don't add the mesh picking plugin by default - Remove error spam
This commit is contained in:
parent
a644ac73f7
commit
54b323ec80
5 changed files with 126 additions and 157 deletions
|
@ -1,5 +1,5 @@
|
||||||
use bevy_math::{Dir3, Mat4, Ray3d, Vec3};
|
use bevy_math::{Dir3, Mat4, Ray3d, Vec3};
|
||||||
use bevy_picking::{mesh_picking::ray_cast, prelude::*};
|
use bevy_picking::mesh_picking::ray_cast;
|
||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
|
|
||||||
fn ptoxznorm(p: u32, size: u32) -> (f32, f32) {
|
fn ptoxznorm(p: u32, size: u32) -> (f32, f32) {
|
||||||
|
|
|
@ -277,24 +277,10 @@ pub struct DefaultPickingPlugins;
|
||||||
|
|
||||||
impl PluginGroup for DefaultPickingPlugins {
|
impl PluginGroup for DefaultPickingPlugins {
|
||||||
fn build(self) -> PluginGroupBuilder {
|
fn build(self) -> PluginGroupBuilder {
|
||||||
#[cfg_attr(
|
PluginGroupBuilder::start::<Self>()
|
||||||
not(feature = "bevy_mesh"),
|
|
||||||
expect(
|
|
||||||
unused_mut,
|
|
||||||
reason = "Group is not mutated when `bevy_mesh` is not enabled."
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
let mut group = PluginGroupBuilder::start::<Self>()
|
|
||||||
.add(input::PointerInputPlugin::default())
|
.add(input::PointerInputPlugin::default())
|
||||||
.add(PickingPlugin::default())
|
.add(PickingPlugin::default())
|
||||||
.add(InteractionPlugin);
|
.add(InteractionPlugin)
|
||||||
|
|
||||||
#[cfg(feature = "bevy_mesh")]
|
|
||||||
{
|
|
||||||
group = group.add(mesh_picking::MeshPickingPlugin);
|
|
||||||
};
|
|
||||||
|
|
||||||
group
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use bevy_math::{bounding::Aabb3d, Dir3, Mat4, Ray3d, Vec3, Vec3A};
|
use bevy_math::{bounding::Aabb3d, Dir3, Mat4, Ray3d, Vec3, Vec3A};
|
||||||
use bevy_reflect::Reflect;
|
use bevy_reflect::Reflect;
|
||||||
use bevy_render::mesh::{Indices, Mesh, PrimitiveTopology, VertexAttributeValues};
|
use bevy_render::mesh::{Indices, Mesh, PrimitiveTopology};
|
||||||
use bevy_utils::tracing::{error, warn};
|
|
||||||
|
|
||||||
use super::Backfaces;
|
use super::Backfaces;
|
||||||
|
|
||||||
|
@ -17,7 +16,7 @@ pub struct RayMeshHit {
|
||||||
/// The distance from the ray origin to the intersection point.
|
/// The distance from the ray origin to the intersection point.
|
||||||
pub distance: f32,
|
pub distance: f32,
|
||||||
/// The vertices of the triangle that was hit.
|
/// The vertices of the triangle that was hit.
|
||||||
pub triangle: Option<[Vec3A; 3]>,
|
pub triangle: Option<[Vec3; 3]>,
|
||||||
/// The index of the triangle that was hit.
|
/// The index of the triangle that was hit.
|
||||||
pub triangle_index: Option<usize>,
|
pub triangle_index: Option<usize>,
|
||||||
}
|
}
|
||||||
|
@ -32,84 +31,41 @@ pub struct RayTriangleHit {
|
||||||
/// Casts a ray on a mesh, and returns the intersection.
|
/// Casts a ray on a mesh, and returns the intersection.
|
||||||
pub(super) fn ray_intersection_over_mesh(
|
pub(super) fn ray_intersection_over_mesh(
|
||||||
mesh: &Mesh,
|
mesh: &Mesh,
|
||||||
mesh_transform: &Mat4,
|
transform: &Mat4,
|
||||||
ray: Ray3d,
|
ray: Ray3d,
|
||||||
backface_culling: Backfaces,
|
culling: Backfaces,
|
||||||
) -> Option<RayMeshHit> {
|
) -> Option<RayMeshHit> {
|
||||||
if mesh.primitive_topology() != PrimitiveTopology::TriangleList {
|
if mesh.primitive_topology() != PrimitiveTopology::TriangleList {
|
||||||
error!(
|
return None; // ray_mesh_intersection assumes vertices are laid out in a triangle list
|
||||||
"Invalid intersection check: `TriangleList` is the only supported `PrimitiveTopology`"
|
|
||||||
);
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
// Vertex positions are required
|
||||||
|
let positions = mesh.attribute(Mesh::ATTRIBUTE_POSITION)?.as_float3()?;
|
||||||
|
|
||||||
// Get the vertex positions and normals from the mesh.
|
// Normals are optional
|
||||||
let vertex_positions: &Vec<[f32; 3]> = match mesh.attribute(Mesh::ATTRIBUTE_POSITION) {
|
let normals = mesh
|
||||||
None => {
|
.attribute(Mesh::ATTRIBUTE_NORMAL)
|
||||||
error!("Mesh does not contain vertex positions");
|
.and_then(|normal_values| normal_values.as_float3());
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(vertex_values) => match &vertex_values {
|
|
||||||
VertexAttributeValues::Float32x3(positions) => positions,
|
|
||||||
_ => {
|
|
||||||
error!("Unexpected types in {:?}", Mesh::ATTRIBUTE_POSITION);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let vertex_normals: Option<&[[f32; 3]]> =
|
|
||||||
if let Some(normal_values) = mesh.attribute(Mesh::ATTRIBUTE_NORMAL) {
|
|
||||||
match &normal_values {
|
|
||||||
VertexAttributeValues::Float32x3(normals) => Some(normals),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(indices) = &mesh.indices() {
|
match mesh.indices() {
|
||||||
match indices {
|
Some(Indices::U16(indices)) => {
|
||||||
Indices::U16(vertex_indices) => ray_mesh_intersection(
|
ray_mesh_intersection(ray, transform, positions, normals, Some(indices), culling)
|
||||||
ray,
|
|
||||||
mesh_transform,
|
|
||||||
vertex_positions,
|
|
||||||
vertex_normals,
|
|
||||||
Some(vertex_indices),
|
|
||||||
backface_culling,
|
|
||||||
),
|
|
||||||
Indices::U32(vertex_indices) => ray_mesh_intersection(
|
|
||||||
ray,
|
|
||||||
mesh_transform,
|
|
||||||
vertex_positions,
|
|
||||||
vertex_normals,
|
|
||||||
Some(vertex_indices),
|
|
||||||
backface_culling,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
} else {
|
Some(Indices::U32(indices)) => {
|
||||||
ray_mesh_intersection(
|
ray_mesh_intersection(ray, transform, positions, normals, Some(indices), culling)
|
||||||
ray,
|
}
|
||||||
mesh_transform,
|
None => ray_mesh_intersection::<usize>(ray, transform, positions, normals, None, culling),
|
||||||
vertex_positions,
|
|
||||||
vertex_normals,
|
|
||||||
None::<&[usize]>,
|
|
||||||
backface_culling,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a ray intersects a mesh, and returns the nearest intersection if one exists.
|
/// Checks if a ray intersects a mesh, and returns the nearest intersection if one exists.
|
||||||
pub fn ray_mesh_intersection<Index: Clone + Copy>(
|
pub fn ray_mesh_intersection<I: TryInto<usize> + Clone + Copy>(
|
||||||
ray: Ray3d,
|
ray: Ray3d,
|
||||||
mesh_transform: &Mat4,
|
mesh_transform: &Mat4,
|
||||||
vertex_positions: &[[f32; 3]],
|
positions: &[[f32; 3]],
|
||||||
vertex_normals: Option<&[[f32; 3]]>,
|
vertex_normals: Option<&[[f32; 3]]>,
|
||||||
indices: Option<&[Index]>,
|
indices: Option<&[I]>,
|
||||||
backface_culling: Backfaces,
|
backface_culling: Backfaces,
|
||||||
) -> Option<RayMeshHit>
|
) -> Option<RayMeshHit> {
|
||||||
where
|
|
||||||
usize: TryFrom<Index>,
|
|
||||||
{
|
|
||||||
// The ray cast can hit the same mesh many times, so we need to track which hit is
|
// The ray cast can hit the same mesh many times, so we need to track which hit is
|
||||||
// closest to the camera, and record that.
|
// closest to the camera, and record that.
|
||||||
let mut closest_hit_distance = f32::MAX;
|
let mut closest_hit_distance = f32::MAX;
|
||||||
|
@ -123,38 +79,36 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(indices) = indices {
|
if let Some(indices) = indices {
|
||||||
// Make sure this chunk has 3 vertices to avoid a panic.
|
// The index list must be a multiple of three. If not, the mesh is malformed and the raycast
|
||||||
|
// result might be nonsensical.
|
||||||
if indices.len() % 3 != 0 {
|
if indices.len() % 3 != 0 {
|
||||||
warn!("Index list not a multiple of 3");
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we're in the vector of vertex indices, we want to look at the vertex
|
for triangle in indices.chunks_exact(3) {
|
||||||
// positions for each triangle, so we'll take indices in chunks of three, where each
|
let [a, b, c] = [
|
||||||
// chunk of three indices are references to the three vertices of a triangle.
|
triangle[0].try_into().ok()?,
|
||||||
for index_chunk in indices.chunks_exact(3) {
|
triangle[1].try_into().ok()?,
|
||||||
let [index1, index2, index3] = [
|
triangle[2].try_into().ok()?,
|
||||||
usize::try_from(index_chunk[0]).ok()?,
|
|
||||||
usize::try_from(index_chunk[1]).ok()?,
|
|
||||||
usize::try_from(index_chunk[2]).ok()?,
|
|
||||||
];
|
];
|
||||||
let triangle_index = Some(index1);
|
|
||||||
let tri_vertex_positions = [
|
let triangle_index = Some(a);
|
||||||
Vec3A::from(vertex_positions[index1]),
|
let tri_vertex_positions = &[
|
||||||
Vec3A::from(vertex_positions[index2]),
|
Vec3::from(positions[a]),
|
||||||
Vec3A::from(vertex_positions[index3]),
|
Vec3::from(positions[b]),
|
||||||
|
Vec3::from(positions[c]),
|
||||||
];
|
];
|
||||||
let tri_normals = vertex_normals.map(|normals| {
|
let tri_normals = vertex_normals.map(|normals| {
|
||||||
[
|
[
|
||||||
Vec3A::from(normals[index1]),
|
Vec3::from(normals[a]),
|
||||||
Vec3A::from(normals[index2]),
|
Vec3::from(normals[b]),
|
||||||
Vec3A::from(normals[index3]),
|
Vec3::from(normals[c]),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
let Some(hit) = triangle_intersection(
|
let Some(hit) = triangle_intersection(
|
||||||
tri_vertex_positions,
|
tri_vertex_positions,
|
||||||
tri_normals,
|
tri_normals.as_ref(),
|
||||||
closest_hit_distance,
|
closest_hit_distance,
|
||||||
&mesh_space_ray,
|
&mesh_space_ray,
|
||||||
backface_culling,
|
backface_culling,
|
||||||
|
@ -171,9 +125,9 @@ where
|
||||||
.length(),
|
.length(),
|
||||||
triangle: hit.triangle.map(|tri| {
|
triangle: hit.triangle.map(|tri| {
|
||||||
[
|
[
|
||||||
mesh_transform.transform_point3a(tri[0]),
|
mesh_transform.transform_point3(tri[0]),
|
||||||
mesh_transform.transform_point3a(tri[1]),
|
mesh_transform.transform_point3(tri[1]),
|
||||||
mesh_transform.transform_point3a(tri[2]),
|
mesh_transform.transform_point3(tri[2]),
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
triangle_index,
|
triangle_index,
|
||||||
|
@ -181,23 +135,23 @@ where
|
||||||
closest_hit_distance = hit.distance;
|
closest_hit_distance = hit.distance;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (i, chunk) in vertex_positions.chunks_exact(3).enumerate() {
|
for (i, triangle) in positions.chunks_exact(3).enumerate() {
|
||||||
let &[a, b, c] = chunk else {
|
let &[a, b, c] = triangle else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let triangle_index = Some(i);
|
let triangle_index = Some(i);
|
||||||
let tri_vertex_positions = [Vec3A::from(a), Vec3A::from(b), Vec3A::from(c)];
|
let tri_vertex_positions = &[Vec3::from(a), Vec3::from(b), Vec3::from(c)];
|
||||||
let tri_normals = vertex_normals.map(|normals| {
|
let tri_normals = vertex_normals.map(|normals| {
|
||||||
[
|
[
|
||||||
Vec3A::from(normals[i]),
|
Vec3::from(normals[i]),
|
||||||
Vec3A::from(normals[i + 1]),
|
Vec3::from(normals[i + 1]),
|
||||||
Vec3A::from(normals[i + 2]),
|
Vec3::from(normals[i + 2]),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
let Some(hit) = triangle_intersection(
|
let Some(hit) = triangle_intersection(
|
||||||
tri_vertex_positions,
|
tri_vertex_positions,
|
||||||
tri_normals,
|
tri_normals.as_ref(),
|
||||||
closest_hit_distance,
|
closest_hit_distance,
|
||||||
&mesh_space_ray,
|
&mesh_space_ray,
|
||||||
backface_culling,
|
backface_culling,
|
||||||
|
@ -214,9 +168,9 @@ where
|
||||||
.length(),
|
.length(),
|
||||||
triangle: hit.triangle.map(|tri| {
|
triangle: hit.triangle.map(|tri| {
|
||||||
[
|
[
|
||||||
mesh_transform.transform_point3a(tri[0]),
|
mesh_transform.transform_point3(tri[0]),
|
||||||
mesh_transform.transform_point3a(tri[1]),
|
mesh_transform.transform_point3(tri[1]),
|
||||||
mesh_transform.transform_point3a(tri[2]),
|
mesh_transform.transform_point3(tri[2]),
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
triangle_index,
|
triangle_index,
|
||||||
|
@ -228,15 +182,14 @@ where
|
||||||
closest_hit
|
closest_hit
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn triangle_intersection(
|
fn triangle_intersection(
|
||||||
tri_vertices: [Vec3A; 3],
|
tri_vertices: &[Vec3; 3],
|
||||||
tri_normals: Option<[Vec3A; 3]>,
|
tri_normals: Option<&[Vec3; 3]>,
|
||||||
max_distance: f32,
|
max_distance: f32,
|
||||||
ray: &Ray3d,
|
ray: &Ray3d,
|
||||||
backface_culling: Backfaces,
|
backface_culling: Backfaces,
|
||||||
) -> Option<RayMeshHit> {
|
) -> Option<RayMeshHit> {
|
||||||
let hit = ray_triangle_intersection(ray, &tri_vertices, backface_culling)?;
|
let hit = ray_triangle_intersection(ray, tri_vertices, backface_culling)?;
|
||||||
|
|
||||||
if hit.distance < 0.0 || hit.distance > max_distance {
|
if hit.distance < 0.0 || hit.distance > max_distance {
|
||||||
return None;
|
return None;
|
||||||
|
@ -258,25 +211,24 @@ fn triangle_intersection(
|
||||||
|
|
||||||
Some(RayMeshHit {
|
Some(RayMeshHit {
|
||||||
point,
|
point,
|
||||||
normal: normal.into(),
|
normal,
|
||||||
barycentric_coords: barycentric,
|
barycentric_coords: barycentric,
|
||||||
distance: hit.distance,
|
distance: hit.distance,
|
||||||
triangle: Some(tri_vertices),
|
triangle: Some(*tri_vertices),
|
||||||
triangle_index: None,
|
triangle_index: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes a ray and triangle and computes the intersection.
|
/// Takes a ray and triangle and computes the intersection.
|
||||||
#[inline(always)]
|
|
||||||
fn ray_triangle_intersection(
|
fn ray_triangle_intersection(
|
||||||
ray: &Ray3d,
|
ray: &Ray3d,
|
||||||
triangle: &[Vec3A; 3],
|
triangle: &[Vec3; 3],
|
||||||
backface_culling: Backfaces,
|
backface_culling: Backfaces,
|
||||||
) -> Option<RayTriangleHit> {
|
) -> Option<RayTriangleHit> {
|
||||||
// Source: https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection
|
// Source: https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection
|
||||||
let vector_v0_to_v1: Vec3A = triangle[1] - triangle[0];
|
let vector_v0_to_v1: Vec3 = triangle[1] - triangle[0];
|
||||||
let vector_v0_to_v2: Vec3A = triangle[2] - triangle[0];
|
let vector_v0_to_v2: Vec3 = triangle[2] - triangle[0];
|
||||||
let p_vec: Vec3A = (Vec3A::from(*ray.direction)).cross(vector_v0_to_v2);
|
let p_vec: Vec3 = ray.direction.cross(vector_v0_to_v2);
|
||||||
let determinant: f32 = vector_v0_to_v1.dot(p_vec);
|
let determinant: f32 = vector_v0_to_v1.dot(p_vec);
|
||||||
|
|
||||||
match backface_culling {
|
match backface_culling {
|
||||||
|
@ -298,14 +250,14 @@ fn ray_triangle_intersection(
|
||||||
|
|
||||||
let determinant_inverse = 1.0 / determinant;
|
let determinant_inverse = 1.0 / determinant;
|
||||||
|
|
||||||
let t_vec = Vec3A::from(ray.origin) - triangle[0];
|
let t_vec = ray.origin - triangle[0];
|
||||||
let u = t_vec.dot(p_vec) * determinant_inverse;
|
let u = t_vec.dot(p_vec) * determinant_inverse;
|
||||||
if !(0.0..=1.0).contains(&u) {
|
if !(0.0..=1.0).contains(&u) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let q_vec = t_vec.cross(vector_v0_to_v1);
|
let q_vec = t_vec.cross(vector_v0_to_v1);
|
||||||
let v = Vec3A::from(*ray.direction).dot(q_vec) * determinant_inverse;
|
let v = (*ray.direction).dot(q_vec) * determinant_inverse;
|
||||||
if v < 0.0 || u + v > 1.0 {
|
if v < 0.0 || u + v > 1.0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,23 @@
|
||||||
//! A simple 3D scene to demonstrate mesh picking.
|
//! A simple 3D scene to demonstrate mesh picking.
|
||||||
//!
|
//!
|
||||||
//! By default, all meshes are pickable. Picking can be disabled for individual entities
|
//! [`bevy::picking::backend`] provides an API for adding picking hit tests to any entity. To get
|
||||||
//! by adding [`PickingBehavior::IGNORE`].
|
//! started with picking 3d meshes, the [`MeshPickingPlugin`] is provided as a simple starting
|
||||||
|
//! point, especially useful for debugging. For your game, you may want to use a 3d picking backend
|
||||||
|
//! provided by your physics engine, or a picking shader, depending on your specific use case.
|
||||||
//!
|
//!
|
||||||
//! If you want mesh picking to be entirely opt-in, you can set [`MeshPickingSettings::require_markers`]
|
//! [`bevy::picking`] allows you to compose backends together to make any entity on screen pickable
|
||||||
//! to `true` and add a [`RayCastPickable`] component to the desired camera and target entities.
|
//! with pointers, regardless of how that entity is rendered. For example, `bevy_ui` and
|
||||||
|
//! `bevy_sprite` provide their own picking backends that can be enabled at the same time as this
|
||||||
|
//! mesh picking backend. This makes it painless to deal with cases like the UI or sprites blocking
|
||||||
|
//! meshes underneath them, or vice versa.
|
||||||
|
//!
|
||||||
|
//! If you want to build more complex interactions than afforded by the provided pointer events, you
|
||||||
|
//! may want to use [`MeshRayCast`] or a full physics engine with raycasting capabilities.
|
||||||
|
//!
|
||||||
|
//! By default, the mesh picking plugin will raycast against all entities, which is especially
|
||||||
|
//! useful for debugging. If you want mesh picking to be opt-in, you can set
|
||||||
|
//! [`MeshPickingSettings::require_markers`] to `true` and add a [`RayCastPickable`] component to
|
||||||
|
//! the desired camera and target entities.
|
||||||
|
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
@ -19,7 +32,12 @@ use bevy::{
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins((
|
||||||
|
DefaultPlugins,
|
||||||
|
// The mesh picking plugin is not enabled by default, because raycasting against all
|
||||||
|
// meshes has a performance cost.
|
||||||
|
MeshPickingPlugin,
|
||||||
|
))
|
||||||
.init_resource::<SceneMaterials>()
|
.init_resource::<SceneMaterials>()
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
.add_systems(Update, (on_mesh_hover, rotate))
|
.add_systems(Update, (on_mesh_hover, rotate))
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
//! A simple scene to demonstrate picking events
|
//! A simple scene to demonstrate picking events for UI and mesh entities.
|
||||||
|
|
||||||
use bevy::{color::palettes::tailwind::CYAN_400, prelude::*};
|
use bevy::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut app = App::new();
|
App::new()
|
||||||
app.add_plugins(DefaultPlugins);
|
.add_plugins((
|
||||||
|
DefaultPlugins,
|
||||||
app.add_systems(Startup, setup);
|
MeshPickingPlugin, // Needed for mesh picking, not added by default
|
||||||
|
))
|
||||||
app.run();
|
.add_systems(Startup, setup)
|
||||||
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// set up a simple 3D scene
|
/// set up a simple 3D scene
|
||||||
|
@ -19,7 +20,7 @@ fn setup(
|
||||||
) {
|
) {
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn((
|
||||||
Text::new("Click Me to get a box"),
|
Text::new("Click Me to get a box\nDrag cubes to rotate"),
|
||||||
Node {
|
Node {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::Percent(12.0),
|
top: Val::Percent(12.0),
|
||||||
|
@ -27,20 +28,7 @@ fn setup(
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.observe(
|
.observe(on_pointer_click_spawn_cube)
|
||||||
|_click: Trigger<Pointer<Click>>,
|
|
||||||
mut commands: Commands,
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
||||||
mut num: Local<usize>| {
|
|
||||||
commands.spawn((
|
|
||||||
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
|
|
||||||
MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
|
|
||||||
Transform::from_xyz(0.0, 0.5 + 1.1 * *num as f32, 0.0),
|
|
||||||
));
|
|
||||||
*num += 1;
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.observe(
|
.observe(
|
||||||
|evt: Trigger<Pointer<Out>>, mut texts: Query<&mut TextColor>| {
|
|evt: Trigger<Pointer<Out>>, mut texts: Query<&mut TextColor>| {
|
||||||
let mut color = texts.get_mut(evt.entity()).unwrap();
|
let mut color = texts.get_mut(evt.entity()).unwrap();
|
||||||
|
@ -50,7 +38,7 @@ fn setup(
|
||||||
.observe(
|
.observe(
|
||||||
|evt: Trigger<Pointer<Over>>, mut texts: Query<&mut TextColor>| {
|
|evt: Trigger<Pointer<Over>>, mut texts: Query<&mut TextColor>| {
|
||||||
let mut color = texts.get_mut(evt.entity()).unwrap();
|
let mut color = texts.get_mut(evt.entity()).unwrap();
|
||||||
color.0 = CYAN_400.into();
|
color.0 = bevy::color::palettes::tailwind::CYAN_400.into();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// circular base
|
// circular base
|
||||||
|
@ -73,3 +61,28 @@ fn setup(
|
||||||
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_pointer_click_spawn_cube(
|
||||||
|
_click: Trigger<Pointer<Click>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
mut num: Local<usize>,
|
||||||
|
) {
|
||||||
|
commands
|
||||||
|
.spawn((
|
||||||
|
Mesh3d(meshes.add(Cuboid::new(0.5, 0.5, 0.5))),
|
||||||
|
MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
|
||||||
|
Transform::from_xyz(0.0, 0.25 + 0.55 * *num as f32, 0.0),
|
||||||
|
))
|
||||||
|
// With the MeshPickingPlugin added, you can add pointer event observers to meshes as well:
|
||||||
|
.observe(
|
||||||
|
|drag: Trigger<Pointer<Drag>>, mut transforms: Query<&mut Transform>| {
|
||||||
|
if let Ok(mut transform) = transforms.get_mut(drag.entity()) {
|
||||||
|
transform.rotate_y(drag.delta.x * 0.02);
|
||||||
|
transform.rotate_x(drag.delta.y * 0.02);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
*num += 1;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue