/rust/registry/src/index.crates.io-1949cf8c6b5b557f/plotters-backend-0.3.7/src/rasterizer/path.rs
Line | Count | Source |
1 | | use crate::BackendCoord; |
2 | | |
3 | | // Compute the tanginal and normal vectors of the given straight line. |
4 | 0 | fn get_dir_vector(from: BackendCoord, to: BackendCoord, flag: bool) -> ((f64, f64), (f64, f64)) { |
5 | 0 | let v = (i64::from(to.0 - from.0), i64::from(to.1 - from.1)); |
6 | 0 | let l = ((v.0 * v.0 + v.1 * v.1) as f64).sqrt(); |
7 | | |
8 | 0 | let v = (v.0 as f64 / l, v.1 as f64 / l); |
9 | | |
10 | 0 | if flag { |
11 | 0 | (v, (v.1, -v.0)) |
12 | | } else { |
13 | 0 | (v, (-v.1, v.0)) |
14 | | } |
15 | 0 | } |
16 | | |
17 | | // Compute the polygonized vertex of the given angle |
18 | | // d is the distance between the polygon edge and the actual line. |
19 | | // d can be negative, this will emit a vertex on the other side of the line. |
20 | 0 | fn compute_polygon_vertex(triple: &[BackendCoord; 3], d: f64, buf: &mut Vec<BackendCoord>) { |
21 | 0 | buf.clear(); |
22 | | |
23 | | // Compute the tanginal and normal vectors of the given straight line. |
24 | 0 | let (a_t, a_n) = get_dir_vector(triple[0], triple[1], false); |
25 | 0 | let (b_t, b_n) = get_dir_vector(triple[2], triple[1], true); |
26 | | |
27 | | // Compute a point that is d away from the line for line a and line b. |
28 | 0 | let a_p = ( |
29 | 0 | f64::from(triple[1].0) + d * a_n.0, |
30 | 0 | f64::from(triple[1].1) + d * a_n.1, |
31 | 0 | ); |
32 | 0 | let b_p = ( |
33 | 0 | f64::from(triple[1].0) + d * b_n.0, |
34 | 0 | f64::from(triple[1].1) + d * b_n.1, |
35 | 0 | ); |
36 | | |
37 | | // Check if 3 points are colinear, up to precision. If so, just emit the point. |
38 | 0 | if (a_t.1 * b_t.0 - a_t.0 * b_t.1).abs() <= f64::EPSILON { |
39 | 0 | buf.push((a_p.0 as i32, a_p.1 as i32)); |
40 | 0 | return; |
41 | 0 | } |
42 | | |
43 | | // So we are actually computing the intersection of two lines: |
44 | | // a_p + u * a_t and b_p + v * b_t. |
45 | | // We can solve the following vector equation: |
46 | | // u * a_t + a_p = v * b_t + b_p |
47 | | // |
48 | | // which is actually a equation system: |
49 | | // u * a_t.0 - v * b_t.0 = b_p.0 - a_p.0 |
50 | | // u * a_t.1 - v * b_t.1 = b_p.1 - a_p.1 |
51 | | |
52 | | // The following vars are coefficients of the linear equation system. |
53 | | // a0*u + b0*v = c0 |
54 | | // a1*u + b1*v = c1 |
55 | | // in which x and y are the coordinates that two polygon edges intersect. |
56 | | |
57 | 0 | let a0 = a_t.0; |
58 | 0 | let b0 = -b_t.0; |
59 | 0 | let c0 = b_p.0 - a_p.0; |
60 | 0 | let a1 = a_t.1; |
61 | 0 | let b1 = -b_t.1; |
62 | 0 | let c1 = b_p.1 - a_p.1; |
63 | | |
64 | | // Since the points are not collinear, the determinant is not 0, and we can get a intersection point. |
65 | 0 | let u = (c0 * b1 - c1 * b0) / (a0 * b1 - a1 * b0); |
66 | 0 | let x = a_p.0 + u * a_t.0; |
67 | 0 | let y = a_p.1 + u * a_t.1; |
68 | | |
69 | 0 | let cross_product = a_t.0 * b_t.1 - a_t.1 * b_t.0; |
70 | 0 | if (cross_product < 0.0 && d < 0.0) || (cross_product > 0.0 && d > 0.0) { |
71 | | // Then we are at the outer side of the angle, so we need to consider a cap. |
72 | 0 | let dist_square = (x - triple[1].0 as f64).powi(2) + (y - triple[1].1 as f64).powi(2); |
73 | | // If the point is too far away from the line, we need to cap it. |
74 | 0 | if dist_square > d * d * 16.0 { |
75 | 0 | buf.push((a_p.0.round() as i32, a_p.1.round() as i32)); |
76 | 0 | buf.push((b_p.0.round() as i32, b_p.1.round() as i32)); |
77 | 0 | return; |
78 | 0 | } |
79 | 0 | } |
80 | | |
81 | 0 | buf.push((x.round() as i32, y.round() as i32)); |
82 | 0 | } |
83 | | |
84 | 0 | fn traverse_vertices<'a>( |
85 | 0 | mut vertices: impl Iterator<Item = &'a BackendCoord>, |
86 | 0 | width: u32, |
87 | 0 | mut op: impl FnMut(BackendCoord), |
88 | 0 | ) { |
89 | 0 | let mut a = vertices.next().unwrap(); |
90 | 0 | let mut b = vertices.next().unwrap(); |
91 | | |
92 | 0 | while a == b { |
93 | 0 | a = b; |
94 | 0 | if let Some(new_b) = vertices.next() { |
95 | 0 | b = new_b; |
96 | 0 | } else { |
97 | 0 | return; |
98 | | } |
99 | | } |
100 | | |
101 | 0 | let (_, n) = get_dir_vector(*a, *b, false); |
102 | | |
103 | 0 | op(( |
104 | 0 | (f64::from(a.0) + n.0 * f64::from(width) / 2.0).round() as i32, |
105 | 0 | (f64::from(a.1) + n.1 * f64::from(width) / 2.0).round() as i32, |
106 | 0 | )); |
107 | | |
108 | 0 | let mut recent = [(0, 0), *a, *b]; |
109 | 0 | let mut vertex_buf = Vec::with_capacity(3); |
110 | | |
111 | 0 | for p in vertices { |
112 | 0 | if *p == recent[2] { |
113 | 0 | continue; |
114 | 0 | } |
115 | 0 | recent.swap(0, 1); |
116 | 0 | recent.swap(1, 2); |
117 | 0 | recent[2] = *p; |
118 | 0 | compute_polygon_vertex(&recent, f64::from(width) / 2.0, &mut vertex_buf); |
119 | 0 | vertex_buf.iter().cloned().for_each(&mut op); |
120 | | } |
121 | | |
122 | 0 | let b = recent[1]; |
123 | 0 | let a = recent[2]; |
124 | | |
125 | 0 | let (_, n) = get_dir_vector(a, b, true); |
126 | | |
127 | 0 | op(( |
128 | 0 | (f64::from(a.0) + n.0 * f64::from(width) / 2.0).round() as i32, |
129 | 0 | (f64::from(a.1) + n.1 * f64::from(width) / 2.0).round() as i32, |
130 | 0 | )); |
131 | 0 | } Unexecuted instantiation: plotters_backend::rasterizer::path::traverse_vertices::<core::slice::iter::Iter<(i32, i32)>, plotters_backend::rasterizer::path::polygonize::{closure#0}>Unexecuted instantiation: plotters_backend::rasterizer::path::traverse_vertices::<core::iter::adapters::rev::Rev<core::slice::iter::Iter<(i32, i32)>>, plotters_backend::rasterizer::path::polygonize::{closure#1}> |
132 | | |
133 | | /// Covert a path with >1px stroke width into polygon. |
134 | 0 | pub fn polygonize(vertices: &[BackendCoord], stroke_width: u32) -> Vec<BackendCoord> { |
135 | 0 | if vertices.len() < 2 { |
136 | 0 | return vec![]; |
137 | 0 | } |
138 | | |
139 | 0 | let mut ret = vec![]; |
140 | | |
141 | 0 | traverse_vertices(vertices.iter(), stroke_width, |v| ret.push(v)); |
142 | 0 | traverse_vertices(vertices.iter().rev(), stroke_width, |v| ret.push(v)); |
143 | | |
144 | 0 | ret |
145 | 0 | } |
146 | | |
147 | | #[cfg(test)] |
148 | | mod test { |
149 | | use super::*; |
150 | | |
151 | | /// Test for regression with respect to https://github.com/plotters-rs/plotters/issues/562 |
152 | | #[test] |
153 | | fn test_no_inf_in_compute_polygon_vertex() { |
154 | | let path = [(335, 386), (338, 326), (340, 286)]; |
155 | | let mut buf = Vec::new(); |
156 | | compute_polygon_vertex(&path, 2.0, buf.as_mut()); |
157 | | assert!(!buf.is_empty()); |
158 | | let nani32 = f64::INFINITY as i32; |
159 | | assert!(!buf.iter().any(|&v| v.0 == nani32 || v.1 == nani32)); |
160 | | } |
161 | | |
162 | | /// Correct 90 degree turn to the right |
163 | | #[test] |
164 | | fn standard_corner() { |
165 | | let path = [(10, 10), (20, 10), (20, 20)]; |
166 | | let mut buf = Vec::new(); |
167 | | compute_polygon_vertex(&path, 2.0, buf.as_mut()); |
168 | | assert!(!buf.is_empty()); |
169 | | let buf2 = vec![(18, 12)]; |
170 | | assert_eq!(buf, buf2); |
171 | | } |
172 | | } |