#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct SolidifyConfig {
pub thickness: f32,
pub offset: f32,
pub fill_rim: bool,
}
impl Default for SolidifyConfig {
fn default() -> Self {
Self {
thickness: 0.05,
offset: 0.5,
fill_rim: true,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SolidifyResult {
pub positions: Vec<[f32; 3]>,
pub indices: Vec<u32>,
pub rim_face_count: usize,
}
pub fn solidify(
positions: &[[f32; 3]],
indices: &[u32],
normals: &[[f32; 3]],
cfg: &SolidifyConfig,
) -> SolidifyResult {
let nv = positions.len();
let min_n = nv.min(normals.len());
let mut out_pos: Vec<[f32; 3]> = Vec::with_capacity(nv * 2);
let t = cfg.thickness;
let off = cfg.offset.clamp(0.0, 1.0);
for i in 0..nv {
let p = positions[i];
let n = if i < min_n {
normals[i]
} else {
[0.0, 1.0, 0.0]
};
let top = [
p[0] + n[0] * t * off,
p[1] + n[1] * t * off,
p[2] + n[2] * t * off,
];
out_pos.push(top);
}
for i in 0..nv {
let p = positions[i];
let n = if i < min_n {
normals[i]
} else {
[0.0, 1.0, 0.0]
};
let bot = [
p[0] - n[0] * t * (1.0 - off),
p[1] - n[1] * t * (1.0 - off),
p[2] - n[2] * t * (1.0 - off),
];
out_pos.push(bot);
}
let mut out_idx: Vec<u32> = indices.to_vec();
for tri in indices.chunks(3) {
if tri.len() < 3 {
continue;
}
out_idx.extend_from_slice(&[tri[2] + nv as u32, tri[1] + nv as u32, tri[0] + nv as u32]);
}
let mut rim_faces = 0usize;
if cfg.fill_rim {
let boundary = find_boundary_edges(indices);
for (a, b) in boundary {
let ta = a as u32;
let tb = b as u32;
let ba = a as u32 + nv as u32;
let bb = b as u32 + nv as u32;
out_idx.extend_from_slice(&[ta, tb, bb]);
out_idx.extend_from_slice(&[ta, bb, ba]);
rim_faces += 2;
}
}
SolidifyResult {
positions: out_pos,
indices: out_idx,
rim_face_count: rim_faces,
}
}
pub fn default_solidify_config() -> SolidifyConfig {
SolidifyConfig::default()
}
pub fn solidify_vertex_count(r: &SolidifyResult) -> usize {
r.positions.len()
}
pub fn solidify_triangle_count(r: &SolidifyResult) -> usize {
r.indices.len() / 3
}
pub fn shell_thickness(cfg: &SolidifyConfig) -> f32 {
cfg.thickness
}
pub struct SolidifyParams {
pub thickness: f32,
pub offset: f32,
pub fill_rim: bool,
}
pub fn new_solidify_params(thickness: f32) -> SolidifyParams {
SolidifyParams {
thickness: thickness.max(0.0),
offset: 0.0,
fill_rim: true,
}
}
pub fn solidify_vertex(
pos: [f32; 3],
normal: [f32; 3],
params: &SolidifyParams,
) -> ([f32; 3], [f32; 3]) {
let t = params.thickness;
let off = params.offset;
let inner = [
pos[0] - normal[0] * t * (1.0 - off),
pos[1] - normal[1] * t * (1.0 - off),
pos[2] - normal[2] * t * (1.0 - off),
];
let outer = [
pos[0] + normal[0] * t * off,
pos[1] + normal[1] * t * off,
pos[2] + normal[2] * t * off,
];
(inner, outer)
}
pub fn solidify_face_count(input_face_count: usize, params: &SolidifyParams) -> usize {
if params.fill_rim {
input_face_count * 2 + input_face_count
} else {
input_face_count * 2
}
}
pub fn solidify_flip_normal(n: [f32; 3]) -> [f32; 3] {
[-n[0], -n[1], -n[2]]
}
pub fn solidify_vert_count(input_vertex_count: usize) -> usize {
input_vertex_count * 2
}
fn find_boundary_edges(indices: &[u32]) -> Vec<(usize, usize)> {
use std::collections::HashMap;
let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
for tri in indices.chunks(3) {
if tri.len() < 3 {
continue;
}
for &(u, v) in &[(tri[0], tri[1]), (tri[1], tri[2]), (tri[2], tri[0])] {
let key = (u.min(v) as usize, u.max(v) as usize);
*edge_count.entry(key).or_insert(0) += 1;
}
}
edge_count
.into_iter()
.filter(|(_, c)| *c == 1)
.map(|(e, _)| e)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn flat_tri() -> (Vec<[f32; 3]>, Vec<u32>, Vec<[f32; 3]>) {
let p = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.5, 1.0, 0.0]];
let i = vec![0u32, 1, 2];
let n = vec![[0.0, 1.0, 0.0], [0.0, 1.0, 0.0], [0.0, 1.0, 0.0]];
(p, i, n)
}
#[test]
fn test_solidify_doubles_vertices() {
let (p, i, n) = flat_tri();
let r = solidify(&p, &i, &n, &default_solidify_config());
assert_eq!(solidify_vertex_count(&r), p.len() * 2);
}
#[test]
fn test_solidify_min_triangle_count() {
let (p, i, n) = flat_tri();
let r = solidify(
&p,
&i,
&n,
&SolidifyConfig {
thickness: 0.1,
offset: 0.5,
fill_rim: false,
},
);
assert!(solidify_triangle_count(&r) >= 2);
}
#[test]
fn test_solidify_with_rim() {
let (p, i, n) = flat_tri();
let no_rim = solidify(
&p,
&i,
&n,
&SolidifyConfig {
thickness: 0.1,
offset: 0.5,
fill_rim: false,
},
);
let with_rim = solidify(&p, &i, &n, &default_solidify_config());
assert!(solidify_triangle_count(&with_rim) >= solidify_triangle_count(&no_rim));
}
#[test]
fn test_shell_thickness_accessor() {
let cfg = SolidifyConfig {
thickness: 0.25,
offset: 0.5,
fill_rim: false,
};
assert!((shell_thickness(&cfg) - 0.25).abs() < 1e-6);
}
#[test]
fn test_default_config_positive_thickness() {
assert!(default_solidify_config().thickness > 0.0);
}
#[test]
fn test_solidify_empty_indices() {
let p = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]];
let i: Vec<u32> = vec![];
let n = vec![[0.0, 1.0, 0.0], [0.0, 1.0, 0.0]];
let r = solidify(&p, &i, &n, &default_solidify_config());
assert_eq!(solidify_vertex_count(&r), 4);
}
#[test]
fn test_top_shell_pos_above_original() {
let p = vec![[0.0, 0.0, 0.0]];
let n = vec![[0.0, 1.0, 0.0]];
let cfg = SolidifyConfig {
thickness: 0.1,
offset: 1.0,
fill_rim: false,
};
let r = solidify(&p, &[], &n, &cfg);
assert!(r.positions[0][1] > p[0][1]);
}
#[test]
fn test_solidify_rim_count_positive() {
let (p, i, n) = flat_tri();
let r = solidify(&p, &i, &n, &default_solidify_config());
assert!(r.rim_face_count > 0);
}
#[test]
fn test_find_boundary_edges_single_tri() {
let i: Vec<u32> = vec![0, 1, 2];
let be = find_boundary_edges(&i);
assert_eq!(be.len(), 3);
}
}