Skip to main content

brep_kernel/props/mass_properties/
measures.rs

1use super::*;
2
3pub fn face_area(face: &FaceRecord) -> Result<f64, String> {
4    if is_affine(&face.surface)? {
5        return Ok(parameter_space_area(face)?.abs() * planar_metric(face)?);
6    }
7    if let Some(values) = biperiodic_band_integral(face, &[Integrand::Area])? {
8        return Ok(values[0]);
9    }
10    if is_untrimmed(face)? {
11        integrate_untrimmed(face, Integrand::Area)
12    } else {
13        integrate_trimmed(face, Integrand::Area)
14    }
15}
16
17/// Exact 3D arc length of a NURBS curve over the parameter range `[t0, t1]` —
18/// ∫|C'(t)| dt evaluated with a composite 8-point Gauss–Legendre rule paneled at
19/// the curve's interior knots (each knot span further halved for headroom). The
20/// speed integrand `|C'(t)|` is polynomial-exact for the line/circle-arc curves
21/// the kernel emits and converges tightly for general NURBS. Returns `0.0` for a
22/// zero-width (or inverted) range.
23pub fn curve_arc_length(curve: &NurbsCurve, t0: f64, t1: f64) -> Result<f64, String> {
24    let (lo, hi) = (t0.min(t1), t0.max(t1));
25    if !(hi > lo) || !hi.is_finite() || !lo.is_finite() {
26        return Ok(0.0);
27    }
28    // Break the range at the curve's interior knots so a panel never straddles a
29    // C0 kink; then halve each span for extra Gauss headroom.
30    let mut breaks: Vec<f64> = vec![lo, hi];
31    for &knot in &curve.knots {
32        if knot > lo + 1e-12 && knot < hi - 1e-12 {
33            breaks.push(knot);
34        }
35    }
36    breaks.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
37    breaks.dedup_by(|a, b| (*a - *b).abs() <= 1e-12);
38
39    let mut length = 0.0;
40    for window in breaks.windows(2) {
41        let (a, b) = (window[0], window[1]);
42        if !(b > a) {
43            continue;
44        }
45        // Two sub-panels per knot span.
46        for sub in 0..2 {
47            let sa = a + (b - a) * sub as f64 / 2.0;
48            let sb = a + (b - a) * (sub + 1) as f64 / 2.0;
49            let middle = 0.5 * (sa + sb);
50            let half = 0.5 * (sb - sa);
51            for index in 0..GAUSS_X.len() {
52                let t = middle + half * GAUSS_X[index];
53                let speed = curve.derivatives(t, 1)?[1].length();
54                length += GAUSS_W[index] * half * speed;
55            }
56        }
57    }
58    Ok(length)
59}
60
61/// Exact 3D arc length of one edge over its active parameter range `[t0, t1]`
62/// (the same range the display sampler walks). Degenerate edges measure `0`.
63pub fn edge_arc_length(edge: &EdgeRecord) -> Result<f64, String> {
64    if edge.degenerate {
65        return Ok(0.0);
66    }
67    curve_arc_length(&edge.curve, edge.t0, edge.t1)
68}
69
70/// Total 3D arc length of every non-degenerate edge of a solid (each shared edge
71/// counted ONCE — the solid's edge table is already edge-unique). This is the
72/// solid's "total edge length" measurement.
73pub fn solid_edge_length_total(solid: &BrepSolid) -> Result<f64, String> {
74    let mut total = 0.0;
75    for edge in &solid.edges {
76        total += edge_arc_length(edge)?;
77    }
78    Ok(total)
79}
80
81/// Total 3D arc length of a face's boundary edges: the unique edges referenced by
82/// the face's loop coedges, resolved against the owning `solid`'s edge table and
83/// summed once each. An edge referenced twice by the same face (e.g. a seam) is
84/// still counted once.
85pub fn face_boundary_length(solid: &BrepSolid, face: &FaceRecord) -> Result<f64, String> {
86    let mut edge_ids: Vec<u64> = Vec::new();
87    for loop_record in &face.loops {
88        for coedge in &loop_record.coedges {
89            if !edge_ids.contains(&coedge.edge_id) {
90                edge_ids.push(coedge.edge_id);
91            }
92        }
93    }
94    let mut total = 0.0;
95    for id in edge_ids {
96        if let Some(edge) = solid.edges.iter().find(|edge| edge.id == id) {
97            total += edge_arc_length(edge)?;
98        }
99    }
100    Ok(total)
101}
102
103pub fn face_volume_contribution(face: &FaceRecord) -> Result<f64, String> {
104    if is_affine(&face.surface)? {
105        let signed_area = parameter_space_area(face)? * planar_metric(face)?;
106        let ku = crate::KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)?;
107        let kv = crate::KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)?;
108        let u = ku.domain()[0];
109        let v = kv.domain()[0];
110        let point = face.surface.evaluate(u, v)?;
111        let normal = face.surface.normal(u, v)?;
112        return Ok(point.dot(normal) * signed_area / 3.0);
113    }
114    if let Some(values) = biperiodic_band_integral(face, &[Integrand::Volume])? {
115        return Ok(values[0] / 3.0);
116    }
117    let integral = if is_untrimmed(face)? {
118        integrate_untrimmed(face, Integrand::Volume)?
119    } else {
120        integrate_trimmed(face, Integrand::Volume)?
121    };
122    Ok(integral / 3.0)
123}
124
125pub(super) fn face_volume_contribution_about(face: &FaceRecord, reference: Vec3) -> Result<f64, String> {
126    if is_affine(&face.surface)? {
127        let signed_area = parameter_space_area(face)? * planar_metric(face)?;
128        let ku = crate::KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)?;
129        let kv = crate::KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)?;
130        let u = ku.domain()[0];
131        let v = kv.domain()[0];
132        let point = face.surface.evaluate(u, v)?;
133        let normal = face.surface.normal(u, v)?;
134        return Ok(point.sub(reference).dot(normal) * signed_area / 3.0);
135    }
136    let kind = Integrand::VolumeAbout(reference);
137    if let Some(values) = biperiodic_band_integral(face, &[kind])? {
138        return Ok(values[0] / 3.0);
139    }
140    let integral = if is_untrimmed(face)? {
141        integrate_untrimmed(face, kind)?
142    } else {
143        integrate_trimmed(face, kind)?
144    };
145    Ok(integral / 3.0)
146}