brep_kernel/props/mass_properties/
integration.rs1pub(super) use crate::curve::interior_knots;
2use super::*;
3
4pub(super) fn curve_breaks(curve: &NurbsCurve) -> Result<Vec<f64>, String> {
5 let [start, end] = curve.domain()?;
6 let mut result = vec![start];
7 result.extend(interior_knots(&curve.knots, curve.degree));
8 result.push(end);
9 Ok(result)
10}
11
12pub fn parameter_space_area(face: &FaceRecord) -> Result<f64, String> {
13 let mut area = 0.0;
14 for loop_record in &face.loops {
15 for coedge in &loop_record.coedges {
16 for pair in curve_breaks(&coedge.pcurve)?.windows(2) {
17 let half = (pair[1] - pair[0]) * 0.5;
18 let middle = (pair[1] + pair[0]) * 0.5;
19 for index in 0..GAUSS_X.len() {
20 let parameter = middle + half * GAUSS_X[index];
21 let (point, tangent) = coedge.pcurve.deriv1(parameter)?;
22 area +=
23 GAUSS_W[index] * half * 0.5 * (point.x * tangent.y - point.y * tangent.x);
24 }
25 }
26 }
27 }
28 Ok(area)
29}
30
31pub(super) fn is_affine(surface: &NurbsSurface) -> Result<bool, String> {
32 surface.is_affine()
34}
35
36pub(super) fn surface_breaks(surface: &NurbsSurface) -> Result<(Vec<f64>, Vec<f64>), String> {
37 let ku = crate::KnotVector::new(surface.knots_u.clone(), surface.degree_u)?;
38 let kv = crate::KnotVector::new(surface.knots_v.clone(), surface.degree_v)?;
39 let [u0, u1] = ku.domain();
40 let [v0, v1] = kv.domain();
41 let mut u = vec![u0];
42 u.extend(interior_knots(&surface.knots_u, surface.degree_u));
43 u.push(u1);
44 let mut v = vec![v0];
45 v.extend(interior_knots(&surface.knots_v, surface.degree_v));
46 v.push(v1);
47 Ok((u, v))
48}
49
50pub(super) fn integrand_value(kind: Integrand, point: Vec3, weighted_normal: Vec3) -> f64 {
51 let (x, y, z) = (point.x, point.y, point.z);
52 match kind {
53 Integrand::Area => weighted_normal.length(),
54 Integrand::Volume => point.dot(weighted_normal),
55 Integrand::VolumeAbout(reference) => point.sub(reference).dot(weighted_normal),
56 Integrand::MomentX => 0.5 * x * x * weighted_normal.x,
57 Integrand::MomentY => 0.5 * y * y * weighted_normal.y,
58 Integrand::MomentZ => 0.5 * z * z * weighted_normal.z,
59 Integrand::SecondXX => x * x * x / 3.0 * weighted_normal.x,
60 Integrand::SecondYY => y * y * y / 3.0 * weighted_normal.y,
61 Integrand::SecondZZ => z * z * z / 3.0 * weighted_normal.z,
62 Integrand::ProductXY => 0.5 * x * x * y * weighted_normal.x,
63 Integrand::ProductXZ => 0.5 * x * x * z * weighted_normal.x,
64 Integrand::ProductYZ => 0.5 * y * y * z * weighted_normal.y,
65 }
66}
67
68pub(super) fn evaluate_integrand(face: &FaceRecord, u: f64, v: f64, kind: Integrand) -> Result<f64, String> {
69 let (point, su, sv) = face.surface.deriv1(u, v)?;
70 let sign = if face.same_sense { 1.0 } else { -1.0 };
71 let weighted_normal = su.cross(sv).scale(sign);
72 Ok(integrand_value(kind, point, weighted_normal))
73}
74
75pub(super) fn integrate_untrimmed(face: &FaceRecord, kind: Integrand) -> Result<f64, String> {
76 let (u_breaks, v_breaks) = surface_breaks(&face.surface)?;
77 let mut total = 0.0;
78 for upair in u_breaks.windows(2) {
79 let half_u = (upair[1] - upair[0]) * 0.5;
80 let middle_u = (upair[1] + upair[0]) * 0.5;
81 for vpair in v_breaks.windows(2) {
82 let half_v = (vpair[1] - vpair[0]) * 0.5;
83 let middle_v = (vpair[1] + vpair[0]) * 0.5;
84 for i in 0..GAUSS_X.len() {
85 for j in 0..GAUSS_X.len() {
86 total += GAUSS_W[i]
87 * GAUSS_W[j]
88 * half_u
89 * half_v
90 * evaluate_integrand(
91 face,
92 middle_u + half_u * GAUSS_X[i],
93 middle_v + half_v * GAUSS_X[j],
94 kind,
95 )?;
96 }
97 }
98 }
99 }
100 Ok(total)
101}
102
103#[derive(Clone, Copy)]
110pub(super) struct BiBand {
111 p_is_u: bool,
113 q_lo: f64,
115 q_hi: f64,
116 complement: bool,
120}
121
122pub(super) fn biperiodic_band_range(face: &FaceRecord) -> Result<Option<BiBand>, String> {
129 let surface = &face.surface;
130 let (closed_u, closed_v) = surface.closed_directions()?;
131 if !(closed_u && closed_v) {
132 return Ok(None);
133 }
134 let [u0, u1] = surface.domain_u()?;
135 let [v0, v1] = surface.domain_v()?;
136 let u_span = (u1 - u0).abs().max(1e-30);
137 let v_span = (v1 - v0).abs().max(1e-30);
138 let mut loop_points: Vec<Vec<[f64; 2]>> = Vec::with_capacity(2);
144 for loop_record in &face.loops {
145 let mut points = Vec::new();
146 for coedge in &loop_record.coedges {
147 let [d0, d1] = coedge.pcurve.domain()?;
148 let samples = 12;
149 for k in 0..=samples {
150 let t = d0 + (d1 - d0) * k as f64 / samples as f64;
151 let p = coedge.pcurve.evaluate(t)?;
152 points.push([p.x, p.y]);
153 }
154 }
155 if points.len() < 2 {
156 return Ok(None);
157 }
158 let (mut umin, mut umax, mut vmin, mut vmax) = (
159 f64::INFINITY,
160 f64::NEG_INFINITY,
161 f64::INFINITY,
162 f64::NEG_INFINITY,
163 );
164 for pt in &points {
165 umin = umin.min(pt[0]);
166 umax = umax.max(pt[0]);
167 vmin = vmin.min(pt[1]);
168 vmax = vmax.max(pt[1]);
169 }
170 if (umax - umin) <= 1e-3 * u_span && (vmax - vmin) <= 1e-3 * v_span {
171 continue; }
173 loop_points.push(points);
174 }
175 if loop_points.len() != 2 {
176 return Ok(None);
177 }
178 for p_is_u in [true, false] {
179 let (period, _p0, _p1) = if p_is_u {
180 (u1 - u0, u0, u1)
181 } else {
182 (v1 - v0, v0, v1)
183 };
184 let (q_dom_lo, q_dom_hi) = if p_is_u { (v0, v1) } else { (u0, u1) };
185 let q_extent = (q_dom_hi - q_dom_lo).abs().max(1e-30);
186 if !(period > 0.0) {
187 continue;
188 }
189 let coord = |pt: &[f64; 2]| -> (f64, f64) {
190 if p_is_u {
191 (pt[0], pt[1])
192 } else {
193 (pt[1], pt[0])
194 }
195 };
196 let mut rings: Vec<(f64, i32)> = Vec::with_capacity(2);
199 let mut clean = true;
200 for points in &loop_points {
201 let (mut pmin, mut pmax, mut qmin, mut qmax) = (
202 f64::INFINITY,
203 f64::NEG_INFINITY,
204 f64::INFINITY,
205 f64::NEG_INFINITY,
206 );
207 for pt in points {
208 let (p, q) = coord(pt);
209 pmin = pmin.min(p);
210 pmax = pmax.max(p);
211 qmin = qmin.min(q);
212 qmax = qmax.max(q);
213 }
214 if (pmax - pmin) < 0.6 * period || (qmax - qmin) > 0.05 * q_extent {
215 clean = false;
216 break;
217 }
218 let mut net = 0.0;
219 for pair in points.windows(2) {
220 let mut delta = coord(&pair[1]).0 - coord(&pair[0]).0;
221 if delta > 0.5 * period {
222 delta -= period;
223 } else if delta < -0.5 * period {
224 delta += period;
225 }
226 net += delta;
227 }
228 let direction = if net > 0.25 * period {
229 1
230 } else if net < -0.25 * period {
231 -1
232 } else {
233 0
234 };
235 rings.push((0.5 * (qmin + qmax), direction));
236 }
237 if !clean || rings.len() != 2 {
238 continue;
239 }
240 rings.sort_by(|a, b| a.0.total_cmp(&b.0));
241 let (q_lo, lower_dir) = rings[0];
242 let (q_hi, upper_dir) = rings[1];
243 let inconclusive = lower_dir == 0 || upper_dir == 0 || lower_dir == upper_dir;
244 let between_rims_is_ccw_uv = if p_is_u { lower_dir > 0 } else { lower_dir < 0 };
245 let complement = !inconclusive && between_rims_is_ccw_uv != face.same_sense;
246 return Ok(Some(BiBand {
247 p_is_u,
248 q_lo,
249 q_hi,
250 complement,
251 }));
252 }
253 Ok(None)
254}
255
256pub(super) fn integrate_rectangle(
259 face: &FaceRecord,
260 u_lo: f64,
261 u_hi: f64,
262 v_lo: f64,
263 v_hi: f64,
264 kind: Integrand,
265) -> Result<f64, String> {
266 let (u_full, v_full) = surface_breaks(&face.surface)?;
267 let clamp = |breaks: &[f64], lo: f64, hi: f64| -> Vec<f64> {
268 let eps = 1e-9 * (hi - lo).abs().max(1e-30);
269 let mut out = vec![lo];
270 for &b in breaks {
271 if b > lo + eps && b < hi - eps {
272 out.push(b);
273 }
274 }
275 out.push(hi);
276 out
277 };
278 let u_breaks = clamp(&u_full, u_lo, u_hi);
279 let v_breaks = clamp(&v_full, v_lo, v_hi);
280 let mut total = 0.0;
281 for upair in u_breaks.windows(2) {
282 let half_u = (upair[1] - upair[0]) * 0.5;
283 let middle_u = (upair[1] + upair[0]) * 0.5;
284 for vpair in v_breaks.windows(2) {
285 let half_v = (vpair[1] - vpair[0]) * 0.5;
286 let middle_v = (vpair[1] + vpair[0]) * 0.5;
287 for i in 0..GAUSS_X.len() {
288 for j in 0..GAUSS_X.len() {
289 total += GAUSS_W[i]
290 * GAUSS_W[j]
291 * half_u
292 * half_v
293 * evaluate_integrand(
294 face,
295 middle_u + half_u * GAUSS_X[i],
296 middle_v + half_v * GAUSS_X[j],
297 kind,
298 )?;
299 }
300 }
301 }
302 }
303 Ok(total)
304}
305
306pub(super) fn biperiodic_band_integral(
312 face: &FaceRecord,
313 kinds: &[Integrand],
314) -> Result<Option<Vec<f64>>, String> {
315 let Some(band) = biperiodic_band_range(face)? else {
316 return Ok(None);
317 };
318 let [u0, u1] = face.surface.domain_u()?;
319 let [v0, v1] = face.surface.domain_v()?;
320 let (u_lo, u_hi, v_lo, v_hi) = if band.p_is_u {
321 (u0, u1, band.q_lo, band.q_hi)
322 } else {
323 (band.q_lo, band.q_hi, v0, v1)
324 };
325 let mut out = Vec::with_capacity(kinds.len());
326 for &kind in kinds {
327 let strip = integrate_rectangle(face, u_lo, u_hi, v_lo, v_hi, kind)?;
328 let value = if band.complement {
329 integrate_untrimmed(face, kind)? - strip
330 } else {
331 strip
332 };
333 out.push(value);
334 }
335 Ok(Some(out))
336}