brep_kernel/props/mass_properties/
integration.rs1use super::*;
2
3pub(super) fn interior_knots(knots: &[f64], degree: usize) -> Vec<f64> {
4 let start = knots[degree];
5 let end = knots[knots.len() - 1 - degree];
6 let mut result = Vec::new();
7 for &knot in knots {
8 if knot <= start + 1e-12 || knot >= end - 1e-12 {
9 continue;
10 }
11 if result
12 .last()
13 .is_none_or(|previous: &f64| (knot - *previous).abs() > 1e-12)
14 {
15 result.push(knot);
16 }
17 }
18 result
19}
20
21pub(super) fn curve_breaks(curve: &NurbsCurve) -> Result<Vec<f64>, String> {
22 let [start, end] = curve.domain()?;
23 let mut result = vec![start];
24 result.extend(interior_knots(&curve.knots, curve.degree));
25 result.push(end);
26 Ok(result)
27}
28
29pub fn parameter_space_area(face: &FaceRecord) -> Result<f64, String> {
30 let mut area = 0.0;
31 for loop_record in &face.loops {
32 for coedge in &loop_record.coedges {
33 for pair in curve_breaks(&coedge.pcurve)?.windows(2) {
34 let half = (pair[1] - pair[0]) * 0.5;
35 let middle = (pair[1] + pair[0]) * 0.5;
36 for index in 0..GAUSS_X.len() {
37 let parameter = middle + half * GAUSS_X[index];
38 let (point, tangent) = coedge.pcurve.deriv1(parameter)?;
39 area +=
40 GAUSS_W[index] * half * 0.5 * (point.x * tangent.y - point.y * tangent.x);
41 }
42 }
43 }
44 }
45 Ok(area)
46}
47
48pub(super) fn is_affine(surface: &NurbsSurface) -> Result<bool, String> {
49 if surface.degree_u != 1
50 || surface.degree_v != 1
51 || surface.control_points.len() != 2
52 || surface.control_points[0].len() != 2
53 {
54 return Ok(false);
55 }
56 let points = &surface.control_points;
57 let weight = points[0][0].w;
58 if [points[0][1], points[1][0], points[1][1]]
59 .iter()
60 .any(|point| (point.w - weight).abs() > 1e-12)
61 {
62 return Ok(false);
63 }
64 let p00 = points[0][0].point()?;
65 let p01 = points[0][1].point()?;
66 let p10 = points[1][0].point()?;
67 let p11 = points[1][1].point()?;
68 let gap = p11.sub(p10).sub(p01.sub(p00)).length();
69 Ok(gap <= 1e-9 * (1.0 + p00.length().max(p11.length())))
70}
71
72pub(super) fn surface_breaks(surface: &NurbsSurface) -> Result<(Vec<f64>, Vec<f64>), String> {
73 let ku = crate::KnotVector::new(surface.knots_u.clone(), surface.degree_u)?;
74 let kv = crate::KnotVector::new(surface.knots_v.clone(), surface.degree_v)?;
75 let [u0, u1] = ku.domain();
76 let [v0, v1] = kv.domain();
77 let mut u = vec![u0];
78 u.extend(interior_knots(&surface.knots_u, surface.degree_u));
79 u.push(u1);
80 let mut v = vec![v0];
81 v.extend(interior_knots(&surface.knots_v, surface.degree_v));
82 v.push(v1);
83 Ok((u, v))
84}
85
86pub(super) fn integrand_value(kind: Integrand, point: Vec3, weighted_normal: Vec3) -> f64 {
87 let (x, y, z) = (point.x, point.y, point.z);
88 match kind {
89 Integrand::Area => weighted_normal.length(),
90 Integrand::Volume => point.dot(weighted_normal),
91 Integrand::VolumeAbout(reference) => point.sub(reference).dot(weighted_normal),
92 Integrand::MomentX => 0.5 * x * x * weighted_normal.x,
93 Integrand::MomentY => 0.5 * y * y * weighted_normal.y,
94 Integrand::MomentZ => 0.5 * z * z * weighted_normal.z,
95 Integrand::SecondXX => x * x * x / 3.0 * weighted_normal.x,
96 Integrand::SecondYY => y * y * y / 3.0 * weighted_normal.y,
97 Integrand::SecondZZ => z * z * z / 3.0 * weighted_normal.z,
98 Integrand::ProductXY => 0.5 * x * x * y * weighted_normal.x,
99 Integrand::ProductXZ => 0.5 * x * x * z * weighted_normal.x,
100 Integrand::ProductYZ => 0.5 * y * y * z * weighted_normal.y,
101 }
102}
103
104pub(super) fn evaluate_integrand(face: &FaceRecord, u: f64, v: f64, kind: Integrand) -> Result<f64, String> {
105 let (point, su, sv) = face.surface.deriv1(u, v)?;
106 let sign = if face.same_sense { 1.0 } else { -1.0 };
107 let weighted_normal = su.cross(sv).scale(sign);
108 Ok(integrand_value(kind, point, weighted_normal))
109}
110
111pub(super) fn integrate_untrimmed(face: &FaceRecord, kind: Integrand) -> Result<f64, String> {
112 let (u_breaks, v_breaks) = surface_breaks(&face.surface)?;
113 let mut total = 0.0;
114 for upair in u_breaks.windows(2) {
115 let half_u = (upair[1] - upair[0]) * 0.5;
116 let middle_u = (upair[1] + upair[0]) * 0.5;
117 for vpair in v_breaks.windows(2) {
118 let half_v = (vpair[1] - vpair[0]) * 0.5;
119 let middle_v = (vpair[1] + vpair[0]) * 0.5;
120 for i in 0..GAUSS_X.len() {
121 for j in 0..GAUSS_X.len() {
122 total += GAUSS_W[i]
123 * GAUSS_W[j]
124 * half_u
125 * half_v
126 * evaluate_integrand(
127 face,
128 middle_u + half_u * GAUSS_X[i],
129 middle_v + half_v * GAUSS_X[j],
130 kind,
131 )?;
132 }
133 }
134 }
135 }
136 Ok(total)
137}
138
139#[derive(Clone, Copy)]
146pub(super) struct BiBand {
147 p_is_u: bool,
149 q_lo: f64,
151 q_hi: f64,
152 complement: bool,
156}
157
158pub(super) fn biperiodic_band_range(face: &FaceRecord) -> Result<Option<BiBand>, String> {
165 let surface = &face.surface;
166 let (closed_u, closed_v) = surface.closed_directions()?;
167 if !(closed_u && closed_v) {
168 return Ok(None);
169 }
170 let [u0, u1] = surface.domain_u()?;
171 let [v0, v1] = surface.domain_v()?;
172 let u_span = (u1 - u0).abs().max(1e-30);
173 let v_span = (v1 - v0).abs().max(1e-30);
174 let mut loop_points: Vec<Vec<[f64; 2]>> = Vec::with_capacity(2);
180 for loop_record in &face.loops {
181 let mut points = Vec::new();
182 for coedge in &loop_record.coedges {
183 let [d0, d1] = coedge.pcurve.domain()?;
184 let samples = 12;
185 for k in 0..=samples {
186 let t = d0 + (d1 - d0) * k as f64 / samples as f64;
187 let p = coedge.pcurve.evaluate(t)?;
188 points.push([p.x, p.y]);
189 }
190 }
191 if points.len() < 2 {
192 return Ok(None);
193 }
194 let (mut umin, mut umax, mut vmin, mut vmax) = (
195 f64::INFINITY,
196 f64::NEG_INFINITY,
197 f64::INFINITY,
198 f64::NEG_INFINITY,
199 );
200 for pt in &points {
201 umin = umin.min(pt[0]);
202 umax = umax.max(pt[0]);
203 vmin = vmin.min(pt[1]);
204 vmax = vmax.max(pt[1]);
205 }
206 if (umax - umin) <= 1e-3 * u_span && (vmax - vmin) <= 1e-3 * v_span {
207 continue; }
209 loop_points.push(points);
210 }
211 if loop_points.len() != 2 {
212 return Ok(None);
213 }
214 for p_is_u in [true, false] {
215 let (period, _p0, _p1) = if p_is_u {
216 (u1 - u0, u0, u1)
217 } else {
218 (v1 - v0, v0, v1)
219 };
220 let (q_dom_lo, q_dom_hi) = if p_is_u { (v0, v1) } else { (u0, u1) };
221 let q_extent = (q_dom_hi - q_dom_lo).abs().max(1e-30);
222 if !(period > 0.0) {
223 continue;
224 }
225 let coord = |pt: &[f64; 2]| -> (f64, f64) {
226 if p_is_u {
227 (pt[0], pt[1])
228 } else {
229 (pt[1], pt[0])
230 }
231 };
232 let mut rings: Vec<(f64, i32)> = Vec::with_capacity(2);
235 let mut clean = true;
236 for points in &loop_points {
237 let (mut pmin, mut pmax, mut qmin, mut qmax) = (
238 f64::INFINITY,
239 f64::NEG_INFINITY,
240 f64::INFINITY,
241 f64::NEG_INFINITY,
242 );
243 for pt in points {
244 let (p, q) = coord(pt);
245 pmin = pmin.min(p);
246 pmax = pmax.max(p);
247 qmin = qmin.min(q);
248 qmax = qmax.max(q);
249 }
250 if (pmax - pmin) < 0.6 * period || (qmax - qmin) > 0.05 * q_extent {
251 clean = false;
252 break;
253 }
254 let mut net = 0.0;
255 for pair in points.windows(2) {
256 let mut delta = coord(&pair[1]).0 - coord(&pair[0]).0;
257 if delta > 0.5 * period {
258 delta -= period;
259 } else if delta < -0.5 * period {
260 delta += period;
261 }
262 net += delta;
263 }
264 let direction = if net > 0.25 * period {
265 1
266 } else if net < -0.25 * period {
267 -1
268 } else {
269 0
270 };
271 rings.push((0.5 * (qmin + qmax), direction));
272 }
273 if !clean || rings.len() != 2 {
274 continue;
275 }
276 rings.sort_by(|a, b| a.0.total_cmp(&b.0));
277 let (q_lo, lower_dir) = rings[0];
278 let (q_hi, upper_dir) = rings[1];
279 let inconclusive = lower_dir == 0 || upper_dir == 0 || lower_dir == upper_dir;
280 let between_rims_is_ccw_uv = if p_is_u { lower_dir > 0 } else { lower_dir < 0 };
281 let complement = !inconclusive && between_rims_is_ccw_uv != face.same_sense;
282 return Ok(Some(BiBand {
283 p_is_u,
284 q_lo,
285 q_hi,
286 complement,
287 }));
288 }
289 Ok(None)
290}
291
292pub(super) fn integrate_rectangle(
295 face: &FaceRecord,
296 u_lo: f64,
297 u_hi: f64,
298 v_lo: f64,
299 v_hi: f64,
300 kind: Integrand,
301) -> Result<f64, String> {
302 let (u_full, v_full) = surface_breaks(&face.surface)?;
303 let clamp = |breaks: &[f64], lo: f64, hi: f64| -> Vec<f64> {
304 let eps = 1e-9 * (hi - lo).abs().max(1e-30);
305 let mut out = vec![lo];
306 for &b in breaks {
307 if b > lo + eps && b < hi - eps {
308 out.push(b);
309 }
310 }
311 out.push(hi);
312 out
313 };
314 let u_breaks = clamp(&u_full, u_lo, u_hi);
315 let v_breaks = clamp(&v_full, v_lo, v_hi);
316 let mut total = 0.0;
317 for upair in u_breaks.windows(2) {
318 let half_u = (upair[1] - upair[0]) * 0.5;
319 let middle_u = (upair[1] + upair[0]) * 0.5;
320 for vpair in v_breaks.windows(2) {
321 let half_v = (vpair[1] - vpair[0]) * 0.5;
322 let middle_v = (vpair[1] + vpair[0]) * 0.5;
323 for i in 0..GAUSS_X.len() {
324 for j in 0..GAUSS_X.len() {
325 total += GAUSS_W[i]
326 * GAUSS_W[j]
327 * half_u
328 * half_v
329 * evaluate_integrand(
330 face,
331 middle_u + half_u * GAUSS_X[i],
332 middle_v + half_v * GAUSS_X[j],
333 kind,
334 )?;
335 }
336 }
337 }
338 }
339 Ok(total)
340}
341
342pub(super) fn biperiodic_band_integral(
348 face: &FaceRecord,
349 kinds: &[Integrand],
350) -> Result<Option<Vec<f64>>, String> {
351 let Some(band) = biperiodic_band_range(face)? else {
352 return Ok(None);
353 };
354 let [u0, u1] = face.surface.domain_u()?;
355 let [v0, v1] = face.surface.domain_v()?;
356 let (u_lo, u_hi, v_lo, v_hi) = if band.p_is_u {
357 (u0, u1, band.q_lo, band.q_hi)
358 } else {
359 (band.q_lo, band.q_hi, v0, v1)
360 };
361 let mut out = Vec::with_capacity(kinds.len());
362 for &kind in kinds {
363 let strip = integrate_rectangle(face, u_lo, u_hi, v_lo, v_hi, kind)?;
364 let value = if band.complement {
365 integrate_untrimmed(face, kind)? - strip
366 } else {
367 strip
368 };
369 out.push(value);
370 }
371 Ok(Some(out))
372}
373
374pub(super) fn sample_coedge(
376 coedge: &crate::topology::CoedgeRecord,
377 offset: [f64; 2],
378 polygon: &mut Vec<[f64; 2]>,
379) -> Result<(), String> {
380 let curve = &coedge.pcurve;
381 let [start, end] = curve.domain()?;
382 let interior = interior_knots(&curve.knots, curve.degree);
383 let rational = curve
384 .control_points
385 .iter()
386 .any(|point| (point.w - curve.control_points[0].w).abs() > 1e-12);
387 if curve.degree == 1 && !rational {
388 for parameter in std::iter::once(start).chain(interior.iter().copied()) {
389 let point = curve.evaluate(parameter)?;
390 polygon.push([point.x + offset[0], point.y + offset[1]]);
391 }
392 } else {
393 let sample_count = (24usize).max((interior.len() + 1) * 16).min(128);
394 for index in 0..sample_count {
395 let point =
396 curve.evaluate(start + (end - start) * index as f64 / sample_count as f64)?;
397 polygon.push([point.x + offset[0], point.y + offset[1]]);
398 }
399 }
400 Ok(())
401}