Skip to content

Interest in surface export functionality? #327

@nhemsley

Description

@nhemsley

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions