use crate::{
profiles::ProfileProcessor, scale_segments, Error, Mesh, Point3, Result, TessellationQuality,
Vector3,
};
use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
use crate::router::GeometryProcessor;
pub(crate) fn build_tube_rmf(
curve_points: &[Point3<f64>],
) -> (Vec<Vector3<f64>>, Vec<Vector3<f64>>, Vec<Vector3<f64>>) {
let n = curve_points.len();
let mut tangents = Vec::with_capacity(n);
let mut perp1s = Vec::with_capacity(n);
let mut perp2s = Vec::with_capacity(n);
if n < 2 {
return (tangents, perp1s, perp2s);
}
for i in 0..n {
let t = if i == 0 {
(curve_points[1] - curve_points[0]).normalize()
} else if i == n - 1 {
(curve_points[i] - curve_points[i - 1]).normalize()
} else {
((curve_points[i + 1] - curve_points[i - 1]) / 2.0).normalize()
};
tangents.push(t);
}
let up0 = if tangents[0].x.abs() < 0.9 {
Vector3::new(1.0, 0.0, 0.0)
} else {
Vector3::new(0.0, 1.0, 0.0)
};
let mut perp1 = tangents[0].cross(&up0).normalize();
let mut perp2 = tangents[0].cross(&perp1).normalize();
perp1s.push(perp1);
perp2s.push(perp2);
for i in 1..n {
let prev = tangents[i - 1];
let curr = tangents[i];
let cos_a = prev.dot(&curr).clamp(-1.0, 1.0);
let axis = prev.cross(&curr);
let axis_norm = axis.norm();
if axis_norm > 1e-9 && cos_a < 1.0 - 1e-12 {
let axis = axis / axis_norm;
let sin_a = (1.0 - cos_a * cos_a).max(0.0).sqrt();
perp1 = perp1 * cos_a
+ axis.cross(&perp1) * sin_a
+ axis * axis.dot(&perp1) * (1.0 - cos_a);
perp1 = perp1.normalize();
perp2 = curr.cross(&perp1).normalize();
}
perp1s.push(perp1);
perp2s.push(perp2);
}
(tangents, perp1s, perp2s)
}
fn build_tube(
curve_points: &[Point3<f64>],
radius: f64,
inner_radius: Option<f64>,
segments: usize,
) -> (Vec<f32>, Vec<u32>) {
let n = curve_points.len();
let seg = segments as u32;
let walls = if inner_radius.is_some() { 2 } else { 1 };
let mut positions: Vec<f32> = Vec::with_capacity(3 * (walls * n * segments + 2));
let mut indices: Vec<u32> = Vec::with_capacity(6 * segments * walls * n);
let (_, perp1s, perp2s) = build_tube_rmf(curve_points);
let unit_circle: Vec<(f64, f64)> = (0..segments)
.map(|j| {
let angle = 2.0 * std::f64::consts::PI * j as f64 / segments as f64;
(angle.cos(), angle.sin())
})
.collect();
let push_ring = |positions: &mut Vec<f32>, i: usize, r: f64| {
let p = curve_points[i];
for &(cos, sin) in &unit_circle {
let vertex = p + (perp1s[i] * (r * cos) + perp2s[i] * (r * sin));
positions.extend_from_slice(&[vertex.x as f32, vertex.y as f32, vertex.z as f32]);
}
};
let push_strip = |indices: &mut Vec<u32>, a: u32, b: u32, outward: bool| {
for j in 0..seg {
let j_next = (j + 1) % seg;
if outward {
indices.extend_from_slice(&[a + j, b + j_next, b + j]);
indices.extend_from_slice(&[a + j, a + j_next, b + j_next]);
} else {
indices.extend_from_slice(&[a + j, b + j, b + j_next]);
indices.extend_from_slice(&[a + j, b + j_next, a + j_next]);
}
}
};
let ring = |i: usize| (i * segments) as u32;
for i in 0..n {
push_ring(&mut positions, i, radius);
}
for i in 0..n - 1 {
push_strip(&mut indices, ring(i), ring(i + 1), true);
}
match inner_radius {
Some(inner) => {
let bore = |i: usize| ring(n + i);
for i in 0..n {
push_ring(&mut positions, i, inner);
}
for i in 0..n - 1 {
push_strip(&mut indices, bore(i), bore(i + 1), false);
}
push_strip(&mut indices, ring(0), bore(0), false);
push_strip(&mut indices, ring(n - 1), bore(n - 1), true);
}
None => {
let (c0, c1) = (ring(n), ring(n) + 1);
for p in [curve_points[0], curve_points[n - 1]] {
positions.extend_from_slice(&[p.x as f32, p.y as f32, p.z as f32]);
}
let (o0, o1) = (ring(0), ring(n - 1));
for j in 0..seg {
let jn = (j + 1) % seg;
indices.extend_from_slice(&[c0, o0 + jn, o0 + j]); indices.extend_from_slice(&[c1, o1 + j, o1 + jn]); }
}
}
(positions, indices)
}
pub struct SweptDiskSolidProcessor {
profile_processor: ProfileProcessor,
}
impl SweptDiskSolidProcessor {
pub fn new(schema: IfcSchema) -> Self {
Self {
profile_processor: ProfileProcessor::new(schema),
}
}
}
impl GeometryProcessor for SweptDiskSolidProcessor {
fn process(
&self,
entity: &DecodedEntity,
decoder: &mut EntityDecoder,
_schema: &IfcSchema,
quality: TessellationQuality,
) -> Result<Mesh> {
let directrix_attr = entity
.get(0)
.ok_or_else(|| Error::geometry("SweptDiskSolid missing Directrix".to_string()))?;
let radius = entity
.get_float(1)
.ok_or_else(|| Error::geometry("SweptDiskSolid missing Radius".to_string()))?;
let inner_radius = entity
.get_float(2)
.filter(|&r| r.is_finite() && r > 0.0 && r < radius);
let start_param = entity.get_float(3);
let end_param = entity.get_float(4);
let directrix = decoder
.resolve_ref(directrix_attr)?
.ok_or_else(|| Error::geometry("Failed to resolve Directrix".to_string()))?;
self.profile_processor.set_tessellation_quality(quality);
let has_trim = start_param.is_some() || end_param.is_some();
let curve_points = if has_trim
&& directrix.ifc_type.is_subtype_of(IfcType::IfcCompositeCurve)
{
self.profile_processor
.get_composite_curve_points_trimmed(
&directrix,
decoder,
start_param,
end_param,
)?
} else if has_trim && directrix.ifc_type == IfcType::IfcPolyline {
self.profile_processor
.get_polyline_points_trimmed(&directrix, decoder, start_param, end_param)?
} else if has_trim && directrix.ifc_type == IfcType::IfcLine {
self.profile_processor.get_line_points_3d(
&directrix,
decoder,
start_param.unwrap_or(0.0),
end_param.unwrap_or(1.0),
)?
} else {
self.profile_processor
.get_curve_points(&directrix, decoder, quality)?
};
if curve_points.len() < 2 {
return Ok(Mesh::new()); }
let segments = scale_segments(24, 8, 96, quality);
let (positions, indices) = build_tube(&curve_points, radius, inner_radius, segments);
let mut mesh = Mesh {
positions,
normals: Vec::new(),
indices,
rtc_applied: false,
origin: [0.0; 3],
welded_in_object_frame: false,
instance_meta: None, local_bounds: None, local_to_world: None };
crate::calculate_normals(&mut mesh);
Ok(mesh)
}
fn supported_types(&self) -> Vec<IfcType> {
vec![IfcType::IfcSweptDiskSolid]
}
}
impl Default for SweptDiskSolidProcessor {
fn default() -> Self {
Self::new(IfcSchema::new())
}
}
#[cfg(test)]
#[path = "disk_tests.rs"]
mod tests;