rive_rs/shapes/
path.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::component_dirt::ComponentDirt;
6use crate::core::{Core, CoreContext, Object, ObjectRef, OnAdded, Property};
7use crate::dyn_vec::DynVec;
8use crate::math::{self, Mat};
9use crate::node::Node;
10use crate::option_cell::OptionCell;
11use crate::shapes::command_path::CommandPathBuilder;
12use crate::shapes::{CommandPath, CubicVertex, PathVertex, PointsPath, Shape, StraightVertex};
13use crate::status_code::StatusCode;
14use crate::{Component, TransformComponent};
15
16#[derive(Debug, Default)]
17pub struct Path {
18    node: Node,
19    path_flags: Property<u64>,
20    shape: OptionCell<Object<Shape>>,
21    pub(crate) vertices: DynVec<Object<PathVertex>>,
22    command_path: OptionCell<CommandPath>,
23}
24
25impl ObjectRef<'_, Path> {
26    pub fn path_flags(&self) -> u64 {
27        self.path_flags.get()
28    }
29
30    pub fn set_path_flags(&self, path_flags: u64) {
31        self.path_flags.set(path_flags);
32    }
33}
34
35impl ObjectRef<'_, Path> {
36    pub fn shape(&self) -> Option<Object<Shape>> {
37        self.shape.get()
38    }
39
40    pub fn transform(&self) -> Mat {
41        if let Some(points_path) = self.try_cast::<PointsPath>() {
42            return points_path.transform();
43        }
44
45        self.cast::<TransformComponent>().world_transform()
46    }
47
48    pub fn push_vertex(&self, path_vertex: Object<PathVertex>) {
49        self.vertices.push(path_vertex);
50    }
51
52    pub(crate) fn vertices(&self) -> impl Iterator<Item = Object<PathVertex>> + '_ {
53        self.vertices.iter()
54    }
55
56    pub(crate) fn with_command_path(&self, f: impl FnMut(Option<&CommandPath>)) {
57        self.command_path.with(f);
58    }
59
60    pub fn mark_path_dirty(&self) {
61        if let Some(points_path) = self.try_cast::<PointsPath>() {
62            points_path.mark_path_dirty();
63        }
64
65        self.cast::<Component>().add_dirt(ComponentDirt::PATH, false);
66
67        if let Some(shape) = self.shape() {
68            shape.as_ref().path_changed();
69        }
70    }
71
72    pub fn is_path_closed(&self) -> bool {
73        if let Some(points_path) = self.try_cast::<PointsPath>() {
74            return points_path.is_path_closed();
75        }
76
77        true
78    }
79
80    pub fn build_dependencies(&self) {
81        self.cast::<TransformComponent>().build_dependencies();
82    }
83
84    pub fn on_dirty(&self, dirt: ComponentDirt) {
85        if let Some(shape) = self.shape() {
86            if Component::value_has_dirt(dirt, ComponentDirt::WORLD_TRANSFORM) {
87                shape.as_ref().path_changed();
88            }
89        }
90    }
91
92    pub fn update(&self, value: ComponentDirt) {
93        self.cast::<TransformComponent>().update(value);
94
95        if Component::value_has_dirt(value, ComponentDirt::PATH) {
96            self.command_path.set(Some(self.build_path()));
97        }
98    }
99
100    fn build_path(&self) -> CommandPath {
101        let mut builder = CommandPathBuilder::new();
102
103        if self.vertices.len() < 2 {
104            return builder.build();
105        }
106
107        enum Dir {
108            In,
109            Out,
110        }
111
112        fn path_vertex_get_or_translation(
113            path_vertex: ObjectRef<'_, PathVertex>,
114            dir: Dir,
115        ) -> math::Vec {
116            path_vertex
117                .try_cast::<CubicVertex>()
118                .map(|cubic| match dir {
119                    Dir::In => cubic.render_in(),
120                    Dir::Out => cubic.render_out(),
121                })
122                .unwrap_or_else(|| path_vertex.render_translation())
123        }
124
125        let first_point = self.vertices.index(0);
126
127        let mut out;
128        let mut prev_is_cubic;
129
130        let start;
131        let start_in;
132        let start_is_cubic;
133
134        if let Some(cubic) = first_point.try_cast::<CubicVertex>() {
135            let cubic = cubic.as_ref();
136            prev_is_cubic = true;
137            start_is_cubic = true;
138
139            start_in = cubic.render_in();
140            out = cubic.render_out();
141            start = cubic.cast::<PathVertex>().render_translation();
142
143            builder.move_to(start);
144        } else {
145            prev_is_cubic = false;
146            start_is_cubic = false;
147
148            let point = first_point.cast::<StraightVertex>();
149            let point = point.as_ref();
150            let radius = point.radius();
151
152            if radius > 0.0 {
153                let prev = self.vertices.index(self.vertices.len() - 1);
154
155                let pos = point.cast::<PathVertex>().render_translation();
156                let to_prev = path_vertex_get_or_translation(prev.as_ref(), Dir::Out) - pos;
157                let to_prev_length = to_prev.length();
158                let to_prev = to_prev * to_prev_length.recip();
159
160                let next = self.vertices.index(1);
161                let to_next = path_vertex_get_or_translation(next.as_ref(), Dir::In) - pos;
162                let to_next_length = to_next.length();
163                let to_next = to_next * to_next_length.recip();
164
165                let render_radius = to_prev_length.min(to_next_length.min(radius));
166
167                let translation = pos + to_prev * render_radius;
168
169                start = translation;
170                start_in = translation;
171
172                builder.move_to(translation);
173
174                let out_point = pos + to_prev * (1.0 - math::CIRCLE_CONSTANT) * render_radius;
175                let in_point = pos + to_next * (1.0 - math::CIRCLE_CONSTANT) * render_radius;
176
177                out = pos + to_next * render_radius;
178
179                builder.cubic_to(out_point, in_point, out);
180            } else {
181                let translation = point.cast::<PathVertex>().render_translation();
182
183                start = translation;
184                start_in = translation;
185                out = translation;
186
187                builder.move_to(translation);
188            }
189        }
190
191        for (i, vertex) in self.vertices.iter().enumerate().skip(1) {
192            let vertex = vertex.as_ref();
193
194            if let Some(cubic) = vertex.try_cast::<CubicVertex>() {
195                let in_point = cubic.render_in();
196                let translation = vertex.render_translation();
197
198                builder.cubic_to(out, in_point, translation);
199
200                prev_is_cubic = true;
201                out = cubic.render_out();
202            } else {
203                let point = vertex.cast::<StraightVertex>();
204                let pos = vertex.render_translation();
205                let radius = point.radius();
206
207                if radius > 0.0 {
208                    let to_prev = out - pos;
209                    let to_prev_length = to_prev.length();
210                    let to_prev = to_prev * to_prev_length.recip();
211
212                    let next = self.vertices.index((i + 1) % self.vertices.len());
213                    let to_next = path_vertex_get_or_translation(next.as_ref(), Dir::In) - pos;
214                    let to_next_length = to_next.length();
215                    let to_next = to_next * to_next_length.recip();
216
217                    let render_radius = to_prev_length.min(to_next_length.min(radius));
218
219                    let translation = pos + to_prev * render_radius;
220
221                    if prev_is_cubic {
222                        builder.cubic_to(out, translation, translation);
223                    } else {
224                        builder.line_to(translation);
225                    }
226
227                    let out_point = pos + to_prev * (1.0 - math::CIRCLE_CONSTANT) * render_radius;
228                    let in_point = pos + to_next * (1.0 - math::CIRCLE_CONSTANT) * render_radius;
229
230                    out = pos + to_next * render_radius;
231
232                    builder.cubic_to(out_point, in_point, out);
233
234                    prev_is_cubic = false;
235                } else if prev_is_cubic {
236                    builder.cubic_to(out, pos, pos);
237
238                    prev_is_cubic = false;
239                    out = pos;
240                } else {
241                    builder.line_to(pos);
242
243                    out = pos;
244                }
245            }
246        }
247
248        if self.is_path_closed() {
249            if prev_is_cubic || start_is_cubic {
250                builder.cubic_to(out, start_in, start);
251            }
252
253            builder.close();
254        }
255
256        builder.build()
257    }
258}
259
260impl Core for Path {
261    parent_types![(node, Node)];
262
263    properties![(128, path_flags, set_path_flags), node];
264}
265
266impl OnAdded for ObjectRef<'_, Path> {
267    on_added!([on_added_dirty, import], Node);
268
269    fn on_added_clean(&self, context: &dyn CoreContext) -> StatusCode {
270        let code = self.cast::<Node>().on_added_clean(context);
271        if code != StatusCode::Ok {
272            return code;
273        }
274
275        for parent in self.cast::<Component>().parents() {
276            if let Some(shape) = parent.try_cast::<Shape>() {
277                self.shape.set(Some(shape.clone()));
278                shape.as_ref().push_path(self.as_object());
279                return StatusCode::Ok;
280            }
281        }
282
283        StatusCode::MissingObject
284    }
285}