1use scenix_core::Color;
2use scenix_math::Vec3;
3
4use crate::{EPSILON, LineGeometry};
5
6#[derive(Clone, Copy, Debug, PartialEq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct ArrowHelper {
10 pub origin: Vec3,
12 pub direction: Vec3,
14 pub length: f32,
16 pub color: Color,
18 pub head_length: f32,
20 pub head_width: f32,
22}
23
24impl ArrowHelper {
25 #[inline]
27 pub fn new(origin: Vec3, direction: Vec3, length: f32, color: Color) -> Self {
28 let length = length.abs().max(EPSILON);
29 Self {
30 origin,
31 direction: normalize_direction(direction),
32 length,
33 color,
34 head_length: length * 0.2,
35 head_width: length * 0.08,
36 }
37 }
38
39 #[inline]
41 pub fn head(mut self, length: f32, width: f32) -> Self {
42 self.head_length = length.abs().min(self.length).max(EPSILON);
43 self.head_width = width.abs().max(EPSILON);
44 self
45 }
46
47 pub fn to_geometry(&self) -> LineGeometry {
49 let direction = normalize_direction(self.direction);
50 let length = self.length.abs().max(EPSILON);
51 let head_length = self.head_length.abs().min(length).max(EPSILON);
52 let head_width = self.head_width.abs().max(EPSILON);
53 let tip = self.origin + direction * length;
54 let shaft_end = tip - direction * head_length;
55 let basis = perpendicular_basis(direction);
56 let right = basis.0 * head_width;
57 let up = basis.1 * head_width;
58
59 let mut geometry = LineGeometry::new();
60 geometry.push_segment(self.origin, shaft_end, self.color);
61 geometry.push_segment(tip, shaft_end + right, self.color);
62 geometry.push_segment(tip, shaft_end - right, self.color);
63 geometry.push_segment(tip, shaft_end + up, self.color);
64 geometry.push_segment(tip, shaft_end - up, self.color);
65 geometry
66 }
67}
68
69impl Default for ArrowHelper {
70 #[inline]
71 fn default() -> Self {
72 Self::new(Vec3::ZERO, Vec3::NEG_Z, 1.0, Color::WHITE)
73 }
74}
75
76pub(crate) fn normalize_direction(direction: Vec3) -> Vec3 {
77 let direction = direction.normalize();
78 if direction.length_squared() <= EPSILON {
79 Vec3::NEG_Z
80 } else {
81 direction
82 }
83}
84
85pub(crate) fn perpendicular_basis(direction: Vec3) -> (Vec3, Vec3) {
86 let reference = if direction.y.abs() > 0.9 {
87 Vec3::X
88 } else {
89 Vec3::Y
90 };
91 let right = direction.cross(reference).normalize();
92 let up = right.cross(direction).normalize();
93 (right, up)
94}