Files
MetaCrate/crates/libremetaverse-prim-mesher/src/prim_mesher.rs
Chili Palmer a18b09d928
All checks were successful
Imaging and meshing gate / native (push) Successful in 2m40s
JPEG 2000 feature / linux (push) Successful in 1m35s
Native Rust workspace compile / compile (push) Successful in 1m51s
Skia feature / linux (push) Successful in 31m16s
Fix Rust 1.97 imaging gate lints
2026-08-09 13:05:21 +00:00

2013 lines
68 KiB
Rust

//! Native port of `PrimMesher.cs` profile and extrusion geometry.
#![allow(clippy::cast_possible_truncation)] // Reference index and step calculations narrow explicitly.
#![allow(clippy::cast_precision_loss)] // C# geometry converts integer sides and steps to Single.
#![allow(clippy::cast_sign_loss)] // Bounds checks precede C# float-to-index conversions.
#![allow(clippy::approx_constant)] // Decimal compatibility constants are intentional.
#![allow(clippy::float_cmp)] // Exact parameter branches are part of the golden algorithm.
#![allow(clippy::inconsistent_struct_constructor)] // Field order is fixed by the public API map.
#![allow(clippy::inherent_to_string)] // The mapped C# methods are named ToString.
#![allow(clippy::inherent_to_string_shadow_display)] // Coord also needs Display for raw output.
#![allow(clippy::many_single_char_names)] // Vector and UV formulas follow the reference notation.
#![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the compatibility mapping.
#![allow(clippy::must_use_candidate)] // Attributes are not part of the mapped C# surface.
#![allow(clippy::needless_pass_by_value)] // Owned value parameters preserve mapped signatures.
#![allow(clippy::should_implement_trait)] // Operator entry points have fixed generated names.
#![allow(clippy::similar_names)] // Profile start/stop and step names mirror the reference.
#![allow(clippy::struct_excessive_bools)] // PrimMesh exposes the reference mode flags.
#![allow(clippy::struct_field_names)] // Angle.angle is the corresponding C# field.
#![allow(clippy::too_many_arguments)] // Constructor and extrusion helpers mirror fixed APIs.
#![allow(clippy::too_many_lines)] // The extrusion sequence remains reviewable against C#.
use crate::{Error, PathType, VertexIndexer};
use std::f32::consts::{PI, TAU};
use std::fmt;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::PathBuf;
const MAG_THRESHOLD: f32 = 0.000_000_1;
const MAX_PROFILE_VERTICES: usize = 65_536;
const MAX_PATH_NODES: usize = 65_536;
const MAX_MESH_VERTICES: usize = 16_777_216;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Coord {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Coord {
pub fn new(x: f32, y: f32, z: f32) -> Result<Self, Error> {
Ok(Self { x, y, z })
}
pub fn length(&self) -> Result<f32, Error> {
Ok((self.x * self.x + self.y * self.y + self.z * self.z).sqrt())
}
pub fn invert(&mut self) -> Result<Self, Error> {
self.x = -self.x;
self.y = -self.y;
self.z = -self.z;
Ok(*self)
}
pub fn normalize(&mut self) -> Result<Self, Error> {
let mag = self.length()?;
if mag > MAG_THRESHOLD {
let inverse = 1.0 / mag;
self.x *= inverse;
self.y *= inverse;
self.z *= inverse;
} else {
self.x = 0.0;
self.y = 0.0;
self.z = 0.0;
}
Ok(*self)
}
pub fn cross(c1: Self, c2: Self) -> Result<Self, Error> {
Ok(Self::cross_raw(c1, c2))
}
fn cross_raw(c1: Self, c2: Self) -> Self {
Self {
x: c1.y * c2.z - c2.y * c1.z,
y: c1.z * c2.x - c2.z * c1.x,
z: c1.x * c2.y - c2.x * c1.y,
}
}
pub fn add(v: Self, a: Self) -> Self {
Self {
x: v.x + a.x,
y: v.y + a.y,
z: v.z + a.z,
}
}
pub fn mul_with_coord_coord(v: Self, m: Self) -> Self {
Self {
x: v.x * m.x,
y: v.y * m.y,
z: v.z * m.z,
}
}
pub fn mul_with_coord_quat(v: Self, q: Quat) -> Self {
Self {
x: q.w * q.w * v.x + 2.0 * q.y * q.w * v.z - 2.0 * q.z * q.w * v.y
+ q.x * q.x * v.x
+ 2.0 * q.y * q.x * v.y
+ 2.0 * q.z * q.x * v.z
- q.z * q.z * v.x
- q.y * q.y * v.x,
y: 2.0 * q.x * q.y * v.x
+ q.y * q.y * v.y
+ 2.0 * q.z * q.y * v.z
+ 2.0 * q.w * q.z * v.x
- q.z * q.z * v.y
+ q.w * q.w * v.y
- 2.0 * q.x * q.w * v.z
- q.x * q.x * v.y,
z: 2.0 * q.x * q.z * v.x + 2.0 * q.y * q.z * v.y + q.z * q.z * v.z
- 2.0 * q.w * q.y * v.x
- q.y * q.y * v.z
+ 2.0 * q.w * q.x * v.y
- q.x * q.x * v.z
+ q.w * q.w * v.z,
}
}
}
impl fmt::Display for Coord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {}", self.x, self.y, self.z)
}
}
impl Coord {
pub fn to_string(&self) -> String {
format!("{self}")
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Quat {
pub w: f32,
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Quat {
pub fn new_with_single_single_single_single(
x: f32,
y: f32,
z: f32,
w: f32,
) -> Result<Self, Error> {
Ok(Self { x, y, z, w })
}
pub fn new_with_coord_single(mut axis: Coord, mut angle: f32) -> Result<Self, Error> {
axis.normalize()?;
angle *= 0.5;
let mut value = Self {
x: axis.x * angle.sin(),
y: axis.y * angle.sin(),
z: axis.z * angle.sin(),
w: angle.cos(),
};
value.normalize()?;
Ok(value)
}
pub fn length(&self) -> Result<f32, Error> {
Ok((self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w).sqrt())
}
pub fn normalize(&mut self) -> Result<Self, Error> {
let mag = self.length()?;
if mag > MAG_THRESHOLD {
let i = 1.0 / mag;
self.x *= i;
self.y *= i;
self.z *= i;
self.w *= i;
} else {
self.x = 0.0;
self.y = 0.0;
self.z = 0.0;
self.w = 1.0;
}
Ok(*self)
}
pub fn mul(q1: Self, q2: Self) -> Self {
Self {
x: q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y,
y: q1.w * q2.y - q1.x * q2.z + q1.y * q2.w + q1.z * q2.x,
z: q1.w * q2.z + q1.x * q2.y - q1.y * q2.x + q1.z * q2.w,
w: q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z,
}
}
pub fn to_string(&self) -> String {
format!(
"< X: {}, Y: {}, Z: {}, W: {}>",
self.x, self.y, self.z, self.w
)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct UVCoord {
pub u: f32,
pub v: f32,
}
impl UVCoord {
pub fn new(u: f32, v: f32) -> Result<Self, Error> {
Ok(Self { u, v })
}
pub fn flip(&mut self) -> Result<Self, Error> {
self.u = 1.0 - self.u;
self.v = 1.0 - self.v;
Ok(*self)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Face {
pub n1: i32,
pub n2: i32,
pub n3: i32,
pub prim_face: i32,
pub uv1: i32,
pub uv2: i32,
pub uv3: i32,
pub v1: i32,
pub v2: i32,
pub v3: i32,
}
impl Face {
fn vertices(v1: i32, v2: i32, v3: i32) -> Self {
Self {
v1,
v2,
v3,
..Self::default()
}
}
pub fn new_with_int32_int32_int32(v1: i32, v2: i32, v3: i32) -> Result<Self, Error> {
Ok(Self::vertices(v1, v2, v3))
}
pub fn new_with_int32_int32_int32_int32_int32_int32(
v1: i32,
v2: i32,
v3: i32,
n1: i32,
n2: i32,
n3: i32,
) -> Result<Self, Error> {
Ok(Self {
v1,
v2,
v3,
n1,
n2,
n3,
..Self::default()
})
}
pub fn surface_normal(&mut self, coords: Vec<Coord>) -> Result<Coord, Error> {
surface_normal_for(&coords, *self)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ViewerFace {
pub coord_index1: i32,
pub coord_index2: i32,
pub coord_index3: i32,
pub n1: Coord,
pub n2: Coord,
pub n3: Coord,
pub prim_face_number: i32,
pub uv1: UVCoord,
pub uv2: UVCoord,
pub uv3: UVCoord,
pub v1: Coord,
pub v2: Coord,
pub v3: Coord,
}
impl ViewerFace {
pub fn new(prim_face_number: i32) -> Result<Self, Error> {
Ok(Self {
prim_face_number,
coord_index1: -1,
coord_index2: -1,
coord_index3: -1,
..Self::default()
})
}
pub fn scale(&mut self, x: f32, y: f32, z: f32) -> Result<(), Error> {
let m = Coord { x, y, z };
self.v1 = Coord::mul_with_coord_coord(self.v1, m);
self.v2 = Coord::mul_with_coord_coord(self.v2, m);
self.v3 = Coord::mul_with_coord_coord(self.v3, m);
Ok(())
}
pub fn add_pos(&mut self, x: f32, y: f32, z: f32) -> Result<(), Error> {
let p = Coord { x, y, z };
self.v1 = Coord::add(self.v1, p);
self.v2 = Coord::add(self.v2, p);
self.v3 = Coord::add(self.v3, p);
Ok(())
}
pub fn add_rot(&mut self, q: Quat) -> Result<(), Error> {
self.v1 = Coord::mul_with_coord_quat(self.v1, q);
self.v2 = Coord::mul_with_coord_quat(self.v2, q);
self.v3 = Coord::mul_with_coord_quat(self.v3, q);
self.n1 = Coord::mul_with_coord_quat(self.n1, q);
self.n2 = Coord::mul_with_coord_quat(self.n2, q);
self.n3 = Coord::mul_with_coord_quat(self.n3, q);
Ok(())
}
pub fn calc_surface_normal(&mut self) -> Result<(), Error> {
let e1 = Coord {
x: self.v2.x - self.v1.x,
y: self.v2.y - self.v1.y,
z: self.v2.z - self.v1.z,
};
let e2 = Coord {
x: self.v3.x - self.v1.x,
y: self.v3.y - self.v1.y,
z: self.v3.z - self.v1.z,
};
let mut n = Coord::cross_raw(e1, e2);
n.normalize()?;
self.n1 = n;
self.n2 = n;
self.n3 = n;
Ok(())
}
}
#[derive(Clone, Copy, Debug, Default)]
struct Angle {
angle: f32,
x: f32,
y: f32,
}
fn make_angles(sides: i32, start: f32, stop: f32) -> Result<(Vec<Angle>, Vec<Coord>), Error> {
if sides < 1 || stop <= start || !start.is_finite() || !stop.is_finite() {
return Err(Error::Argument);
}
let sides_usize = usize::try_from(sides).map_err(|_| Error::Argument)?;
if sides_usize > MAX_PROFILE_VERTICES {
return Err(Error::Argument);
}
if matches!(sides, 3 | 4 | 24) {
return make_special_angles(sides, start, stop);
}
let step = TAU / (sides as f32);
let first = (start / step).floor() as i32;
let last = ((stop / step).ceil() as i32).min(sides);
let mut angles =
Vec::with_capacity(usize::try_from(last - first + 1).map_err(|_| Error::Argument)?);
for index in first..=last {
let a = (index as f32) * step;
angles.push(Angle {
angle: a,
x: a.cos(),
y: a.sin(),
});
}
if start > angles.first().map_or(start, |a| a.angle) {
let first = angles.first().copied().ok_or(Error::Argument)?;
let second = angles.get(1).copied().ok_or(Error::Argument)?;
angles[0] = intersect_angle(first, second, start);
}
if angles.len() > 1 {
let end = angles.len() - 1;
if stop < angles[end].angle {
angles[end] = intersect_angle(angles[end - 1], angles[end], stop);
}
}
let normals = if sides < 5 {
angles
.iter()
.map(|a| {
let previous = a.angle - step * 0.5;
let mut n = Coord {
x: previous.cos(),
y: previous.sin(),
z: 0.0,
};
let _ = n.normalize();
n
})
.collect()
} else {
Vec::new()
};
Ok((angles, normals))
}
fn make_special_angles(
sides: i32,
start: f32,
stop: f32,
) -> Result<(Vec<Angle>, Vec<Coord>), Error> {
let start = start / TAU;
let stop = stop / TAU;
let source: Vec<Angle> = (0..=sides)
.map(|index| {
let angle = (index as f32) / (sides as f32);
Angle {
angle,
x: (angle * TAU).cos(),
y: (angle * TAU).sin(),
}
})
.collect();
let start_index = (start * sides as f32) as usize;
let mut end_index = source.len() - 1;
if stop < 1.0 {
end_index = (stop * sides as f32) as usize + 1;
}
if end_index == start_index {
end_index += 1;
}
let mut angles = source
.get(start_index..=end_index)
.ok_or(Error::Argument)?
.to_vec();
let normal_source: Vec<Coord> = if sides < 5 {
(0..=sides)
.map(|index| {
let angle = ((index as f32) + 0.5) * TAU / sides as f32;
Coord {
x: angle.cos(),
y: angle.sin(),
z: 0.0,
}
})
.collect()
} else {
Vec::new()
};
let normals = if sides < 5 {
normal_source[start_index..=end_index].to_vec()
} else {
Vec::new()
};
if start > 0.0 {
let first = angles[0];
let second = angles[1];
angles[0] = interpolate_angle(start, first, second);
}
if stop < 1.0 {
let last = angles.len() - 1;
angles[last] = interpolate_angle(stop, angles[last - 1], angles[last]);
}
Ok((angles, normals))
}
fn interpolate_angle(point: f32, first: Angle, second: Angle) -> Angle {
let ratio = (point - first.angle) / (second.angle - first.angle);
Angle {
angle: point,
x: first.x + ratio * (second.x - first.x),
y: first.y + ratio * (second.y - first.y),
}
}
fn intersect_angle(p1: Angle, p2: Angle, angle: f32) -> Angle {
let x3 = 0.0_f64;
let y3 = 0.0_f64;
let x4 = f64::from(angle.cos());
let y4 = f64::from(angle.sin());
let x1 = f64::from(p1.x);
let y1 = f64::from(p1.y);
let x2 = f64::from(p2.x);
let y2 = f64::from(p2.y);
let denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if denominator == 0.0 {
return Angle {
angle,
x: p1.x,
y: p1.y,
};
}
let numerator = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
let u = numerator / denominator;
Angle {
angle,
x: (x1 + u * (x2 - x1)) as f32,
y: (y1 + u * (y2 - y1)) as f32,
}
}
fn idx(value: i32, len: usize) -> Result<usize, Error> {
let value = usize::try_from(value).map_err(|_| Error::IndexOutOfRange)?;
if value >= len {
Err(Error::IndexOutOfRange)
} else {
Ok(value)
}
}
fn surface_normal_for(coords: &[Coord], face: Face) -> Result<Coord, Error> {
let c1 = coords[idx(face.v1, coords.len())?];
let c2 = coords[idx(face.v2, coords.len())?];
let c3 = coords[idx(face.v3, coords.len())?];
let e1 = Coord {
x: c2.x - c1.x,
y: c2.y - c1.y,
z: c2.z - c1.z,
};
let e2 = Coord {
x: c3.x - c1.x,
y: c3.y - c1.y,
z: c3.z - c1.z,
};
let mut n = Coord::cross_raw(e1, e2);
n.normalize()?;
Ok(n)
}
#[derive(Clone, Debug, Default)]
pub struct Profile {
pub bottom_face_number: i32,
pub calc_vertex_normals: bool,
pub coords: Vec<Coord>,
pub cut1_coord_indices: Vec<i32>,
pub cut2_coord_indices: Vec<i32>,
pub cut_normal1: Coord,
pub cut_normal2: Coord,
pub error_message: String,
pub face_normal: Coord,
pub face_numbers: Vec<i32>,
pub face_u_vs: Vec<UVCoord>,
pub faces: Vec<Face>,
pub hollow_coord_indices: Vec<i32>,
pub hollow_face_number: i32,
pub num_hollow_verts: i32,
pub num_outer_verts: i32,
pub num_prim_faces: i32,
pub outer_coord_indices: Vec<i32>,
pub outer_face_number: i32,
pub us: Vec<f32>,
pub vertex_normals: Vec<Coord>,
}
impl Profile {
pub fn new_with_constructor() -> Result<Self, Error> {
Ok(Self {
face_normal: Coord {
x: 0.0,
y: 0.0,
z: 1.0,
},
hollow_face_number: -1,
outer_face_number: -1,
..Self::default()
})
}
#[allow(clippy::too_many_arguments)]
pub fn new_with_int32_single_single_single_int32_boolean_boolean(
sides: i32,
profile_start: f32,
profile_end: f32,
hollow: f32,
hollow_sides: i32,
create_faces: bool,
calc_vertex_normals: bool,
) -> Result<Self, Error> {
let mut value = Self::new_with_constructor()?;
value.calc_vertex_normals = calc_vertex_normals;
let (angles, outer_normals) = make_angles(sides, profile_start * TAU, profile_end * TAU)?;
let has_hollow = hollow > 0.0;
let has_cut = profile_start > 0.0 || profile_end < 1.0;
let simple = sides < 5 && !has_hollow && !has_cut;
let (x_scale, y_scale) = if sides == 4 {
(0.707_107, 0.707_107)
} else {
(0.5, 0.5)
};
value.num_outer_verts = i32::try_from(angles.len()).map_err(|_| Error::Argument)?;
let hollow_angles = if has_hollow {
if hollow_sides == sides {
angles.clone()
} else {
make_angles(hollow_sides, profile_start * TAU, profile_end * TAU)?.0
}
} else {
Vec::new()
};
value.num_hollow_verts = i32::try_from(hollow_angles.len()).map_err(|_| Error::Argument)?;
let total = angles
.len()
.checked_add(hollow_angles.len())
.and_then(|n| n.checked_add(1))
.ok_or(Error::Argument)?;
if total > MAX_PROFILE_VERTICES {
return Err(Error::Argument);
}
if !has_hollow && !simple {
value.coords.push(Coord::default());
if calc_vertex_normals {
value.vertex_normals.push(value.face_normal);
}
value.us.push(0.0);
}
for (i, angle) in angles.iter().enumerate() {
value.coords.push(Coord {
x: angle.x * x_scale,
y: angle.y * y_scale,
z: 0.0,
});
if calc_vertex_normals {
value
.outer_coord_indices
.push(i32::try_from(value.coords.len() - 1).map_err(|_| Error::Argument)?);
value.vertex_normals.push(if sides < 5 {
outer_normals.get(i).copied().unwrap_or(Coord {
x: angle.x,
y: angle.y,
z: 0.0,
})
} else {
Coord {
x: angle.x,
y: angle.y,
z: 0.0,
}
});
value.us.push(angle.angle);
}
if !has_hollow && !simple && create_faces && angle.angle > 0.0001 {
let index = i32::try_from(i).map_err(|_| Error::Argument)?;
value.faces.push(Face::vertices(0, index, index + 1));
}
}
if has_hollow {
let mut inner = Vec::with_capacity(hollow_angles.len());
let mut inner_normals = Vec::with_capacity(hollow_angles.len());
let mut inner_us = Vec::with_capacity(hollow_angles.len());
for (i, angle) in hollow_angles.iter().enumerate() {
inner.push(Coord {
x: hollow * x_scale * angle.x,
y: hollow * y_scale * angle.y,
z: 0.0,
});
if calc_vertex_normals {
let mut normal = if hollow_sides < 5 {
make_angles(hollow_sides, profile_start * TAU, profile_end * TAU)?
.1
.get(i)
.copied()
.unwrap_or(Coord {
x: angle.x,
y: angle.y,
z: 0.0,
})
} else {
Coord {
x: angle.x,
y: angle.y,
z: 0.0,
}
};
normal.x = -normal.x;
normal.y = -normal.y;
normal.z = -normal.z;
inner_normals.push(normal);
inner_us.push(if hollow_sides == 4 {
angle.angle * hollow * 0.707_107
} else {
angle.angle * hollow
});
}
}
inner.reverse();
inner_normals.reverse();
inner_us.reverse();
if create_faces {
triangulate_ring(&angles, &hollow_angles, &mut value.faces)?;
}
for coord in inner {
value.coords.push(coord);
if calc_vertex_normals {
value
.hollow_coord_indices
.push(i32::try_from(value.coords.len() - 1).map_err(|_| Error::Argument)?);
}
}
value.vertex_normals.extend(inner_normals);
value.us.extend(inner_us);
}
if simple && create_faces {
if sides == 3 {
value.faces.push(Face::vertices(0, 1, 2));
} else if sides == 4 {
value.faces.push(Face::vertices(0, 1, 2));
value.faces.push(Face::vertices(0, 2, 3));
}
}
if calc_vertex_normals && has_cut {
value.initialize_cut_normals(has_hollow)?;
}
value.make_face_u_vs()?;
if calc_vertex_normals {
value.assign_face_numbers(sides, has_hollow, has_cut);
}
Ok(value)
}
fn initialize_cut_normals(&mut self, has_hollow: bool) -> Result<(), Error> {
let last_outer = usize::try_from(self.num_outer_verts - 1).map_err(|_| Error::Argument)?;
if has_hollow {
self.cut1_coord_indices.extend([
0,
i32::try_from(self.coords.len() - 1).map_err(|_| Error::Argument)?,
]);
self.cut2_coord_indices.extend([
i32::try_from(last_outer + 1).map_err(|_| Error::Argument)?,
i32::try_from(last_outer).map_err(|_| Error::Argument)?,
]);
self.cut_normal1 = Coord {
x: self.coords[0].y - self.coords[self.coords.len() - 1].y,
y: -(self.coords[0].x - self.coords[self.coords.len() - 1].x),
z: 0.0,
};
self.cut_normal2 = Coord {
x: self.coords[last_outer + 1].y - self.coords[last_outer].y,
y: -(self.coords[last_outer + 1].x - self.coords[last_outer].x),
z: 0.0,
};
} else {
self.cut1_coord_indices.extend([0, 1]);
self.cut2_coord_indices
.extend([i32::try_from(last_outer).map_err(|_| Error::Argument)?, 0]);
let first = *self.vertex_normals.get(1).ok_or(Error::IndexOutOfRange)?;
let last = *self
.vertex_normals
.get(self.vertex_normals.len().saturating_sub(2))
.ok_or(Error::IndexOutOfRange)?;
self.cut_normal1 = Coord {
x: first.y,
y: -first.x,
z: 0.0,
};
self.cut_normal2 = Coord {
x: -last.y,
y: last.x,
z: 0.0,
};
}
self.cut_normal1.normalize()?;
self.cut_normal2.normalize()?;
Ok(())
}
fn assign_face_numbers(&mut self, sides: i32, has_hollow: bool, has_cut: bool) {
let mut face = 1;
self.outer_face_number = face;
if has_cut && !has_hollow {
self.face_numbers.push(-1);
}
for i in 0..self.num_outer_verts - 1 {
if sides < 5 && i <= sides {
self.face_numbers.push(face);
face += 1;
} else {
self.face_numbers.push(face);
}
}
if has_cut {
self.face_numbers.push(-1);
} else {
self.face_numbers.push(face);
face += 1;
}
if sides > 4 && (has_hollow || has_cut) {
face += 1;
}
if sides < 5 && (has_hollow || has_cut) && self.num_outer_verts < sides {
face += 1;
}
if has_hollow {
for _ in 0..self.num_hollow_verts {
self.face_numbers.push(face);
}
self.hollow_face_number = face;
face += 1;
}
self.bottom_face_number = face;
face += 1;
if has_hollow && has_cut {
self.face_numbers.push(face);
face += 1;
}
for number in &mut self.face_numbers {
if *number == -1 {
*number = face;
face += 1;
}
}
self.num_prim_faces = face;
}
pub fn make_face_u_vs(&mut self) -> Result<(), Error> {
self.face_u_vs = self
.coords
.iter()
.map(|c| UVCoord {
u: 1.0 - (0.5 + c.x),
v: 1.0 - (0.5 - c.y),
})
.collect();
Ok(())
}
pub fn copy_with_method(&self) -> Result<Self, Error> {
self.copy_with_boolean(true)
}
pub fn copy_with_boolean(&self, need_faces: bool) -> Result<Self, Error> {
let mut copy = self.clone();
if !need_faces {
copy.faces.clear();
}
Ok(copy)
}
pub fn add_pos_with_coord(&mut self, v: Coord) -> Result<(), Error> {
self.add_pos_with_single_single_single(v.x, v.y, v.z)
}
pub fn add_pos_with_single_single_single(
&mut self,
x: f32,
y: f32,
z: f32,
) -> Result<(), Error> {
for c in &mut self.coords {
c.x += x;
c.y += y;
c.z += z;
}
Ok(())
}
pub fn add_rot(&mut self, q: Quat) -> Result<(), Error> {
for c in &mut self.coords {
*c = Coord::mul_with_coord_quat(*c, q);
}
if self.calc_vertex_normals {
for n in &mut self.vertex_normals {
*n = Coord::mul_with_coord_quat(*n, q);
}
self.face_normal = Coord::mul_with_coord_quat(self.face_normal, q);
self.cut_normal1 = Coord::mul_with_coord_quat(self.cut_normal1, q);
self.cut_normal2 = Coord::mul_with_coord_quat(self.cut_normal2, q);
}
Ok(())
}
pub fn scale(&mut self, x: f32, y: f32) -> Result<(), Error> {
for c in &mut self.coords {
c.x *= x;
c.y *= y;
}
Ok(())
}
pub fn flip_normals(&mut self) -> Result<(), Error> {
for f in &mut self.faces {
std::mem::swap(&mut f.v1, &mut f.v3);
}
if self.calc_vertex_normals
&& let Some(n) = self.vertex_normals.last_mut()
{
n.z = -n.z;
}
self.face_normal.x = -self.face_normal.x;
self.face_normal.y = -self.face_normal.y;
self.face_normal.z = -self.face_normal.z;
for uv in &mut self.face_u_vs {
uv.v = 1.0 - uv.v;
}
Ok(())
}
pub fn add_value2_face_vertex_indices(&mut self, num: i32) -> Result<(), Error> {
for f in &mut self.faces {
f.v1 = f.v1.checked_add(num).ok_or(Error::Argument)?;
f.v2 = f.v2.checked_add(num).ok_or(Error::Argument)?;
f.v3 = f.v3.checked_add(num).ok_or(Error::Argument)?;
}
Ok(())
}
pub fn add_value2_face_normal_indices(&mut self, num: i32) -> Result<(), Error> {
if self.calc_vertex_normals {
for f in &mut self.faces {
f.n1 = f.n1.checked_add(num).ok_or(Error::Argument)?;
f.n2 = f.n2.checked_add(num).ok_or(Error::Argument)?;
f.n3 = f.n3.checked_add(num).ok_or(Error::Argument)?;
}
}
Ok(())
}
pub fn dump_raw(&self, path: String, name: String, title: String) -> Result<(), Error> {
dump_raw_geometry(&self.coords, &self.faces, path, name, title)
}
}
fn triangulate_ring(outer: &[Angle], inner: &[Angle], faces: &mut Vec<Face>) -> Result<(), Error> {
let outer_count = outer.len();
let inner_count = inner.len();
let total = outer_count
.checked_add(inner_count)
.ok_or(Error::Argument)?;
if outer_count == inner_count {
for i in 0..outer_count.saturating_sub(1) {
faces.push(Face::vertices(
i32::try_from(i).map_err(|_| Error::Argument)?,
i32::try_from(i + 1).map_err(|_| Error::Argument)?,
i32::try_from(total - i - 1).map_err(|_| Error::Argument)?,
));
faces.push(Face::vertices(
i32::try_from(i + 1).map_err(|_| Error::Argument)?,
i32::try_from(total - i - 2).map_err(|_| Error::Argument)?,
i32::try_from(total - i - 1).map_err(|_| Error::Argument)?,
));
}
return Ok(());
}
let mut oi = 0usize;
let mut hi = 0usize;
while oi + 1 < outer_count || hi + 1 < inner_count {
let outer_next = outer.get(oi + 1).map_or(f32::INFINITY, |a| a.angle);
let inner_next = inner.get(hi + 1).map_or(f32::INFINITY, |a| a.angle);
let inner_current = total - hi - 1;
if outer_next <= inner_next {
faces.push(Face::vertices(
i32::try_from(oi).map_err(|_| Error::Argument)?,
i32::try_from(oi + 1).map_err(|_| Error::Argument)?,
i32::try_from(inner_current).map_err(|_| Error::Argument)?,
));
oi += 1;
} else {
faces.push(Face::vertices(
i32::try_from(oi).map_err(|_| Error::Argument)?,
i32::try_from(inner_current - 1).map_err(|_| Error::Argument)?,
i32::try_from(inner_current).map_err(|_| Error::Argument)?,
));
hi += 1;
}
}
Ok(())
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct PathNode {
pub percent_of_path: f32,
pub position: Coord,
pub rotation: Quat,
pub x_scale: f32,
pub y_scale: f32,
}
#[derive(Clone, Debug)]
pub struct Path {
pub dimple_begin: f32,
pub dimple_end: f32,
pub hole_size_x: f32,
pub hole_size_y: f32,
pub path_cut_begin: f32,
pub path_cut_end: f32,
pub path_nodes: Vec<PathNode>,
pub radius: f32,
pub revolutions: f32,
pub skew: f32,
pub steps_per_revolution: i32,
pub taper_x: f32,
pub taper_y: f32,
pub top_shear_x: f32,
pub top_shear_y: f32,
pub twist_begin: f32,
pub twist_end: f32,
}
impl Default for Path {
fn default() -> Self {
Self {
dimple_begin: 0.0,
dimple_end: 1.0,
hole_size_x: 1.0,
hole_size_y: 0.25,
path_cut_begin: 0.0,
path_cut_end: 1.0,
path_nodes: Vec::new(),
radius: 0.0,
revolutions: 1.0,
skew: 0.0,
steps_per_revolution: 24,
taper_x: 0.0,
taper_y: 0.0,
top_shear_x: 0.0,
top_shear_y: 0.0,
twist_begin: 0.0,
twist_end: 0.0,
}
}
}
impl Path {
pub fn new() -> Result<Self, Error> {
Ok(Self::default())
}
pub fn create(&mut self, path_type: PathType, mut steps: i32) -> Result<(), Error> {
if steps < 1
|| self.path_cut_end <= self.path_cut_begin
|| !self.path_cut_begin.is_finite()
|| !self.path_cut_end.is_finite()
{
return Err(Error::Argument);
}
self.taper_x = self.taper_x.clamp(-0.999, 0.999);
self.taper_y = self.taper_y.clamp(-0.999, 0.999);
self.path_nodes.clear();
if matches!(path_type, PathType::Linear | PathType::Flexible) {
let length = self.path_cut_end - self.path_cut_begin;
let twist_total = self.twist_end - self.twist_begin;
if twist_total.abs() > 0.01 {
steps = steps
.checked_add((twist_total.abs() * 3.66) as i32)
.ok_or(Error::Argument)?;
}
let count = usize::try_from(steps)
.map_err(|_| Error::Argument)?
.checked_add(1)
.ok_or(Error::Argument)?;
if count > MAX_PATH_NODES {
return Err(Error::Argument);
}
self.path_nodes.reserve(count);
let step_size = length / (steps as f32);
let percent_step = step_size * 0.999_999;
let mut percent = self.path_cut_begin;
let mut x = self.top_shear_x * self.path_cut_begin;
let mut y = self.top_shear_y * self.path_cut_begin;
let mut z = -0.5 + percent;
let dx = self.top_shear_x * length / (steps as f32);
let dy = self.top_shear_y * length / (steps as f32);
for step in 0..=steps {
let x_scale = if self.taper_x == 0.0 {
1.0
} else if self.taper_x > 0.0 {
1.0 - percent * self.taper_x
} else {
1.0 + (1.0 - percent) * self.taper_x
};
let y_scale = if self.taper_y == 0.0 {
1.0
} else if self.taper_y > 0.0 {
1.0 - percent * self.taper_y
} else {
1.0 + (1.0 - percent) * self.taper_y
};
let twist = self.twist_begin + twist_total * percent;
self.path_nodes.push(PathNode {
percent_of_path: percent,
position: Coord { x, y, z },
rotation: Quat::new_with_coord_single(
Coord {
x: 0.0,
y: 0.0,
z: 1.0,
},
twist,
)?,
x_scale,
y_scale,
});
if step < steps {
percent += percent_step;
x += dx;
y += dy;
z += step_size;
if percent > self.path_cut_end {
break;
}
}
}
} else {
if self.steps_per_revolution < 1 || self.revolutions <= 0.0 {
return Err(Error::Argument);
}
let twist_total = self.twist_end - self.twist_begin;
if twist_total.abs() > 0.01 {
if twist_total.abs() > PI * 1.5 {
steps.checked_mul(2).ok_or(Error::Argument)?;
}
if twist_total.abs() > PI * 3.0 {
steps.checked_mul(4).ok_or(Error::Argument)?;
}
}
let y_path_scale = self.hole_size_y * 0.5;
let path_length = self.path_cut_end - self.path_cut_begin;
let total_skew = self.skew * 2.0 * path_length;
let skew_start = self.path_cut_begin * 2.0 * self.skew - self.skew;
let x_shear = self.top_shear_x * (0.25 + 0.5 * (0.5 - self.hole_size_y));
let y_comp = 1.0 + self.top_shear_y.abs() * 0.25;
let start = TAU * self.path_cut_begin * self.revolutions - self.top_shear_y * 0.9;
let end = TAU * self.path_cut_end * self.revolutions - self.top_shear_y * 0.9;
let step_size = TAU / (self.steps_per_revolution as f32);
let estimated = ((end - start) / step_size).ceil() as usize + 2;
if estimated > MAX_PATH_NODES {
return Err(Error::Argument);
}
self.path_nodes.reserve(estimated);
let mut step = (start / step_size) as i32;
let mut angle = start;
loop {
let percent = angle / (TAU * self.revolutions);
let angle_percent = (angle - start) / (end - start);
let mut xs = (1.0 - self.skew.abs()) * self.hole_size_x;
let mut ys = self.hole_size_y;
if self.taper_x > 0.01 {
xs *= 1.0 - percent * self.taper_x;
} else if self.taper_x < -0.01 {
xs *= 1.0 + (1.0 - percent) * self.taper_x;
}
if self.taper_y > 0.01 {
ys *= 1.0 - percent * self.taper_y;
} else if self.taper_y < -0.01 {
ys *= 1.0 + (1.0 - percent) * self.taper_y;
}
let radius_scale = if self.radius > 0.001 {
1.0 - self.radius * percent
} else if self.radius < 0.001 {
1.0 + self.radius * (1.0 - percent)
} else {
1.0
};
let twist = self.twist_begin + twist_total * percent;
let x = 0.5 * (skew_start + total_skew * angle_percent) + angle.sin() * x_shear;
let y = y_comp * angle.cos() * (0.5 - y_path_scale) * radius_scale;
let z = (angle + self.top_shear_y).sin() * (0.5 - y_path_scale) * radius_scale;
let mut rotation = Quat::new_with_coord_single(
Coord {
x: 1.0,
y: 0.0,
z: 0.0,
},
angle + self.top_shear_y,
)?;
if twist_total != 0.0 || self.twist_begin != 0.0 {
rotation = Quat::mul(
rotation,
Quat::new_with_coord_single(
Coord {
x: 0.0,
y: 0.0,
z: 1.0,
},
twist,
)?,
);
}
self.path_nodes.push(PathNode {
percent_of_path: percent,
position: Coord { x, y, z },
rotation,
x_scale: xs,
y_scale: ys,
});
if self.path_nodes.len() > MAX_PATH_NODES {
return Err(Error::Argument);
}
if angle >= end - 0.01 {
break;
}
step = step.checked_add(1).ok_or(Error::Argument)?;
angle = step_size * (step as f32);
if angle > end {
angle = end;
}
}
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct PrimMesh {
pub calc_vertex_normals: bool,
pub coords: Vec<Coord>,
pub dimple_begin: f32,
pub dimple_end: f32,
pub error_message: String,
pub faces: Vec<Face>,
pub hole_size_x: f32,
pub hole_size_y: f32,
pub normals: Vec<Coord>,
pub num_prim_faces: i32,
pub path_cut_begin: f32,
pub path_cut_end: f32,
pub radius: f32,
pub revolutions: f32,
pub skew: f32,
pub sphere_mode: bool,
pub steps_per_revolution: i32,
pub taper_x: f32,
pub taper_y: f32,
pub top_shear_x: f32,
pub top_shear_y: f32,
pub twist_begin: i32,
pub twist_end: i32,
pub viewer_faces: Vec<ViewerFace>,
pub viewer_mode: bool,
sides: i32,
profile_start: f32,
profile_end: f32,
hollow: f32,
hollow_sides: i32,
normals_processed: bool,
profile_outer_face_number: i32,
profile_hollow_face_number: i32,
has_profile_cut: bool,
has_hollow: bool,
}
impl PrimMesh {
pub fn new(
mut sides: i32,
mut profile_start: f32,
mut profile_end: f32,
mut hollow: f32,
mut hollow_sides: i32,
) -> Result<Self, Error> {
if !profile_start.is_finite() || !profile_end.is_finite() || !hollow.is_finite() {
return Err(Error::Argument);
}
sides = sides.max(3);
hollow_sides = hollow_sides.max(3);
profile_start = profile_start.max(0.0);
profile_end = profile_end.clamp(0.02, 1.0);
if profile_start >= profile_end {
profile_start = profile_end - 0.02;
}
hollow = hollow.clamp(0.0, 0.99);
Ok(Self {
calc_vertex_normals: false,
coords: Vec::new(),
dimple_begin: 0.0,
dimple_end: 1.0,
error_message: String::new(),
faces: Vec::new(),
hole_size_x: 1.0,
hole_size_y: 0.25,
normals: Vec::new(),
num_prim_faces: 0,
path_cut_begin: 0.0,
path_cut_end: 1.0,
radius: 0.0,
revolutions: 1.0,
skew: 0.0,
sphere_mode: false,
steps_per_revolution: 24,
taper_x: 0.0,
taper_y: 0.0,
top_shear_x: 0.0,
top_shear_y: 0.0,
twist_begin: 0,
twist_end: 0,
viewer_faces: Vec::new(),
viewer_mode: false,
sides,
profile_start,
profile_end,
hollow,
hollow_sides,
normals_processed: false,
profile_outer_face_number: -1,
profile_hollow_face_number: -1,
has_profile_cut: profile_start > 0.0 || profile_end < 1.0,
has_hollow: hollow > 0.0,
})
}
pub fn extrude(&mut self, path_type: PathType) -> Result<(), Error> {
self.validate_parameters()?;
self.coords.clear();
self.faces.clear();
self.normals.clear();
self.viewer_faces.clear();
self.normals_processed = false;
if self.viewer_mode {
self.calc_vertex_normals = true;
}
let twist_begin = self.twist_begin as f32;
let twist_end = self.twist_end as f32;
let twist_total = twist_end - twist_begin;
let mut steps: i32 = 1;
if twist_total.abs() > 0.01 {
steps = steps
.checked_add((twist_total.abs() * 3.66) as i32)
.ok_or(Error::Argument)?;
}
let need_end_faces = if path_type == PathType::Circular {
self.path_cut_begin != 0.0
|| self.path_cut_end != 1.0
|| self.taper_x != 0.0
|| self.taper_y != 0.0
|| self.skew != 0.0
|| twist_total != 0.0
|| self.radius != 0.0
} else {
true
};
let mut adjusted_hollow = self.hollow;
let mut initial_rotation = 0.0;
if path_type == PathType::Circular {
match self.sides {
3 => {
initial_rotation = PI;
if self.hollow_sides == 4 {
adjusted_hollow = adjusted_hollow.min(0.7) * 0.707;
} else {
adjusted_hollow *= 0.5;
}
}
4 => {
initial_rotation = 0.25 * PI;
if self.hollow_sides != 4 {
adjusted_hollow *= 0.707;
}
}
_ if self.sides > 4 => {
initial_rotation = PI;
if self.hollow_sides == 4 {
adjusted_hollow = adjusted_hollow.min(0.7) / 0.7;
}
}
_ => {}
}
} else {
match self.sides {
3 => {
if self.hollow_sides == 4 {
adjusted_hollow = adjusted_hollow.min(0.7) * 0.707;
} else {
adjusted_hollow *= 0.5;
}
}
4 => {
initial_rotation = 1.25 * PI;
if self.hollow_sides != 4 {
adjusted_hollow *= 0.707;
}
}
24 if self.hollow_sides == 4 => adjusted_hollow *= 1.414,
_ => {}
}
}
let mut profile = Profile::new_with_int32_single_single_single_int32_boolean_boolean(
self.sides,
self.profile_start,
self.profile_end,
adjusted_hollow,
self.hollow_sides,
true,
self.calc_vertex_normals,
)?;
self.error_message.clone_from(&profile.error_message);
let mut cut1_face = profile.bottom_face_number + 1;
let mut cut2_face = cut1_face + 1;
if !need_end_faces {
cut1_face -= 2;
cut2_face -= 2;
}
self.profile_outer_face_number = profile.outer_face_number - i32::from(!need_end_faces);
self.profile_hollow_face_number = if self.has_hollow {
profile.hollow_face_number - i32::from(!need_end_faces)
} else {
-1
};
let cut1_vert = if self.has_profile_cut {
if self.has_hollow {
i32::try_from(profile.coords.len() - 1).map_err(|_| Error::Argument)?
} else {
0
}
} else {
-1
};
let cut2_vert = if self.has_profile_cut {
if self.has_hollow {
profile.num_outer_verts - 1
} else {
profile.num_outer_verts
}
} else {
-1
};
if initial_rotation != 0.0 {
profile.add_rot(Quat::new_with_coord_single(
Coord {
x: 0.0,
y: 0.0,
z: 1.0,
},
initial_rotation,
)?)?;
if self.viewer_mode {
profile.make_face_u_vs()?;
}
}
let mut path = Path {
twist_begin,
twist_end,
top_shear_x: self.top_shear_x,
top_shear_y: self.top_shear_y,
path_cut_begin: self.path_cut_begin,
path_cut_end: self.path_cut_end,
dimple_begin: self.dimple_begin,
dimple_end: self.dimple_end,
skew: self.skew,
hole_size_x: self.hole_size_x,
hole_size_y: self.hole_size_y,
taper_x: self.taper_x,
taper_y: self.taper_y,
radius: self.radius,
revolutions: self.revolutions,
steps_per_revolution: self.steps_per_revolution,
..Path::default()
};
path.create(path_type, steps)?;
let total_vertices = path
.path_nodes
.len()
.checked_mul(profile.coords.len())
.ok_or(Error::Argument)?;
if total_vertices > MAX_MESH_VERTICES {
return Err(Error::Argument);
}
self.coords.reserve(total_vertices);
let mut last_cut1 = Coord::default();
let mut last_cut2 = Coord::default();
let mut last_v = 0.0;
for (node_index, node) in path.path_nodes.iter().copied().enumerate() {
let coords_offset = self.coords.len();
let normals_offset = self.normals.len();
let transformed: Vec<Coord> = profile
.coords
.iter()
.map(|v| {
let scaled = Coord {
x: v.x * node.x_scale,
y: v.y * node.y_scale,
z: v.z,
};
Coord::add(
Coord::mul_with_coord_quat(scaled, node.rotation),
node.position,
)
})
.collect();
self.coords.extend_from_slice(&transformed);
if self.calc_vertex_normals {
self.normals.extend(
profile
.vertex_normals
.iter()
.map(|n| Coord::mul_with_coord_quat(*n, node.rotation)),
);
}
if node.percent_of_path < self.path_cut_begin + 0.01
|| node.percent_of_path > self.path_cut_end - 0.01
{
for source in &profile.faces {
let mut face = *source;
face.v1 = add_index(face.v1, coords_offset)?;
face.v2 = add_index(face.v2, coords_offset)?;
face.v3 = add_index(face.v3, coords_offset)?;
if self.calc_vertex_normals {
face.n1 = add_index(face.n1, normals_offset)?;
face.n2 = add_index(face.n2, normals_offset)?;
face.n3 = add_index(face.n3, normals_offset)?;
}
self.faces.push(face);
}
}
let this_v = 1.0 - node.percent_of_path;
if node_index > 0 {
self.add_side_faces(
&profile,
coords_offset,
this_v,
last_v,
cut1_vert,
cut2_vert,
cut1_face,
cut2_face,
need_end_faces,
last_cut1,
last_cut2,
node.rotation,
)?;
}
last_cut1 = profile.cut_normal1;
last_cut2 = profile.cut_normal2;
last_v = this_v;
if need_end_faces && node_index + 1 == path.path_nodes.len() && self.viewer_mode {
for face in &profile.faces {
let v1 = idx(face.v1, transformed.len())?;
let v2 = idx(face.v2, transformed.len())?;
let v3 = idx(face.v3, transformed.len())?;
let mut viewer = ViewerFace::new(0)?;
viewer.v1 = transformed[v1];
viewer.v2 = transformed[v2];
viewer.v3 = transformed[v3];
viewer.coord_index1 = add_index(face.v1, coords_offset)?;
viewer.coord_index2 = add_index(face.v2, coords_offset)?;
viewer.coord_index3 = add_index(face.v3, coords_offset)?;
let normal = profile.face_normal;
viewer.n1 = normal;
viewer.n2 = normal;
viewer.n3 = normal;
viewer.uv1 = profile.face_u_vs[v1];
viewer.uv2 = profile.face_u_vs[v2];
viewer.uv3 = profile.face_u_vs[v3];
if path_type == PathType::Linear {
viewer.uv1.flip()?;
viewer.uv2.flip()?;
viewer.uv3.flip()?;
}
self.viewer_faces.push(viewer);
}
}
}
if self.viewer_mode {
self.num_prim_faces = self
.viewer_faces
.iter()
.map(|face| face.prim_face_number)
.max()
.unwrap_or(-1)
+ 1;
} else {
self.num_prim_faces = profile.num_prim_faces;
}
self.validate_geometry()?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn add_side_faces(
&mut self,
profile: &Profile,
offset: usize,
this_v: f32,
last_v: f32,
cut1: i32,
cut2: i32,
cut1_face: i32,
cut2_face: i32,
need_end_faces: bool,
last_cut1: Coord,
last_cut2: Coord,
rotation: Quat,
) -> Result<(), Error> {
let count = profile.coords.len();
let mut start = offset.checked_add(1).ok_or(Error::Argument)?;
if self.sides < 5 || self.has_profile_cut || self.has_hollow {
start -= 1;
}
let end = offset.checked_add(count).ok_or(Error::Argument)?;
for current in start..end {
let next = if current + 1 == end {
start
} else {
current + 1
};
let previous = current.checked_sub(count).ok_or(Error::IndexOutOfRange)?;
let previous_next = next.checked_sub(count).ok_or(Error::IndexOutOfRange)?;
let f1 = Face {
v1: to_i32(current)?,
v2: to_i32(previous)?,
v3: to_i32(next)?,
n1: to_i32(current)?,
n2: to_i32(previous)?,
n3: to_i32(next)?,
..Face::default()
};
let f2 = Face {
v1: to_i32(next)?,
v2: to_i32(previous)?,
v3: to_i32(previous_next)?,
n1: to_i32(next)?,
n2: to_i32(previous)?,
n3: to_i32(previous_next)?,
..Face::default()
};
self.faces.push(f1);
self.faces.push(f2);
if !self.viewer_mode {
continue;
}
let which = i32::try_from(current - start).map_err(|_| Error::Argument)?;
let mut prim = profile
.face_numbers
.get(usize::try_from(which).map_err(|_| Error::Argument)?)
.copied()
.or_else(|| profile.face_numbers.first().copied())
.unwrap_or(0);
if !need_end_faces {
prim = (prim - 1).max(0);
}
let mut vf1 = ViewerFace::new(prim)?;
let mut vf2 = ViewerFace::new(prim)?;
let mut u_index = usize::try_from(which).map_err(|_| Error::Argument)?;
if !self.has_hollow && self.sides > 4 && u_index < profile.us.len().saturating_sub(1) {
u_index += 1;
}
let (mut u1, mut u2) = if profile.us.is_empty() {
(0.0, 0.0)
} else {
u_index = u_index.min(profile.us.len() - 1);
(
profile.us[u_index],
profile.us.get(u_index + 1).copied().unwrap_or(1.0),
)
};
if which == cut1 || which == cut2 {
u1 = 0.0;
u2 = 1.0;
} else if self.sides < 5 && which < profile.num_outer_verts {
u1 *= self.sides as f32;
u2 *= self.sides as f32;
u2 -= u1 as i32 as f32;
u1 -= u1 as i32 as f32;
if u2 < 0.1 {
u2 = 1.0;
}
}
if self.sphere_mode && which != cut1 && which != cut2 {
u1 = u1 * 2.0 - 1.0;
u2 = u2 * 2.0 - 1.0;
if which >= profile.num_outer_verts {
u1 -= self.hollow;
u2 -= self.hollow;
}
}
vf1.uv1 = UVCoord { u: u1, v: this_v };
vf1.uv2 = UVCoord { u: u1, v: last_v };
vf1.uv3 = UVCoord { u: u2, v: this_v };
vf2.uv1 = UVCoord { u: u2, v: this_v };
vf2.uv2 = UVCoord { u: u1, v: last_v };
vf2.uv3 = UVCoord { u: u2, v: last_v };
assign_viewer_coords(&mut vf1, &self.coords, f1)?;
assign_viewer_coords(&mut vf2, &self.coords, f2)?;
if which == cut1 {
let current_normal = Coord::mul_with_coord_quat(profile.cut_normal1, rotation);
vf1.prim_face_number = cut1_face;
vf2.prim_face_number = cut1_face;
vf1.n1 = current_normal;
vf1.n2 = last_cut1;
vf1.n3 = last_cut1;
vf2.n1 = current_normal;
vf2.n2 = last_cut1;
vf2.n3 = current_normal;
} else if which == cut2 {
let current_normal = Coord::mul_with_coord_quat(profile.cut_normal2, rotation);
vf1.prim_face_number = cut2_face;
vf2.prim_face_number = cut2_face;
vf1.n1 = current_normal;
vf1.n2 = last_cut2;
vf1.n3 = last_cut2;
vf2.n1 = current_normal;
vf2.n2 = last_cut2;
vf2.n3 = current_normal;
} else if (self.sides < 5 && which < profile.num_outer_verts)
|| (self.hollow_sides < 5 && which >= profile.num_outer_verts)
{
vf1.calc_surface_normal()?;
vf2.calc_surface_normal()?;
} else {
assign_viewer_normals(&mut vf1, &self.normals, f1)?;
assign_viewer_normals(&mut vf2, &self.normals, f2)?;
}
self.viewer_faces.push(vf1);
self.viewer_faces.push(vf2);
}
Ok(())
}
fn validate_parameters(&self) -> Result<(), Error> {
let values = [
self.dimple_begin,
self.dimple_end,
self.hole_size_x,
self.hole_size_y,
self.path_cut_begin,
self.path_cut_end,
self.radius,
self.revolutions,
self.skew,
self.taper_x,
self.taper_y,
self.top_shear_x,
self.top_shear_y,
];
if values.iter().any(|v| !v.is_finite()) || self.path_cut_end <= self.path_cut_begin {
return Err(Error::Argument);
}
Ok(())
}
fn validate_geometry(&self) -> Result<(), Error> {
if self
.coords
.iter()
.any(|c| !c.x.is_finite() || !c.y.is_finite() || !c.z.is_finite())
|| self
.normals
.iter()
.any(|c| !c.x.is_finite() || !c.y.is_finite() || !c.z.is_finite())
{
return Err(Error::InvalidOperation);
}
for face in &self.faces {
idx(face.v1, self.coords.len())?;
idx(face.v2, self.coords.len())?;
idx(face.v3, self.coords.len())?;
}
Ok(())
}
pub fn surface_normal(&self, face_index: i32) -> Result<Coord, Error> {
let face = *self
.faces
.get(idx(face_index, self.faces.len())?)
.ok_or(Error::IndexOutOfRange)?;
surface_normal_for(&self.coords, face)
}
pub fn calc_normals(&mut self) -> Result<(), Error> {
if self.normals_processed {
return Ok(());
}
self.normals_processed = true;
if !self.calc_vertex_normals {
self.normals = Vec::with_capacity(self.faces.len());
}
for index in 0..self.faces.len() {
let normal = self.surface_normal(i32::try_from(index).map_err(|_| Error::Argument)?)?;
self.normals.push(normal);
let normal_index =
i32::try_from(self.normals.len() - 1).map_err(|_| Error::Argument)?;
self.faces[index].n1 = normal_index;
self.faces[index].n2 = normal_index;
self.faces[index].n3 = normal_index;
}
Ok(())
}
pub fn copy(&self) -> Result<Self, Error> {
Ok(self.clone())
}
pub fn add_pos(&mut self, x: f32, y: f32, z: f32) -> Result<(), Error> {
for c in &mut self.coords {
c.x += x;
c.y += y;
c.z += z;
}
for face in &mut self.viewer_faces {
face.add_pos(x, y, z)?;
}
Ok(())
}
pub fn add_rot(&mut self, q: Quat) -> Result<(), Error> {
for c in &mut self.coords {
*c = Coord::mul_with_coord_quat(*c, q);
}
for n in &mut self.normals {
*n = Coord::mul_with_coord_quat(*n, q);
}
for face in &mut self.viewer_faces {
face.add_rot(q)?;
}
Ok(())
}
pub fn scale(&mut self, x: f32, y: f32, z: f32) -> Result<(), Error> {
let m = Coord { x, y, z };
for c in &mut self.coords {
*c = Coord::mul_with_coord_coord(*c, m);
}
for face in &mut self.viewer_faces {
face.scale(x, y, z)?;
}
Ok(())
}
pub fn get_vertex_indexer(&self) -> Result<Option<VertexIndexer>, Error> {
if self.viewer_mode && !self.viewer_faces.is_empty() {
Ok(Some(VertexIndexer::from_viewer_faces(&self.viewer_faces)?))
} else {
Ok(None)
}
}
pub fn dump_raw(&self, path: String, name: String, title: String) -> Result<(), Error> {
dump_raw_geometry(&self.coords, &self.faces, path, name, title)
}
pub fn profile_outer_face_number(&self) -> i32 {
self.profile_outer_face_number
}
pub fn set_profile_outer_face_number(&mut self, value: i32) {
self.profile_outer_face_number = value;
}
pub fn profile_hollow_face_number(&self) -> i32 {
self.profile_hollow_face_number
}
pub fn set_profile_hollow_face_number(&mut self, value: i32) {
self.profile_hollow_face_number = value;
}
pub fn has_profile_cut(&self) -> bool {
self.has_profile_cut
}
pub fn set_has_profile_cut(&mut self, value: bool) {
self.has_profile_cut = value;
}
pub fn has_hollow(&self) -> bool {
self.has_hollow
}
pub fn set_has_hollow(&mut self, value: bool) {
self.has_hollow = value;
}
pub fn params_to_display_string(&self) -> Result<String, Error> {
Ok(format!(
"sides..................: {}\nhollowSides..........: {}\nprofileStart.........: {}\nprofileEnd...........: {}\nhollow...............: {}\ntwistBegin...........: {}\ntwistEnd.............: {}\ntopShearX............: {}\ntopShearY............: {}\npathCutBegin.........: {}\npathCutEnd...........: {}\ndimpleBegin..........: {}\ndimpleEnd............: {}\nskew.................: {}\nholeSizeX............: {}\nholeSizeY............: {}\ntaperX...............: {}\ntaperY...............: {}\nradius...............: {}\nrevolutions..........: {}\nstepsPerRevolution...: {}\nsphereMode...........: {}\nhasProfileCut........: {}\nhasHollow............: {}\nviewerMode...........: {}",
self.sides,
self.hollow_sides,
self.profile_start,
self.profile_end,
self.hollow,
self.twist_begin,
self.twist_end,
self.top_shear_x,
self.top_shear_y,
self.path_cut_begin,
self.path_cut_end,
self.dimple_begin,
self.dimple_end,
self.skew,
self.hole_size_x,
self.hole_size_y,
self.taper_x,
self.taper_y,
self.radius,
self.revolutions,
self.steps_per_revolution,
self.sphere_mode,
self.has_profile_cut,
self.has_hollow,
self.viewer_mode
))
}
}
fn to_i32(value: usize) -> Result<i32, Error> {
i32::try_from(value).map_err(|_| Error::Argument)
}
fn add_index(value: i32, offset: usize) -> Result<i32, Error> {
value.checked_add(to_i32(offset)?).ok_or(Error::Argument)
}
fn assign_viewer_coords(
viewer: &mut ViewerFace,
coords: &[Coord],
face: Face,
) -> Result<(), Error> {
viewer.v1 = coords[idx(face.v1, coords.len())?];
viewer.v2 = coords[idx(face.v2, coords.len())?];
viewer.v3 = coords[idx(face.v3, coords.len())?];
viewer.coord_index1 = face.v1;
viewer.coord_index2 = face.v2;
viewer.coord_index3 = face.v3;
Ok(())
}
fn assign_viewer_normals(
viewer: &mut ViewerFace,
normals: &[Coord],
face: Face,
) -> Result<(), Error> {
if normals.is_empty() {
return viewer.calc_surface_normal();
}
viewer.n1 = normals[idx(face.n1, normals.len())?];
viewer.n2 = normals[idx(face.n2, normals.len())?];
viewer.n3 = normals[idx(face.n3, normals.len())?];
Ok(())
}
pub(crate) fn dump_raw_geometry(
coords: &[Coord],
faces: &[Face],
path: String,
name: String,
title: String,
) -> Result<(), Error> {
let mut target = PathBuf::from(path);
target.push(format!("{name}_{title}.raw"));
let file = File::create(target).map_err(|_| Error::InvalidOperation)?;
let mut writer = BufWriter::new(file);
for face in faces {
let a = coords[idx(face.v1, coords.len())?];
let b = coords[idx(face.v2, coords.len())?];
let c = coords[idx(face.v3, coords.len())?];
writeln!(writer, "{a} {b} {c}").map_err(|_| Error::InvalidOperation)?;
}
writer.flush().map_err(|_| Error::InvalidOperation)
}
#[cfg(test)]
mod tests {
use super::*;
fn mesh(sides: i32, hollow: f32, viewer: bool) -> PrimMesh {
let mut mesh = PrimMesh::new(sides, 0.0, 1.0, hollow, 4).expect("valid mesh");
mesh.viewer_mode = viewer;
mesh.calc_vertex_normals = viewer;
mesh
}
#[test]
fn golden_box_and_torus_summaries_match_reference() {
let mut box_mesh = mesh(4, 0.0, true);
box_mesh.extrude(PathType::Linear).expect("box extrusion");
assert_eq!(
(
box_mesh.coords.len(),
box_mesh.faces.len(),
box_mesh.viewer_faces.len(),
box_mesh.num_prim_faces,
),
(10, 14, 12, 6)
);
let mut torus = mesh(24, 0.0, true);
torus.hole_size_x = 1.0;
torus.hole_size_y = 0.25;
torus.extrude(PathType::Circular).expect("torus extrusion");
assert_eq!(
(
torus.coords.len(),
torus.faces.len(),
torus.viewer_faces.len(),
torus.num_prim_faces,
),
(650, 1248, 1200, 1)
);
}
#[test]
fn special_profile_tables_keep_normalized_u_coordinates() {
let profile = Profile::new_with_int32_single_single_single_int32_boolean_boolean(
4, 0.0, 1.0, 0.0, 4, true, true,
)
.expect("square profile");
assert_eq!(profile.us, vec![0.0, 0.25, 0.5, 0.75, 1.0]);
assert!((profile.vertex_normals[0].x - std::f32::consts::FRAC_1_SQRT_2).abs() < 1e-6);
assert!((profile.vertex_normals[0].y - std::f32::consts::FRAC_1_SQRT_2).abs() < 1e-6);
}
#[test]
fn complex_circular_extrusion_is_deterministic_finite_and_bounded() {
fn build() -> PrimMesh {
let mut mesh = PrimMesh::new(8, 0.1, 0.85, 0.35, 4).expect("valid mesh");
mesh.viewer_mode = true;
mesh.calc_vertex_normals = true;
mesh.twist_begin = -1;
mesh.twist_end = 2;
mesh.top_shear_x = 0.2;
mesh.top_shear_y = -0.15;
mesh.path_cut_begin = 0.05;
mesh.path_cut_end = 0.9;
mesh.skew = 0.15;
mesh.taper_x = 0.25;
mesh.taper_y = -0.2;
mesh.radius = 0.2;
mesh.revolutions = 1.5;
mesh.steps_per_revolution = 32;
mesh.extrude(PathType::Circular).expect("complex extrusion");
mesh
}
let first = build();
let second = build();
assert_eq!(first.coords, second.coords);
assert_eq!(first.faces, second.faces);
assert_eq!(first.viewer_faces, second.viewer_faces);
for face in &first.faces {
for index in [face.v1, face.v2, face.v3] {
assert!(usize::try_from(index).is_ok_and(|index| index < first.coords.len()));
}
}
for coord in &first.coords {
assert!(coord.x.is_finite() && coord.y.is_finite() && coord.z.is_finite());
}
for face in &first.viewer_faces {
for value in [
face.uv1.u, face.uv1.v, face.uv2.u, face.uv2.v, face.uv3.u, face.uv3.v,
] {
assert!(value.is_finite());
}
}
}
#[test]
fn excessive_profile_and_path_sizes_are_rejected() {
assert!(matches!(
Profile::new_with_int32_single_single_single_int32_boolean_boolean(
i32::try_from(MAX_PROFILE_VERTICES + 1).expect("test limit"),
0.0,
1.0,
0.0,
4,
true,
false,
),
Err(Error::Argument)
));
let mut path = Path::new().expect("path");
assert!(matches!(
path.create(
PathType::Linear,
i32::try_from(MAX_PATH_NODES).expect("test limit")
),
Err(Error::Argument)
));
}
}