rive_rs/shapes/
path.rs
1use 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}