use super::*;
use super::mass_profile;
pub fn face_area(face: &FaceRecord) -> Result<f64, String> {
if is_affine(&face.surface)? {
return Ok(closed_parameter_space_area(face)?.abs() * planar_metric(face)?);
}
if let Some(values) = biperiodic_band_integral(face, &[Integrand::Area])? {
return Ok(values[0]);
}
if is_untrimmed(face)? {
integrate_untrimmed(face, Integrand::Area)
} else {
integrate_trimmed(face, Integrand::Area)
}
}
pub fn curve_arc_length(curve: &NurbsCurve, t0: f64, t1: f64) -> Result<f64, String> {
let (lo, hi) = (t0.min(t1), t0.max(t1));
if !(hi > lo) || !hi.is_finite() || !lo.is_finite() {
return Ok(0.0);
}
let mut breaks: Vec<f64> = vec![lo, hi];
for &knot in &curve.knots {
if knot > lo + 1e-12 && knot < hi - 1e-12 {
breaks.push(knot);
}
}
breaks.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
breaks.dedup_by(|a, b| (*a - *b).abs() <= 1e-12);
let mut length = 0.0;
for window in breaks.windows(2) {
let (a, b) = (window[0], window[1]);
if !(b > a) {
continue;
}
for sub in 0..2 {
let sa = a + (b - a) * sub as f64 / 2.0;
let sb = a + (b - a) * (sub + 1) as f64 / 2.0;
for panel in rule::curve_panels(curve, sa, sb)? {
let half = 0.5 * (panel.hi - panel.lo);
let middle = 0.5 * (panel.lo + panel.hi);
let (nodes, weights) = panel.nodes();
for index in 0..nodes.len() {
let t = middle + half * nodes[index];
let speed = curve.derivatives(t, 1)?[1].length();
length += weights[index] * half * speed;
}
}
}
}
Ok(length)
}
pub fn edge_arc_length(edge: &EdgeRecord) -> Result<f64, String> {
if edge.degenerate {
return Ok(0.0);
}
curve_arc_length(&edge.curve, edge.t0, edge.t1)
}
pub fn solid_edge_length_total(solid: &BrepSolid) -> Result<f64, String> {
let mut total = 0.0;
for edge in &solid.edges {
total += edge_arc_length(edge)?;
}
Ok(total)
}
pub fn face_boundary_length(solid: &BrepSolid, face: &FaceRecord) -> Result<f64, String> {
let mut edge_ids: Vec<u64> = Vec::new();
for loop_record in &face.loops {
for coedge in &loop_record.coedges {
if !edge_ids.contains(&coedge.edge_id) {
edge_ids.push(coedge.edge_id);
}
}
}
let mut total = 0.0;
for id in edge_ids {
if let Some(edge) = solid.edges.iter().find(|edge| edge.id == id) {
total += edge_arc_length(edge)?;
}
}
Ok(total)
}
pub fn face_volume_contribution(face: &FaceRecord) -> Result<f64, String> {
if is_affine(&face.surface)? {
let signed_area = closed_parameter_space_area(face)? * planar_metric(face)?;
let ku = crate::KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)?;
let kv = crate::KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)?;
let u = ku.domain()[0];
let v = kv.domain()[0];
let point = face.surface.evaluate(u, v)?;
let normal = face.surface.normal(u, v)?;
return Ok(point.dot(normal) * signed_area / 3.0);
}
if let Some(values) = biperiodic_band_integral(face, &[Integrand::Volume])? {
return Ok(values[0] / 3.0);
}
let integral = if is_untrimmed(face)? {
integrate_untrimmed(face, Integrand::Volume)?
} else {
integrate_trimmed(face, Integrand::Volume)?
};
Ok(integral / 3.0)
}
pub(super) fn affine_area_and_volume_about(
face: &FaceRecord,
reference: Vec3,
) -> Result<(f64, f64), String> {
let parameter_area = closed_parameter_space_area(face)?;
let metric = planar_metric(face)?;
let ku = crate::KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)?;
let kv = crate::KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)?;
let u = ku.domain()[0];
let v = kv.domain()[0];
let point = face.surface.evaluate(u, v)?;
let normal = face.surface.normal(u, v)?;
let signed_area = parameter_area * metric;
Ok((
parameter_area.abs() * metric,
point.sub(reference).dot(normal) * signed_area / 3.0,
))
}
pub(super) fn face_volume_contribution_about(face: &FaceRecord, reference: Vec3) -> Result<f64, String> {
if is_affine(&face.surface)? {
let signed_area = closed_parameter_space_area(face)? * planar_metric(face)?;
let ku = crate::KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)?;
let kv = crate::KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)?;
let u = ku.domain()[0];
let v = kv.domain()[0];
let point = face.surface.evaluate(u, v)?;
let normal = face.surface.normal(u, v)?;
return Ok(point.sub(reference).dot(normal) * signed_area / 3.0);
}
let kind = Integrand::VolumeAbout(reference);
let gate_started = super::profile::profile_started();
if let Some(values) = biperiodic_band_integral(face, &[kind])? {
mass_profile(|p| {
p.route_biperiodic += 1;
p.gate_ms += super::profile::elapsed_ms(gate_started);
});
return Ok(values[0] / 3.0);
}
let integral = if is_untrimmed(face)? {
mass_profile(|p| {
p.route_untrimmed += 1;
p.gate_ms += super::profile::elapsed_ms(gate_started);
});
let started = super::profile::profile_started();
let value = integrate_untrimmed(face, kind)?;
mass_profile(|p| p.untrimmed_ms += super::profile::elapsed_ms(started));
value
} else {
mass_profile(|p| {
p.trimmed_faces += 1;
p.gate_ms += super::profile::elapsed_ms(gate_started);
});
integrate_trimmed(face, kind)?
};
Ok(integral / 3.0)
}
pub(super) fn face_volume_terms_about(face: &FaceRecord, reference: Vec3) -> Result<[f64; 4], String> {
if is_affine(&face.surface)? {
let signed_area = closed_parameter_space_area(face)? * planar_metric(face)?;
let ku = crate::KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)?;
let kv = crate::KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)?;
let (u, v) = (ku.domain()[0], kv.domain()[0]);
let point = face.surface.evaluate(u, v)?;
let normal = face.surface.normal(u, v)?;
return Ok([
point.sub(reference).dot(normal) * signed_area / 3.0,
normal.x * signed_area / 3.0,
normal.y * signed_area / 3.0,
normal.z * signed_area / 3.0,
]);
}
let kinds = [Integrand::VolumeAbout(reference), Integrand::NormalX, Integrand::NormalY, Integrand::NormalZ];
let gate_started = super::profile::profile_started();
let values = if let Some(values) = biperiodic_band_integral(face, &kinds)? {
mass_profile(|p| {
p.route_biperiodic += 1;
p.gate_ms += super::profile::elapsed_ms(gate_started);
});
values
} else if is_untrimmed(face)? {
mass_profile(|p| {
p.route_untrimmed += 1;
p.gate_ms += super::profile::elapsed_ms(gate_started);
});
let started = super::profile::profile_started();
let values = integrate_untrimmed_multi(face, &kinds)?;
mass_profile(|p| p.untrimmed_ms += super::profile::elapsed_ms(started));
values
} else if let Some(rectangle) = iso_rectangle_route_on().then(|| iso_rectangle_trim(face)).transpose()?.flatten() {
mass_profile(|p| {
p.route_iso_rectangle += 1;
p.gate_ms += super::profile::elapsed_ms(gate_started);
});
let started = super::profile::profile_started();
let values = integrate_iso_rectangle_multi(face, rectangle, &kinds)?;
mass_profile(|p| p.untrimmed_ms += super::profile::elapsed_ms(started));
values
} else {
mass_profile(|p| {
p.trimmed_faces += 1;
p.gate_ms += super::profile::elapsed_ms(gate_started);
});
integrate_trimmed_multi(face, &kinds)?
};
Ok([values[0] / 3.0, values[1] / 3.0, values[2] / 3.0, values[3] / 3.0])
}
fn iso_rectangle_route_on() -> bool {
switch!("BREP_MASS_ISO_RECT", true)
}