use super::*;
pub fn solid_mass_properties(solid: &BrepSolid) -> Result<MassProperties, String> {
let mut properties = MassProperties {
surface_area: 0.0,
volume: 0.0,
};
let mut volume_compensation = 0.0;
for shell in &solid.shells {
if shell.faces.is_empty() {
continue;
}
let reference = shell_volume_reference(shell)?;
let kinds = [Integrand::Area, Integrand::VolumeAbout(reference)];
for face in &shell.faces {
let (area, volume) = if let Some(values) = biperiodic_band_integral(face, &kinds)? {
(values[0], values[1] / 3.0)
} else if !is_affine(&face.surface)? && !is_untrimmed(face)? {
let values = integrate_trimmed_multi(face, &kinds)?;
(values[0], values[1] / 3.0)
} else {
(
face_area(face)?,
face_volume_contribution_about(face, reference)?,
)
};
properties.surface_area += area;
let next = properties.volume + volume;
if properties.volume.abs() >= volume.abs() {
volume_compensation += (properties.volume - next) + volume;
} else {
volume_compensation += (volume - next) + properties.volume;
}
properties.volume = next;
}
}
properties.volume += volume_compensation;
Ok(properties)
}
pub fn solid_signed_volume(solid: &BrepSolid) -> Result<f64, String> {
let mut volume = 0.0;
for shell in &solid.shells {
volume += shell_signed_volume(shell)?;
}
Ok(volume)
}
pub(super) fn shell_volume_reference(shell: &ShellRecord) -> Result<Vec3, String> {
let finite = |point: Vec3| point.x.is_finite() && point.y.is_finite() && point.z.is_finite();
for face in &shell.faces {
if let Some(coedge) = face
.loops
.first()
.and_then(|loop_record| loop_record.coedges.first())
{
if let Ok(domain) = coedge.pcurve.domain() {
if let Ok(uv) = coedge.pcurve.evaluate(domain[0]) {
if let Ok(point) = face.surface.evaluate(uv.x, uv.y) {
if finite(point) {
return Ok(point);
}
}
}
}
}
if let Ok((u_breaks, v_breaks)) = surface_breaks(&face.surface) {
let u = 0.5 * (u_breaks[0] + u_breaks[u_breaks.len() - 1]);
let v = 0.5 * (v_breaks[0] + v_breaks[v_breaks.len() - 1]);
if let Ok(point) = face.surface.evaluate(u, v) {
if finite(point) {
return Ok(point);
}
}
}
for row in &face.surface.control_points {
for control in row {
if let Ok(point) = control.point() {
if finite(point) {
return Ok(point);
}
}
}
}
}
Err("mass_properties: shell has no finite geometric reference".to_string())
}
pub(crate) fn shell_signed_volume(shell: &ShellRecord) -> Result<f64, String> {
let reference = shell_volume_reference(shell)?;
let mut volume = 0.0;
let mut compensation = 0.0;
for face in &shell.faces {
let contribution = face_volume_contribution_about(face, reference)?;
let next = volume + contribution;
if volume.abs() >= contribution.abs() {
compensation += (volume - next) + contribution;
} else {
compensation += (contribution - next) + volume;
}
volume = next;
}
Ok(volume + compensation)
}
pub(super) fn affine_moment(face: &FaceRecord, kind: Integrand) -> Result<f64, String> {
let ku = crate::KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)?;
let kv = crate::KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)?;
let [u0, u1] = ku.domain();
let [v0, v1] = kv.domain();
let points = &face.surface.control_points;
let p00 = points[0][0].point()?;
let p10 = points[1][0].point()?;
let p01 = points[0][1].point()?;
let du = p10.sub(p00).scale(1.0 / (u1 - u0));
let dv = p01.sub(p00).scale(1.0 / (v1 - v0));
let weighted_normal = du.cross(dv);
let g = |u: f64, v: f64| {
let point = p00.add(du.scale(u - u0)).add(dv.scale(v - v0));
integrand_value(kind, point, weighted_normal)
};
let inner = |u: f64, v: f64| {
let half = (u - u0) * 0.5;
let middle = (u + u0) * 0.5;
let mut sum = 0.0;
for index in 0..GAUSS_X.len() {
sum += GAUSS_W[index] * g(middle + half * GAUSS_X[index], v);
}
sum * half
};
let mut total = 0.0;
for loop_record in &face.loops {
for coedge in &loop_record.coedges {
for pair in curve_breaks(&coedge.pcurve)?.windows(2) {
let half = (pair[1] - pair[0]) * 0.5;
let middle = (pair[1] + pair[0]) * 0.5;
for index in 0..GAUSS_X.len() {
let parameter = middle + half * GAUSS_X[index];
let (point, tangent) = coedge.pcurve.deriv1(parameter)?;
total += GAUSS_W[index] * half * inner(point.x, point.y) * tangent.y;
}
}
}
}
Ok(total)
}
pub(super) fn face_moment(face: &FaceRecord, kind: Integrand) -> Result<f64, String> {
if is_affine(&face.surface)? {
return affine_moment(face, kind);
}
if let Some(values) = biperiodic_band_integral(face, &[kind])? {
return Ok(values[0]);
}
if is_untrimmed(face)? {
integrate_untrimmed(face, kind)
} else {
integrate_trimmed(face, kind)
}
}
pub(super) fn mat3_mul(a: [[f64; 3]; 3], b: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
let mut r = [[0.0f64; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
r[i][j] += a[i][k] * b[k][j];
}
}
}
r
}
pub(super) fn mat3_transpose(a: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
let mut r = [[0.0f64; 3]; 3];
for i in 0..3 {
for j in 0..3 {
r[i][j] = a[j][i];
}
}
r
}
pub(super) fn jacobi_eigen_symmetric_3x3(matrix: [[f64; 3]; 3]) -> ([f64; 3], [[f64; 3]; 3]) {
let mut a = [
[matrix[0][0], 0.0, 0.0],
[0.0, matrix[1][1], 0.0],
[0.0, 0.0, matrix[2][2]],
];
a[0][1] = 0.5 * (matrix[0][1] + matrix[1][0]);
a[1][0] = a[0][1];
a[0][2] = 0.5 * (matrix[0][2] + matrix[2][0]);
a[2][0] = a[0][2];
a[1][2] = 0.5 * (matrix[1][2] + matrix[2][1]);
a[2][1] = a[1][2];
let mut v = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let scale = a[0][0].abs() + a[1][1].abs() + a[2][2].abs() + 1.0;
for _sweep in 0..64 {
let pairs = [(0usize, 1usize), (0, 2), (1, 2)];
let (mut p, mut q, mut best) = (0usize, 1usize, 0.0f64);
for &(i, j) in &pairs {
if a[i][j].abs() > best {
best = a[i][j].abs();
p = i;
q = j;
}
}
if best <= 1e-18 * scale {
break;
}
let theta = (a[q][q] - a[p][p]) / (2.0 * a[p][q]);
let t = if theta == 0.0 {
1.0
} else {
theta.signum() / (theta.abs() + (theta * theta + 1.0).sqrt())
};
let c = 1.0 / (t * t + 1.0).sqrt();
let s = t * c;
let mut j = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
j[p][p] = c;
j[q][q] = c;
j[p][q] = s;
j[q][p] = -s;
a = mat3_mul(mat3_transpose(j), mat3_mul(a, j));
v = mat3_mul(v, j);
}
([a[0][0], a[1][1], a[2][2]], v)
}
pub(super) fn principal_frame(inertia: [[f64; 3]; 3]) -> ([f64; 3], [[f64; 3]; 3]) {
let (values, vectors) = jacobi_eigen_symmetric_3x3(inertia);
let mut order = [0usize, 1, 2];
order.sort_by(|&a, &b| values[a].total_cmp(&values[b]));
let mut moments = [0.0f64; 3];
let mut axes = [[0.0f64; 3]; 3];
for (slot, &k) in order.iter().enumerate() {
moments[slot] = values[k];
let mut axis = [vectors[0][k], vectors[1][k], vectors[2][k]];
let length = (axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]).sqrt();
if length > 0.0 {
axis = [axis[0] / length, axis[1] / length, axis[2] / length];
}
axes[slot] = axis;
}
let cross = [
axes[0][1] * axes[1][2] - axes[0][2] * axes[1][1],
axes[0][2] * axes[1][0] - axes[0][0] * axes[1][2],
axes[0][0] * axes[1][1] - axes[0][1] * axes[1][0],
];
let det = cross[0] * axes[2][0] + cross[1] * axes[2][1] + cross[2] * axes[2][2];
if det < 0.0 {
axes[2] = [-axes[2][0], -axes[2][1], -axes[2][2]];
}
(moments, axes)
}
pub fn solid_mass_properties_full(solid: &BrepSolid) -> Result<FullMassProperties, String> {
let base = solid_mass_properties(solid)?;
const MOMENT_KINDS: [Integrand; 9] = [
Integrand::MomentX,
Integrand::MomentY,
Integrand::MomentZ,
Integrand::SecondXX,
Integrand::SecondYY,
Integrand::SecondZZ,
Integrand::ProductXY,
Integrand::ProductXZ,
Integrand::ProductYZ,
];
let mut moments = [0.0f64; 3];
let mut seconds = [0.0f64; 3];
let mut products = [0.0f64; 3];
for shell in &solid.shells {
for face in &shell.faces {
let results = if !is_affine(&face.surface)? && !is_untrimmed(face)? {
integrate_trimmed_multi(face, &MOMENT_KINDS)?
} else {
MOMENT_KINDS
.iter()
.map(|kind| face_moment(face, *kind))
.collect::<Result<Vec<_>, _>>()?
};
moments[0] += results[0];
moments[1] += results[1];
moments[2] += results[2];
seconds[0] += results[3];
seconds[1] += results[4];
seconds[2] += results[5];
products[0] += results[6];
products[1] += results[7];
products[2] += results[8];
}
}
let volume = base.volume;
if volume.abs() <= 1e-30 {
return Err("solid_mass_properties_full: non-positive volume".into());
}
let centroid = Vec3::new(
moments[0] / volume,
moments[1] / volume,
moments[2] / volume,
);
let ixx =
seconds[1] + seconds[2] - volume * (centroid.y * centroid.y + centroid.z * centroid.z);
let iyy =
seconds[0] + seconds[2] - volume * (centroid.x * centroid.x + centroid.z * centroid.z);
let izz =
seconds[0] + seconds[1] - volume * (centroid.x * centroid.x + centroid.y * centroid.y);
let ixy = -(products[0] - volume * centroid.x * centroid.y);
let ixz = -(products[1] - volume * centroid.x * centroid.z);
let iyz = -(products[2] - volume * centroid.y * centroid.z);
let inertia = [[ixx, ixy, ixz], [ixy, iyy, iyz], [ixz, iyz, izz]];
let (principal_moments, principal_axes) = principal_frame(inertia);
Ok(FullMassProperties {
surface_area: base.surface_area,
volume,
centroid,
inertia,
principal_moments,
principal_axes,
})
}