Browse Source

feat(mesh): implement and verify surface area and volume integration

- Implement robust surface area and volume computation for polymesh_t
- Support arbitrary polygonal faces (triangles, quads, etc.) via fan triangulation
- Add vector math utilities: cross, dot, norm, and signed volume
- Validate algorithm correctness using unit cube test case
- Ensure right-hand rule compliance for accurate volume sign

Test: Added test case with unit cube (8 vertices, 6 quads)
Expected: area = 6.0, volume = 1.0 → actual results match
feat-integrator
mckay 2 weeks ago
parent
commit
b91ef3fe45
  1. 104
      surface_integral/interface/mesh_algorithm.hpp
  2. 37
      surface_integral/test/test_mesh.cpp
  3. 8
      surface_integral/xmake.lua

104
surface_integral/interface/mesh_algorithm.hpp

@ -0,0 +1,104 @@
#include <cmath>
#include <cstdio>
#include <solve.h> // polymesh_t
// Vector subtraction: a - b
inline vector3d vec3_sub(const vector3d& a, const vector3d& b) {
return { a.x - b.x, a.y - b.y, a.z - b.z };
}
// Vector cross product: a × b
inline vector3d vec3_cross(const vector3d& a, const vector3d& b) {
return {
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
};
}
// Vector dot product: a · b
inline double vec3_dot(const vector3d& a, const vector3d& b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
// Vector length: ||a||
inline double vec3_norm(const vector3d& a) {
return std::sqrt(a.x*a.x + a.y*a.y + a.z*a.z);
}
// Compute triangle area: 0.5 * || (b-a) × (c-a) ||
inline double triangle_area(const vector3d& a, const vector3d& b, const vector3d& c) {
vector3d ab = vec3_sub(b, a);
vector3d ac = vec3_sub(c, a);
vector3d cross_product = vec3_cross(ab, ac);
return 0.5 * vec3_norm(cross_product);
}
// Compute triangle signed volume contribution (w.r.t origin): (a · (b × c)) / 6
inline double triangle_signed_volume(const vector3d& a, const vector3d& b, const vector3d& c) {
vector3d cross_product = vec3_cross(b, c);
return vec3_dot(a, cross_product) / 6.0;
}
// Compute polygon face area (triangulate as fan)
double polygon_area(const vector3d* vertices, const uint32_t* face_indices, uint32_t n) {
if (n < 3) return 0.0;
double area = 0.0;
const vector3d& v0 = vertices[face_indices[0]];
for (uint32_t i = 1; i < n - 1; ++i) {
const vector3d& v1 = vertices[face_indices[i]];
const vector3d& v2 = vertices[face_indices[i + 1]];
area += triangle_area(v0, v1, v2);
}
return area;
}
// Compute polygon signed volume contribution (sum of triangle contributions)
double polygon_signed_volume(const vector3d* vertices, const uint32_t* face_indices, uint32_t n) {
if (n < 3) return 0.0;
double volume = 0.0;
const vector3d& v0 = vertices[face_indices[0]];
for (uint32_t i = 1; i < n - 1; ++i) {
const vector3d& v1 = vertices[face_indices[i]];
const vector3d& v2 = vertices[face_indices[i + 1]];
volume += triangle_signed_volume(v0, v1, v2);
}
return volume;
}
// 🟩 Main: compute total mesh surface area
double compute_surface_area(const polymesh_t* mesh) {
if (!mesh || !mesh->vertices || !mesh->faces || !mesh->vertex_counts) {
return 0.0;
}
double total_area = 0.0;
const uint32_t* face_ptr = mesh->faces; // current index into faces array
for (uint32_t i = 0; i < mesh->num_faces; ++i) {
uint32_t n = mesh->vertex_counts[i];
total_area += polygon_area(mesh->vertices, face_ptr, n);
face_ptr += n; // move to the next face's start index
}
return total_area;
}
// 🟦 Main: compute total mesh volume (requires closed mesh with outward normals)
double compute_volume(const polymesh_t* mesh) {
if (!mesh || !mesh->vertices || !mesh->faces || !mesh->vertex_counts) {
return 0.0;
}
double total_volume = 0.0;
const uint32_t* face_ptr = mesh->faces;
for (uint32_t i = 0; i < mesh->num_faces; ++i) {
uint32_t n = mesh->vertex_counts[i];
total_volume += polygon_signed_volume(mesh->vertices, face_ptr, n);
face_ptr += n;
}
return std::abs(total_volume); // ensure positive volume
}

37
surface_integral/test/test_mesh.cpp

@ -0,0 +1,37 @@
#include <iostream>
#include <mesh_algorithm.hpp>
int main() {
// 8 vertices: unit cube
vector3d verts[8] = {
{0,0,0}, {1,0,0}, {1,1,0}, {0,1,0},
{0,0,1}, {1,0,1}, {1,1,1}, {0,1,1}
};
// 6 faces, each with 4 vertices (counter-clockwise order when viewed from outside)
uint32_t face_data[] = {
0,1,2,3, // bottom face
7,6,5,4, // top face (note CCW when seen from outside)
0,4,5,1, // front face
1,5,6,2, // right face
3,2,6,7, // back face
0,3,7,4 // left face
};
uint32_t vertex_counts[6] = {4, 4, 4, 4, 4, 4};
polymesh_t mesh;
mesh.vertices = verts;
mesh.faces = face_data;
mesh.vertex_counts = vertex_counts;
mesh.num_vertices = 8;
mesh.num_faces = 6;
double area = compute_surface_area(&mesh);
double volume = compute_volume(&mesh);
std::cout << "area: " << area << std::endl; // expected: 6
std::cout << "volume: " << volume << std::endl; // expected: 1
return 0;
}

8
surface_integral/xmake.lua

@ -12,3 +12,11 @@ target("newton_method_test")
add_files("test/SurfaceIntegrator_test.cpp")
add_packages("gtest")
target_end()
target("mesh_test")
set_kind("binary")
add_deps("surface_integrator")
add_deps("frontend") -- data structures from frontend, like polygon_mesh_t
add_files("test/test_mesh.cpp")
target_end()

Loading…
Cancel
Save