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