Skip to main content

agg_rust/
array.rs

1//! Container utilities and vertex sequence types.
2//!
3//! Port of `agg_array.h` and `agg_vertex_sequence.h`.
4//!
5//! Most of AGG's C++ containers (`pod_vector`, `pod_array`, `pod_bvector`) map
6//! directly to Rust's `Vec<T>`. This module provides the algorithms and
7//! specialized types that don't have direct Rust equivalents.
8
9use crate::math::{calc_distance, VERTEX_DIST_EPSILON};
10
11// ============================================================================
12// Sorting and searching utilities
13// ============================================================================
14
15/// Threshold below which quicksort switches to insertion sort.
16/// Matches C++ `quick_sort_threshold`.
17pub const QUICK_SORT_THRESHOLD: usize = 9;
18
19/// Sort a mutable slice using AGG's quicksort algorithm.
20/// Uses insertion sort for small partitions.
21///
22/// This is a direct port of C++ `quick_sort` from `agg_array.h`.
23/// For most Rust code you'd use `slice::sort_by`, but this is provided
24/// for exact behavioral matching where sort order matters.
25pub fn quick_sort<T, F>(arr: &mut [T], less: &F)
26where
27    T: Copy,
28    F: Fn(&T, &T) -> bool,
29{
30    if arr.len() < 2 {
31        return;
32    }
33
34    let mut stack = [0i32; 80];
35    let mut top: usize = 0;
36    let mut limit = arr.len() as i32;
37    let mut base = 0i32;
38
39    loop {
40        let len = limit - base;
41
42        if len > QUICK_SORT_THRESHOLD as i32 {
43            let pivot = base + len / 2;
44            arr.swap(base as usize, pivot as usize);
45
46            let mut i = base + 1;
47            let mut j = limit - 1;
48
49            // Ensure arr[i] <= arr[base] <= arr[j]
50            if less(&arr[j as usize], &arr[i as usize]) {
51                arr.swap(j as usize, i as usize);
52            }
53            if less(&arr[base as usize], &arr[i as usize]) {
54                arr.swap(base as usize, i as usize);
55            }
56            if less(&arr[j as usize], &arr[base as usize]) {
57                arr.swap(j as usize, base as usize);
58            }
59
60            loop {
61                loop {
62                    i += 1;
63                    if !less(&arr[i as usize], &arr[base as usize]) {
64                        break;
65                    }
66                }
67                loop {
68                    j -= 1;
69                    if !less(&arr[base as usize], &arr[j as usize]) {
70                        break;
71                    }
72                }
73                if i > j {
74                    break;
75                }
76                arr.swap(i as usize, j as usize);
77            }
78
79            arr.swap(base as usize, j as usize);
80
81            // Push the larger sub-array
82            if j - base > limit - i {
83                stack[top] = base;
84                stack[top + 1] = j;
85                base = i;
86            } else {
87                stack[top] = i;
88                stack[top + 1] = limit;
89                limit = j;
90            }
91            top += 2;
92        } else {
93            // Insertion sort for small partitions
94            let mut j = base;
95            let mut i = j + 1;
96            while i < limit {
97                while j >= base && less(&arr[(j + 1) as usize], &arr[j as usize]) {
98                    arr.swap((j + 1) as usize, j as usize);
99                    if j == base {
100                        break;
101                    }
102                    j -= 1;
103                }
104                i += 1;
105                j = i - 1;
106            }
107
108            if top > 0 {
109                top -= 2;
110                base = stack[top];
111                limit = stack[top + 1];
112            } else {
113                break;
114            }
115        }
116    }
117}
118
119/// Remove duplicates from a sorted slice. Returns the number of remaining elements.
120/// The slice is modified in place (duplicates are overwritten, tail is unchanged).
121///
122/// Port of C++ `remove_duplicates`.
123pub fn remove_duplicates<T, F>(arr: &mut [T], equal: &F) -> usize
124where
125    T: Copy,
126    F: Fn(&T, &T) -> bool,
127{
128    if arr.len() < 2 {
129        return arr.len();
130    }
131    let mut j = 1usize;
132    for i in 1..arr.len() {
133        if !equal(&arr[i], &arr[i - 1]) {
134            arr[j] = arr[i];
135            j += 1;
136        }
137    }
138    j
139}
140
141/// Reverse the elements of a slice in place.
142/// Port of C++ `invert_container`.
143pub fn invert_container<T>(arr: &mut [T]) {
144    arr.reverse();
145}
146
147/// Binary search for the insertion position of `val` in a sorted slice.
148/// Returns the index where `val` would be inserted to maintain sort order.
149///
150/// Port of C++ `binary_search_pos`.
151pub fn binary_search_pos<T, V, F>(arr: &[T], val: &V, less: &F) -> usize
152where
153    F: Fn(&V, &T) -> bool,
154{
155    if arr.is_empty() {
156        return 0;
157    }
158
159    let mut beg = 0usize;
160    let mut end = arr.len() - 1;
161
162    if less(val, &arr[0]) {
163        return 0;
164    }
165    // Need a reverse less for this check
166    // In C++: if(less(arr[end], val)) return end + 1;
167    // We check: if val > arr[end]
168    if !less(val, &arr[end]) {
169        return end + 1;
170    }
171
172    while end - beg > 1 {
173        let mid = (end + beg) >> 1;
174        if less(val, &arr[mid]) {
175            end = mid;
176        } else {
177            beg = mid;
178        }
179    }
180    end
181}
182
183// ============================================================================
184// Vertex dist types
185// ============================================================================
186
187/// A vertex with coordinates and the distance to the next vertex.
188/// Port of C++ `vertex_dist`.
189///
190/// The `calc_dist` method computes the distance to another vertex_dist
191/// and returns `true` if the distance exceeds `VERTEX_DIST_EPSILON`
192/// (i.e., the vertices are not coincident).
193#[derive(Debug, Clone, Copy)]
194pub struct VertexDist {
195    pub x: f64,
196    pub y: f64,
197    pub dist: f64,
198}
199
200impl VertexDist {
201    pub fn new(x: f64, y: f64) -> Self {
202        Self { x, y, dist: 0.0 }
203    }
204
205    /// Calculate distance to `val` and store it. Returns `true` if the
206    /// points are not coincident (distance > VERTEX_DIST_EPSILON).
207    /// If coincident, sets dist to `1.0 / VERTEX_DIST_EPSILON`.
208    pub fn calc_dist(&mut self, val: &VertexDist) -> bool {
209        self.dist = calc_distance(self.x, self.y, val.x, val.y);
210        let ret = self.dist > VERTEX_DIST_EPSILON;
211        if !ret {
212            self.dist = 1.0 / VERTEX_DIST_EPSILON;
213        }
214        ret
215    }
216}
217
218/// Like `VertexDist` but with an additional path command.
219/// Port of C++ `vertex_dist_cmd`.
220#[derive(Debug, Clone, Copy)]
221pub struct VertexDistCmd {
222    pub x: f64,
223    pub y: f64,
224    pub dist: f64,
225    pub cmd: u32,
226}
227
228impl VertexDistCmd {
229    pub fn new(x: f64, y: f64, cmd: u32) -> Self {
230        Self {
231            x,
232            y,
233            dist: 0.0,
234            cmd,
235        }
236    }
237
238    /// Calculate distance to `val`. Returns `true` if non-coincident.
239    pub fn calc_dist(&mut self, val: &VertexDistCmd) -> bool {
240        self.dist = calc_distance(self.x, self.y, val.x, val.y);
241        let ret = self.dist > VERTEX_DIST_EPSILON;
242        if !ret {
243            self.dist = 1.0 / VERTEX_DIST_EPSILON;
244        }
245        ret
246    }
247}
248
249// ============================================================================
250// Vertex sequence
251// ============================================================================
252
253/// A sequence of vertices that automatically filters coincident points.
254///
255/// Port of C++ `vertex_sequence<T>` which inherits from `pod_bvector`.
256/// When a new vertex is added, it calculates the distance from the previous
257/// vertex. If the previous vertex is coincident (distance <= epsilon),
258/// it is removed.
259///
260/// This is the Rust equivalent using `Vec<VertexDist>` as backing storage.
261pub struct VertexSequence {
262    vertices: Vec<VertexDist>,
263}
264
265impl VertexSequence {
266    pub fn new() -> Self {
267        Self {
268            vertices: Vec::new(),
269        }
270    }
271
272    pub fn size(&self) -> usize {
273        self.vertices.len()
274    }
275
276    pub fn is_empty(&self) -> bool {
277        self.vertices.is_empty()
278    }
279
280    /// Add a vertex to the sequence, removing the previous vertex if it's
281    /// coincident with the one before it.
282    ///
283    /// The C++ version checks if `vertices[size-2]` is coincident with
284    /// `vertices[size-1]`, and if so removes `vertices[size-1]`. This is a
285    /// "lazy" check — coincident pairs are cleaned up when the NEXT vertex
286    /// is added.
287    pub fn add(&mut self, val: VertexDist) {
288        if self.vertices.len() > 1 {
289            let len = self.vertices.len();
290            let last = self.vertices[len - 1];
291            let keep = self.vertices[len - 2].calc_dist(&last);
292            if !keep {
293                self.vertices.pop();
294            }
295        }
296        self.vertices.push(val);
297    }
298
299    /// Modify the last vertex.
300    pub fn modify_last(&mut self, val: VertexDist) {
301        self.vertices.pop();
302        self.add(val);
303    }
304
305    /// Close the sequence, removing trailing coincident vertices.
306    /// If `closed` is true, also removes the last vertex if it's coincident
307    /// with the first.
308    pub fn close(&mut self, closed: bool) {
309        // C++: while(size > 1) { if((*this)[size-2]((*this)[size-1])) break; ... }
310        // operator() stores distance on [size-2], so we must mutate the actual
311        // vector element, not a copy.
312        while self.vertices.len() > 1 {
313            let len = self.vertices.len();
314            let last = self.vertices[len - 1]; // copy to avoid double borrow
315            let keep = self.vertices[len - 2].calc_dist(&last); // mutate in-place
316            if keep {
317                break;
318            }
319            let t = self.vertices[self.vertices.len() - 1];
320            self.vertices.pop();
321            self.modify_last(t);
322        }
323
324        if closed {
325            while self.vertices.len() > 1 {
326                let len = self.vertices.len();
327                // For closing: check distance from last vertex to first
328                let first = self.vertices[0]; // copy
329                let keep = self.vertices[len - 1].calc_dist(&first); // mutate in-place
330                if keep {
331                    break;
332                }
333                self.vertices.pop();
334            }
335        }
336    }
337
338    pub fn remove_all(&mut self) {
339        self.vertices.clear();
340    }
341
342    pub fn clear(&mut self) {
343        self.vertices.clear();
344    }
345
346    /// Remove the last vertex.
347    /// Port of C++ `pod_bvector::remove_last`.
348    pub fn remove_last(&mut self) {
349        self.vertices.pop();
350    }
351
352    /// Get the previous vertex (wrapping index).
353    /// Port of C++ `pod_bvector::prev`.
354    pub fn prev(&self, idx: usize) -> &VertexDist {
355        let len = self.vertices.len();
356        &self.vertices[(idx + len - 1) % len]
357    }
358
359    /// Get the current vertex.
360    /// Port of C++ `pod_bvector::curr`.
361    pub fn curr(&self, idx: usize) -> &VertexDist {
362        &self.vertices[idx]
363    }
364
365    /// Get the next vertex (wrapping index).
366    /// Port of C++ `pod_bvector::next`.
367    pub fn next(&self, idx: usize) -> &VertexDist {
368        let len = self.vertices.len();
369        &self.vertices[(idx + 1) % len]
370    }
371
372    /// Get a reference to the underlying vertex slice.
373    pub fn as_slice(&self) -> &[VertexDist] {
374        &self.vertices
375    }
376
377    /// Get a mutable reference to the underlying vertex slice.
378    pub fn as_mut_slice(&mut self) -> &mut [VertexDist] {
379        &mut self.vertices
380    }
381}
382
383impl Default for VertexSequence {
384    fn default() -> Self {
385        Self::new()
386    }
387}
388
389impl core::ops::Index<usize> for VertexSequence {
390    type Output = VertexDist;
391
392    fn index(&self, i: usize) -> &VertexDist {
393        &self.vertices[i]
394    }
395}
396
397impl core::ops::IndexMut<usize> for VertexSequence {
398    fn index_mut(&mut self, i: usize) -> &mut VertexDist {
399        &mut self.vertices[i]
400    }
401}
402
403// ============================================================================
404// shorten_path
405// ============================================================================
406
407/// Shorten a vertex sequence by removing `s` distance-units from the end.
408///
409/// Port of C++ `shorten_path` from `agg_shorten_path.h`.
410pub fn shorten_path(vs: &mut VertexSequence, s: f64, closed: u32) {
411    if s > 0.0 && vs.size() > 1 {
412        let mut s = s;
413        let mut n = vs.size() as i32 - 2;
414        while n > 0 {
415            let d = vs[n as usize].dist;
416            if d > s {
417                break;
418            }
419            vs.remove_last();
420            s -= d;
421            n -= 1;
422        }
423        if vs.size() < 2 {
424            vs.remove_all();
425        } else {
426            let n = vs.size() - 1;
427            let prev_dist = vs[n - 1].dist;
428            let d = (prev_dist - s) / prev_dist;
429            let prev_x = vs[n - 1].x;
430            let prev_y = vs[n - 1].y;
431            let last_x = vs[n].x;
432            let last_y = vs[n].y;
433            vs[n].x = prev_x + (last_x - prev_x) * d;
434            vs[n].y = prev_y + (last_y - prev_y) * d;
435            // Check if prev and modified last are not coincident
436            let last_copy = vs[n];
437            if !vs.as_mut_slice()[n - 1].calc_dist(&last_copy) {
438                vs.remove_last();
439            }
440            vs.close(closed != 0);
441        }
442    }
443}
444
445// ============================================================================
446// Tests
447// ============================================================================
448
449#[cfg(test)]
450mod tests {
451    use super::*;
452
453    #[test]
454    fn test_quick_sort_basic() {
455        let mut arr = [5, 3, 8, 1, 9, 2, 7, 4, 6, 0];
456        quick_sort(&mut arr, &|a: &i32, b: &i32| *a < *b);
457        assert_eq!(arr, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
458    }
459
460    #[test]
461    fn test_quick_sort_already_sorted() {
462        let mut arr = [0, 1, 2, 3, 4];
463        quick_sort(&mut arr, &|a: &i32, b: &i32| *a < *b);
464        assert_eq!(arr, [0, 1, 2, 3, 4]);
465    }
466
467    #[test]
468    fn test_quick_sort_reverse() {
469        let mut arr = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
470        quick_sort(&mut arr, &|a: &i32, b: &i32| *a < *b);
471        assert_eq!(arr, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
472    }
473
474    #[test]
475    fn test_quick_sort_empty_and_single() {
476        let mut empty: [i32; 0] = [];
477        quick_sort(&mut empty, &|a: &i32, b: &i32| *a < *b);
478
479        let mut single = [42];
480        quick_sort(&mut single, &|a: &i32, b: &i32| *a < *b);
481        assert_eq!(single, [42]);
482    }
483
484    #[test]
485    fn test_quick_sort_small_partition() {
486        // Test with array size <= QUICK_SORT_THRESHOLD to exercise insertion sort
487        let mut arr = [5, 3, 1, 4, 2];
488        quick_sort(&mut arr, &|a: &i32, b: &i32| *a < *b);
489        assert_eq!(arr, [1, 2, 3, 4, 5]);
490    }
491
492    #[test]
493    fn test_quick_sort_large() {
494        let mut arr: Vec<i32> = (0..100).rev().collect();
495        quick_sort(&mut arr, &|a: &i32, b: &i32| *a < *b);
496        let expected: Vec<i32> = (0..100).collect();
497        assert_eq!(arr, expected);
498    }
499
500    #[test]
501    fn test_remove_duplicates() {
502        let mut arr = [1, 1, 2, 3, 3, 3, 4, 5, 5];
503        let n = remove_duplicates(&mut arr, &|a: &i32, b: &i32| *a == *b);
504        assert_eq!(n, 5);
505        assert_eq!(&arr[..n], &[1, 2, 3, 4, 5]);
506    }
507
508    #[test]
509    fn test_remove_duplicates_no_dups() {
510        let mut arr = [1, 2, 3, 4, 5];
511        let n = remove_duplicates(&mut arr, &|a: &i32, b: &i32| *a == *b);
512        assert_eq!(n, 5);
513    }
514
515    #[test]
516    fn test_remove_duplicates_all_same() {
517        let mut arr = [1, 1, 1, 1];
518        let n = remove_duplicates(&mut arr, &|a: &i32, b: &i32| *a == *b);
519        assert_eq!(n, 1);
520    }
521
522    #[test]
523    fn test_invert_container() {
524        let mut arr = [1, 2, 3, 4, 5];
525        invert_container(&mut arr);
526        assert_eq!(arr, [5, 4, 3, 2, 1]);
527    }
528
529    #[test]
530    fn test_binary_search_pos() {
531        let arr = [10, 20, 30, 40, 50];
532        assert_eq!(binary_search_pos(&arr, &0, &|a: &i32, b: &i32| *a < *b), 0);
533        assert_eq!(binary_search_pos(&arr, &25, &|a: &i32, b: &i32| *a < *b), 2);
534        assert_eq!(binary_search_pos(&arr, &60, &|a: &i32, b: &i32| *a < *b), 5);
535    }
536
537    #[test]
538    fn test_vertex_dist_coincident() {
539        let mut v1 = VertexDist::new(1.0, 2.0);
540        let v2 = VertexDist::new(1.0, 2.0);
541        assert!(!v1.calc_dist(&v2));
542        assert_eq!(v1.dist, 1.0 / VERTEX_DIST_EPSILON);
543    }
544
545    #[test]
546    fn test_vertex_dist_non_coincident() {
547        let mut v1 = VertexDist::new(0.0, 0.0);
548        let v2 = VertexDist::new(3.0, 4.0);
549        assert!(v1.calc_dist(&v2));
550        assert!((v1.dist - 5.0).abs() < 1e-10);
551    }
552
553    #[test]
554    fn test_vertex_sequence_basic() {
555        let mut seq = VertexSequence::new();
556        seq.add(VertexDist::new(0.0, 0.0));
557        seq.add(VertexDist::new(1.0, 0.0));
558        seq.add(VertexDist::new(2.0, 0.0));
559        assert_eq!(seq.size(), 3);
560    }
561
562    #[test]
563    fn test_vertex_sequence_removes_coincident() {
564        let mut seq = VertexSequence::new();
565        seq.add(VertexDist::new(0.0, 0.0));
566        seq.add(VertexDist::new(1.0, 0.0));
567        // Add a point coincident with the previous
568        seq.add(VertexDist::new(1.0, 0.0));
569        // Lazy check: coincident pair isn't removed until next add
570        assert_eq!(seq.size(), 3);
571        // Adding another point triggers removal of the coincident vertex
572        seq.add(VertexDist::new(2.0, 0.0));
573        assert_eq!(seq.size(), 3); // (0,0), (1,0), (2,0) — one of the duplicate (1,0) removed
574    }
575
576    #[test]
577    fn test_vertex_sequence_close() {
578        let mut seq = VertexSequence::new();
579        seq.add(VertexDist::new(0.0, 0.0));
580        seq.add(VertexDist::new(1.0, 0.0));
581        seq.add(VertexDist::new(0.0, 0.0)); // Same as first
582                                            // Close with closed=true should remove vertex coincident with first
583        seq.close(true);
584        assert_eq!(seq.size(), 2);
585    }
586
587    #[test]
588    fn test_vertex_sequence_prev_curr_next() {
589        let mut seq = VertexSequence::new();
590        seq.add(VertexDist::new(0.0, 0.0));
591        seq.add(VertexDist::new(1.0, 0.0));
592        seq.add(VertexDist::new(2.0, 0.0));
593
594        // curr
595        assert_eq!(seq.curr(1).x, 1.0);
596        // next wraps
597        assert_eq!(seq.next(2).x, 0.0);
598        // prev wraps
599        assert_eq!(seq.prev(0).x, 2.0);
600        // normal prev
601        assert_eq!(seq.prev(2).x, 1.0);
602    }
603
604    #[test]
605    fn test_vertex_sequence_remove_last() {
606        let mut seq = VertexSequence::new();
607        seq.add(VertexDist::new(0.0, 0.0));
608        seq.add(VertexDist::new(1.0, 0.0));
609        seq.add(VertexDist::new(2.0, 0.0));
610        seq.remove_last();
611        assert_eq!(seq.size(), 2);
612        assert_eq!(seq[1].x, 1.0);
613    }
614
615    #[test]
616    fn test_shorten_path_basic() {
617        let mut seq = VertexSequence::new();
618        seq.add(VertexDist::new(0.0, 0.0));
619        seq.add(VertexDist::new(10.0, 0.0));
620        seq.add(VertexDist::new(20.0, 0.0));
621        // Force distances by closing (which triggers calc_dist)
622        seq.close(false);
623
624        // Shorten by 5 units from the end
625        shorten_path(&mut seq, 5.0, 0);
626        assert!(seq.size() >= 2);
627        // The last vertex should have moved closer
628        let last = seq[seq.size() - 1];
629        assert!(last.x < 20.0, "Last x={} should be < 20", last.x);
630    }
631
632    #[test]
633    fn test_shorten_path_zero() {
634        let mut seq = VertexSequence::new();
635        seq.add(VertexDist::new(0.0, 0.0));
636        seq.add(VertexDist::new(10.0, 0.0));
637        seq.close(false);
638
639        // Shorten by 0 — no change
640        shorten_path(&mut seq, 0.0, 0);
641        assert_eq!(seq.size(), 2);
642    }
643
644    #[test]
645    fn test_shorten_path_exceeds_length() {
646        let mut seq = VertexSequence::new();
647        seq.add(VertexDist::new(0.0, 0.0));
648        seq.add(VertexDist::new(10.0, 0.0));
649        seq.close(false);
650
651        // Shorten by more than total length — C++ overshoots (doesn't empty)
652        shorten_path(&mut seq, 100.0, 0);
653        // Path still has 2 vertices: the last is moved beyond start
654        assert_eq!(seq.size(), 2);
655        // Last x is negative (overshot past the start)
656        assert!(
657            seq[1].x < 0.0,
658            "Last vertex x={} should be negative (overshot)",
659            seq[1].x
660        );
661    }
662
663    #[test]
664    fn test_shorten_path_removes_segment() {
665        let mut seq = VertexSequence::new();
666        seq.add(VertexDist::new(0.0, 0.0));
667        seq.add(VertexDist::new(5.0, 0.0));
668        seq.add(VertexDist::new(10.0, 0.0));
669        seq.add(VertexDist::new(20.0, 0.0));
670        seq.close(false);
671
672        // Shorten by 12 — should remove last segment (len=10) and trim the one before
673        shorten_path(&mut seq, 12.0, 0);
674        // Path should be shorter than original
675        assert!(seq.size() >= 2);
676        let last = seq[seq.size() - 1];
677        // The path was shortened; last vertex should be closer to start
678        assert!(last.x < 20.0, "Last x={} should be < 20", last.x);
679    }
680}