1 | /*! |
2 | * \file path_dash_effect.cpp |
3 | * \brief file path_dash_effect.cpp |
4 | * |
5 | * Copyright 2019 by Intel. |
6 | * |
7 | * Contact: kevin.rogovin@gmail.com |
8 | * |
9 | * This Source Code Form is subject to the |
10 | * terms of the Mozilla Public License, v. 2.0. |
11 | * If a copy of the MPL was not distributed with |
12 | * this file, You can obtain one at |
13 | * http://mozilla.org/MPL/2.0/. |
14 | * |
15 | * \author Kevin Rogovin <kevin.rogovin@gmail.com> |
16 | * |
17 | */ |
18 | |
19 | #include <vector> |
20 | #include <algorithm> |
21 | #include <fastuidraw/path_dash_effect.hpp> |
22 | #include <private/util_private.hpp> |
23 | #include <cmath> |
24 | |
25 | namespace |
26 | { |
27 | class DashElement |
28 | { |
29 | public: |
30 | float m_start_distance, m_end_distance; |
31 | float m_draw_length, m_skip_length; |
32 | }; |
33 | |
34 | class DashElementProxy |
35 | { |
36 | public: |
37 | DashElementProxy(int idx, |
38 | int cycle, |
39 | const DashElement &d, |
40 | float increase_by): |
41 | m_idx(idx), |
42 | m_cycle(cycle), |
43 | m_start_distance(increase_by + d.m_start_distance), |
44 | m_end_distance(increase_by + d.m_end_distance), |
45 | m_draw_length(d.m_draw_length), |
46 | m_skip_length(d.m_skip_length) |
47 | {} |
48 | |
49 | float |
50 | end_draw(void) const |
51 | { |
52 | return m_start_distance + m_draw_length; |
53 | } |
54 | |
55 | int m_idx, m_cycle; |
56 | float m_start_distance, m_end_distance; |
57 | float m_draw_length, m_skip_length; |
58 | }; |
59 | |
60 | inline |
61 | bool |
62 | compare_dash_element_against_distance(const DashElement &lhs, float rhs) |
63 | { |
64 | return lhs.m_end_distance < rhs; |
65 | } |
66 | |
67 | void |
68 | add_cap(const fastuidraw::TessellatedPath::segment &S, |
69 | float length_from_start_S, |
70 | bool is_start_cap, |
71 | fastuidraw::PathEffect::Storage &dst) |
72 | { |
73 | fastuidraw::TessellatedPath::cap C; |
74 | float s, t, n; |
75 | |
76 | t = length_from_start_S / S.m_length; |
77 | s = 1.0f - t; |
78 | n = (is_start_cap) ? -1.0f : +1.0f; |
79 | if (S.m_type == fastuidraw::TessellatedPath::line_segment) |
80 | { |
81 | C.m_position = s * S.m_start_pt + t * S.m_end_pt; |
82 | C.m_unit_vector = n * S.m_enter_segment_unit_vector; |
83 | } |
84 | else |
85 | { |
86 | float angle(s * S.m_arc_angle.m_begin + t * S.m_arc_angle.m_end); |
87 | fastuidraw::vec2 cs(fastuidraw::t_cos(angle), fastuidraw::t_sin(angle)); |
88 | |
89 | if (S.m_arc_angle.m_begin > S.m_arc_angle.m_end) |
90 | { |
91 | n = -n; |
92 | } |
93 | C.m_position = S.m_center + S.m_radius * cs; |
94 | C.m_unit_vector = n * fastuidraw::vec2(-cs.y(), cs.x()); |
95 | } |
96 | C.m_contour_length = S.m_contour_length; |
97 | C.m_edge_length = S.m_edge_length; |
98 | C.m_distance_from_contour_start = S.m_distance_from_contour_start + t * S.m_length; |
99 | C.m_distance_from_edge_start = S.m_distance_from_edge_start + t * S.m_length; |
100 | C.m_is_starting_cap = is_start_cap; |
101 | C.m_contour_id = S.m_contour_id; |
102 | dst.add_cap(C); |
103 | } |
104 | |
105 | class PathDashEffectPrivate |
106 | { |
107 | public: |
108 | PathDashEffectPrivate(void): |
109 | m_dash_offset(0.0f) |
110 | {} |
111 | |
112 | float |
113 | current_length(void) const |
114 | { |
115 | return (m_lengths.empty()) ? |
116 | 0.0f: |
117 | m_lengths.back().m_end_distance; |
118 | } |
119 | |
120 | void |
121 | process_segment(const fastuidraw::TessellatedPath::segment &S, |
122 | DashElementProxy &P, |
123 | fastuidraw::PathEffect::Storage &dst); |
124 | |
125 | DashElementProxy |
126 | proxy(int I) const; |
127 | |
128 | /* TODO: add parameters to PathDashEffect to select if distance |
129 | * value starts at contour, since the last join or a distance |
130 | * since the last join. |
131 | */ |
132 | float |
133 | distance(const fastuidraw::TessellatedPath::segment &S) |
134 | { |
135 | return m_dash_offset + S.m_distance_from_contour_start; |
136 | } |
137 | |
138 | float |
139 | distance(const fastuidraw::TessellatedPath::join &J) |
140 | { |
141 | return m_dash_offset + J.m_distance_from_contour_start; |
142 | } |
143 | |
144 | float |
145 | distance(const fastuidraw::TessellatedPath::cap &C) |
146 | { |
147 | return (C.m_is_starting_cap) ? |
148 | m_dash_offset: |
149 | m_dash_offset + C.m_contour_length; |
150 | } |
151 | |
152 | int |
153 | find_index(float f) const; |
154 | |
155 | std::vector<DashElement>::const_iterator |
156 | find_iterator(float &f, int &N) const; |
157 | |
158 | bool |
159 | point_inside_draw_interval(float f) const; |
160 | |
161 | float m_dash_offset; |
162 | std::vector<DashElement> m_lengths; |
163 | }; |
164 | } |
165 | |
166 | //////////////////////////////// |
167 | // PathDashEffectPrivate methods |
168 | DashElementProxy |
169 | PathDashEffectPrivate:: |
170 | proxy(int I) const |
171 | { |
172 | unsigned int N(I / m_lengths.size()); |
173 | unsigned int srcI(I - N * m_lengths.size()); |
174 | const DashElement &src(m_lengths[srcI]); |
175 | float fN(N); |
176 | DashElementProxy return_value(I, N, src, fN * current_length()); |
177 | |
178 | return return_value; |
179 | } |
180 | |
181 | std::vector<DashElement>::const_iterator |
182 | PathDashEffectPrivate:: |
183 | find_iterator(float &f, int &N) const |
184 | { |
185 | float c(current_length()); |
186 | std::vector<DashElement>::const_iterator iter; |
187 | |
188 | if (f >= c || f < 0.0f) |
189 | { |
190 | /* Compute r, fN so that |
191 | * 1) f = r + fN * c |
192 | * 2) 0.0 <= r < c |
193 | */ |
194 | float ff(f / c), fN, r; |
195 | |
196 | r = std::modf(ff, &fN); |
197 | if (r < 0.0f) |
198 | { |
199 | r += 1.0f; |
200 | fN -= 1.0f; |
201 | } |
202 | N = fN; |
203 | f = r * c; |
204 | } |
205 | else |
206 | { |
207 | N = 0; |
208 | } |
209 | |
210 | /* iter points to the first element with iter->m_end_distance >= f */ |
211 | iter = std::lower_bound(m_lengths.begin(), m_lengths.end(), |
212 | f, compare_dash_element_against_distance); |
213 | |
214 | return iter; |
215 | } |
216 | |
217 | int |
218 | PathDashEffectPrivate:: |
219 | find_index(float f) const |
220 | { |
221 | std::vector<DashElement>::const_iterator iter; |
222 | int N; |
223 | |
224 | iter = find_iterator(f, N); |
225 | return std::distance(m_lengths.begin(), iter) + m_lengths.size() * N; |
226 | } |
227 | |
228 | bool |
229 | PathDashEffectPrivate:: |
230 | point_inside_draw_interval(float f) const |
231 | { |
232 | /* We shall interpret an empty-dash pattern as no dashing at all */ |
233 | if (m_lengths.empty()) |
234 | { |
235 | return true; |
236 | } |
237 | |
238 | std::vector<DashElement>::const_iterator iter; |
239 | int N; |
240 | |
241 | iter = find_iterator(f, N); |
242 | return iter == m_lengths.end() |
243 | || iter->m_start_distance + iter->m_draw_length >= f; |
244 | } |
245 | |
246 | void |
247 | PathDashEffectPrivate:: |
248 | process_segment(const fastuidraw::TessellatedPath::segment &pS, |
249 | DashElementProxy &P, |
250 | fastuidraw::PathEffect::Storage &dst) |
251 | { |
252 | fastuidraw::TessellatedPath::segment S(pS); |
253 | float t, startS(distance(S)), endS(startS + S.m_length); |
254 | bool need_to_begin_chain(true); |
255 | fastuidraw::TessellatedPath::segment A, B; |
256 | |
257 | for (;;) |
258 | { |
259 | FASTUIDRAWassert(startS >= P.m_start_distance); |
260 | if (P.end_draw() >= startS && P.end_draw() <= endS) |
261 | { |
262 | /* Draw interval of P ends within S, so add an ending cap. */ |
263 | add_cap(S, P.end_draw() - startS, false, dst); |
264 | S.m_last_segment_of_edge = true; |
265 | } |
266 | |
267 | if (P.end_draw() >= startS && P.m_draw_length > 0.0f) |
268 | { |
269 | if (P.end_draw() < endS) |
270 | { |
271 | t = (P.end_draw() - startS) / S.m_length; |
272 | |
273 | /* add the draw element of S on [0, t] */ |
274 | S.split_segment(t, &A, &B); |
275 | A.m_last_segment_of_edge = true; |
276 | dst.add_segment(A); |
277 | |
278 | /* continue with the S on [t, 1], note that we |
279 | * need to start a new chain |
280 | */ |
281 | S = B; |
282 | S.m_continuation_with_predecessor = false; |
283 | S.m_first_segment_of_edge = true; |
284 | startS = distance(S); |
285 | need_to_begin_chain = true; |
286 | } |
287 | else |
288 | { |
289 | /* The draw interval extends past the ending of S, |
290 | * so we are done. |
291 | */ |
292 | dst.add_segment(S); |
293 | return; |
294 | } |
295 | } |
296 | |
297 | if (P.m_end_distance <= endS) |
298 | { |
299 | /* The skip interval ends within S, add a starting cap for |
300 | * the next draw interval. |
301 | */ |
302 | add_cap(S, P.m_end_distance - startS, true, dst); |
303 | } |
304 | else |
305 | { |
306 | /* the skip interval extends past S, so we are done with S */ |
307 | return; |
308 | } |
309 | |
310 | /* S extends past the skip interval, take the portion of S that does */ |
311 | t = (P.m_end_distance - startS) / S.m_length; |
312 | S.split_segment(t, &A, &B); |
313 | S = B; |
314 | S.m_first_segment_of_edge = true; |
315 | S.m_continuation_with_predecessor = false; |
316 | startS = distance(S); |
317 | |
318 | P = proxy(P.m_idx + 1); |
319 | if (need_to_begin_chain) |
320 | { |
321 | dst.begin_chain(nullptr); |
322 | need_to_begin_chain = false; |
323 | } |
324 | } |
325 | } |
326 | |
327 | ////////////////////////////////////// |
328 | // fastuidraw::PathDashEffect methods |
329 | fastuidraw::PathDashEffect:: |
330 | PathDashEffect(void) |
331 | { |
332 | m_d = FASTUIDRAWnew PathDashEffectPrivate(); |
333 | } |
334 | |
335 | fastuidraw::PathDashEffect:: |
336 | ~PathDashEffect(void) |
337 | { |
338 | PathDashEffectPrivate *d; |
339 | d = static_cast<PathDashEffectPrivate*>(m_d); |
340 | FASTUIDRAWdelete(d); |
341 | } |
342 | |
343 | fastuidraw::PathDashEffect& |
344 | fastuidraw::PathDashEffect:: |
345 | dash_offset(float v) |
346 | { |
347 | PathDashEffectPrivate *d; |
348 | d = static_cast<PathDashEffectPrivate*>(m_d); |
349 | d->m_dash_offset = v; |
350 | return *this; |
351 | } |
352 | |
353 | fastuidraw::PathDashEffect& |
354 | fastuidraw::PathDashEffect:: |
355 | clear(void) |
356 | { |
357 | PathDashEffectPrivate *d; |
358 | d = static_cast<PathDashEffectPrivate*>(m_d); |
359 | d->m_lengths.clear(); |
360 | return *this; |
361 | } |
362 | |
363 | fastuidraw::PathDashEffect& |
364 | fastuidraw::PathDashEffect:: |
365 | add_dash(float draw, float skip) |
366 | { |
367 | PathDashEffectPrivate *d; |
368 | d = static_cast<PathDashEffectPrivate*>(m_d); |
369 | |
370 | if (draw < 0.0f || skip < 0.0f) |
371 | { |
372 | FASTUIDRAWmessaged_assert(draw >= 0.0f, "Bad draw length passed to add_dash" ); |
373 | FASTUIDRAWmessaged_assert(skip >= 0.0f, "Bad skip length passed to add_dash" ); |
374 | return *this; |
375 | } |
376 | |
377 | if (!d->m_lengths.empty() && d->m_lengths.back().m_skip_length == 0.0f) |
378 | { |
379 | d->m_lengths.back().m_draw_length += draw; |
380 | d->m_lengths.back().m_skip_length += skip; |
381 | d->m_lengths.back().m_end_distance += draw + skip; |
382 | return *this; |
383 | } |
384 | |
385 | DashElement E; |
386 | |
387 | E.m_draw_length = draw; |
388 | E.m_skip_length = skip; |
389 | E.m_start_distance = d->current_length(); |
390 | E.m_end_distance = E.m_start_distance + draw + skip; |
391 | |
392 | d->m_lengths.push_back(E); |
393 | return *this; |
394 | } |
395 | |
396 | void |
397 | fastuidraw::PathDashEffect:: |
398 | process_join(const TessellatedPath::join &join, Storage &dst) const |
399 | { |
400 | PathDashEffectPrivate *d; |
401 | d = static_cast<PathDashEffectPrivate*>(m_d); |
402 | |
403 | float dist(d->distance(join)); |
404 | if (d->point_inside_draw_interval(dist)) |
405 | { |
406 | dst.add_join(join); |
407 | } |
408 | } |
409 | |
410 | void |
411 | fastuidraw::PathDashEffect:: |
412 | process_cap(const TessellatedPath::cap &cap, Storage &dst) const |
413 | { |
414 | PathDashEffectPrivate *d; |
415 | d = static_cast<PathDashEffectPrivate*>(m_d); |
416 | |
417 | float dist(d->distance(cap)); |
418 | if (d->point_inside_draw_interval(dist)) |
419 | { |
420 | dst.add_cap(cap); |
421 | } |
422 | } |
423 | |
424 | void |
425 | fastuidraw::PathDashEffect:: |
426 | process_chain(const segment_chain &chain, Storage &dst) const |
427 | { |
428 | PathDashEffectPrivate *d; |
429 | d = static_cast<PathDashEffectPrivate*>(m_d); |
430 | |
431 | if (chain.m_segments.empty()) |
432 | { |
433 | return; |
434 | } |
435 | |
436 | if (d->m_lengths.empty()) |
437 | { |
438 | dst.begin_chain(chain.m_prev_to_start); |
439 | for (const auto &S : chain.m_segments) |
440 | { |
441 | dst.add_segment(S); |
442 | } |
443 | return; |
444 | } |
445 | |
446 | const TessellatedPath::segment *prev_segment(chain.m_prev_to_start); |
447 | const TessellatedPath::segment &front(chain.m_segments.front()); |
448 | float dist(d->distance(front)); |
449 | DashElementProxy P(d->proxy(d->find_index(dist))); |
450 | |
451 | dst.begin_chain(prev_segment); |
452 | |
453 | /* check if we need to give a starting cap to the |
454 | * first segment of the chain; this is only needed |
455 | * for the first segment of the first edge. |
456 | */ |
457 | if (front.m_edge_id == 0 && front.m_first_segment_of_edge) |
458 | { |
459 | add_cap(front, 0.0f, true, dst); |
460 | } |
461 | |
462 | for (const auto &S : chain.m_segments) |
463 | { |
464 | d->process_segment(S, P, dst); |
465 | } |
466 | } |
467 | |