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