-
Notifications
You must be signed in to change notification settings - Fork 439
Description
Hello,
I have recently implemented surface export of fluid surfaces to .stl files, it is in a tree with a bunch of other changes, related to a project I am working on. If there is interest in merging this with FluidX3D proper, I can look into extracting the functionality as a 'clean' branch.
This has helped greatly to view, and replay the output of a simulation, in a format, and size which is not so prohibitive. Output from a rtx 5060 5GB simulation, can be replayed in real-time. This can probably be extended to vorticity etc.
Currently it only works on one GPU, as stitching between GPU's requires extra changes. I would like to solve this problem somehow.
As it stands the algorithm does the following (if i remember correctly):
Count the number of triangles, on first frame, copy to host.
Set the triangle buffer to be 1.x multiple of this.
run the existing marching cubes as a kernel function, storing in the surface vertex buffer, with the triangle count.
copy to host, output to stl.
If the triangle count approaches a certain fraction of the triangle buffer, resize the buffer.
Would you be interested in integrating this into FluidX3D?
If so I can look at extracting this functionality in a form easily merged/rebased.
Thanks, Nick
relevant opencl kernel code:
#ifdef SURFACE_EXPORT
//================================================================================================
// SURFACE EXPORT
//================================================================================================
// Functions for exporting fluid surface mesh data for external visualization/processing
kernel void count_surface_triangles(const global float *phi, global uint *triangle_count
#ifdef SURFACE_EXPORT_IGNORE_BOUNDARIES
, const global uchar *flags
#endif
) {
const uxx n = get_global_id(0);
if(n >= def_N) return;
// Get 3D coordinates
const uint3 xyz = coordinates(n);
const uint x = xyz.x;
const uint y = xyz.y;
const uint z = xyz.z;
// Skip boundary cells
if(x >= def_Nx-1u || y >= def_Ny-1u || z >= def_Nz-1u) return;
// Get indices of 8 cube corners
const uxx x0 = (uxx)x, xp = (uxx)(x+1u);
const uxx y0 = (uxx)(y*def_Nx), yp = (uxx)((y+1u)*def_Nx);
const uxx z0 = (uxx)(z)*(uxx)(def_Ny*def_Nx), zp = (uxx)(z+1u)*(uxx)(def_Ny*def_Nx);
#ifdef SURFACE_EXPORT_IGNORE_BOUNDARIES
// Check if any of the 8 cube corners is a solid boundary cell
const uxx corners[8] = {
x0+y0+z0, xp+y0+z0, xp+y0+zp, x0+y0+zp,
x0+yp+z0, xp+yp+z0, xp+yp+zp, x0+yp+zp
};
for(uint i = 0u; i < 8u; i++) {
if(flags[corners[i]] & TYPE_S) return; // Skip this cube if any corner touches a solid boundary
}
#endif
float v[8];
v[0] = phi[x0+y0+z0]; v[1] = phi[xp+y0+z0];
v[2] = phi[xp+y0+zp]; v[3] = phi[x0+y0+zp];
v[4] = phi[x0+yp+z0]; v[5] = phi[xp+yp+z0];
v[6] = phi[xp+yp+zp]; v[7] = phi[x0+yp+zp];
// Check if cube intersects surface
float3 triangles[15];
const uint tn = marching_cubes(v, 0.5f, triangles);
if(tn > 0u) {
atomic_add(triangle_count, tn);
}
}
kernel void export_surface(const global float *phi, global float *vertices,
global uint *triangle_count, const ulong max_triangles
#ifdef SURFACE_EXPORT_IGNORE_BOUNDARIES
, const global uchar *flags
#endif
) {
const uxx n = get_global_id(0);
if(n >= def_N) return;
// Get 3D coordinates
const uint3 xyz = coordinates(n);
const uint x = xyz.x;
const uint y = xyz.y;
const uint z = xyz.z;
// Skip boundary cells
if(x >= def_Nx-1u || y >= def_Ny-1u || z >= def_Nz-1u) return;
// Get indices of 8 cube corners
const uxx x0 = (uxx)x, xp = (uxx)(x+1u);
const uxx y0 = (uxx)(y*def_Nx), yp = (uxx)((y+1u)*def_Nx);
const uxx z0 = (uxx)(z)*(uxx)(def_Ny*def_Nx), zp = (uxx)(z+1u)*(uxx)(def_Ny*def_Nx);
#ifdef SURFACE_EXPORT_IGNORE_BOUNDARIES
// Check if any of the 8 cube corners is a solid boundary cell
const uxx corners[8] = {
x0+y0+z0, xp+y0+z0, xp+y0+zp, x0+y0+zp,
x0+yp+z0, xp+yp+z0, xp+yp+zp, x0+yp+zp
};
for(uint i = 0u; i < 8u; i++) {
if(flags[corners[i]] & TYPE_S) return; // Skip this cube if any corner touches a solid boundary
}
#endif
float v[8];
v[0] = phi[x0+y0+z0]; v[1] = phi[xp+y0+z0];
v[2] = phi[xp+y0+zp]; v[3] = phi[x0+y0+zp];
v[4] = phi[x0+yp+z0]; v[5] = phi[xp+yp+z0];
v[6] = phi[xp+yp+zp]; v[7] = phi[x0+yp+zp];
// Get triangles from marching cubes
float3 triangles[15];
const uint tn = marching_cubes(v, 0.5f, triangles);
if(tn > 0u) {
// Atomically reserve space in output buffer
const uint base_index = atomic_add(triangle_count, tn);
// Check buffer overflow
if(base_index + tn > max_triangles) return;
// Write triangles to global memory
const float3 offset = (float3)((float)x, (float)y, (float)z);
for(uint i = 0u; i < tn; i++) {
const uint vertex_index = (base_index + i) * 9u;
const float3 p0 = triangles[3u*i] + offset;
const float3 p1 = triangles[3u*i+1u] + offset;
const float3 p2 = triangles[3u*i+2u] + offset;
// Write triangle vertices (9 floats per triangle)
vertices[vertex_index+0u] = p0.x;
vertices[vertex_index+1u] = p0.y;
vertices[vertex_index+2u] = p0.z;
vertices[vertex_index+3u] = p1.x;
vertices[vertex_index+4u] = p1.y;
vertices[vertex_index+5u] = p1.z;
vertices[vertex_index+6u] = p2.x;
vertices[vertex_index+7u] = p2.y;
vertices[vertex_index+8u] = p2.z;
}
}
}
#endif // SURFACE_EXPORT