Skip to main content

ranim_items/vitem/geometry/
line.rs

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