Skip to main content

ranim_items/vitem/geometry/
line.rs

1use ranim_core::{
2    Extract,
3    color::{AlphaColor, Srgb},
4    core_item::{CoreItem, vitem::DEFAULT_STROKE_WIDTH},
5    glam::DVec3,
6    traits::{Aabb, ApplyTransform, Discard, Locate, Opacity, StrokeColor, StrokeWidth, With},
7};
8use ranim_macros::Interpolatable;
9
10use crate::vitem::VItem;
11
12/// A line segment.
13#[derive(Debug, Clone, Interpolatable)]
14pub struct Line {
15    /// The start and end points of the line.
16    pub points: [DVec3; 2],
17    /// The distance two endpoints extends or shrinks from its original position.
18    /// Positive value means extension and negative value means shrinking.
19    pub extrude: [f64; 2],
20    /// Stroke RGBA values.
21    pub stroke_rgba: AlphaColor<Srgb>,
22    /// Stroke width.
23    pub stroke_width: f32,
24}
25
26impl Line {
27    /// Creates a new line segment with the given start and end points.
28    pub fn new(start: DVec3, end: DVec3) -> Self {
29        Self {
30            points: [start, end],
31            extrude: [0., 0.],
32            stroke_rgba: AlphaColor::WHITE,
33            stroke_width: DEFAULT_STROKE_WIDTH,
34        }
35    }
36
37    /// Inverts the direction of the line segment.
38    pub fn invert(&mut self) -> &mut Self {
39        self.points.reverse();
40        self.extrude.reverse();
41        self
42    }
43
44    /// Returns the start and end points of the line segment considering the extrusion distance.
45    pub fn points_with_extrude(&self) -> [DVec3; 2] {
46        let [p1, p2] = self.points;
47        let [ext1, ext2] = self.extrude;
48        let d = (p2 - p1).normalize();
49        [p1 - d * ext1, p2 + d * ext2]
50    }
51}
52
53impl Locate<Line> for f64 {
54    fn locate(&self, target: &Line) -> DVec3 {
55        let [p1, p2] = target.points;
56        p1.lerp(p2, *self)
57    }
58}
59
60impl Aabb for Line {
61    fn aabb(&self) -> [DVec3; 2] {
62        self.points_with_extrude().aabb()
63    }
64}
65
66impl<G: Into<ranim_core::glam::DAffine3>> ApplyTransform<G> for Line {
67    fn apply(&mut self, transform: G) -> &mut Self {
68        let transform = transform.into();
69        let direction = (self.points[1] - self.points[0]).try_normalize();
70        self.points.apply(transform);
71        if let Some(direction) = direction {
72            let length_scale = transform.transform_vector3(direction).length();
73            self.extrude.iter_mut().for_each(|e| *e *= length_scale);
74        }
75        self
76    }
77}
78
79impl StrokeColor for Line {
80    fn stroke_color(&self) -> AlphaColor<Srgb> {
81        self.stroke_rgba
82    }
83
84    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
85        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
86        self
87    }
88
89    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
90        self.stroke_rgba = color;
91        self
92    }
93}
94
95impl From<Line> for VItem {
96    fn from(value: Line) -> Self {
97        let [p1, p2] = value.points_with_extrude();
98        VItem::from_vpoints(vec![p1, (p1 + p2) / 2., p2]).with(|item| {
99            item.set_stroke_color(value.stroke_rgba)
100                .set_stroke_width(value.stroke_width)
101                .discard()
102        })
103    }
104}
105
106impl Opacity for Line {
107    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
108        self.set_stroke_opacity(opacity);
109        self
110    }
111}
112
113impl Extract for Line {
114    type Target = CoreItem;
115
116    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
117        VItem::from(self.clone()).extract_into(buf);
118    }
119}