parry2d/transformation/
utils.rs

1//! Low-level utilities for mesh and geometry generation.
2//!
3//! This module provides primitive building blocks for constructing triangle meshes and other
4//! geometric structures. These functions are primarily used internally by Parry's shape-to-mesh
5//! conversion utilities, but are exposed for users who need fine-grained control over mesh
6//! generation.
7//!
8//! # Overview
9//!
10//! The utilities fall into several categories:
11//!
12//! ## Point Transformations
13//! - [`transform`] / [`transformed`] - Apply rigid transformations (rotation + translation)
14//! - [`scaled`] - Apply non-uniform scaling
15//!
16//! ## Vertex Generation
17//! - `push_circle` - Generate circle points in 3D (XZ plane)
18#![cfg_attr(
19    feature = "dim2",
20    doc = "- [`push_xy_arc`] - Generate arc points in 2D (XY plane)"
21)]
22//! - [`push_arc`] - Generate arc points between two endpoints
23//!
24//! ## Index Buffer Generation
25//! - `push_ring_indices` / `push_open_ring_indices` - Connect two circles into a tube
26//! - `push_rectangle_indices` - Generate two triangles forming a quad
27//! - `push_degenerate_top_ring_indices` - Connect circle to a single apex point
28//! - `push_filled_circle_indices` - Fill a circle with triangles (fan triangulation)
29//!
30//! ## Edge/Outline Generation
31//! - `push_circle_outline_indices` - Edge loop for a closed circle
32//! - `push_open_circle_outline_indices` - Edge chain (not closed)
33//! - `push_arc_idx` - Edge indices for an arc
34//!
35//! ## Advanced Operations
36//! - `reverse_clockwising` - Flip triangle winding order (reverse normals)
37//! - `apply_revolution` - Create surface of revolution from a profile curve
38//! - `push_arc_and_idx` - Generate arc geometry and indices together
39//!
40//! # Usage Patterns
41//!
42//! ## Building a Cylinder
43//!
44//! ```
45//! # #[cfg(all(feature = "dim3", feature = "f32"))]
46//! # {
47//! use parry3d::transformation::utils::{push_circle, push_ring_indices, push_filled_circle_indices};
48//! use parry3d::math::Point;
49//! use std::f32::consts::PI;
50//!
51//! let mut vertices = Vec::new();
52//! let mut indices = Vec::new();
53//!
54//! let radius = 2.0;
55//! let height = 10.0;
56//! let nsubdiv = 16;
57//! let dtheta = 2.0 * PI / nsubdiv as f32;
58//!
59//! // Create bottom and top circles
60//! push_circle(radius, nsubdiv, dtheta, 0.0, &mut vertices);      // Bottom at y=0
61//! push_circle(radius, nsubdiv, dtheta, height, &mut vertices);   // Top at y=height
62//!
63//! // Connect the circles to form the cylinder body
64//! push_ring_indices(0, nsubdiv, nsubdiv, &mut indices);
65//!
66//! // Cap the bottom
67//! push_filled_circle_indices(0, nsubdiv, &mut indices);
68//!
69//! // Cap the top
70//! push_filled_circle_indices(nsubdiv, nsubdiv, &mut indices);
71//!
72//! println!("Cylinder: {} vertices, {} triangles", vertices.len(), indices.len());
73//! # }
74//! ```
75//!
76//! ## Building a Cone
77//!
78//! ```
79//! # #[cfg(all(feature = "dim3", feature = "f32"))]
80//! # {
81//! use parry3d::transformation::utils::{push_circle, push_degenerate_top_ring_indices, push_filled_circle_indices};
82//! use parry3d::math::Point;
83//! use std::f32::consts::PI;
84//!
85//! let mut vertices = Vec::new();
86//! let mut indices = Vec::new();
87//!
88//! let radius = 3.0;
89//! let height = 5.0;
90//! let nsubdiv = 20;
91//! let dtheta = 2.0 * PI / nsubdiv as f32;
92//!
93//! // Create the base circle
94//! push_circle(radius, nsubdiv, dtheta, 0.0, &mut vertices);
95//!
96//! // Add apex point at the top
97//! vertices.push(Point::new(0.0, height, 0.0));
98//! let apex_idx = (vertices.len() - 1) as u32;
99//!
100//! // Connect base circle to apex
101//! push_degenerate_top_ring_indices(0, apex_idx, nsubdiv, &mut indices);
102//!
103//! // Cap the base
104//! push_filled_circle_indices(0, nsubdiv, &mut indices);
105//!
106//! println!("Cone: {} vertices, {} triangles", vertices.len(), indices.len());
107//! # }
108//! ```
109//!
110//! ## Transforming Existing Geometry
111//!
112//! ```
113//! # #[cfg(all(feature = "dim3", feature = "f32"))]
114//! # {
115//! use parry3d::transformation::utils::{transform, scaled};
116//! use parry3d::math::{Point, Vector, Isometry};
117//! use parry3d::na::{Translation3, UnitQuaternion};
118//! use std::f32::consts::PI;
119//!
120//! let mut points = vec![
121//!     Point::new(1.0, 0.0, 0.0),
122//!     Point::new(0.0, 1.0, 0.0),
123//!     Point::new(0.0, 0.0, 1.0),
124//! ];
125//!
126//! // First, scale non-uniformly
127//! let points = scaled(points, Vector::new(2.0, 1.0, 0.5));
128//!
129//! // Then rotate 45 degrees around Y axis
130//! let rotation = UnitQuaternion::from_axis_angle(&Vector::y_axis(), PI / 4.0);
131//! let translation = Translation3::new(10.0, 5.0, 0.0);
132//! let isometry = Isometry::from_parts(translation.into(), rotation);
133//!
134//! let final_points = parry3d::transformation::utils::transformed(points, isometry);
135//! # }
136//! ```
137//!
138//! # Design Philosophy
139//!
140//! These functions follow a "builder" pattern where:
141//! 1. Vertices are pushed to a `Vec<Point<Real>>`
142//! 2. Indices are pushed to a `Vec<[u32; 3]>` (triangles) or `Vec<[u32; 2]>` (edges)
143//! 3. Functions work with index offsets, allowing incremental construction
144//! 4. No memory is allocated except for the output buffers
145//!
146//! This design allows for efficient, flexible mesh construction with minimal overhead.
147//!
148//! # Performance Notes
149//!
150//! - All functions use simple loops without SIMD (suitable for small to medium subdivisions)
151//! - Index generation has O(n) complexity where n is the subdivision count
152//! - Point generation involves trigonometric functions (sin/cos) per subdivision
153//! - For high subdivision counts (>1000), consider caching generated geometry
154//!
155//! # See Also
156//!
157//! - `to_trimesh` module - High-level shape to mesh conversion (see individual shape `to_trimesh()` methods)
158//! - [`convex_hull`](crate::transformation::convex_hull) - Convex hull computation
159//! - [`TriMesh`](crate::shape::TriMesh) - Triangle mesh shape
160
161use crate::math::{Isometry, Point, Real, Vector};
162use crate::na::ComplexField;
163use alloc::vec::Vec;
164#[cfg(feature = "dim3")]
165use {crate::math::DIM, num::Zero};
166
167/// Applies in-place a transformation to an array of points.
168///
169/// This function modifies each point in the slice by applying the given isometry
170/// (rigid transformation consisting of rotation and translation).
171///
172/// # Arguments
173/// * `points` - A mutable slice of points to transform
174/// * `m` - The isometry (rigid transformation) to apply
175///
176/// # Example
177///
178/// ```
179/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
180/// use parry3d::transformation::utils::transform;
181/// use parry3d::math::{Point, Isometry, Vector};
182/// use parry3d::na::Translation3;
183///
184/// // Create some points
185/// let mut points = vec![
186///     Point::new(1.0, 0.0, 0.0),
187///     Point::new(0.0, 1.0, 0.0),
188///     Point::new(0.0, 0.0, 1.0),
189/// ];
190///
191/// // Create a translation
192/// let transform_iso = Isometry::from_parts(
193///     Translation3::new(10.0, 20.0, 30.0).into(),
194///     parry3d::na::UnitQuaternion::identity()
195/// );
196///
197/// // Apply the transformation in-place
198/// transform(&mut points, transform_iso);
199///
200/// assert_eq!(points[0], Point::new(11.0, 20.0, 30.0));
201/// assert_eq!(points[1], Point::new(10.0, 21.0, 30.0));
202/// assert_eq!(points[2], Point::new(10.0, 20.0, 31.0));
203/// # }
204/// ```
205pub fn transform(points: &mut [Point<Real>], m: Isometry<Real>) {
206    points.iter_mut().for_each(|p| *p = m * *p);
207}
208
209/// Returns the transformed version of a vector of points.
210///
211/// This function takes ownership of a vector of points, applies the given isometry
212/// transformation, and returns the transformed vector.
213///
214/// # Arguments
215/// * `points` - A vector of points to transform (ownership is taken)
216/// * `m` - The isometry (rigid transformation) to apply
217///
218/// # Returns
219/// A new vector containing the transformed points
220///
221/// # Example
222///
223/// ```
224/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
225/// use parry2d::transformation::utils::transformed;
226/// use parry2d::math::{Point, Isometry};
227/// use parry2d::na::{Translation2, UnitComplex};
228/// use std::f32::consts::PI;
229///
230/// let points = vec![
231///     Point::new(1.0, 0.0),
232///     Point::new(0.0, 1.0),
233/// ];
234///
235/// // Rotate 90 degrees counter-clockwise around origin
236/// let rotation = UnitComplex::new(PI / 2.0);
237/// let transform = Isometry::from_parts(Translation2::identity().into(), rotation);
238///
239/// let result = transformed(points, transform);
240///
241/// // Points are now rotated
242/// assert!((result[0].x - 0.0).abs() < 1e-6);
243/// assert!((result[0].y - 1.0).abs() < 1e-6);
244/// # }
245/// ```
246pub fn transformed(mut points: Vec<Point<Real>>, m: Isometry<Real>) -> Vec<Point<Real>> {
247    transform(&mut points, m);
248    points
249}
250
251/// Returns the scaled version of a vector of points.
252///
253/// This function takes ownership of a vector of points and applies a non-uniform
254/// scale factor to each component. Unlike [`transformed`], this is a non-rigid
255/// transformation that can stretch or compress points along different axes.
256///
257/// # Arguments
258/// * `points` - A vector of points to scale (ownership is taken)
259/// * `scale` - The scale factor for each axis
260///
261/// # Returns
262/// A new vector containing the scaled points
263///
264/// # Example
265///
266/// ```
267/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
268/// use parry3d::transformation::utils::scaled;
269/// use parry3d::math::{Point, Vector};
270///
271/// let points = vec![
272///     Point::new(1.0, 2.0, 3.0),
273///     Point::new(4.0, 5.0, 6.0),
274/// ];
275///
276/// // Scale x by 2, y by 3, z by 0.5
277/// let scale = Vector::new(2.0, 3.0, 0.5);
278/// let result = scaled(points, scale);
279///
280/// assert_eq!(result[0], Point::new(2.0, 6.0, 1.5));
281/// assert_eq!(result[1], Point::new(8.0, 15.0, 3.0));
282/// # }
283/// ```
284pub fn scaled(mut points: Vec<Point<Real>>, scale: Vector<Real>) -> Vec<Point<Real>> {
285    points
286        .iter_mut()
287        .for_each(|p| p.coords.component_mul_assign(&scale));
288    points
289}
290
291// TODO: remove that in favor of `push_xy_circle` ?
292/// Pushes a discretized counterclockwise circle to a buffer.
293///
294/// This function generates points along a circle in the XZ plane at a given Y coordinate.
295/// Points are generated counter-clockwise when viewed from above (positive Y direction).
296///
297/// # Arguments
298/// * `radius` - The radius of the circle
299/// * `nsubdiv` - Number of subdivisions (points to generate)
300/// * `dtheta` - Angle increment between consecutive points (in radians)
301/// * `y` - The Y coordinate of the circle plane
302/// * `out` - Output buffer where circle points will be pushed
303///
304/// # Example
305///
306/// ```
307/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
308/// use parry3d::transformation::utils::push_circle;
309/// use parry3d::math::Point;
310/// use std::f32::consts::PI;
311///
312/// let mut points = Vec::new();
313/// let radius = 5.0;
314/// let nsubdiv = 8; // Octagon
315/// let dtheta = 2.0 * PI / nsubdiv as f32;
316/// let y_level = 10.0;
317///
318/// push_circle(radius, nsubdiv, dtheta, y_level, &mut points);
319///
320/// assert_eq!(points.len(), 8);
321/// // All points are at Y = 10.0
322/// assert!(points.iter().all(|p| (p.y - 10.0).abs() < 1e-6));
323/// // First point is at (radius, y, 0)
324/// assert!((points[0].x - radius).abs() < 1e-6);
325/// assert!((points[0].z).abs() < 1e-6);
326/// # }
327/// ```
328#[cfg(feature = "dim3")]
329#[inline]
330pub fn push_circle(radius: Real, nsubdiv: u32, dtheta: Real, y: Real, out: &mut Vec<Point<Real>>) {
331    let mut curr_theta = Real::zero();
332
333    for _ in 0..nsubdiv {
334        out.push(Point::new(
335            ComplexField::cos(curr_theta) * radius,
336            y,
337            ComplexField::sin(curr_theta) * radius,
338        ));
339        curr_theta += dtheta;
340    }
341}
342
343/// Pushes a discretized counterclockwise arc to a buffer.
344///
345/// This function generates points along an arc in the XY plane (2D).
346/// The arc is contained on the plane spanned by the X and Y axes.
347/// Points are generated counter-clockwise starting from angle 0 (positive X axis).
348///
349/// # Arguments
350/// * `radius` - The radius of the arc
351/// * `nsubdiv` - Number of subdivisions (points to generate)
352/// * `dtheta` - Angle increment between consecutive points (in radians)
353/// * `out` - Output buffer where arc points will be pushed
354///
355/// # Example
356///
357/// ```
358/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
359/// use parry2d::transformation::utils::push_xy_arc;
360/// use parry2d::math::Point;
361/// use std::f32::consts::PI;
362///
363/// let mut points = Vec::new();
364/// let radius = 3.0;
365/// let nsubdiv = 4; // Quarter circle
366/// let dtheta = PI / 2.0 / (nsubdiv - 1) as f32;
367///
368/// push_xy_arc(radius, nsubdiv, dtheta, &mut points);
369///
370/// assert_eq!(points.len(), 4);
371/// // First point is approximately at (radius, 0)
372/// assert!((points[0].x - radius).abs() < 1e-6);
373/// assert!((points[0].y).abs() < 1e-6);
374/// # }
375/// ```
376#[inline]
377#[cfg(feature = "dim2")]
378pub fn push_xy_arc(radius: Real, nsubdiv: u32, dtheta: Real, out: &mut Vec<Point<Real>>) {
379    let mut curr_theta: Real = 0.0;
380
381    for _ in 0..nsubdiv {
382        let mut pt_coords = Vector::zeros();
383
384        pt_coords[0] = ComplexField::cos(curr_theta) * radius;
385        pt_coords[1] = ComplexField::sin(curr_theta) * radius;
386        out.push(Point::from(pt_coords));
387
388        curr_theta += dtheta;
389    }
390}
391
392/// Creates the triangle faces connecting two circles with the same discretization.
393///
394/// This function generates triangle indices to form a closed ring (tube segment) between
395/// two parallel circles. The circles must have the same number of points. The ring wraps
396/// around completely, connecting the last points back to the first.
397///
398/// # Arguments
399/// * `base_lower_circle` - Index of the first point of the lower circle
400/// * `base_upper_circle` - Index of the first point of the upper circle
401/// * `nsubdiv` - Number of points in each circle
402/// * `out` - Output buffer where triangle indices will be pushed
403///
404/// # Example
405///
406/// ```
407/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
408/// use parry3d::transformation::utils::{push_circle, push_ring_indices};
409/// use parry3d::math::Point;
410/// use std::f32::consts::PI;
411///
412/// let mut vertices = Vec::new();
413/// let mut indices = Vec::new();
414///
415/// let nsubdiv = 8;
416/// let dtheta = 2.0 * PI / nsubdiv as f32;
417///
418/// // Create two circles at different heights
419/// push_circle(2.0, nsubdiv, dtheta, 0.0, &mut vertices);  // Lower circle
420/// push_circle(2.0, nsubdiv, dtheta, 5.0, &mut vertices);  // Upper circle
421///
422/// // Connect them with triangles
423/// push_ring_indices(0, nsubdiv, nsubdiv, &mut indices);
424///
425/// // A ring with n subdivisions creates 2*n triangles
426/// assert_eq!(indices.len(), 2 * nsubdiv as usize);
427/// # }
428/// ```
429#[cfg(feature = "dim3")]
430#[inline]
431pub fn push_ring_indices(
432    base_lower_circle: u32,
433    base_upper_circle: u32,
434    nsubdiv: u32,
435    out: &mut Vec<[u32; DIM]>,
436) {
437    push_open_ring_indices(base_lower_circle, base_upper_circle, nsubdiv, out);
438
439    // adjust the last two triangles
440    push_rectangle_indices(
441        base_upper_circle,
442        base_upper_circle + nsubdiv - 1,
443        base_lower_circle,
444        base_lower_circle + nsubdiv - 1,
445        out,
446    );
447}
448
449/// Creates the triangle faces connecting two circles, leaving the ring open.
450///
451/// This is similar to `push_ring_indices`, but doesn't close the ring. The connection
452/// between the last point and the first point is not made, leaving a gap. This is useful
453/// for creating open cylinders or partial tubes.
454///
455/// # Arguments
456/// * `base_lower_circle` - Index of the first point of the lower circle
457/// * `base_upper_circle` - Index of the first point of the upper circle
458/// * `nsubdiv` - Number of points in each circle
459/// * `out` - Output buffer where triangle indices will be pushed
460///
461/// # Panics
462/// Panics if `nsubdiv` is 0.
463///
464/// # Example
465///
466/// ```
467/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
468/// use parry3d::transformation::utils::{push_circle, push_open_ring_indices};
469/// use parry3d::math::Point;
470/// use std::f32::consts::PI;
471///
472/// let mut vertices = Vec::new();
473/// let mut indices = Vec::new();
474///
475/// let nsubdiv = 8;
476/// let dtheta = 2.0 * PI / nsubdiv as f32;
477///
478/// // Create two circles
479/// push_circle(2.0, nsubdiv, dtheta, 0.0, &mut vertices);
480/// push_circle(2.0, nsubdiv, dtheta, 5.0, &mut vertices);
481///
482/// // Connect them without closing the ring
483/// push_open_ring_indices(0, nsubdiv, nsubdiv, &mut indices);
484///
485/// // Open ring has 2 fewer triangles than closed ring
486/// assert_eq!(indices.len(), 2 * (nsubdiv - 1) as usize);
487/// # }
488/// ```
489#[cfg(feature = "dim3")]
490#[inline]
491pub fn push_open_ring_indices(
492    base_lower_circle: u32,
493    base_upper_circle: u32,
494    nsubdiv: u32,
495    out: &mut Vec<[u32; DIM]>,
496) {
497    assert!(nsubdiv > 0);
498
499    for i in 0..nsubdiv - 1 {
500        let bli = base_lower_circle + i;
501        let bui = base_upper_circle + i;
502        push_rectangle_indices(bui + 1, bui, bli + 1, bli, out);
503    }
504}
505
506/// Creates the faces from a circle and a point that is shared by all triangle.
507#[cfg(feature = "dim3")]
508#[inline]
509pub fn push_degenerate_top_ring_indices(
510    base_circle: u32,
511    point: u32,
512    nsubdiv: u32,
513    out: &mut Vec<[u32; DIM]>,
514) {
515    push_degenerate_open_top_ring_indices(base_circle, point, nsubdiv, out);
516
517    out.push([base_circle + nsubdiv - 1, point, base_circle]);
518}
519
520/// Creates the faces from a circle and a point that is shared by all triangle.
521#[cfg(feature = "dim3")]
522#[inline]
523pub fn push_degenerate_open_top_ring_indices(
524    base_circle: u32,
525    point: u32,
526    nsubdiv: u32,
527    out: &mut Vec<[u32; DIM]>,
528) {
529    assert!(nsubdiv > 0);
530
531    for i in 0..nsubdiv - 1 {
532        out.push([base_circle + i, point, base_circle + i + 1]);
533    }
534}
535
536/// Pushes indices so that a circle is filled with triangles. Each triangle will have the
537/// `base_circle` point in common.
538/// Pushes `nsubdiv - 2` elements to `out`.
539#[cfg(feature = "dim3")]
540#[inline]
541pub fn push_filled_circle_indices(base_circle: u32, nsubdiv: u32, out: &mut Vec<[u32; DIM]>) {
542    for i in base_circle + 1..base_circle + nsubdiv - 1 {
543        out.push([base_circle, i, i + 1]);
544    }
545}
546
547/// Pushes two triangles forming a rectangle to the index buffer.
548///
549/// Given four corner point indices, this function creates two counter-clockwise triangles
550/// that form a rectangle (quad). The winding order ensures the normal points in the
551/// consistent direction based on the right-hand rule.
552///
553/// # Arguments
554/// * `ul` - Index of the upper-left point
555/// * `ur` - Index of the upper-right point
556/// * `dl` - Index of the down-left point
557/// * `dr` - Index of the down-right point
558/// * `out` - Output buffer where triangle indices will be pushed
559///
560/// # Example
561///
562/// ```
563/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
564/// use parry3d::transformation::utils::push_rectangle_indices;
565///
566/// let mut indices = Vec::new();
567///
568/// // Create a quad from points 0, 1, 2, 3
569/// // Layout:  0 --- 1
570/// //          |     |
571/// //          2 --- 3
572/// push_rectangle_indices(0, 1, 2, 3, &mut indices);
573///
574/// assert_eq!(indices.len(), 2); // Two triangles
575/// assert_eq!(indices[0], [0, 2, 3]); // First triangle
576/// assert_eq!(indices[1], [3, 1, 0]); // Second triangle
577/// # }
578/// ```
579#[cfg(feature = "dim3")]
580#[inline]
581pub fn push_rectangle_indices(ul: u32, ur: u32, dl: u32, dr: u32, out: &mut Vec<[u32; DIM]>) {
582    out.push([ul, dl, dr]);
583    out.push([dr, ur, ul]);
584}
585
586/// Reverses the winding order of triangle faces.
587///
588/// This function flips the winding order of triangles from counter-clockwise to clockwise
589/// or vice versa. This effectively flips the direction of face normals, which is useful
590/// when you need to invert a mesh or correct winding order issues.
591///
592/// # Arguments
593/// * `indices` - Mutable slice of triangle indices to reverse
594///
595/// # Example
596///
597/// ```
598/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
599/// use parry3d::transformation::utils::reverse_clockwising;
600///
601/// let mut triangles = vec![
602///     [0, 1, 2],
603///     [2, 3, 0],
604/// ];
605///
606/// // Reverse the winding order
607/// reverse_clockwising(&mut triangles);
608///
609/// // First two vertices of each triangle are swapped
610/// assert_eq!(triangles[0], [1, 0, 2]);
611/// assert_eq!(triangles[1], [3, 2, 0]);
612/// # }
613/// ```
614#[cfg(feature = "dim3")]
615#[inline]
616pub fn reverse_clockwising(indices: &mut [[u32; DIM]]) {
617    indices.iter_mut().for_each(|idx| idx.swap(0, 1));
618}
619
620/// Pushes the index buffer of a closed loop.
621#[cfg(feature = "dim3")]
622#[inline]
623pub fn push_circle_outline_indices(indices: &mut Vec<[u32; 2]>, range: core::ops::Range<u32>) {
624    indices.extend((range.start..range.end - 1).map(|i| [i, i + 1]));
625    indices.push([range.end - 1, range.start]);
626}
627
628/// Pushes the index buffer of an open chain.
629#[cfg(feature = "dim3")]
630#[inline]
631pub fn push_open_circle_outline_indices(indices: &mut Vec<[u32; 2]>, range: core::ops::Range<u32>) {
632    indices.extend((range.start..range.end - 1).map(|i| [i, i + 1]));
633}
634
635/// Pushes to `out_vtx` a set of points forming an arc starting at `start`, ending at `end` with
636/// revolution center at `center`. The curve is approximated by pushing `nsubdivs` points.
637/// The `start` and `end` point are not pushed to `out_vtx`.
638///
639/// Also pushes to `out_idx` the appropriate index buffer to form the arc (including attaches to
640/// the `start` and `end` points).
641#[cfg(feature = "dim3")]
642pub fn push_arc_and_idx(
643    center: Point<Real>,
644    start: u32,
645    end: u32,
646    nsubdivs: u32,
647    out_vtx: &mut Vec<Point<Real>>,
648    out_idx: &mut Vec<[u32; 2]>,
649) {
650    let base = out_vtx.len() as u32;
651    push_arc(
652        center,
653        out_vtx[start as usize],
654        out_vtx[end as usize],
655        nsubdivs,
656        out_vtx,
657    );
658    push_arc_idx(start, base..base + nsubdivs - 1, end, out_idx);
659}
660
661/// Pushes points forming an arc between two points around a center.
662///
663/// This function generates intermediate points along a circular arc from `start` to `end`,
664/// rotating around `center`. The arc is approximated by `nsubdivs` points. The `start` and
665/// `end` points themselves are NOT added to the output buffer - only intermediate points.
666///
667/// The function interpolates both the angle and the radius, so it can handle arcs where
668/// the start and end points are at different distances from the center (spiral-like paths).
669///
670/// # Arguments
671/// * `center` - The center point of rotation
672/// * `start` - Starting point of the arc (not included in output)
673/// * `end` - Ending point of the arc (not included in output)
674/// * `nsubdivs` - Number of intermediate points to generate
675/// * `out` - Output buffer where arc points will be pushed
676///
677/// # Panics
678/// Panics if `nsubdivs` is 0.
679///
680/// # Example
681///
682/// ```
683/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
684/// use parry2d::transformation::utils::push_arc;
685/// use parry2d::math::Point;
686///
687/// let mut points = Vec::new();
688/// let center = Point::new(0.0, 0.0);
689/// let start = Point::new(5.0, 0.0);  // 5 units to the right
690/// let end = Point::new(0.0, 5.0);    // 5 units up (90 degree arc)
691///
692/// // Generate 3 intermediate points
693/// push_arc(center, start, end, 3, &mut points);
694///
695/// // Should have 2 intermediate points (nsubdivs - 1)
696/// assert_eq!(points.len(), 2);
697/// # }
698/// ```
699pub fn push_arc(
700    center: Point<Real>,
701    start: Point<Real>,
702    end: Point<Real>,
703    nsubdivs: u32,
704    out: &mut Vec<Point<Real>>,
705) {
706    assert!(nsubdivs > 0);
707    if let (Some((start_dir, start_len)), Some((end_dir, end_len))) = (
708        na::Unit::try_new_and_get(start - center, 0.0),
709        na::Unit::try_new_and_get(end - center, 0.0),
710    ) {
711        let len_inc = (end_len - start_len) / nsubdivs as Real;
712
713        #[cfg(feature = "dim2")]
714        let rot = Some(na::UnitComplex::scaled_rotation_between_axis(
715            &start_dir,
716            &end_dir,
717            1.0 / nsubdivs as Real,
718        ));
719
720        #[cfg(feature = "dim3")]
721        let rot = na::UnitQuaternion::scaled_rotation_between_axis(
722            &start_dir,
723            &end_dir,
724            1.0 / nsubdivs as Real,
725        );
726
727        if let Some(rot) = rot {
728            let mut curr_dir = start_dir;
729            let mut curr_len = start_len;
730
731            for _ in 0..nsubdivs - 1 {
732                curr_dir = rot * curr_dir;
733                curr_len += len_inc;
734
735                out.push(center + *curr_dir * curr_len);
736            }
737        }
738    }
739}
740
741/// Pushes the index buffer for an arc between `start` and `end` and intermediate points in the
742/// range `arc`.
743#[cfg(feature = "dim3")]
744pub fn push_arc_idx(start: u32, arc: core::ops::Range<u32>, end: u32, out: &mut Vec<[u32; 2]>) {
745    if arc.is_empty() {
746        out.push([start, end]);
747    } else {
748        out.push([start, arc.start]);
749        for i in arc.start..arc.end - 1 {
750            out.push([i, i + 1])
751        }
752        out.push([arc.end - 1, end])
753    }
754}
755
756/// Applies a revolution, using the Y symmetry axis passing through the origin.
757#[cfg(feature = "dim3")]
758pub fn apply_revolution(
759    collapse_bottom: bool,
760    collapse_top: bool,
761    circle_ranges: &[core::ops::Range<u32>],
762    nsubdivs: u32,
763    out_vtx: &mut Vec<Point<Real>>, // Must be set to the half-profile.
764    out_idx: &mut Vec<[u32; 2]>,
765) {
766    use na::RealField;
767    let ang_increment = Real::two_pi() / (nsubdivs as Real);
768    let angles = [
769        ang_increment * (nsubdivs / 4) as Real,
770        ang_increment * (nsubdivs / 2) as Real,
771        ang_increment * ((3 * nsubdivs) / 4) as Real,
772    ];
773
774    let half_profile_len = out_vtx.len();
775
776    for k in 0..half_profile_len as u32 - 1 {
777        out_idx.push([k, k + 1]);
778    }
779
780    let mut range = 0..half_profile_len;
781
782    if collapse_bottom {
783        range.start += 1;
784    }
785    if collapse_top {
786        range.end -= 1;
787    }
788
789    // Push rotated profiles.
790    for angle in angles {
791        let base = out_vtx.len() as u32;
792        let rot = na::UnitQuaternion::new(Vector::y() * angle);
793
794        if collapse_bottom {
795            out_idx.push([0, base]);
796        }
797
798        for k in range.clone() {
799            out_vtx.push(rot * out_vtx[k]);
800        }
801
802        for k in 0..range.len() as u32 - 1 {
803            out_idx.push([base + k, base + k + 1]);
804        }
805
806        if collapse_top {
807            out_idx.push([base + range.len() as u32 - 1, half_profile_len as u32 - 1]);
808        }
809    }
810
811    // Push circles.
812    // TODO: right now, this duplicates some points, to simplify the index
813    //       buffer construction.
814    for circle_range in circle_ranges {
815        for i in circle_range.clone() {
816            let pt = out_vtx[i as usize];
817            let base = out_vtx.len() as u32;
818            push_circle(
819                pt.coords.xz().norm(),
820                nsubdivs,
821                ang_increment,
822                pt.y,
823                out_vtx,
824            );
825            push_circle_outline_indices(out_idx, base..base + nsubdivs)
826        }
827    }
828}