1 | //! Perform cached measurements and split operations on a path. |
2 | //! |
3 | use crate::geom::{CubicBezierSegment, LineSegment, QuadraticBezierSegment, Segment}; |
4 | use crate::math::*; |
5 | use crate::path::{ |
6 | builder::PathBuilder, AttributeStore, Attributes, EndpointId, IdEvent, Path, PathSlice, |
7 | PositionStore, |
8 | }; |
9 | use core::ops::Range; |
10 | |
11 | use alloc::vec::Vec; |
12 | |
13 | /// Whether to measure real or normalized (between 0 and 1) distances. |
14 | #[derive (Copy, Clone, Debug, PartialEq, Eq, Hash)] |
15 | pub enum SampleType { |
16 | Distance, |
17 | Normalized, |
18 | } |
19 | |
20 | type EndpointPair = (EndpointId, EndpointId); |
21 | |
22 | enum SegmentWrapper { |
23 | Empty, |
24 | Line(LineSegment<f32>, EndpointPair), |
25 | Quadratic(QuadraticBezierSegment<f32>, EndpointPair), |
26 | Cubic(CubicBezierSegment<f32>, EndpointPair), |
27 | } |
28 | |
29 | impl SegmentWrapper { |
30 | fn split(&self, range: Range<f32>) -> Self { |
31 | match self { |
32 | Self::Empty => Self::Empty, |
33 | Self::Line(segment: &LineSegment, pair: &(EndpointId, EndpointId)) => Self::Line(segment.split_range(range), *pair), |
34 | Self::Quadratic(segment: &QuadraticBezierSegment, pair: &(EndpointId, EndpointId)) => Self::Quadratic(segment.split_range(range), *pair), |
35 | Self::Cubic(segment: &CubicBezierSegment, pair: &(EndpointId, EndpointId)) => Self::Cubic(segment.split_range(range), *pair), |
36 | } |
37 | } |
38 | } |
39 | |
40 | struct Edge { |
41 | // distance from the beginning of the path |
42 | distance: f32, |
43 | // which segment this edge is on |
44 | index: usize, |
45 | // t-value of the endpoint on the segment |
46 | t: f32, |
47 | } |
48 | |
49 | /// The result of sampling a path. |
50 | #[derive (PartialEq, Debug)] |
51 | pub struct PathSample<'l> { |
52 | position: Point, |
53 | tangent: Vector, |
54 | attributes: Attributes<'l>, |
55 | } |
56 | |
57 | impl<'l> PathSample<'l> { |
58 | #[inline ] |
59 | pub fn position(&self) -> Point { |
60 | self.position |
61 | } |
62 | |
63 | #[inline ] |
64 | pub fn tangent(&self) -> Vector { |
65 | self.tangent |
66 | } |
67 | |
68 | // Takes &mut self to allow interpolating attributes lazily (like the stroke tessellator) without changing |
69 | // the API. |
70 | #[inline ] |
71 | pub fn attributes(&mut self) -> Attributes<'l> { |
72 | self.attributes |
73 | } |
74 | } |
75 | |
76 | /// An acceleration structure for sampling distances along a specific path. |
77 | /// |
78 | /// Building the path measurements can be an expensive operation depending on the complexity of the |
79 | /// measured path, so it is usually a good idea to cache and reuse it whenever possible. |
80 | /// |
81 | /// Queries on path measurements are made via a sampler object (see `PathSampler`) which can be configured |
82 | /// to measure real distance or normalized ones (values between 0 and 1 with zero indicating the start |
83 | /// of the path and 1 indicating the end). |
84 | /// |
85 | /// ## Differences with the `PathWalker` |
86 | /// |
87 | /// The `walker` module provides a similar functionality via the `PathWalker`. The main differences are: |
88 | /// - The path walker does all of its computation on the fly without storing any information for later use. |
89 | /// - `PathMeasurements` stores potentially large amounts of data to speed up sample queries. |
90 | /// - The cost of creating `PathMeasurements` is similar to that of walking the entire path once. |
91 | /// - Once the `PathMeasurements` have been created, random samples on the path are much faster than path walking. |
92 | /// - The PathWalker does not handle normalized distances since the length of the path cannot be known without |
93 | /// traversing the entire path at least once. |
94 | /// |
95 | /// Prefer `PathMeasurements` over `PathWalker` if the measurements can be cached and reused for a large number of |
96 | /// queries. |
97 | /// |
98 | /// ## Example |
99 | /// |
100 | /// ``` |
101 | /// use lyon_algorithms::{ |
102 | /// math::point, |
103 | /// path::Path, |
104 | /// length::approximate_length, |
105 | /// measure::{PathMeasurements, SampleType}, |
106 | /// }; |
107 | /// |
108 | /// let mut path = Path::builder(); |
109 | /// path.begin(point(0.0, 0.0)); |
110 | /// path.quadratic_bezier_to(point(1.0, 1.0), point(2.0, 0.0)); |
111 | /// path.end(false); |
112 | /// let path = path.build(); |
113 | /// |
114 | /// // Build the acceleration structure. |
115 | /// let measurements = PathMeasurements::from_path(&path, 1e-3); |
116 | /// let mut sampler = measurements.create_sampler(&path, SampleType::Normalized); |
117 | /// |
118 | /// let sample = sampler.sample(0.5); |
119 | /// println!("Mid-point position: {:?}, tangent: {:?}" , sample.position(), sample.tangent()); |
120 | /// |
121 | /// let mut second_half = Path::builder(); |
122 | /// sampler.split_range(0.5..1.0, &mut second_half); |
123 | /// let second_half = second_half.build(); |
124 | /// assert!((sampler.length() / 2.0 - approximate_length(&second_half, 1e-3)).abs() < 1e-3); |
125 | /// ``` |
126 | /// |
127 | pub struct PathMeasurements { |
128 | events: Vec<IdEvent>, |
129 | edges: Vec<Edge>, |
130 | } |
131 | |
132 | impl PathMeasurements { |
133 | /// Create empty path measurements. |
134 | /// |
135 | /// The measurements cannot be used until it has been initialized. |
136 | pub fn empty() -> Self { |
137 | PathMeasurements { |
138 | events: Vec::new(), |
139 | edges: Vec::new(), |
140 | } |
141 | } |
142 | |
143 | /// Create path measurements initialized with a `Path`. |
144 | pub fn from_path(path: &Path, tolerance: f32) -> Self { |
145 | let mut m = Self::empty(); |
146 | m.initialize(path.id_iter(), path, tolerance); |
147 | |
148 | m |
149 | } |
150 | |
151 | /// Create path measurements initialized with a `PathSlice`. |
152 | pub fn from_path_slice(path: &PathSlice, tolerance: f32) -> Self { |
153 | let mut m = Self::empty(); |
154 | m.initialize(path.id_iter(), path, tolerance); |
155 | |
156 | m |
157 | } |
158 | |
159 | /// Create path measurements initialized with a generic iterator and position store. |
160 | pub fn from_iter<Iter, PS>(path: Iter, positions: &PS, tolerance: f32) -> Self |
161 | where |
162 | Iter: IntoIterator<Item = IdEvent>, |
163 | PS: PositionStore, |
164 | { |
165 | let mut m = Self::empty(); |
166 | m.initialize(path, positions, tolerance); |
167 | |
168 | m |
169 | } |
170 | |
171 | /// Initialize the path measurements with a path. |
172 | pub fn initialize<Iter, PS>(&mut self, path: Iter, position_store: &PS, tolerance: f32) |
173 | where |
174 | Iter: IntoIterator<Item = IdEvent>, |
175 | PS: PositionStore, |
176 | { |
177 | let tolerance = tolerance.max(1e-4); |
178 | let mut events = core::mem::take(&mut self.events); |
179 | events.clear(); |
180 | events.extend(path.into_iter()); |
181 | let mut edges = core::mem::take(&mut self.edges); |
182 | edges.clear(); |
183 | |
184 | let mut distance = 0.0; |
185 | for (index, event) in events.iter().cloned().enumerate() { |
186 | match event { |
187 | IdEvent::Begin { .. } => { |
188 | edges.push(Edge { |
189 | distance, |
190 | index, |
191 | t: 1.0, |
192 | }); |
193 | } |
194 | IdEvent::Line { from, to } => { |
195 | let from = position_store.get_endpoint(from); |
196 | let to = position_store.get_endpoint(to); |
197 | distance += (from - to).length(); |
198 | edges.push(Edge { |
199 | distance, |
200 | index, |
201 | t: 1.0, |
202 | }) |
203 | } |
204 | IdEvent::Quadratic { from, ctrl, to } => { |
205 | let from = position_store.get_endpoint(from); |
206 | let to = position_store.get_endpoint(to); |
207 | let ctrl = position_store.get_control_point(ctrl); |
208 | let segment = QuadraticBezierSegment { from, ctrl, to }; |
209 | segment.for_each_flattened_with_t(tolerance, &mut |line, t| { |
210 | distance += line.length(); |
211 | edges.push(Edge { |
212 | distance, |
213 | index, |
214 | t: t.end, |
215 | }); |
216 | }); |
217 | } |
218 | IdEvent::Cubic { |
219 | from, |
220 | ctrl1, |
221 | ctrl2, |
222 | to, |
223 | } => { |
224 | let from = position_store.get_endpoint(from); |
225 | let to = position_store.get_endpoint(to); |
226 | let ctrl1 = position_store.get_control_point(ctrl1); |
227 | let ctrl2 = position_store.get_control_point(ctrl2); |
228 | let segment = CubicBezierSegment { |
229 | from, |
230 | ctrl1, |
231 | ctrl2, |
232 | to, |
233 | }; |
234 | segment.for_each_flattened_with_t(tolerance, &mut |line, t| { |
235 | distance += line.length(); |
236 | edges.push(Edge { |
237 | distance, |
238 | index, |
239 | t: t.end, |
240 | }); |
241 | }); |
242 | } |
243 | IdEvent::End { |
244 | last, |
245 | first, |
246 | close: true, |
247 | } => { |
248 | let last = position_store.get_endpoint(last); |
249 | let first = position_store.get_endpoint(first); |
250 | distance += (last - first).length(); |
251 | edges.push(Edge { |
252 | distance, |
253 | index, |
254 | t: 1.0, |
255 | }) |
256 | } |
257 | _ => {} |
258 | } |
259 | } |
260 | |
261 | if !edges.is_empty() { |
262 | debug_assert_eq!(edges.first().unwrap().distance, 0.0); |
263 | } |
264 | |
265 | self.events = events; |
266 | self.edges = edges; |
267 | } |
268 | |
269 | /// Initialize the path measurements with a path. |
270 | pub fn initialize_with_path(&mut self, path: &Path, tolerance: f32) { |
271 | self.initialize_with_path_slice(path.as_slice(), tolerance) |
272 | } |
273 | |
274 | /// Initialize the path measurements with a path. |
275 | pub fn initialize_with_path_slice(&mut self, path: PathSlice, tolerance: f32) { |
276 | self.initialize(path.id_iter(), &path, tolerance) |
277 | } |
278 | |
279 | /// Returns the approximate length of the path. |
280 | pub fn length(&self) -> f32 { |
281 | if self.edges.is_empty() { |
282 | 0.0 |
283 | } else { |
284 | self.edges.last().unwrap().distance |
285 | } |
286 | } |
287 | |
288 | /// Create an object that can perform fast sample queries on a path using the cached measurements. |
289 | /// |
290 | /// The returned sampler does not compute interpolated attributes. |
291 | pub fn create_sampler<'l, PS: PositionStore>( |
292 | &'l self, |
293 | positions: &'l PS, |
294 | ty: SampleType, |
295 | ) -> PathSampler<'l, PS, ()> { |
296 | let attr: &'static () = &(); |
297 | PathSampler::new(self, positions, attr, ty) |
298 | } |
299 | |
300 | /// Create an object that can perform fast sample queries on a path using the cached measurements. |
301 | /// |
302 | /// The returned sampler computes interpolated attributes. |
303 | pub fn create_sampler_with_attributes<'l, PS, AS>( |
304 | &'l self, |
305 | positions: &'l PS, |
306 | attributes: &'l AS, |
307 | ty: SampleType, |
308 | ) -> PathSampler<'l, PS, AS> |
309 | where |
310 | PS: PositionStore, |
311 | AS: AttributeStore, |
312 | { |
313 | PathSampler::new(self, positions, attributes, ty) |
314 | } |
315 | } |
316 | |
317 | /// Performs fast sample queries on a path with cached measurements. |
318 | /// |
319 | /// This object contains the mutable state necessary for speeding up the queries, this allows the |
320 | /// path measurements to be immutable and accessible concurrently from multiple threads if needed. |
321 | /// |
322 | /// Reusing a sampler over multiple queries saves a memory allocation if there are custom attributes, |
323 | /// And speeds up queries if they are sequentially ordered along the path. |
324 | pub struct PathSampler<'l, PS, AS> { |
325 | events: &'l [IdEvent], |
326 | edges: &'l [Edge], |
327 | positions: &'l PS, |
328 | attributes: &'l AS, |
329 | attribute_buffer: Vec<f32>, |
330 | cursor: usize, |
331 | sample_type: SampleType, |
332 | } |
333 | |
334 | impl<'l, PS: PositionStore, AS: AttributeStore> PathSampler<'l, PS, AS> { |
335 | /// Create a sampler. |
336 | /// |
337 | /// The provided positions must be the ones used when initializing the path measurements. |
338 | pub fn new( |
339 | measurements: &'l PathMeasurements, |
340 | positions: &'l PS, |
341 | attributes: &'l AS, |
342 | sample_type: SampleType, |
343 | ) -> Self { |
344 | PathSampler { |
345 | events: &measurements.events, |
346 | edges: &measurements.edges, |
347 | positions, |
348 | attributes, |
349 | attribute_buffer: alloc::vec![0.0; attributes.num_attributes()], |
350 | cursor: 0, |
351 | sample_type, |
352 | } |
353 | } |
354 | |
355 | /// Sample at a given distance along the path. |
356 | /// |
357 | /// If the path is empty, the produced sample will contain NaNs. |
358 | pub fn sample(&mut self, dist: f32) -> PathSample { |
359 | self.sample_impl(dist, self.sample_type) |
360 | } |
361 | |
362 | /// Construct a path for a specific sub-range of the measured path. |
363 | /// |
364 | /// The path measurements must have been initialized with the same path. |
365 | /// The distance is clamped to the beginning and end of the path. |
366 | /// Panics if the path is empty. |
367 | pub fn split_range(&mut self, mut range: Range<f32>, output: &mut dyn PathBuilder) { |
368 | let length = self.length(); |
369 | if self.sample_type == SampleType::Normalized { |
370 | range.start *= length; |
371 | range.end *= length; |
372 | } |
373 | range.start = range.start.max(0.0); |
374 | range.end = range.end.max(range.start); |
375 | range.start = range.start.min(length); |
376 | range.end = range.end.min(length); |
377 | |
378 | if range.is_empty() { |
379 | return; |
380 | } |
381 | |
382 | let result = self.sample_impl(range.start, SampleType::Distance); |
383 | output.begin(result.position, result.attributes); |
384 | let (ptr1, seg1) = (self.cursor, self.edges[self.cursor].index); |
385 | self.move_cursor(range.end); |
386 | let (ptr2, seg2) = (self.cursor, self.edges[self.cursor].index); |
387 | |
388 | let mut is_in_subpath = true; |
389 | if seg1 == seg2 { |
390 | self.cursor = ptr1; |
391 | let t_begin = self.t(range.start); |
392 | self.cursor = ptr2; |
393 | let t_end = self.t(range.end); |
394 | self.add_segment(seg1, Some(t_begin..t_end), output, &mut is_in_subpath); |
395 | } else { |
396 | self.cursor = ptr1; |
397 | self.add_segment( |
398 | seg1, |
399 | Some(self.t(range.start)..1.0), |
400 | output, |
401 | &mut is_in_subpath, |
402 | ); |
403 | for seg in (seg1 + 1)..seg2 { |
404 | self.add_segment(seg, None, output, &mut is_in_subpath); |
405 | } |
406 | self.cursor = ptr2; |
407 | self.add_segment( |
408 | seg2, |
409 | Some(0.0..self.t(range.end)), |
410 | output, |
411 | &mut is_in_subpath, |
412 | ); |
413 | } |
414 | |
415 | output.end(false); |
416 | } |
417 | |
418 | /// Returns the approximate length of the path. |
419 | pub fn length(&self) -> f32 { |
420 | if self.edges.is_empty() { |
421 | 0.0 |
422 | } else { |
423 | self.edges.last().unwrap().distance |
424 | } |
425 | } |
426 | |
427 | fn to_segment(&self, event: IdEvent) -> SegmentWrapper { |
428 | match event { |
429 | IdEvent::Line { from, to } => SegmentWrapper::Line( |
430 | LineSegment { |
431 | from: self.positions.get_endpoint(from), |
432 | to: self.positions.get_endpoint(to), |
433 | }, |
434 | (from, to), |
435 | ), |
436 | IdEvent::Quadratic { from, ctrl, to } => SegmentWrapper::Quadratic( |
437 | QuadraticBezierSegment { |
438 | from: self.positions.get_endpoint(from), |
439 | to: self.positions.get_endpoint(to), |
440 | ctrl: self.positions.get_control_point(ctrl), |
441 | }, |
442 | (from, to), |
443 | ), |
444 | IdEvent::Cubic { |
445 | from, |
446 | ctrl1, |
447 | ctrl2, |
448 | to, |
449 | } => SegmentWrapper::Cubic( |
450 | CubicBezierSegment { |
451 | from: self.positions.get_endpoint(from), |
452 | to: self.positions.get_endpoint(to), |
453 | ctrl1: self.positions.get_control_point(ctrl1), |
454 | ctrl2: self.positions.get_control_point(ctrl2), |
455 | }, |
456 | (from, to), |
457 | ), |
458 | IdEvent::End { |
459 | last, |
460 | first, |
461 | close: true, |
462 | } => SegmentWrapper::Line( |
463 | LineSegment { |
464 | from: self.positions.get_endpoint(last), |
465 | to: self.positions.get_endpoint(first), |
466 | }, |
467 | (last, first), |
468 | ), |
469 | _ => SegmentWrapper::Empty, |
470 | } |
471 | } |
472 | |
473 | fn in_bounds(&self, dist: f32) -> bool { |
474 | self.cursor != 0 |
475 | && self.edges[self.cursor - 1].distance <= dist |
476 | && dist <= self.edges[self.cursor].distance |
477 | } |
478 | |
479 | /// Move the pointer so the given point is on the current segment. |
480 | fn move_cursor(&mut self, dist: f32) { |
481 | if dist == 0.0 { |
482 | self.cursor = 1; |
483 | return; |
484 | } |
485 | if self.in_bounds(dist) { |
486 | // No need to move |
487 | return; |
488 | } |
489 | |
490 | // Performs on [first, last) |
491 | // ...TTFFF... |
492 | // ^ |
493 | // sample this point |
494 | fn partition_point(first: usize, last: usize, pred: impl Fn(usize) -> bool) -> usize { |
495 | let mut l = first; |
496 | let mut r = last; |
497 | while l < r { |
498 | let mid = (l + r) / 2; |
499 | if pred(mid) { |
500 | l = mid + 1; |
501 | } else { |
502 | r = mid; |
503 | } |
504 | } |
505 | debug_assert_eq!(l, r); |
506 | debug_assert_ne!(l, last); |
507 | l |
508 | } |
509 | |
510 | fn floor_log2(num: usize) -> u32 { |
511 | core::mem::size_of::<usize>() as u32 * 8 - num.leading_zeros() - 1 |
512 | } |
513 | |
514 | // Here we use a heuristic method combining method 1 & 2 |
515 | // Method 1: Move step by step until we found the corresponding segment, works well on short paths and near queries |
516 | // Time complexity: (expected) (dist - start).abs() / len * num |
517 | // Method 2. Binary search on lengths, works well on long paths and random calls |
518 | // Time complexity: (exact) floor(log2(num)) |
519 | // where `len` and `num` are given as follow |
520 | // |
521 | // According to the benchmark, this method works well in both cases and has low overhead in relative to Method 1 & 2. |
522 | // Benchmark code: https://gist.github.com/Mivik/5f67ae5a72eae3884b2f386370554966 |
523 | let start = self.edges[self.cursor].distance; |
524 | if start < dist { |
525 | let (len, num) = (self.length() - start, self.edges.len() - self.cursor - 1); |
526 | debug_assert_ne!(num, 0); |
527 | if (dist - start) / len * (num as f32) < floor_log2(num) as f32 { |
528 | loop { |
529 | self.cursor += 1; |
530 | if dist <= self.edges[self.cursor].distance { |
531 | break; |
532 | } |
533 | } |
534 | } else { |
535 | self.cursor = partition_point(self.cursor + 1, self.edges.len(), |p| { |
536 | self.edges[p].distance < dist |
537 | }); |
538 | } |
539 | } else { |
540 | let (len, num) = (start, self.cursor + 1); |
541 | debug_assert_ne!(num, 0); |
542 | if (start - dist) / len * (num as f32) < floor_log2(num) as f32 { |
543 | loop { |
544 | self.cursor -= 1; |
545 | if self.cursor == 0 || self.edges[self.cursor - 1].distance < dist { |
546 | break; |
547 | } |
548 | } |
549 | } else { |
550 | self.cursor = partition_point(0, self.cursor, |p| self.edges[p].distance < dist); |
551 | } |
552 | } |
553 | |
554 | debug_assert!(self.in_bounds(dist)); |
555 | } |
556 | |
557 | /// Interpolate the custom attributes. |
558 | fn interpolate_attributes(&mut self, from: EndpointId, to: EndpointId, t: f32) { |
559 | let from = self.attributes.get(from); |
560 | let to = self.attributes.get(to); |
561 | for i in 0..self.attribute_buffer.len() { |
562 | self.attribute_buffer[i] = from[i] * (1.0 - t) + to[i] * t; |
563 | } |
564 | } |
565 | |
566 | /// Returns the relative position (0 ~ 1) of the given point on the current segment. |
567 | fn t(&self, dist: f32) -> f32 { |
568 | debug_assert!(self.in_bounds(dist)); |
569 | let prev = &self.edges[self.cursor - 1]; |
570 | let cur = &self.edges[self.cursor]; |
571 | let t_begin = if prev.index == cur.index { prev.t } else { 0.0 }; |
572 | let t_end = cur.t; |
573 | t_begin + (t_end - t_begin) * ((dist - prev.distance) / (cur.distance - prev.distance)) |
574 | } |
575 | |
576 | fn sample_impl(&mut self, mut dist: f32, sample_type: SampleType) -> PathSample { |
577 | let length = self.length(); |
578 | if length == 0.0 { |
579 | return self.sample_zero_length(); |
580 | } |
581 | if sample_type == SampleType::Normalized { |
582 | dist *= length; |
583 | } |
584 | dist = dist.max(0.0); |
585 | dist = dist.min(length); |
586 | |
587 | self.move_cursor(dist); |
588 | let t = self.t(dist); |
589 | macro_rules! dispatched_call { |
590 | ([$v:expr] ($seg:ident, $pair:ident) => $code:block) => { |
591 | #[allow(unused_parens)] |
592 | match $v { |
593 | SegmentWrapper::Line($seg, $pair) => $code, |
594 | SegmentWrapper::Quadratic($seg, $pair) => $code, |
595 | SegmentWrapper::Cubic($seg, $pair) => $code, |
596 | _ => {} |
597 | } |
598 | }; |
599 | } |
600 | |
601 | dispatched_call!([self.to_segment(self.events[self.edges[self.cursor].index])] (segment, pair) => { |
602 | self.interpolate_attributes(pair.0, pair.1, t); |
603 | return PathSample { |
604 | position: segment.sample(t), |
605 | tangent: segment.derivative(t).normalize(), |
606 | attributes: &self.attribute_buffer, |
607 | } |
608 | }); |
609 | |
610 | unreachable!(); |
611 | } |
612 | |
613 | #[cold ] |
614 | fn sample_zero_length(&mut self) -> PathSample { |
615 | if let Some(IdEvent::Begin { at }) = self.events.first() { |
616 | return PathSample { |
617 | position: self.positions.get_endpoint(*at), |
618 | tangent: vector(0.0, 0.0), |
619 | attributes: self.attributes.get(*at), |
620 | }; |
621 | } |
622 | |
623 | for value in &mut self.attribute_buffer { |
624 | *value = f32::NAN; |
625 | } |
626 | |
627 | PathSample { |
628 | position: point(f32::NAN, f32::NAN), |
629 | tangent: vector(f32::NAN, f32::NAN), |
630 | attributes: &self.attribute_buffer, |
631 | } |
632 | } |
633 | |
634 | /// Caller needs to hold a parameter to keep track of whether we're in a subpath or not, as this would be determined |
635 | /// by prior segments. This function will update `is_in_subpath` based on the segment it adds. |
636 | fn add_segment( |
637 | &mut self, |
638 | ptr: usize, |
639 | range: Option<Range<f32>>, |
640 | dest: &mut dyn PathBuilder, |
641 | is_in_subpath: &mut bool, |
642 | ) { |
643 | let segment = self.to_segment(self.events[ptr]); |
644 | let segment = match range.clone() { |
645 | Some(range) => segment.split(range), |
646 | None => segment, |
647 | }; |
648 | macro_rules! obtain_attrs { |
649 | ($p:ident, $index:tt) => { |
650 | match range.clone() { |
651 | Some(range) => { |
652 | if range.end == 1.0 { |
653 | self.attributes.get($p.$index) |
654 | } else { |
655 | self.interpolate_attributes($p.0, $p.1, range.end); |
656 | &mut self.attribute_buffer |
657 | } |
658 | } |
659 | None => self.attributes.get($p.$index), |
660 | } |
661 | }; |
662 | } |
663 | |
664 | match segment { |
665 | SegmentWrapper::Line(LineSegment { from, to }, pair) => { |
666 | if !*is_in_subpath { |
667 | dest.end(false); |
668 | dest.begin(from, obtain_attrs!(pair, 0)); |
669 | } |
670 | dest.line_to(to, obtain_attrs!(pair, 1)); |
671 | } |
672 | SegmentWrapper::Quadratic(QuadraticBezierSegment { from, ctrl, to }, pair) => { |
673 | if !*is_in_subpath { |
674 | dest.end(false); |
675 | dest.begin(from, obtain_attrs!(pair, 0)); |
676 | } |
677 | dest.quadratic_bezier_to(ctrl, to, obtain_attrs!(pair, 1)); |
678 | } |
679 | SegmentWrapper::Cubic( |
680 | CubicBezierSegment { |
681 | from, |
682 | ctrl1, |
683 | ctrl2, |
684 | to, |
685 | }, |
686 | pair, |
687 | ) => { |
688 | if !*is_in_subpath { |
689 | dest.end(false); |
690 | dest.begin(from, obtain_attrs!(pair, 0)); |
691 | } |
692 | dest.cubic_bezier_to(ctrl1, ctrl2, to, obtain_attrs!(pair, 1)); |
693 | } |
694 | _ => {} |
695 | } |
696 | |
697 | *is_in_subpath = !matches!( |
698 | self.events[ptr], |
699 | IdEvent::End { .. } | IdEvent::Begin { .. } |
700 | ); |
701 | } |
702 | } |
703 | |
704 | #[cfg (test)] |
705 | fn slice(a: &[f32]) -> &[f32] { |
706 | a |
707 | } |
708 | |
709 | #[test ] |
710 | fn measure_line() { |
711 | let mut path = Path::builder(); |
712 | path.begin(point(1.0, 1.0)); |
713 | path.line_to(point(0.0, 0.0)); |
714 | path.end(false); |
715 | let path = path.build(); |
716 | let measure = PathMeasurements::from_path(&path, 0.01); |
717 | let mut sampler = measure.create_sampler(&path, SampleType::Normalized); |
718 | for t in [0.0, 0.2, 0.3, 0.5, 1.0] { |
719 | let result = sampler.sample(t); |
720 | assert!((result.position - point(1.0 - t, 1.0 - t)).length() < 1e-5); |
721 | assert_eq!(result.tangent, vector(-1.0, -1.0).normalize()); |
722 | } |
723 | } |
724 | |
725 | #[test ] |
726 | fn measure_square() { |
727 | let mut path = Path::builder(); |
728 | path.begin(point(0.0, 0.0)); |
729 | path.line_to(point(1.0, 0.0)); |
730 | path.line_to(point(1.0, 1.0)); |
731 | path.line_to(point(0.0, 1.0)); |
732 | path.close(); |
733 | let path = path.build(); |
734 | let measure = PathMeasurements::from_path(&path, 0.01); |
735 | let mut sampler = measure.create_sampler(&path, SampleType::Normalized); |
736 | for (t, position, tangent) in [ |
737 | (0.125, point(0.5, 0.0), vector(1.0, 0.0)), |
738 | (0.375, point(1.0, 0.5), vector(0.0, 1.0)), |
739 | (0.625, point(0.5, 1.0), vector(-1.0, 0.0)), |
740 | (0.875, point(0.0, 0.5), vector(0.0, -1.0)), |
741 | ] { |
742 | let result = sampler.sample(t); |
743 | assert!((result.position - position).length() < 1e-5); |
744 | assert_eq!(result.tangent, tangent); |
745 | } |
746 | } |
747 | |
748 | #[test ] |
749 | fn measure_attributes() { |
750 | let mut path = Path::builder_with_attributes(2); |
751 | path.begin(point(0.0, 0.0), &[1.0, 2.0]); |
752 | path.line_to(point(1.0, 0.0), &[2.0, 3.0]); |
753 | path.line_to(point(1.0, 1.0), &[0.0, 0.0]); |
754 | path.end(false); |
755 | let path = path.build(); |
756 | let measure = PathMeasurements::from_path(&path, 0.01); |
757 | let mut sampler = measure.create_sampler_with_attributes(&path, &path, SampleType::Normalized); |
758 | |
759 | for (t, position, attrs) in [ |
760 | (0.25, point(0.5, 0.0), &[1.5, 2.5]), |
761 | (0.5, point(1.0, 0.0), &[2.0, 3.0]), |
762 | (0.75, point(1.0, 0.5), &[1.0, 1.5]), |
763 | ] { |
764 | let result = sampler.sample(t); |
765 | assert!((result.position - position).length() < 1e-5); |
766 | for i in 0..2 { |
767 | assert!((result.attributes[i] - attrs[i]).abs() < 1e-5); |
768 | } |
769 | } |
770 | } |
771 | |
772 | #[test ] |
773 | fn measure_bezier_curve() { |
774 | let mut path = Path::builder(); |
775 | path.begin(point(0.0, 0.0)); |
776 | path.quadratic_bezier_to(point(0.5, 0.7), point(1.0, 0.0)); |
777 | path.quadratic_bezier_to(point(1.5, -0.7), point(2.0, 0.0)); |
778 | path.end(false); |
779 | let path = path.build(); |
780 | |
781 | let measure = PathMeasurements::from_path(&path, 0.01); |
782 | let mut sampler = measure.create_sampler(&path, SampleType::Normalized); |
783 | |
784 | for t in [0.25, 0.75] { |
785 | let result = sampler.sample(t); |
786 | assert_eq!(result.tangent, vector(1.0, 0.0)); |
787 | } |
788 | for (t, position) in [ |
789 | (0.0, point(0.0, 0.0)), |
790 | (0.5, point(1.0, 0.0)), |
791 | (1.0, point(2.0, 0.0)), |
792 | ] { |
793 | let result = sampler.sample(t); |
794 | assert_eq!(result.position, position); |
795 | } |
796 | } |
797 | |
798 | #[test ] |
799 | fn split_square() { |
800 | use crate::path::Event; |
801 | |
802 | let mut path = Path::builder(); |
803 | path.begin(point(0.0, 0.0)); |
804 | path.line_to(point(1.0, 0.0)); |
805 | path.line_to(point(1.0, 1.0)); |
806 | path.line_to(point(0.0, 1.0)); |
807 | path.close(); |
808 | let path = path.build(); |
809 | let measure = PathMeasurements::from_path(&path, 0.01); |
810 | let mut sampler = measure.create_sampler(&path, SampleType::Normalized); |
811 | let mut path2 = Path::builder(); |
812 | sampler.split_range(0.125..0.625, &mut path2); |
813 | let path2 = path2.build(); |
814 | assert_eq!( |
815 | path2.iter().collect::<Vec<_>>(), |
816 | alloc::vec![ |
817 | Event::Begin { |
818 | at: point(0.5, 0.0) |
819 | }, |
820 | Event::Line { |
821 | from: point(0.5, 0.0), |
822 | to: point(1.0, 0.0) |
823 | }, |
824 | Event::Line { |
825 | from: point(1.0, 0.0), |
826 | to: point(1.0, 1.0) |
827 | }, |
828 | Event::Line { |
829 | from: point(1.0, 1.0), |
830 | to: point(0.5, 1.0) |
831 | }, |
832 | Event::End { |
833 | last: point(0.5, 1.0), |
834 | first: point(0.5, 0.0), |
835 | close: false |
836 | }, |
837 | ] |
838 | ); |
839 | } |
840 | |
841 | #[test ] |
842 | fn split_bezier_curve() { |
843 | use crate::path::Event; |
844 | |
845 | let mut path = Path::builder(); |
846 | path.begin(point(0.0, 0.0)); |
847 | path.quadratic_bezier_to(point(1.0, 1.0), point(2.0, 0.0)); |
848 | path.end(false); |
849 | let path = path.build(); |
850 | let measure = PathMeasurements::from_path(&path, 0.01); |
851 | let mut sampler = measure.create_sampler(&path, SampleType::Normalized); |
852 | |
853 | let mut path2 = Path::builder(); |
854 | sampler.split_range(0.5..1.0, &mut path2); |
855 | let path2 = path2.build(); |
856 | |
857 | assert_eq!( |
858 | path2.iter().collect::<Vec<_>>(), |
859 | alloc::vec![ |
860 | Event::Begin { |
861 | at: point(1.0, 0.5) |
862 | }, |
863 | Event::Quadratic { |
864 | from: point(1.0, 0.5), |
865 | ctrl: point(1.5, 0.5), |
866 | to: point(2.0, 0.0), |
867 | }, |
868 | Event::End { |
869 | last: point(2.0, 0.0), |
870 | first: point(1.0, 0.5), |
871 | close: false |
872 | } |
873 | ] |
874 | ); |
875 | } |
876 | |
877 | #[test ] |
878 | fn split_attributes() { |
879 | use crate::path::Event; |
880 | |
881 | let mut path = Path::builder_with_attributes(2); |
882 | path.begin(point(0.0, 0.0), &[1.0, 2.0]); |
883 | path.line_to(point(1.0, 0.0), &[2.0, 3.0]); |
884 | path.line_to(point(1.0, 1.0), &[0.0, 0.0]); |
885 | path.end(false); |
886 | let path = path.build(); |
887 | let measure = PathMeasurements::from_path(&path, 0.01); |
888 | let mut sampler = measure.create_sampler_with_attributes(&path, &path, SampleType::Normalized); |
889 | |
890 | let mut path2 = Path::builder_with_attributes(2); |
891 | sampler.split_range(0.0..1.0, &mut path2); |
892 | let path2 = path2.build(); |
893 | |
894 | assert_eq!( |
895 | path2.iter_with_attributes().collect::<Vec<_>>(), |
896 | path.iter_with_attributes().collect::<Vec<_>>() |
897 | ); |
898 | |
899 | let mut path3 = Path::builder_with_attributes(2); |
900 | sampler.split_range(0.25..0.75, &mut path3); |
901 | let path3 = path3.build(); |
902 | |
903 | assert_eq!( |
904 | path3.iter_with_attributes().collect::<Vec<_>>(), |
905 | alloc::vec![ |
906 | Event::Begin { |
907 | at: (point(0.5, 0.0), slice(&[1.5, 2.5])) |
908 | }, |
909 | Event::Line { |
910 | from: (point(0.5, 0.0), slice(&[1.5, 2.5])), |
911 | to: (point(1.0, 0.0), slice(&[2.0, 3.0])), |
912 | }, |
913 | Event::Line { |
914 | from: (point(1.0, 0.0), slice(&[2.0, 3.0])), |
915 | to: (point(1.0, 0.5), slice(&[1.0, 1.5])), |
916 | }, |
917 | Event::End { |
918 | last: (point(1.0, 0.5), slice(&[1.0, 1.5])), |
919 | first: (point(0.5, 0.0), slice(&[1.5, 2.5])), |
920 | close: false |
921 | } |
922 | ] |
923 | ); |
924 | } |
925 | |
926 | #[test ] |
927 | fn zero_length() { |
928 | fn expect_nans(sample: PathSample, num_attribs: usize) { |
929 | assert!(sample.position.x.is_nan()); |
930 | assert!(sample.position.y.is_nan()); |
931 | assert!(sample.tangent.x.is_nan()); |
932 | assert!(sample.tangent.y.is_nan()); |
933 | for attr in sample.attributes { |
934 | assert!(attr.is_nan()); |
935 | } |
936 | assert_eq!(sample.attributes.len(), num_attribs); |
937 | } |
938 | |
939 | let mut path = Path::builder_with_attributes(2); |
940 | path.begin(point(1.0, 2.0), &[3.0, 4.0]); |
941 | path.end(false); |
942 | let path = path.build(); |
943 | let measure = PathMeasurements::from_path(&path, 0.01); |
944 | let mut sampler = measure.create_sampler_with_attributes(&path, &path, SampleType::Normalized); |
945 | let expected = PathSample { |
946 | position: point(1.0, 2.0), |
947 | tangent: vector(0.0, 0.0), |
948 | attributes: &[3.0, 4.0], |
949 | }; |
950 | assert_eq!(sampler.sample(0.0), expected); |
951 | assert_eq!(sampler.sample(0.5), expected); |
952 | assert_eq!(sampler.sample(1.0), expected); |
953 | |
954 | let mut path = Path::builder_with_attributes(2); |
955 | path.begin(point(1.0, 2.0), &[3.0, 4.0]); |
956 | path.end(false); |
957 | let path = path.build(); |
958 | let measure = PathMeasurements::from_path(&path, 0.01); |
959 | let mut sampler = measure.create_sampler_with_attributes(&path, &path, SampleType::Distance); |
960 | let expected = PathSample { |
961 | position: point(1.0, 2.0), |
962 | tangent: vector(0.0, 0.0), |
963 | attributes: &[3.0, 4.0], |
964 | }; |
965 | assert_eq!(sampler.sample(0.0), expected); |
966 | assert_eq!(sampler.sample(0.5), expected); |
967 | assert_eq!(sampler.sample(1.0), expected); |
968 | |
969 | let path = Path::builder_with_attributes(2).build(); |
970 | let measure = PathMeasurements::from_path(&path, 0.01); |
971 | let mut sampler = measure.create_sampler_with_attributes(&path, &path, SampleType::Normalized); |
972 | expect_nans(sampler.sample(0.0), 2); |
973 | expect_nans(sampler.sample(0.5), 2); |
974 | expect_nans(sampler.sample(1.0), 2); |
975 | |
976 | let path = Path::builder_with_attributes(2).build(); |
977 | let measure = PathMeasurements::from_path(&path, 0.01); |
978 | let mut sampler = measure.create_sampler_with_attributes(&path, &path, SampleType::Distance); |
979 | expect_nans(sampler.sample(0.0), 2); |
980 | expect_nans(sampler.sample(0.5), 2); |
981 | expect_nans(sampler.sample(1.0), 2); |
982 | } |
983 | |
984 | |
985 | #[test ] |
986 | fn multiple_sub_paths() { |
987 | let mut path = Path::builder(); |
988 | |
989 | path.begin(point(0.0, 0.0)); |
990 | path.line_to(point(10.0, 0.0)); |
991 | path.end(false); |
992 | |
993 | path.begin(point(10.0, 10.0)); |
994 | path.line_to(point(20.0, 10.0)); |
995 | path.end(false); |
996 | |
997 | let path = path.build(); |
998 | let measure = PathMeasurements::from_path(&path, 0.01); |
999 | let mut sampler = measure.create_sampler(&path, SampleType::Normalized); |
1000 | |
1001 | let mut dashes = Path::builder(); |
1002 | sampler.split_range(0.0 .. 0.25, &mut dashes); |
1003 | sampler.split_range(0.25 .. 0.5, &mut dashes); |
1004 | // Avoid starting subpaths exactly on the join as we may begin with a zero-length subpath |
1005 | sampler.split_range(0.6 .. 0.75, &mut dashes); |
1006 | sampler.split_range(0.75 .. 1.0, &mut dashes); |
1007 | let dashes = dashes.build(); |
1008 | |
1009 | let mut iter = dashes.iter(); |
1010 | |
1011 | use crate::path::geom::euclid::approxeq::ApproxEq; |
1012 | fn expect_begin(event: Option<path::PathEvent>, pos: Point) { |
1013 | std::eprintln!("- {:?}" , event); |
1014 | if let Some(path::PathEvent::Begin { at }) = event { |
1015 | assert!(at.approx_eq(&pos), "Expected Begin {:?}, got {:?}" , pos, at); |
1016 | } else { |
1017 | panic!("Expected begin, got {:?}" , event); |
1018 | } |
1019 | } |
1020 | |
1021 | fn expect_end(event: Option<path::PathEvent>, pos: Point) { |
1022 | std::eprintln!("- {:?}" , event); |
1023 | if let Some(path::PathEvent::End { last, .. }) = event { |
1024 | assert!(last.approx_eq(&pos), "Expected End {:?}, got {:?}" , pos, last); |
1025 | } else { |
1026 | panic!("Expected end, got {:?}" , event); |
1027 | } |
1028 | } |
1029 | fn expect_line(event: Option<path::PathEvent>, expect_from: Point, expect_to: Point) { |
1030 | std::eprintln!("- {:?}" , event); |
1031 | if let Some(path::PathEvent::Line { from, to }) = event { |
1032 | assert!(from.approx_eq(&expect_from), "Expected line {:?} {:?}, got {:?} {:?}" , expect_from, expect_to, from, to); |
1033 | assert!(to.approx_eq(&expect_to), "Expected line {:?} {:?}, got {:?} {:?}" , expect_from, expect_to, from, to); |
1034 | } else { |
1035 | panic!("Expected a line {:?} {:?}, got {:?}" , expect_from, expect_to, event); |
1036 | } |
1037 | } |
1038 | |
1039 | expect_begin(iter.next(), point(0.0, 0.0)); |
1040 | expect_line(iter.next(), point(0.0, 0.0), point(5.0, 0.0)); |
1041 | expect_end(iter.next(), point(5.0, 0.0)); |
1042 | |
1043 | expect_begin(iter.next(), point(5.0, 0.0)); |
1044 | expect_line(iter.next(), point(5.0, 0.0), point(10.0, 0.0)); |
1045 | expect_end(iter.next(), point(10.0, 0.0)); |
1046 | |
1047 | expect_begin(iter.next(), point(12.0, 10.0)); |
1048 | expect_line(iter.next(), point(12.0, 10.0), point(15.0, 10.0)); |
1049 | expect_end(iter.next(), point(15.0, 10.0)); |
1050 | |
1051 | expect_begin(iter.next(), point(15.0, 10.0)); |
1052 | expect_line(iter.next(), point(15.0, 10.0), point(20.0, 10.0)); |
1053 | expect_end(iter.next(), point(20.0, 10.0)); |
1054 | } |