use crate::vector3::Vector3;
#[derive(Debug, Clone)]
pub struct TriangularElement {
pub nodes: [Vector3<f64>; 3],
}
impl TriangularElement {
pub fn new(nodes: [Vector3<f64>; 3]) -> Self {
Self { nodes }
}
pub fn shape_functions(&self, xi: f64, eta: f64) -> [f64; 3] {
[1.0 - xi - eta, xi, eta]
}
pub fn shape_gradients(&self) -> [[f64; 2]; 3] {
let p0 = self.nodes[0];
let p1 = self.nodes[1];
let p2 = self.nodes[2];
let dx_dxi = p1.x - p0.x;
let dy_dxi = p1.y - p0.y;
let dx_deta = p2.x - p0.x;
let dy_deta = p2.y - p0.y;
let det_j = dx_dxi * dy_deta - dx_deta * dy_dxi;
let inv_j11 = dy_deta / det_j;
let inv_j12 = -dx_deta / det_j;
let inv_j21 = -dy_dxi / det_j;
let inv_j22 = dx_dxi / det_j;
let dn_dxi = [-1.0, 1.0, 0.0];
let dn_deta = [-1.0, 0.0, 1.0];
let mut gradients = [[0.0; 2]; 3];
for i in 0..3 {
gradients[i][0] = inv_j11 * dn_dxi[i] + inv_j12 * dn_deta[i];
gradients[i][1] = inv_j21 * dn_dxi[i] + inv_j22 * dn_deta[i];
}
gradients
}
pub fn area(&self) -> f64 {
let v1 = self.nodes[1] - self.nodes[0];
let v2 = self.nodes[2] - self.nodes[0];
0.5 * v1.cross(&v2).magnitude()
}
}
#[derive(Debug, Clone)]
pub struct TetrahedralElement {
pub nodes: [Vector3<f64>; 4],
}
impl TetrahedralElement {
pub fn new(nodes: [Vector3<f64>; 4]) -> Self {
Self { nodes }
}
pub fn shape_functions(&self, xi: f64, eta: f64, zeta: f64) -> [f64; 4] {
[1.0 - xi - eta - zeta, xi, eta, zeta]
}
pub fn shape_gradients(&self) -> [[f64; 3]; 4] {
let p0 = self.nodes[0];
let p1 = self.nodes[1];
let p2 = self.nodes[2];
let p3 = self.nodes[3];
let j = [
[p1.x - p0.x, p2.x - p0.x, p3.x - p0.x],
[p1.y - p0.y, p2.y - p0.y, p3.y - p0.y],
[p1.z - p0.z, p2.z - p0.z, p3.z - p0.z],
];
let det = j[0][0] * (j[1][1] * j[2][2] - j[1][2] * j[2][1])
- j[0][1] * (j[1][0] * j[2][2] - j[1][2] * j[2][0])
+ j[0][2] * (j[1][0] * j[2][1] - j[1][1] * j[2][0]);
let inv_j = [
[
(j[1][1] * j[2][2] - j[1][2] * j[2][1]) / det,
(j[0][2] * j[2][1] - j[0][1] * j[2][2]) / det,
(j[0][1] * j[1][2] - j[0][2] * j[1][1]) / det,
],
[
(j[1][2] * j[2][0] - j[1][0] * j[2][2]) / det,
(j[0][0] * j[2][2] - j[0][2] * j[2][0]) / det,
(j[0][2] * j[1][0] - j[0][0] * j[1][2]) / det,
],
[
(j[1][0] * j[2][1] - j[1][1] * j[2][0]) / det,
(j[0][1] * j[2][0] - j[0][0] * j[2][1]) / det,
(j[0][0] * j[1][1] - j[0][1] * j[1][0]) / det,
],
];
let dn_dxi = [-1.0, 1.0, 0.0, 0.0];
let dn_deta = [-1.0, 0.0, 1.0, 0.0];
let dn_dzeta = [-1.0, 0.0, 0.0, 1.0];
let mut gradients = [[0.0; 3]; 4];
#[allow(clippy::needless_range_loop)]
for i in 0..4 {
for j_idx in 0..3 {
gradients[i][j_idx] = inv_j[j_idx][0] * dn_dxi[i]
+ inv_j[j_idx][1] * dn_deta[i]
+ inv_j[j_idx][2] * dn_dzeta[i];
}
}
gradients
}
pub fn volume(&self) -> f64 {
let v1 = self.nodes[1] - self.nodes[0];
let v2 = self.nodes[2] - self.nodes[0];
let v3 = self.nodes[3] - self.nodes[0];
(1.0 / 6.0) * v1.cross(&v2).dot(&v3).abs()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_triangular_element() {
let nodes = [
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 1.0, 0.0),
];
let elem = TriangularElement::new(nodes);
assert!((elem.area() - 0.5).abs() < 1e-10);
let n = elem.shape_functions(1.0 / 3.0, 1.0 / 3.0);
for &val in &n {
assert!((val - 1.0 / 3.0).abs() < 1e-10);
}
}
#[test]
fn test_tetrahedral_element() {
let nodes = [
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 1.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
];
let elem = TetrahedralElement::new(nodes);
assert!((elem.volume() - 1.0 / 6.0).abs() < 1e-10);
let n = elem.shape_functions(0.25, 0.25, 0.25);
for &val in &n {
assert!((val - 0.25).abs() < 1e-10);
}
}
}