use crate::network::Vec3;
const WORLD_X: [f64; 3] = [1.0, 0.0, 0.0];
const WORLD_Y: [f64; 3] = [0.0, 1.0, 0.0];
const _WORLD_Z: [f64; 3] = [0.0, 0.0, 1.0];
pub fn length(v: Vec3) -> f64 {
(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
}
pub fn normalize(v: Vec3) -> Vec3 {
let mut u = v;
if u.iter().all(|&x| x == 0.0 || x.is_nan()) {
return [0.0; 3];
}
let l = length(u).max(1e-14);
u[0] /= l;
u[1] /= l;
u[2] /= l;
u
}
pub fn cross(a: Vec3, b: Vec3) -> Vec3 {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
pub fn dot(a: Vec3, b: Vec3) -> f64 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
pub fn angle_vectors(a: Vec3, b: Vec3) -> f64 {
let na = normalize(a);
let nb = normalize(b);
dot(na, nb).clamp(-1.0, 1.0).acos()
}
pub fn normal_polygon(points: &[Vec3]) -> Vec3 {
if points.len() < 3 {
return [0.0; 3];
}
let mut n = [0.0; 3];
for i in 0..points.len() {
let j = (i + 1) % points.len();
let a = points[i];
let b = points[j];
let c = cross(a, b);
n[0] += c[0];
n[1] += c[1];
n[2] += c[2];
}
n
}
pub fn area_triangle(triangle: [Vec3; 3]) -> f64 {
0.5 * length(normal_triangle(triangle))
}
fn normal_triangle(triangle: [Vec3; 3]) -> Vec3 {
let line_a = [
triangle[0][0] - triangle[1][0],
triangle[0][1] - triangle[1][1],
triangle[0][2] - triangle[1][2],
];
let line_b = [
triangle[2][0] - triangle[1][0],
triangle[2][1] - triangle[1][1],
triangle[2][2] - triangle[1][2],
];
cross(line_a, line_b)
}
pub fn polygon_lcs(polygon: &[Vec3]) -> [[f64; 3]; 3] {
let w = normalize(normal_polygon(polygon));
let threshold = dot(w, WORLD_X).abs() > 0.999;
let vperp = if threshold { WORLD_Y } else { WORLD_X };
let mut v = cross(w, vperp);
v = normalize(v);
let mut u = cross(w, v);
if dot(u, vperp) < 0.0 {
u = [-u[0], -u[1], -u[2]];
}
u = normalize(u);
[u, v, w]
}
pub fn grad_area_triangle(a: Vec3, b: Vec3, c: Vec3) -> (Vec3, Vec3, Vec3) {
let u = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
let v = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
let n = cross(u, v);
let len = length(n).max(1e-14);
let nh = [n[0] / len, n[1] / len, n[2] / len];
let ga = [
0.5 * (v[1] * nh[2] - v[2] * nh[1]),
0.5 * (v[2] * nh[0] - v[0] * nh[2]),
0.5 * (v[0] * nh[1] - v[1] * nh[0]),
];
let gb = [
0.5 * (nh[1] * u[2] - nh[2] * u[1]),
0.5 * (nh[2] * u[0] - nh[0] * u[2]),
0.5 * (nh[0] * u[1] - nh[1] * u[0]),
];
let gc = [
0.5 * (u[1] * nh[2] - u[2] * nh[1]),
0.5 * (u[2] * nh[0] - u[0] * nh[2]),
0.5 * (u[0] * nh[1] - u[1] * nh[0]),
];
(ga, gb, gc)
}
pub fn polygon_planarity(polygon: &[Vec3]) -> f64 {
if polygon.len() < 3 {
return 0.0;
}
let n = normalize(normal_polygon(polygon));
let mut s = 0.0;
for i in 0..polygon.len() {
let j = (i + 1) % polygon.len();
let e = [
polygon[j][0] - polygon[i][0],
polygon[j][1] - polygon[i][1],
polygon[j][2] - polygon[i][2],
];
let el = length(e);
if el < 1e-14 {
continue;
}
let en = [e[0] / el, e[1] / el, e[2] / el];
s += dot(n, en).abs();
}
s
}
pub fn polygon_rectangular_deviation(polygon: &[Vec3]) -> f64 {
let n = polygon.len();
if n < 3 {
return 0.0;
}
let mut s = 0.0;
for i in 0..n {
let im = (i + n - 1) % n;
let ip = (i + 1) % n;
let mut e1 = [
polygon[i][0] - polygon[im][0],
polygon[i][1] - polygon[im][1],
polygon[i][2] - polygon[im][2],
];
let mut e2 = [
polygon[ip][0] - polygon[i][0],
polygon[ip][1] - polygon[i][1],
polygon[ip][2] - polygon[i][2],
];
let l1 = length(e1).max(1e-14);
let l2 = length(e2).max(1e-14);
e1 = [e1[0] / l1, e1[1] / l1, e1[2] / l1];
e2 = [e2[0] / l2, e2[1] / l2, e2[2] / l2];
let d = dot(e1, e2);
s += d * d;
}
s
}
pub fn grad_polygon_rectangular_wrt_nodes(
polygon: &[Vec3],
node_indices: &[usize],
) -> Vec<(usize, Vec3)> {
if polygon.len() < 3 {
return Vec::new();
}
let eps = 1e-8;
let base = polygon_rectangular_deviation(polygon);
let mut out = Vec::new();
for (vi, &node) in node_indices.iter().enumerate() {
let mut g = [0.0; 3];
for d in 0..3 {
let mut pert = polygon.to_vec();
pert[vi][d] += eps;
g[d] = (polygon_rectangular_deviation(&pert) - base) / eps;
}
out.push((node, g));
}
out
}
pub fn grad_polygon_planarity_wrt_nodes(
polygon: &[Vec3],
node_indices: &[usize],
) -> Vec<(usize, Vec3)> {
if polygon.len() < 3 {
return Vec::new();
}
let eps = 1e-8;
let base = polygon_planarity(polygon);
let mut out = Vec::new();
for (vi, &node) in node_indices.iter().enumerate() {
let mut g = [0.0; 3];
for d in 0..3 {
let mut pert = polygon.to_vec();
pert[vi][d] += eps;
g[d] = (polygon_planarity(&pert) - base) / eps;
}
out.push((node, g));
}
out
}
pub fn face_load_global(polygon: &[Vec3], face_load: Vec3) -> Vec3 {
let lcs = polygon_lcs(polygon);
mat_vec3_mul_rows(lcs, face_load)
}
fn mat_vec3_mul_rows(rows: [[f64; 3]; 3], v: Vec3) -> Vec3 {
[
v[0] * rows[0][0] + v[1] * rows[1][0] + v[2] * rows[2][0],
v[0] * rows[0][1] + v[1] * rows[1][1] + v[2] * rows[2][1],
v[0] * rows[0][2] + v[1] * rows[1][2] + v[2] * rows[2][2],
]
}
pub fn jacobian_face_load_global_wrt_polygon(
polygon: &[Vec3],
face_load: Vec3,
) -> Vec<[[f64; 3]; 3]> {
if polygon.len() < 3 {
return vec![[[0.0; 3]; 3]; polygon.len()];
}
let eps = 1e-8;
let mut jac = vec![[[0.0; 3]; 3]; polygon.len()];
for vi in 0..polygon.len() {
for d in 0..3 {
let mut plus = polygon.to_vec();
let mut minus = polygon.to_vec();
plus[vi][d] += eps;
minus[vi][d] -= eps;
let fp = face_load_global(&plus, face_load);
let fm = face_load_global(&minus, face_load);
for c in 0..3 {
jac[vi][c][d] = (fp[c] - fm[c]) / (2.0 * eps);
}
}
}
jac
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn face_load_global_jacobian_matches_fd() {
let poly = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.5, 1.0, 0.2]];
let load = [0.0, 0.0, -1.0];
let j = jacobian_face_load_global_wrt_polygon(&poly, load);
let eps = 1e-7;
for vi in 0..poly.len() {
for d in 0..3 {
let mut p = poly.to_vec();
p[vi][d] += eps;
let fp = face_load_global(&p, load);
let base = face_load_global(&poly, load);
for c in 0..3 {
let fd = (fp[c] - base[c]) / eps;
assert!(
(j[vi][c][d] - fd).abs() < 1e-5,
"vi={vi} c={c} d={d}: jac={} fd={}",
j[vi][c][d],
fd
);
}
}
}
}
}