use bevy::prelude::*;
#[derive(Component)]
pub struct VecArrow {
pub target: Vec3,
pub target_coordinate_space: TargetCoordinateSpace,
pub thickness: f32,
pub color: Color,
pub tip_thickness: f32,
pub tip_length: f32,
}
impl VecArrow {
pub fn new(target: Vec3, target_coordinate_space: TargetCoordinateSpace) -> Self {
Self {
target,
target_coordinate_space,
thickness: 0.1,
color: Color::WHITE,
tip_thickness: 0.075,
tip_length: 0.15,
}
}
pub const fn with_thickness(mut self, thickness: f32) -> Self {
self.thickness = thickness;
self
}
pub const fn with_color(mut self, color: Color) -> Self {
self.color = color;
self
}
}
pub enum TargetCoordinateSpace {
Global,
Local,
}
#[derive(Component)]
pub(crate) struct VecArrowBody {}
#[derive(Component)]
pub(crate) struct VecArrowTip {}
#[derive(Component, Clone, Copy, Debug)]
pub(crate) struct VecArrowParts {
body: Entity,
tip: Entity,
}
pub(crate) fn on_attach_vec_arrow(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
query: Query<(Entity, Option<&GlobalTransform>, &VecArrow), Added<VecArrow>>,
) {
for (new_parent_entity, parent_global_transform, new_arrow) in query.iter() {
commands
.entity(new_parent_entity)
.insert_if_new(Visibility::Inherited)
.insert_if_new(Transform::default());
let body = commands
.spawn((
Mesh3d(meshes.add(Cylinder::new(0.01, 1.0))),
MeshMaterial3d(materials.add(new_arrow.color)),
get_body_transform(
parent_global_transform.cloned(),
&new_arrow.target,
&new_arrow.target_coordinate_space,
),
VecArrowBody {},
Name::new(format!("VecArrowBody for {}", new_parent_entity)),
))
.id();
let tip = commands
.spawn((
Mesh3d(meshes.add(Cone::new(1.0, 1.0))),
MeshMaterial3d(materials.add(new_arrow.color)),
get_tip_transform(
parent_global_transform.cloned(),
&new_arrow.target,
&new_arrow.target_coordinate_space,
new_arrow.tip_length,
new_arrow.tip_thickness,
),
Name::new(format!("VecArrowTip for {}", new_parent_entity)),
VecArrowTip {},
))
.id();
commands
.entity(new_parent_entity)
.insert(VecArrowParts { body, tip });
}
}
pub(crate) fn on_remove_vec_arrow(
mut commands: Commands,
mut parents_with_removed_arrows: RemovedComponents<VecArrow>,
parent_state_query: Query<Option<&VecArrowParts>>,
) {
for entity in parents_with_removed_arrows.read() {
if let Ok(Some(VecArrowParts { body, tip })) = parent_state_query.get(entity) {
commands.entity(*body).despawn();
commands.entity(*tip).despawn();
commands.entity(entity).remove::<VecArrowParts>();
}
}
}
pub(crate) fn update_vec_arrow(
parent_transforms: Query<(&GlobalTransform, &VecArrow, &VecArrowParts)>,
mut body_query: Query<
(
&mut Transform,
&MeshMaterial3d<StandardMaterial>,
&VecArrowBody,
),
Without<VecArrowTip>,
>,
mut tip_query: Query<
(
&mut Transform,
&MeshMaterial3d<StandardMaterial>,
&VecArrowTip,
),
Without<VecArrowBody>,
>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
for (global_transform, vec_arrow, parts) in parent_transforms.iter() {
let new_body_transform = get_body_transform(
Some(*global_transform),
&vec_arrow.target,
&vec_arrow.target_coordinate_space,
);
let new_tip_transform = get_tip_transform(
Some(*global_transform),
&vec_arrow.target,
&vec_arrow.target_coordinate_space,
vec_arrow.tip_length,
vec_arrow.tip_thickness,
);
let (mut body_transform, body_material, _) = body_query.get_mut(parts.body).unwrap();
*body_transform = new_body_transform;
if let Some(material) = materials.get_mut(&body_material.0) {
material.base_color = vec_arrow.color;
}
let (mut tip_transform, tip_material, _) = tip_query.get_mut(parts.tip).unwrap();
*tip_transform = new_tip_transform;
if let Some(material) = materials.get_mut(&tip_material.0) {
material.base_color = vec_arrow.color;
}
}
}
fn get_body_transform(
parent_transform: Option<GlobalTransform>,
target: &Vec3,
target_coordinate_space: &TargetCoordinateSpace,
) -> Transform {
let target = match target_coordinate_space {
TargetCoordinateSpace::Local => *target,
TargetCoordinateSpace::Global => {
-parent_transform.unwrap_or_default().translation() + *target
}
};
let Some(normalized) = target.try_normalize() else {
return Transform::from_scale(Vec3::ZERO);
};
let my_position = target / 2.0;
let mut my_local_transform = Transform::from_translation(my_position);
my_local_transform.rotate(Quat::from_rotation_arc(Vec3::Y, normalized));
let my_scale = Vec3::new(1.0, target.length(), 1.0);
let mut my_local_transform = my_local_transform.with_scale(my_scale);
match target_coordinate_space {
TargetCoordinateSpace::Global => {
my_local_transform.translation += parent_transform.unwrap_or_default().translation();
my_local_transform
}
TargetCoordinateSpace::Local => {
let parent_transform = parent_transform.unwrap_or_default();
let mut my_global_transform = my_local_transform;
my_global_transform.translation = parent_transform
.rotation()
.mul_vec3(my_global_transform.translation)
+ parent_transform.translation();
my_global_transform.rotation = parent_transform
.rotation()
.mul_quat(my_global_transform.rotation);
my_global_transform
}
}
}
fn get_tip_transform(
parent_transform: Option<GlobalTransform>,
target: &Vec3,
target_coordinate_space: &TargetCoordinateSpace,
tip_length: f32,
tip_thickness: f32,
) -> Transform {
let target = match target_coordinate_space {
TargetCoordinateSpace::Local => *target,
TargetCoordinateSpace::Global => {
-parent_transform.unwrap_or_default().translation() + *target
}
};
let Some(normalized) = target.try_normalize() else {
return Transform::from_scale(Vec3::ZERO);
};
let mut my_local_transform = Transform::from_translation(target);
my_local_transform.rotate(Quat::from_rotation_arc(Vec3::Y, normalized));
my_local_transform.scale = Vec3::new(tip_thickness, tip_length, tip_thickness);
match target_coordinate_space {
TargetCoordinateSpace::Global => {
my_local_transform.translation += parent_transform.unwrap_or_default().translation();
my_local_transform
}
TargetCoordinateSpace::Local => {
let parent_transform = parent_transform.unwrap_or_default();
let mut my_global_transform = my_local_transform;
my_global_transform.translation = parent_transform
.rotation()
.mul_vec3(my_global_transform.translation)
+ parent_transform.translation();
my_global_transform.rotation = parent_transform
.rotation()
.mul_quat(my_global_transform.rotation);
my_global_transform
}
}
}