brep_kernel/props/mass_properties/
solid_props.rs1use super::*;
2
3pub fn solid_mass_properties(solid: &BrepSolid) -> Result<MassProperties, String> {
4 let mut properties = MassProperties {
5 surface_area: 0.0,
6 volume: 0.0,
7 };
8 let mut volume_compensation = 0.0;
9 for shell in &solid.shells {
10 if shell.faces.is_empty() {
11 continue;
12 }
13 let reference = shell_volume_reference(shell)?;
17 let kinds = [Integrand::Area, Integrand::VolumeAbout(reference)];
18 for face in &shell.faces {
19 let (area, volume) = if let Some(values) = biperiodic_band_integral(face, &kinds)? {
20 (values[0], values[1] / 3.0)
21 } else if !is_affine(&face.surface)? && !is_untrimmed(face)? {
22 let values = integrate_trimmed_multi(face, &kinds)?;
24 (values[0], values[1] / 3.0)
25 } else {
26 (
27 face_area(face)?,
28 face_volume_contribution_about(face, reference)?,
29 )
30 };
31 properties.surface_area += area;
32 let next = properties.volume + volume;
33 if properties.volume.abs() >= volume.abs() {
34 volume_compensation += (properties.volume - next) + volume;
35 } else {
36 volume_compensation += (volume - next) + properties.volume;
37 }
38 properties.volume = next;
39 }
40 }
41 properties.volume += volume_compensation;
42 Ok(properties)
43}
44
45pub fn solid_signed_volume(solid: &BrepSolid) -> Result<f64, String> {
50 let mut volume = 0.0;
51 for shell in &solid.shells {
52 volume += shell_signed_volume(shell)?;
53 }
54 Ok(volume)
55}
56
57pub(super) fn shell_volume_reference(shell: &ShellRecord) -> Result<Vec3, String> {
58 let finite = |point: Vec3| point.x.is_finite() && point.y.is_finite() && point.z.is_finite();
59 for face in &shell.faces {
60 if let Some(coedge) = face
61 .loops
62 .first()
63 .and_then(|loop_record| loop_record.coedges.first())
64 {
65 if let Ok(domain) = coedge.pcurve.domain() {
66 if let Ok(uv) = coedge.pcurve.evaluate(domain[0]) {
67 if let Ok(point) = face.surface.evaluate(uv.x, uv.y) {
68 if finite(point) {
69 return Ok(point);
70 }
71 }
72 }
73 }
74 }
75 if let Ok((u_breaks, v_breaks)) = surface_breaks(&face.surface) {
76 let u = 0.5 * (u_breaks[0] + u_breaks[u_breaks.len() - 1]);
77 let v = 0.5 * (v_breaks[0] + v_breaks[v_breaks.len() - 1]);
78 if let Ok(point) = face.surface.evaluate(u, v) {
79 if finite(point) {
80 return Ok(point);
81 }
82 }
83 }
84 for row in &face.surface.control_points {
85 for control in row {
86 if let Ok(point) = control.point() {
87 if finite(point) {
88 return Ok(point);
89 }
90 }
91 }
92 }
93 }
94 Err("mass_properties: shell has no finite geometric reference".to_string())
95}
96
97pub(crate) fn shell_signed_volume(shell: &ShellRecord) -> Result<f64, String> {
101 let reference = shell_volume_reference(shell)?;
102
103 let mut volume = 0.0;
109 let mut compensation = 0.0;
110 for face in &shell.faces {
111 let contribution = face_volume_contribution_about(face, reference)?;
112 let next = volume + contribution;
113 if volume.abs() >= contribution.abs() {
114 compensation += (volume - next) + contribution;
115 } else {
116 compensation += (contribution - next) + volume;
117 }
118 volume = next;
119 }
120 Ok(volume + compensation)
121}
122
123pub(super) fn affine_moment(face: &FaceRecord, kind: Integrand) -> Result<f64, String> {
130 let ku = crate::KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)?;
131 let kv = crate::KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)?;
132 let [u0, u1] = ku.domain();
133 let [v0, v1] = kv.domain();
134 let points = &face.surface.control_points;
135 let p00 = points[0][0].point()?;
136 let p10 = points[1][0].point()?;
137 let p01 = points[0][1].point()?;
138 let du = p10.sub(p00).scale(1.0 / (u1 - u0));
139 let dv = p01.sub(p00).scale(1.0 / (v1 - v0));
140 let weighted_normal = du.cross(dv);
141 let g = |u: f64, v: f64| {
142 let point = p00.add(du.scale(u - u0)).add(dv.scale(v - v0));
143 integrand_value(kind, point, weighted_normal)
144 };
145 let inner = |u: f64, v: f64| {
146 let half = (u - u0) * 0.5;
147 let middle = (u + u0) * 0.5;
148 let mut sum = 0.0;
149 for index in 0..GAUSS_X.len() {
150 sum += GAUSS_W[index] * g(middle + half * GAUSS_X[index], v);
151 }
152 sum * half
153 };
154 let mut total = 0.0;
155 for loop_record in &face.loops {
156 for coedge in &loop_record.coedges {
157 for pair in curve_breaks(&coedge.pcurve)?.windows(2) {
158 let half = (pair[1] - pair[0]) * 0.5;
159 let middle = (pair[1] + pair[0]) * 0.5;
160 for index in 0..GAUSS_X.len() {
161 let parameter = middle + half * GAUSS_X[index];
162 let (point, tangent) = coedge.pcurve.deriv1(parameter)?;
163 total += GAUSS_W[index] * half * inner(point.x, point.y) * tangent.y;
164 }
165 }
166 }
167 }
168 Ok(total)
169}
170
171pub(super) fn face_moment(face: &FaceRecord, kind: Integrand) -> Result<f64, String> {
172 if is_affine(&face.surface)? {
173 return affine_moment(face, kind);
174 }
175 if let Some(values) = biperiodic_band_integral(face, &[kind])? {
176 return Ok(values[0]);
177 }
178 if is_untrimmed(face)? {
179 integrate_untrimmed(face, kind)
180 } else {
181 integrate_trimmed(face, kind)
182 }
183}
184
185pub(super) fn mat3_mul(a: [[f64; 3]; 3], b: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
188 let mut r = [[0.0f64; 3]; 3];
189 for i in 0..3 {
190 for j in 0..3 {
191 for k in 0..3 {
192 r[i][j] += a[i][k] * b[k][j];
193 }
194 }
195 }
196 r
197}
198
199pub(super) fn mat3_transpose(a: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
200 let mut r = [[0.0f64; 3]; 3];
201 for i in 0..3 {
202 for j in 0..3 {
203 r[i][j] = a[j][i];
204 }
205 }
206 r
207}
208
209pub(super) fn jacobi_eigen_symmetric_3x3(matrix: [[f64; 3]; 3]) -> ([f64; 3], [[f64; 3]; 3]) {
218 let mut a = [
220 [matrix[0][0], 0.0, 0.0],
221 [0.0, matrix[1][1], 0.0],
222 [0.0, 0.0, matrix[2][2]],
223 ];
224 a[0][1] = 0.5 * (matrix[0][1] + matrix[1][0]);
225 a[1][0] = a[0][1];
226 a[0][2] = 0.5 * (matrix[0][2] + matrix[2][0]);
227 a[2][0] = a[0][2];
228 a[1][2] = 0.5 * (matrix[1][2] + matrix[2][1]);
229 a[2][1] = a[1][2];
230
231 let mut v = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
232 let scale = a[0][0].abs() + a[1][1].abs() + a[2][2].abs() + 1.0;
233 for _sweep in 0..64 {
234 let pairs = [(0usize, 1usize), (0, 2), (1, 2)];
236 let (mut p, mut q, mut best) = (0usize, 1usize, 0.0f64);
237 for &(i, j) in &pairs {
238 if a[i][j].abs() > best {
239 best = a[i][j].abs();
240 p = i;
241 q = j;
242 }
243 }
244 if best <= 1e-18 * scale {
245 break;
246 }
247 let theta = (a[q][q] - a[p][p]) / (2.0 * a[p][q]);
250 let t = if theta == 0.0 {
251 1.0
252 } else {
253 theta.signum() / (theta.abs() + (theta * theta + 1.0).sqrt())
254 };
255 let c = 1.0 / (t * t + 1.0).sqrt();
256 let s = t * c;
257 let mut j = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
259 j[p][p] = c;
260 j[q][q] = c;
261 j[p][q] = s;
262 j[q][p] = -s;
263 a = mat3_mul(mat3_transpose(j), mat3_mul(a, j));
265 v = mat3_mul(v, j);
266 }
267 ([a[0][0], a[1][1], a[2][2]], v)
268}
269
270pub(super) fn principal_frame(inertia: [[f64; 3]; 3]) -> ([f64; 3], [[f64; 3]; 3]) {
276 let (values, vectors) = jacobi_eigen_symmetric_3x3(inertia);
277 let mut order = [0usize, 1, 2];
279 order.sort_by(|&a, &b| values[a].total_cmp(&values[b]));
280 let mut moments = [0.0f64; 3];
281 let mut axes = [[0.0f64; 3]; 3];
282 for (slot, &k) in order.iter().enumerate() {
283 moments[slot] = values[k];
284 let mut axis = [vectors[0][k], vectors[1][k], vectors[2][k]];
285 let length = (axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]).sqrt();
286 if length > 0.0 {
287 axis = [axis[0] / length, axis[1] / length, axis[2] / length];
288 }
289 axes[slot] = axis;
290 }
291 let cross = [
293 axes[0][1] * axes[1][2] - axes[0][2] * axes[1][1],
294 axes[0][2] * axes[1][0] - axes[0][0] * axes[1][2],
295 axes[0][0] * axes[1][1] - axes[0][1] * axes[1][0],
296 ];
297 let det = cross[0] * axes[2][0] + cross[1] * axes[2][1] + cross[2] * axes[2][2];
298 if det < 0.0 {
299 axes[2] = [-axes[2][0], -axes[2][1], -axes[2][2]];
300 }
301 (moments, axes)
302}
303
304pub fn solid_mass_properties_full(solid: &BrepSolid) -> Result<FullMassProperties, String> {
309 let base = solid_mass_properties(solid)?;
310 const MOMENT_KINDS: [Integrand; 9] = [
311 Integrand::MomentX,
312 Integrand::MomentY,
313 Integrand::MomentZ,
314 Integrand::SecondXX,
315 Integrand::SecondYY,
316 Integrand::SecondZZ,
317 Integrand::ProductXY,
318 Integrand::ProductXZ,
319 Integrand::ProductYZ,
320 ];
321 let mut moments = [0.0f64; 3];
322 let mut seconds = [0.0f64; 3];
323 let mut products = [0.0f64; 3];
324 for shell in &solid.shells {
325 for face in &shell.faces {
326 let results = if !is_affine(&face.surface)? && !is_untrimmed(face)? {
327 integrate_trimmed_multi(face, &MOMENT_KINDS)?
330 } else {
331 MOMENT_KINDS
332 .iter()
333 .map(|kind| face_moment(face, *kind))
334 .collect::<Result<Vec<_>, _>>()?
335 };
336 moments[0] += results[0];
337 moments[1] += results[1];
338 moments[2] += results[2];
339 seconds[0] += results[3];
340 seconds[1] += results[4];
341 seconds[2] += results[5];
342 products[0] += results[6];
343 products[1] += results[7];
344 products[2] += results[8];
345 }
346 }
347 let volume = base.volume;
348 if volume.abs() <= 1e-30 {
349 return Err("solid_mass_properties_full: non-positive volume".into());
350 }
351 let centroid = Vec3::new(
352 moments[0] / volume,
353 moments[1] / volume,
354 moments[2] / volume,
355 );
356 let ixx =
358 seconds[1] + seconds[2] - volume * (centroid.y * centroid.y + centroid.z * centroid.z);
359 let iyy =
360 seconds[0] + seconds[2] - volume * (centroid.x * centroid.x + centroid.z * centroid.z);
361 let izz =
362 seconds[0] + seconds[1] - volume * (centroid.x * centroid.x + centroid.y * centroid.y);
363 let ixy = -(products[0] - volume * centroid.x * centroid.y);
364 let ixz = -(products[1] - volume * centroid.x * centroid.z);
365 let iyz = -(products[2] - volume * centroid.y * centroid.z);
366 let inertia = [[ixx, ixy, ixz], [ixy, iyy, iyz], [ixz, iyz, izz]];
367 let (principal_moments, principal_axes) = principal_frame(inertia);
368 Ok(FullMassProperties {
369 surface_area: base.surface_area,
370 volume,
371 centroid,
372 inertia,
373 principal_moments,
374 principal_axes,
375 })
376}