parry3d/transformation/
utils.rs

1//! Utilities useful for various generations tasks.
2
3use crate::math::{Isometry, Point, Real, Vector};
4use crate::na::ComplexField;
5use alloc::vec::Vec;
6#[cfg(feature = "dim3")]
7use {crate::math::DIM, num::Zero};
8
9/// Applies in-place a transformation to an array of points.
10pub fn transform(points: &mut [Point<Real>], m: Isometry<Real>) {
11    points.iter_mut().for_each(|p| *p = m * *p);
12}
13
14/// Returns the transformed version of a vector of points.
15pub fn transformed(mut points: Vec<Point<Real>>, m: Isometry<Real>) -> Vec<Point<Real>> {
16    transform(&mut points, m);
17    points
18}
19
20/// Returns the transformed version of a vector of points.
21pub fn scaled(mut points: Vec<Point<Real>>, scale: Vector<Real>) -> Vec<Point<Real>> {
22    points
23        .iter_mut()
24        .for_each(|p| p.coords.component_mul_assign(&scale));
25    points
26}
27
28// TODO: remove that in favor of `push_xy_circle` ?
29/// Pushes a discretized counterclockwise circle to a buffer.
30#[cfg(feature = "dim3")]
31#[inline]
32pub fn push_circle(radius: Real, nsubdiv: u32, dtheta: Real, y: Real, out: &mut Vec<Point<Real>>) {
33    let mut curr_theta = Real::zero();
34
35    for _ in 0..nsubdiv {
36        out.push(Point::new(
37            ComplexField::cos(curr_theta) * radius,
38            y,
39            ComplexField::sin(curr_theta) * radius,
40        ));
41        curr_theta += dtheta;
42    }
43}
44
45/// Pushes a discretized counterclockwise circle to a buffer.
46/// The circle is contained on the plane spanned by the `x` and `y` axis.
47#[inline]
48#[cfg(feature = "dim2")]
49pub fn push_xy_arc(radius: Real, nsubdiv: u32, dtheta: Real, out: &mut Vec<Point<Real>>) {
50    let mut curr_theta: Real = 0.0;
51
52    for _ in 0..nsubdiv {
53        let mut pt_coords = Vector::zeros();
54
55        pt_coords[0] = ComplexField::cos(curr_theta) * radius;
56        pt_coords[1] = ComplexField::sin(curr_theta) * radius;
57        out.push(Point::from(pt_coords));
58
59        curr_theta += dtheta;
60    }
61}
62
63/// Creates the faces from two circles with the same discretization.
64#[cfg(feature = "dim3")]
65#[inline]
66pub fn push_ring_indices(
67    base_lower_circle: u32,
68    base_upper_circle: u32,
69    nsubdiv: u32,
70    out: &mut Vec<[u32; DIM]>,
71) {
72    push_open_ring_indices(base_lower_circle, base_upper_circle, nsubdiv, out);
73
74    // adjust the last two triangles
75    push_rectangle_indices(
76        base_upper_circle,
77        base_upper_circle + nsubdiv - 1,
78        base_lower_circle,
79        base_lower_circle + nsubdiv - 1,
80        out,
81    );
82}
83
84/// Creates the faces from two circles with the same discretization.
85#[cfg(feature = "dim3")]
86#[inline]
87pub fn push_open_ring_indices(
88    base_lower_circle: u32,
89    base_upper_circle: u32,
90    nsubdiv: u32,
91    out: &mut Vec<[u32; DIM]>,
92) {
93    assert!(nsubdiv > 0);
94
95    for i in 0..nsubdiv - 1 {
96        let bli = base_lower_circle + i;
97        let bui = base_upper_circle + i;
98        push_rectangle_indices(bui + 1, bui, bli + 1, bli, out);
99    }
100}
101
102/// Creates the faces from a circle and a point that is shared by all triangle.
103#[cfg(feature = "dim3")]
104#[inline]
105pub fn push_degenerate_top_ring_indices(
106    base_circle: u32,
107    point: u32,
108    nsubdiv: u32,
109    out: &mut Vec<[u32; DIM]>,
110) {
111    push_degenerate_open_top_ring_indices(base_circle, point, nsubdiv, out);
112
113    out.push([base_circle + nsubdiv - 1, point, base_circle]);
114}
115
116/// Creates the faces from a circle and a point that is shared by all triangle.
117#[cfg(feature = "dim3")]
118#[inline]
119pub fn push_degenerate_open_top_ring_indices(
120    base_circle: u32,
121    point: u32,
122    nsubdiv: u32,
123    out: &mut Vec<[u32; DIM]>,
124) {
125    assert!(nsubdiv > 0);
126
127    for i in 0..nsubdiv - 1 {
128        out.push([base_circle + i, point, base_circle + i + 1]);
129    }
130}
131
132/// Pushes indices so that a circle is filled with triangles. Each triangle will have the
133/// `base_circle` point in common.
134/// Pushes `nsubdiv - 2` elements to `out`.
135#[cfg(feature = "dim3")]
136#[inline]
137pub fn push_filled_circle_indices(base_circle: u32, nsubdiv: u32, out: &mut Vec<[u32; DIM]>) {
138    for i in base_circle + 1..base_circle + nsubdiv - 1 {
139        out.push([base_circle, i, i + 1]);
140    }
141}
142
143/// Given four corner points, pushes to two counterclockwise triangles to `out`.
144///
145/// # Arguments:
146/// * `ul` - the up-left point.
147/// * `dl` - the down-left point.
148/// * `dr` - the down-right point.
149/// * `ur` - the up-right point.
150#[cfg(feature = "dim3")]
151#[inline]
152pub fn push_rectangle_indices(ul: u32, ur: u32, dl: u32, dr: u32, out: &mut Vec<[u32; DIM]>) {
153    out.push([ul, dl, dr]);
154    out.push([dr, ur, ul]);
155}
156
157/// Reverses the clockwising of a set of faces.
158#[cfg(feature = "dim3")]
159#[inline]
160pub fn reverse_clockwising(indices: &mut [[u32; DIM]]) {
161    indices.iter_mut().for_each(|idx| idx.swap(0, 1));
162}
163
164/// Pushes the index buffer of a closed loop.
165#[cfg(feature = "dim3")]
166#[inline]
167pub fn push_circle_outline_indices(indices: &mut Vec<[u32; 2]>, range: core::ops::Range<u32>) {
168    indices.extend((range.start..range.end - 1).map(|i| [i, i + 1]));
169    indices.push([range.end - 1, range.start]);
170}
171
172/// Pushes the index buffer of an open chain.
173#[cfg(feature = "dim3")]
174#[inline]
175pub fn push_open_circle_outline_indices(indices: &mut Vec<[u32; 2]>, range: core::ops::Range<u32>) {
176    indices.extend((range.start..range.end - 1).map(|i| [i, i + 1]));
177}
178
179/// Pushes to `out_vtx` a set of points forming an arc starting at `start`, ending at `end` with
180/// revolution center at `center`. The curve is approximated by pushing `nsubdivs` points.
181/// The `start` and `end` point are not pushed to `out_vtx`.
182///
183/// Also pushes to `out_idx` the appropriate index buffer to form the arc (including attaches to
184/// the `start` and `end` points).
185#[cfg(feature = "dim3")]
186pub fn push_arc_and_idx(
187    center: Point<Real>,
188    start: u32,
189    end: u32,
190    nsubdivs: u32,
191    out_vtx: &mut Vec<Point<Real>>,
192    out_idx: &mut Vec<[u32; 2]>,
193) {
194    let base = out_vtx.len() as u32;
195    push_arc(
196        center,
197        out_vtx[start as usize],
198        out_vtx[end as usize],
199        nsubdivs,
200        out_vtx,
201    );
202    push_arc_idx(start, base..base + nsubdivs - 1, end, out_idx);
203}
204
205/// Pushes to `out` a set of points forming an arc starting at `start`, ending at `end` with
206/// revolution center at `center`. The curve is approximated by pushing `nsubdivs` points.
207/// The `start` and `end` point are not pushed to `out`.
208pub fn push_arc(
209    center: Point<Real>,
210    start: Point<Real>,
211    end: Point<Real>,
212    nsubdivs: u32,
213    out: &mut Vec<Point<Real>>,
214) {
215    assert!(nsubdivs > 0);
216    if let (Some((start_dir, start_len)), Some((end_dir, end_len))) = (
217        na::Unit::try_new_and_get(start - center, 0.0),
218        na::Unit::try_new_and_get(end - center, 0.0),
219    ) {
220        let len_inc = (end_len - start_len) / nsubdivs as Real;
221
222        #[cfg(feature = "dim2")]
223        let rot = Some(na::UnitComplex::scaled_rotation_between_axis(
224            &start_dir,
225            &end_dir,
226            1.0 / nsubdivs as Real,
227        ));
228
229        #[cfg(feature = "dim3")]
230        let rot = na::UnitQuaternion::scaled_rotation_between_axis(
231            &start_dir,
232            &end_dir,
233            1.0 / nsubdivs as Real,
234        );
235
236        if let Some(rot) = rot {
237            let mut curr_dir = start_dir;
238            let mut curr_len = start_len;
239
240            for _ in 0..nsubdivs - 1 {
241                curr_dir = rot * curr_dir;
242                curr_len += len_inc;
243
244                out.push(center + *curr_dir * curr_len);
245            }
246        }
247    }
248}
249
250/// Pushes the index buffer for an arc between `start` and `end` and intermediate points in the
251/// range `arc`.
252#[cfg(feature = "dim3")]
253pub fn push_arc_idx(start: u32, arc: core::ops::Range<u32>, end: u32, out: &mut Vec<[u32; 2]>) {
254    if arc.is_empty() {
255        out.push([start, end]);
256    } else {
257        out.push([start, arc.start]);
258        for i in arc.start..arc.end - 1 {
259            out.push([i, i + 1])
260        }
261        out.push([arc.end - 1, end])
262    }
263}
264
265/// Applies a revolution, using the Y symmetry axis passing through the origin.
266#[cfg(feature = "dim3")]
267pub fn apply_revolution(
268    collapse_bottom: bool,
269    collapse_top: bool,
270    circle_ranges: &[core::ops::Range<u32>],
271    nsubdivs: u32,
272    out_vtx: &mut Vec<Point<Real>>, // Must be set to the half-profile.
273    out_idx: &mut Vec<[u32; 2]>,
274) {
275    use na::RealField;
276    let ang_increment = Real::two_pi() / (nsubdivs as Real);
277    let angles = [
278        ang_increment * (nsubdivs / 4) as Real,
279        ang_increment * (nsubdivs / 2) as Real,
280        ang_increment * ((3 * nsubdivs) / 4) as Real,
281    ];
282
283    let half_profile_len = out_vtx.len();
284
285    for k in 0..half_profile_len as u32 - 1 {
286        out_idx.push([k, k + 1]);
287    }
288
289    let mut range = 0..half_profile_len;
290
291    if collapse_bottom {
292        range.start += 1;
293    }
294    if collapse_top {
295        range.end -= 1;
296    }
297
298    // Push rotated profiles.
299    for angle in angles {
300        let base = out_vtx.len() as u32;
301        let rot = na::UnitQuaternion::new(Vector::y() * angle);
302
303        if collapse_bottom {
304            out_idx.push([0, base]);
305        }
306
307        for k in range.clone() {
308            out_vtx.push(rot * out_vtx[k]);
309        }
310
311        for k in 0..range.len() as u32 - 1 {
312            out_idx.push([base + k, base + k + 1]);
313        }
314
315        if collapse_top {
316            out_idx.push([base + range.len() as u32 - 1, half_profile_len as u32 - 1]);
317        }
318    }
319
320    // Push circles.
321    // TODO: right now, this duplicates some points, to simplify the index
322    //       buffer construction.
323    for circle_range in circle_ranges {
324        for i in circle_range.clone() {
325            let pt = out_vtx[i as usize];
326            let base = out_vtx.len() as u32;
327            push_circle(
328                pt.coords.xz().norm(),
329                nsubdivs,
330                ang_increment,
331                pt.y,
332                out_vtx,
333            );
334            push_circle_outline_indices(out_idx, base..base + nsubdivs)
335        }
336    }
337}