1extern crate core;
87
88use core::ops::Range;
89use core::hint::unreachable_unchecked;
90use core::fmt;
91
92use wasm_bindgen::prelude::*;
93use serde::{Serialize, Deserialize};
94use serde_wasm_bindgen::{to_value, from_value};
95
96#[cfg(feature = "double_precision")]
97type Float = f32;
98#[cfg(not(feature = "double_precision"))]
99type Float = f64;
100
101#[wasm_bindgen]
103#[derive(Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
104pub struct Point { pub x: Float, pub y: Float }
105
106impl fmt::Debug for Point {
107 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108 write!(f, "{{\"x\":{},\"y\":{}}}", self.x, self.y)
109 }
110}
111
112#[wasm_bindgen]
113impl Point {
114 #[wasm_bindgen(constructor)]
115 pub fn new(x: f64, y: f64) -> Point {
116 Point { x, y }
117 }
118 #[inline]
119 fn add(&self, p: Point) -> Point {
120 Point {
121 x: self.x + p.x,
122 y: self.y + p.y
123 }
124 }
125
126 #[inline]
127 fn subtract(&self, p: Point) -> Point {
128 Point {
129 x: self.x - p.x,
130 y: self.y - p.y
131 }
132 }
133
134 #[inline]
135 fn multiply(&self, f: Float) -> Point {
136 Point {
137 x: self.x * f,
138 y: self.y * f
139 }
140 }
141
142 #[inline]
143 fn negate(&self) -> Point {
144 Point {
145 x: -self.x,
146 y: -self.y
147 }
148 }
149
150 #[inline]
151 fn distance(&self, p: Point) -> Float {
152 let dx = p.x - self.x;
153 let dy = p.y - self.y;
154 dx.hypot(dy)
155 }
156
157 #[inline]
158 fn dot(&self, p: Point) -> Float {
159 self.x * p.x + self.y * p.y
160 }
161
162 #[inline]
163 fn normalize(&self, length: Float) -> Point {
164 let current = self.x.hypot(self.y);
165 let scale = if current.abs() > Float::EPSILON { length / current } else { 0.0 };
166 let res = Point { x: self.x * scale, y: self.y * scale };
167 res
168 }
169}
170
171pub const DEFAULT_TOLERANCE: f32 = 2.5;
173pub const TARGET_AUTO_SCALE_AREA: Float = 1000000.0;
174
175#[wasm_bindgen]
195pub fn simplify_js(points_js: &JsValue, tolerance: f64, auto_scale_for_precision: bool) -> JsValue {
196 let mut points: Vec<Point> = from_value(points_js.clone()).unwrap();
198
199 let mut scale = 1.0;
200 if auto_scale_for_precision {
202 let mut area = 0.0;
203 let mut j = points.len() - 1;
204 for i in 0..points.len() {
205 area += (points[j].x + points[i].x) * (points[j].y - points[i].y);
206 j = i;
207 }
208 area *= 0.5;
209 area = area.abs();
210
211 scale = TARGET_AUTO_SCALE_AREA / area;
213
214 for p in points.iter_mut() {
215 p.x *= scale;
216 p.y *= scale;
217 }
218 }
219
220 let scaled_tolerance = tolerance as Float * scale;
221
222 let mut simplified = simplify(&points, scaled_tolerance);
224
225 if auto_scale_for_precision {
227 for p in simplified.iter_mut() {
228 p.x /= scale;
229 p.y /= scale;
230 }
231 }
232
233 to_value(&simplified).unwrap()
235}
236
237pub fn simplify(points: &[Point], tolerance: Float) -> Vec<Point> {
253
254 let mut cur_points = points.windows(2).filter_map(|w| {
256 let first = w.get(0)?;
257 let next = w.get(1)?;
258 if first == next { None } else { Some(*first) }
259 }).collect::<Vec<Point>>();
260
261 if let (Some(last_minus_one), Some(last)) = (points.get(points.len() - 2), points.last()) {
263 if last_minus_one != last { cur_points.push(*last); }
264 }
265
266 if cur_points.len() < 2 {
268 return cur_points;
269 } else if cur_points.len() == 2 {
270 return vec![cur_points[0], cur_points[1]];
271 }
272
273 let closed = cur_points.first() == cur_points.last();
275
276 if closed {
279 let last = match points.last().copied() {
280 Some(s) => s,
281 None => unsafe { unreachable_unchecked() },
282 };
283 let first = match points.first().copied() {
284 Some(s) => s,
285 None => unsafe { unreachable_unchecked() },
286 };
287 let mut new_cur_points = vec![last];
288 new_cur_points.extend(cur_points.drain(..));
289 new_cur_points.push(first);
290 cur_points = new_cur_points;
291 }
292
293 fit(&cur_points[..], tolerance)
294}
295
296#[derive(Clone)]
297struct Split {
298 global_range: Range<usize>,
299 tan1: Point,
300 tan2: Point,
301}
302
303#[inline]
304fn fit(points: &[Point], tolerance: Float) -> Vec<Point> {
305
306 let mut segments = Vec::new();
309 let distances = chord_length_parametrize(points);
310
311 if distances.len() != points.len() {
312 return segments; }
314
315 if points.len() == 0 {
317 return Vec::new();
318 } else if points.len() == 1 {
319 return vec![points[0]];
320 } else if points.len() == 2 {
321 return vec![points[0], points[1]];
322 } else {
323 let mut splits_to_eval = vec![Split {
324 global_range: 0..points.len(),
325 tan1: points[1].subtract(points[0]),
326 tan2: points[points.len() - 2].subtract(points[points.len() - 1]),
327 }];
328
329 while let Some(split) = splits_to_eval.pop() {
330
331 if split.global_range.end > points.len() || split.global_range.end > distances.len() {
333 continue;
334 }
335
336 let result = fit_cubic(FitCubicParams {
337 points: &points[split.global_range.clone()],
338 chord_lengths: &distances[split.global_range.clone()],
339 segments: &mut segments,
340 error: tolerance,
341 tan1: split.tan1,
342 tan2: split.tan2,
343 });
344
345 if let Some(r) = result {
346 if split.global_range.start > split.global_range.start + r + 1 ||
348 split.global_range.start + r > split.global_range.end {
349 continue;
350 }
351 if split.global_range.start + r + 1 >= points.len() || split.global_range.start + r - 1 >= points.len() {
352 continue;
353 }
354 let tan_center = points[split.global_range.start + r - 1].subtract(points[split.global_range.start + r + 1]);
356
357 splits_to_eval.extend_from_slice(&[
358 Split {
359 global_range: (split.global_range.start + r)..split.global_range.end,
360 tan1: tan_center.negate(),
361 tan2: split.tan2,
362 },
363 Split {
364 global_range: split.global_range.start..(split.global_range.start + r + 1),
365 tan1: split.tan1,
366 tan2: tan_center,
367 },
368 ]);
369 }
370 }
371
372 segments
373 }
374
375}
376
377struct FitCubicParams<'a> {
378 segments: &'a mut Vec<Point>,
379 points: &'a [Point],
380 chord_lengths: &'a [Float],
381 error: Float,
382 tan1: Point,
383 tan2: Point,
384}
385
386#[inline]
387fn fit_cubic(params: FitCubicParams) -> Option<usize> {
388
389 let FitCubicParams { segments, points, chord_lengths, error, tan1, tan2 } = params;
390
391 if points.len() < 2 {
393 return None;
394 } else if points.len() == 2 {
395 let pt1 = points[0];
396 let pt2 = points[1];
397 let dist = pt1.distance(pt2) / 3.0;
398 add_curve(segments, &[
399 pt1,
400 pt1.add(tan1.normalize(dist)),
401 pt2.add(tan2.normalize(dist)),
402 pt2
403 ]);
404 return None;
405 }
406
407 let mut u_prime = chord_lengths.to_owned();
412 let u_prime_first = match u_prime.first().copied() {
413 Some(s) => s,
414 None => unsafe { unreachable_unchecked() },
415 };
416 let u_prime_last = match u_prime.last().copied() {
417 Some(s) => s,
418 None => unsafe { unreachable_unchecked() },
419 };
420 let u_prime_last = u_prime_last - u_prime_first;
421 u_prime.iter_mut().for_each(|p| { *p = (*p - u_prime_first) / u_prime_last; });
422
423 let mut max_error = error.max(error.powi(2));
424 let mut parameters_in_order = true;
425 let mut split = 2;
426
427 for _ in 0..4 {
429
430 let curve = generate_bezier(points, &u_prime, tan1, tan2);
431
432 let max = find_max_error(points, &curve, &u_prime);
434
435 if max.error < error && parameters_in_order {
436 add_curve(segments, &curve);
438 return None;
439 }
440
441 split = max.index;
442
443 if max.error >= max_error {
445 break;
446 }
447 parameters_in_order = reparameterize(points, &mut u_prime, &curve);
448 max_error = max.error;
449 }
450
451 Some(split)
452}
453
454#[inline]
455fn add_curve(segments: &mut Vec<Point>, curve: &[Point;4]) {
456 segments.extend_from_slice(curve);
457}
458
459#[inline]
460#[allow(non_snake_case)]
461fn generate_bezier(points: &[Point], u_prime: &[Float], tan1: Point, tan2: Point) -> [Point;4] {
462
463 const BEZIER_EPSILON: Float = 1e-12;
464
465 debug_assert!(u_prime.len() > 2);
466 debug_assert!(points.len() > 2);
467 debug_assert!(u_prime.len() == points.len());
468
469 let pt1 = &points[0];
470 let pt2 = &points[points.len() - 1];
471
472 let mut C = [
474 [0.0, 0.0],
475 [0.0, 0.0]
476 ];
477 let mut X = [0.0, 0.0];
478
479 for (p, u) in points.iter().zip(u_prime.iter()) {
480
481 let t = 1.0 - u;
482 let b = 3.0 * u * t;
483 let b0 = t * t * t;
484 let b1 = b * t;
485 let b2 = b * u;
486 let b3 = u * u * u;
487 let a1 = tan1.normalize(b1);
488 let a2 = tan2.normalize(b2);
489 let pt1_multiplied = pt1.multiply(b0 + b1);
490 let pt2_multiplied = pt2.multiply(b2 + b3);
491 let tmp = p.subtract(pt1_multiplied).subtract(pt2_multiplied);
492
493 C[0][0] += a1.dot(a1);
494 C[0][1] += a1.dot(a2);
495 C[1][0] = C[0][1];
496 C[1][1] += a2.dot(a2);
497
498 X[0] += a1.dot(tmp);
499 X[1] += a2.dot(tmp);
500 }
501
502 let det_c0_c1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
504
505 let mut alpha1;
506 let mut alpha2;
507
508 if det_c0_c1.abs() > BEZIER_EPSILON {
509 let det_c0_x = C[0][0] * X[1] - C[1][0] * X[0];
511 let det_x_c1 = X[0] * C[1][1] - X[1] * C[0][1];
512 alpha1 = det_x_c1 / det_c0_c1;
514 alpha2 = det_c0_x / det_c0_c1;
515 } else {
516 let c0 = C[0][0] + C[0][1];
518 let c1 = C[1][0] + C[1][1];
519 alpha1 = if c0.abs() > BEZIER_EPSILON {
520 X[0] / c0
521 } else if c1.abs() > BEZIER_EPSILON {
522 X[1] / c1
523 } else {
524 0.0
525 };
526 alpha2 = alpha1;
527 }
528
529 let seg_length = pt2.distance(*pt1);
533 let eps = BEZIER_EPSILON * seg_length;
534 let mut handle1_2 = None;
535
536 if alpha1 < eps || alpha2 < eps {
537 alpha1 = seg_length / 3.0;
540 alpha2 = alpha1;
541 } else {
542 let line = pt2.subtract(*pt1);
545
546 let tmp_handle_1 = tan1.normalize(alpha1);
549 let tmp_handle_2 = tan2.normalize(alpha2);
550
551 let seg_length_squared = seg_length * seg_length;
552
553 if tmp_handle_1.dot(line) - tmp_handle_2.dot(line) > seg_length_squared {
554 alpha1 = seg_length / 3.0;
556 alpha2 = alpha1;
557 handle1_2 = None;
559 } else {
560 handle1_2 = Some((tmp_handle_1, tmp_handle_2));
561 }
562 }
563
564 if let Some((h1, h2)) = handle1_2 {
567 [*pt1, pt1.add(h1), pt2.add(h2), *pt2]
568 } else {
569 [*pt1, pt1.add(tan1.normalize(alpha1)), pt2.add(tan2.normalize(alpha2)), *pt2]
570 }
571}
572
573#[inline]
576fn reparameterize(points: &[Point], u: &mut [Float], curve: &[Point;4]) -> bool {
577
578 points.iter().zip(u.iter_mut()).for_each(|(p, u)| { *u = find_root(curve, p, *u); });
579
580 !u.windows(2).any(|w| w[1] <= w[0])
583}
584
585#[inline]
586fn find_root(curve: &[Point;4], point: &Point, u: Float) -> Float {
587
588 let mut curve1 = [Point { x: 0.0, y: 0.0 };3];
589 let mut curve2 = [Point { x: 0.0, y: 0.0 };2];
590
591 for i in 0..curve1.len() {
593 curve1[i] = curve[i + 1].subtract(curve[i]).multiply(3.0);
594 }
595
596 for i in 0..curve2.len() {
598 curve2[i] = curve1[i + 1].subtract(curve1[i]).multiply(2.0);
599 }
600
601 let pt = evaluate_4(&curve, u);
603 let pt1 = evaluate_3(&curve1, u);
604 let pt2 = evaluate_2(&curve2, u);
605 let diff = pt.subtract(*point);
606 let df = pt1.dot(pt1) + diff.dot(pt2);
607
608 if df.abs() < Float::EPSILON {
610 u
611 } else {
612 u - diff.dot(pt1) / df
613 }
614}
615
616macro_rules! evaluate {
617 ($curve:expr, $t:expr) => {{
618 let mut tmp = *$curve;
620
621 for i in 1..$curve.len() {
623 for j in 0..($curve.len() - i) {
624 tmp[j] = tmp[j].multiply(1.0 - $t).add(tmp[j + 1].multiply($t));
625 }
626 }
627
628 tmp[0]
629 }};
630}
631
632#[inline]
634fn evaluate_4(curve: &[Point;4], t: Float) -> Point { let ret = evaluate!(curve, t); ret }
635#[inline]
636fn evaluate_3(curve: &[Point;3], t: Float) -> Point { let ret = evaluate!(curve, t); ret }
637#[inline]
638fn evaluate_2(curve: &[Point;2], t: Float) -> Point { let ret = evaluate!(curve, t); ret }
639
640#[inline]
642fn chord_length_parametrize(points: &[Point]) -> Vec<Float> {
643
644 let mut u = vec![0.0;points.len()];
645 let mut last_dist = 0.0;
646
647 for (prev, (next_id, next)) in points.iter().zip(points.iter().enumerate().skip(1)) {
648 let new_dist = last_dist + prev.distance(*next);
649 unsafe { *u.get_unchecked_mut(next_id) = new_dist; }
650 last_dist = new_dist;
651 }
652
653 for val in u.iter_mut() {
654 *val /= last_dist;
655 }
656
657 u
658}
659
660struct FindMaxErrorReturn {
661 error: Float,
662 index: usize
663}
664
665#[inline]
667fn find_max_error(points: &[Point], curve: &[Point;4], u: &[Float]) -> FindMaxErrorReturn {
668
669 let mut index = points.len() / 2.0 as usize;
670 let mut max_dist = 0.0;
671
672 for (i, (p, u)) in points.iter().zip(u.iter()).enumerate() {
673 let point_on_curve = evaluate_4(curve, *u);
674 let dist = point_on_curve.subtract(*p);
675 let dist_squared = dist.x.mul_add(dist.x, dist.y.powi(2)); if dist_squared >= max_dist {
678 max_dist = dist_squared;
679 index = i;
680 }
681 }
682
683 FindMaxErrorReturn {
684 error: max_dist,
685 index: index
686 }
687}