1use crate::array::VertexDist;
8use crate::basics::{PointD, PI};
9use crate::math::{calc_distance, calc_intersection, cross_product};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum LineCap {
18 Butt = 0,
19 Square = 1,
20 Round = 2,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum LineJoin {
26 Miter = 0,
27 MiterRevert = 1,
28 Round = 2,
29 Bevel = 3,
30 MiterRound = 4,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum InnerJoin {
36 Bevel = 0,
37 Miter = 1,
38 Jag = 2,
39 Round = 3,
40}
41
42pub struct MathStroke {
53 width: f64,
54 width_abs: f64,
55 width_eps: f64,
56 width_sign: i32,
57 miter_limit: f64,
58 inner_miter_limit: f64,
59 approx_scale: f64,
60 line_cap: LineCap,
61 line_join: LineJoin,
62 inner_join: InnerJoin,
63}
64
65impl MathStroke {
66 pub fn new() -> Self {
67 Self {
68 width: 0.5,
69 width_abs: 0.5,
70 width_eps: 0.5 / 1024.0,
71 width_sign: 1,
72 miter_limit: 4.0,
73 inner_miter_limit: 1.01,
74 approx_scale: 1.0,
75 line_cap: LineCap::Butt,
76 line_join: LineJoin::Miter,
77 inner_join: InnerJoin::Miter,
78 }
79 }
80
81 pub fn set_line_cap(&mut self, lc: LineCap) {
82 self.line_cap = lc;
83 }
84 pub fn line_cap(&self) -> LineCap {
85 self.line_cap
86 }
87
88 pub fn set_line_join(&mut self, lj: LineJoin) {
89 self.line_join = lj;
90 }
91 pub fn line_join(&self) -> LineJoin {
92 self.line_join
93 }
94
95 pub fn set_inner_join(&mut self, ij: InnerJoin) {
96 self.inner_join = ij;
97 }
98 pub fn inner_join(&self) -> InnerJoin {
99 self.inner_join
100 }
101
102 pub fn set_width(&mut self, w: f64) {
103 self.width = w * 0.5;
104 if self.width < 0.0 {
105 self.width_abs = -self.width;
106 self.width_sign = -1;
107 } else {
108 self.width_abs = self.width;
109 self.width_sign = 1;
110 }
111 self.width_eps = self.width / 1024.0;
112 }
113
114 pub fn width(&self) -> f64 {
115 self.width * 2.0
116 }
117
118 pub fn set_miter_limit(&mut self, ml: f64) {
119 self.miter_limit = ml;
120 }
121 pub fn miter_limit(&self) -> f64 {
122 self.miter_limit
123 }
124
125 pub fn set_miter_limit_theta(&mut self, t: f64) {
126 self.miter_limit = 1.0 / (t * 0.5).sin();
127 }
128
129 pub fn set_inner_miter_limit(&mut self, ml: f64) {
130 self.inner_miter_limit = ml;
131 }
132 pub fn inner_miter_limit(&self) -> f64 {
133 self.inner_miter_limit
134 }
135
136 pub fn set_approximation_scale(&mut self, s: f64) {
137 self.approx_scale = s;
138 }
139 pub fn approximation_scale(&self) -> f64 {
140 self.approx_scale
141 }
142
143 pub fn calc_cap(&self, vc: &mut Vec<PointD>, v0: &VertexDist, v1: &VertexDist, len: f64) {
148 vc.clear();
149
150 let mut dx1 = (v1.y - v0.y) / len;
151 let mut dy1 = (v1.x - v0.x) / len;
152 let mut dx2 = 0.0;
153 let mut dy2 = 0.0;
154
155 dx1 *= self.width;
156 dy1 *= self.width;
157
158 if self.line_cap != LineCap::Round {
159 if self.line_cap == LineCap::Square {
160 dx2 = dy1 * self.width_sign as f64;
161 dy2 = dx1 * self.width_sign as f64;
162 }
163 vc.push(PointD {
164 x: v0.x - dx1 - dx2,
165 y: v0.y + dy1 - dy2,
166 });
167 vc.push(PointD {
168 x: v0.x + dx1 - dx2,
169 y: v0.y - dy1 - dy2,
170 });
171 } else {
172 let da = (self.width_abs / (self.width_abs + 0.125 / self.approx_scale)).acos() * 2.0;
173 let n = (PI / da) as i32;
174 let da = PI / (n + 1) as f64;
175
176 vc.push(PointD {
177 x: v0.x - dx1,
178 y: v0.y + dy1,
179 });
180
181 if self.width_sign > 0 {
182 let mut a1 = dy1.atan2(-dx1);
183 a1 += da;
184 for _ in 0..n {
185 vc.push(PointD {
186 x: v0.x + a1.cos() * self.width,
187 y: v0.y + a1.sin() * self.width,
188 });
189 a1 += da;
190 }
191 } else {
192 let mut a1 = (-dy1).atan2(dx1);
193 a1 -= da;
194 for _ in 0..n {
195 vc.push(PointD {
196 x: v0.x + a1.cos() * self.width,
197 y: v0.y + a1.sin() * self.width,
198 });
199 a1 -= da;
200 }
201 }
202
203 vc.push(PointD {
204 x: v0.x + dx1,
205 y: v0.y - dy1,
206 });
207 }
208 }
209
210 pub fn calc_join(
215 &self,
216 vc: &mut Vec<PointD>,
217 v0: &VertexDist,
218 v1: &VertexDist,
219 v2: &VertexDist,
220 len1: f64,
221 len2: f64,
222 ) {
223 let dx1 = self.width * (v1.y - v0.y) / len1;
224 let dy1 = self.width * (v1.x - v0.x) / len1;
225 let dx2 = self.width * (v2.y - v1.y) / len2;
226 let dy2 = self.width * (v2.x - v1.x) / len2;
227
228 vc.clear();
229
230 let cp = cross_product(v0.x, v0.y, v1.x, v1.y, v2.x, v2.y);
231 if cp != 0.0 && (cp > 0.0) == (self.width > 0.0) {
232 let mut limit = if len1 < len2 { len1 } else { len2 } / self.width_abs;
234 if limit < self.inner_miter_limit {
235 limit = self.inner_miter_limit;
236 }
237
238 match self.inner_join {
239 InnerJoin::Bevel => {
240 vc.push(PointD {
241 x: v1.x + dx1,
242 y: v1.y - dy1,
243 });
244 vc.push(PointD {
245 x: v1.x + dx2,
246 y: v1.y - dy2,
247 });
248 }
249 InnerJoin::Miter => {
250 self.calc_miter(
251 vc,
252 v0,
253 v1,
254 v2,
255 dx1,
256 dy1,
257 dx2,
258 dy2,
259 LineJoin::MiterRevert,
260 limit,
261 0.0,
262 );
263 }
264 InnerJoin::Jag | InnerJoin::Round => {
265 let d = (dx1 - dx2) * (dx1 - dx2) + (dy1 - dy2) * (dy1 - dy2);
266 if d < len1 * len1 && d < len2 * len2 {
267 self.calc_miter(
268 vc,
269 v0,
270 v1,
271 v2,
272 dx1,
273 dy1,
274 dx2,
275 dy2,
276 LineJoin::MiterRevert,
277 limit,
278 0.0,
279 );
280 } else if self.inner_join == InnerJoin::Jag {
281 vc.push(PointD {
282 x: v1.x + dx1,
283 y: v1.y - dy1,
284 });
285 vc.push(PointD { x: v1.x, y: v1.y });
286 vc.push(PointD {
287 x: v1.x + dx2,
288 y: v1.y - dy2,
289 });
290 } else {
291 vc.push(PointD {
292 x: v1.x + dx1,
293 y: v1.y - dy1,
294 });
295 vc.push(PointD { x: v1.x, y: v1.y });
296 self.calc_arc(vc, v1.x, v1.y, dx2, -dy2, dx1, -dy1);
297 vc.push(PointD { x: v1.x, y: v1.y });
298 vc.push(PointD {
299 x: v1.x + dx2,
300 y: v1.y - dy2,
301 });
302 }
303 }
304 }
305 } else {
306 let dx = (dx1 + dx2) / 2.0;
308 let dy = (dy1 + dy2) / 2.0;
309 let dbevel = (dx * dx + dy * dy).sqrt();
310
311 if (self.line_join == LineJoin::Round || self.line_join == LineJoin::Bevel)
312 && self.approx_scale * (self.width_abs - dbevel) < self.width_eps
313 {
314 if let Some((ix, iy)) = calc_intersection(
315 v0.x + dx1,
316 v0.y - dy1,
317 v1.x + dx1,
318 v1.y - dy1,
319 v1.x + dx2,
320 v1.y - dy2,
321 v2.x + dx2,
322 v2.y - dy2,
323 ) {
324 vc.push(PointD { x: ix, y: iy });
325 } else {
326 vc.push(PointD {
327 x: v1.x + dx1,
328 y: v1.y - dy1,
329 });
330 }
331 return;
332 }
333
334 match self.line_join {
335 LineJoin::Miter | LineJoin::MiterRevert | LineJoin::MiterRound => {
336 self.calc_miter(
337 vc,
338 v0,
339 v1,
340 v2,
341 dx1,
342 dy1,
343 dx2,
344 dy2,
345 self.line_join,
346 self.miter_limit,
347 dbevel,
348 );
349 }
350 LineJoin::Round => {
351 self.calc_arc(vc, v1.x, v1.y, dx1, -dy1, dx2, -dy2);
352 }
353 LineJoin::Bevel => {
354 vc.push(PointD {
355 x: v1.x + dx1,
356 y: v1.y - dy1,
357 });
358 vc.push(PointD {
359 x: v1.x + dx2,
360 y: v1.y - dy2,
361 });
362 }
363 }
364 }
365 }
366
367 fn add_vertex(vc: &mut Vec<PointD>, x: f64, y: f64) {
368 vc.push(PointD { x, y });
369 }
370
371 #[allow(clippy::too_many_arguments)]
372 fn calc_arc(
373 &self,
374 vc: &mut Vec<PointD>,
375 x: f64,
376 y: f64,
377 dx1: f64,
378 dy1: f64,
379 dx2: f64,
380 dy2: f64,
381 ) {
382 let mut a1 = (dy1 * self.width_sign as f64).atan2(dx1 * self.width_sign as f64);
383 let a2_init = (dy2 * self.width_sign as f64).atan2(dx2 * self.width_sign as f64);
384
385 let da = (self.width_abs / (self.width_abs + 0.125 / self.approx_scale)).acos() * 2.0;
386
387 Self::add_vertex(vc, x + dx1, y + dy1);
388
389 if self.width_sign > 0 {
390 let mut a2 = a2_init;
391 if a1 > a2 {
392 a2 += 2.0 * PI;
393 }
394 let n = ((a2 - a1) / da) as i32;
395 let da = (a2 - a1) / (n + 1) as f64;
396 a1 += da;
397 for _ in 0..n {
398 Self::add_vertex(vc, x + a1.cos() * self.width, y + a1.sin() * self.width);
399 a1 += da;
400 }
401 } else {
402 let mut a2 = a2_init;
403 if a1 < a2 {
404 a2 -= 2.0 * PI;
405 }
406 let n = ((a1 - a2) / da) as i32;
407 let da = (a1 - a2) / (n + 1) as f64;
408 a1 -= da;
409 for _ in 0..n {
410 Self::add_vertex(vc, x + a1.cos() * self.width, y + a1.sin() * self.width);
411 a1 -= da;
412 }
413 }
414
415 Self::add_vertex(vc, x + dx2, y + dy2);
416 }
417
418 #[allow(clippy::too_many_arguments)]
419 fn calc_miter(
420 &self,
421 vc: &mut Vec<PointD>,
422 v0: &VertexDist,
423 v1: &VertexDist,
424 v2: &VertexDist,
425 dx1: f64,
426 dy1: f64,
427 dx2: f64,
428 dy2: f64,
429 lj: LineJoin,
430 mut mlimit: f64,
431 dbevel: f64,
432 ) {
433 let mut xi = v1.x;
434 let mut yi = v1.y;
435 let mut di = 1.0;
436 let lim = self.width_abs * mlimit;
437 let mut miter_limit_exceeded = true;
438 let mut intersection_failed = true;
439
440 if let Some((ix, iy)) = calc_intersection(
441 v0.x + dx1,
442 v0.y - dy1,
443 v1.x + dx1,
444 v1.y - dy1,
445 v1.x + dx2,
446 v1.y - dy2,
447 v2.x + dx2,
448 v2.y - dy2,
449 ) {
450 xi = ix;
451 yi = iy;
452 di = calc_distance(v1.x, v1.y, xi, yi);
453 if di <= lim {
454 Self::add_vertex(vc, xi, yi);
455 miter_limit_exceeded = false;
456 }
457 intersection_failed = false;
458 } else {
459 let x2 = v1.x + dx1;
460 let y2 = v1.y - dy1;
461 if (cross_product(v0.x, v0.y, v1.x, v1.y, x2, y2) < 0.0)
462 == (cross_product(v1.x, v1.y, v2.x, v2.y, x2, y2) < 0.0)
463 {
464 Self::add_vertex(vc, v1.x + dx1, v1.y - dy1);
465 miter_limit_exceeded = false;
466 }
467 }
468
469 if miter_limit_exceeded {
470 match lj {
471 LineJoin::MiterRevert => {
472 Self::add_vertex(vc, v1.x + dx1, v1.y - dy1);
473 Self::add_vertex(vc, v1.x + dx2, v1.y - dy2);
474 }
475 LineJoin::MiterRound => {
476 self.calc_arc(vc, v1.x, v1.y, dx1, -dy1, dx2, -dy2);
477 }
478 _ => {
479 if intersection_failed {
480 mlimit *= self.width_sign as f64;
481 Self::add_vertex(vc, v1.x + dx1 + dy1 * mlimit, v1.y - dy1 + dx1 * mlimit);
482 Self::add_vertex(vc, v1.x + dx2 - dy2 * mlimit, v1.y - dy2 - dx2 * mlimit);
483 } else {
484 let x1 = v1.x + dx1;
485 let y1 = v1.y - dy1;
486 let x2 = v1.x + dx2;
487 let y2 = v1.y - dy2;
488 di = (lim - dbevel) / (di - dbevel);
489 Self::add_vertex(vc, x1 + (xi - x1) * di, y1 + (yi - y1) * di);
490 Self::add_vertex(vc, x2 + (xi - x2) * di, y2 + (yi - y2) * di);
491 }
492 }
493 }
494 }
495 }
496}
497
498impl Default for MathStroke {
499 fn default() -> Self {
500 Self::new()
501 }
502}
503
504#[cfg(test)]
509mod tests {
510 use super::*;
511
512 fn vd(x: f64, y: f64, dist: f64) -> VertexDist {
513 VertexDist { x, y, dist }
514 }
515
516 #[test]
517 fn test_defaults() {
518 let ms = MathStroke::new();
519 assert!((ms.width() - 1.0).abs() < 1e-10); assert_eq!(ms.line_cap(), LineCap::Butt);
521 assert_eq!(ms.line_join(), LineJoin::Miter);
522 assert_eq!(ms.inner_join(), InnerJoin::Miter);
523 assert!((ms.miter_limit() - 4.0).abs() < 1e-10);
524 assert!((ms.inner_miter_limit() - 1.01).abs() < 1e-10);
525 assert!((ms.approximation_scale() - 1.0).abs() < 1e-10);
526 }
527
528 #[test]
529 fn test_width_setter() {
530 let mut ms = MathStroke::new();
531 ms.set_width(2.0);
532 assert!((ms.width() - 2.0).abs() < 1e-10);
533
534 ms.set_width(-2.0);
535 assert!((ms.width() + 2.0).abs() < 1e-10);
536 }
537
538 #[test]
539 fn test_butt_cap() {
540 let ms = MathStroke::new();
541 let mut vc = Vec::new();
542 let v0 = vd(0.0, 0.0, 10.0);
543 let v1 = vd(10.0, 0.0, 0.0);
544 ms.calc_cap(&mut vc, &v0, &v1, 10.0);
545 assert_eq!(vc.len(), 2);
547 assert!((vc[0].y - 0.5).abs() < 1e-6);
549 assert!((vc[1].y + 0.5).abs() < 1e-6);
550 }
551
552 #[test]
553 fn test_square_cap() {
554 let mut ms = MathStroke::new();
555 ms.set_line_cap(LineCap::Square);
556 let mut vc = Vec::new();
557 let v0 = vd(0.0, 0.0, 10.0);
558 let v1 = vd(10.0, 0.0, 0.0);
559 ms.calc_cap(&mut vc, &v0, &v1, 10.0);
560 assert_eq!(vc.len(), 2);
561 assert!(vc[0].x < 0.0); }
564
565 #[test]
566 fn test_round_cap() {
567 let mut ms = MathStroke::new();
568 ms.set_line_cap(LineCap::Round);
569 let mut vc = Vec::new();
570 let v0 = vd(0.0, 0.0, 10.0);
571 let v1 = vd(10.0, 0.0, 0.0);
572 ms.calc_cap(&mut vc, &v0, &v1, 10.0);
573 assert!(vc.len() > 2);
575 for p in &vc {
577 let d = (p.x * p.x + p.y * p.y).sqrt();
578 assert!(d < ms.width() + 1e-6);
579 }
580 }
581
582 #[test]
583 fn test_bevel_join() {
584 let mut ms = MathStroke::new();
585 ms.set_line_join(LineJoin::Bevel);
586 let mut vc = Vec::new();
587 let v0 = vd(0.0, 0.0, 10.0);
588 let v1 = vd(10.0, 0.0, 10.0);
589 let v2 = vd(10.0, 10.0, 0.0);
590 ms.calc_join(&mut vc, &v0, &v1, &v2, 10.0, 10.0);
591 assert!(!vc.is_empty());
593 }
594
595 #[test]
596 fn test_miter_join() {
597 let ms = MathStroke::new(); let mut vc = Vec::new();
599 let v0 = vd(0.0, 0.0, 10.0);
600 let v1 = vd(10.0, 0.0, 10.0);
601 let v2 = vd(10.0, 10.0, 0.0);
602 ms.calc_join(&mut vc, &v0, &v1, &v2, 10.0, 10.0);
603 assert!(!vc.is_empty());
604 }
605
606 #[test]
607 fn test_round_join() {
608 let mut ms = MathStroke::new();
609 ms.set_line_join(LineJoin::Round);
610 let mut vc = Vec::new();
611 let v0 = vd(0.0, 0.0, 10.0);
612 let v1 = vd(10.0, 0.0, 10.0);
613 let v2 = vd(10.0, 10.0, 0.0);
614 ms.calc_join(&mut vc, &v0, &v1, &v2, 10.0, 10.0);
615 assert!(vc.len() > 2);
617 }
618
619 #[test]
620 fn test_miter_limit_theta() {
621 let mut ms = MathStroke::new();
622 ms.set_miter_limit_theta(PI / 4.0); let expected = 1.0 / (PI / 8.0).sin();
624 assert!((ms.miter_limit() - expected).abs() < 1e-10);
625 }
626
627 #[test]
628 fn test_setters_getters() {
629 let mut ms = MathStroke::new();
630 ms.set_line_cap(LineCap::Round);
631 ms.set_line_join(LineJoin::MiterRevert);
632 ms.set_inner_join(InnerJoin::Jag);
633 ms.set_miter_limit(10.0);
634 ms.set_inner_miter_limit(2.0);
635 ms.set_approximation_scale(0.5);
636
637 assert_eq!(ms.line_cap(), LineCap::Round);
638 assert_eq!(ms.line_join(), LineJoin::MiterRevert);
639 assert_eq!(ms.inner_join(), InnerJoin::Jag);
640 assert!((ms.miter_limit() - 10.0).abs() < 1e-10);
641 assert!((ms.inner_miter_limit() - 2.0).abs() < 1e-10);
642 assert!((ms.approximation_scale() - 0.5).abs() < 1e-10);
643 }
644
645 #[test]
646 fn test_inner_join_bevel() {
647 let mut ms = MathStroke::new();
648 ms.set_inner_join(InnerJoin::Bevel);
649 let mut vc = Vec::new();
650 let v0 = vd(0.0, 0.0, 10.0);
652 let v1 = vd(10.0, 0.0, 10.0);
653 let v2 = vd(20.0, 0.0, 0.0);
654 ms.calc_join(&mut vc, &v0, &v1, &v2, 10.0, 10.0);
655 assert!(!vc.is_empty());
656 }
657
658 #[test]
659 fn test_collinear_segments() {
660 let ms = MathStroke::new();
662 let mut vc = Vec::new();
663 let v0 = vd(0.0, 0.0, 10.0);
664 let v1 = vd(10.0, 0.0, 10.0);
665 let v2 = vd(20.0, 0.0, 0.0);
666 ms.calc_join(&mut vc, &v0, &v1, &v2, 10.0, 10.0);
667 assert!(!vc.is_empty());
668 }
669}