// David Eberly, Geometric Tools, Redmond WA 98052 // Copyright (c) 1998-2020 // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt // https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt // Version: 4.0.2019.08.13 #pragma once #include #include #include #include namespace WwiseGTE { template class SurfaceExtractorMC : public MarchingCubes { public: // Construction and destruction. virtual ~SurfaceExtractorMC() { } SurfaceExtractorMC(Image3 const& image) : mImage(image) { } // Object copies are not allowed. SurfaceExtractorMC() = delete; SurfaceExtractorMC(SurfaceExtractorMC const&) = delete; SurfaceExtractorMC const& operator=(SurfaceExtractorMC const&) = delete; struct Mesh { // All members are set to zeros. Mesh() { std::array zero = { (Real)0, (Real)0, (Real)0 }; std::fill(vertices.begin(), vertices.end(), zero); } Topology topology; std::array, MAX_VERTICES> vertices; }; // Extract the triangle mesh approximating F = 0 for a single voxel. // The input function values must be stored as // F[0] = function(0,0,0), F[4] = function(0,0,1), // F[1] = function(1,0,0), F[5] = function(1,0,1), // F[2] = function(0,1,0), F[6] = function(0,1,1), // F[3] = function(1,1,0), F[7] = function(1,1,1). // Thus, F[k] = function(k & 1, (k & 2) >> 1, (k & 4) >> 2). // The return value is 'true' iff the F[] values are all nonzero. // If they are not, the returned 'mesh' has no vertices and no // triangles--as if F[] had all positive or all negative values. bool Extract(std::array const& F, Mesh& mesh) const { int entry = 0; for (int i = 0, mask = 1; i < 8; ++i, mask <<= 1) { if (F[i] < (Real)0) { entry |= mask; } else if (F[i] == (Real)0) { return false; } } mesh.topology = GetTable(entry); for (int i = 0; i < mesh.topology.numVertices; ++i) { int j0 = mesh.topology.vpair[i][0]; int j1 = mesh.topology.vpair[i][1]; Real corner0[3]; corner0[0] = static_cast(j0 & 1); corner0[1] = static_cast((j0 & 2) >> 1); corner0[2] = static_cast((j0 & 4) >> 2); Real corner1[3]; corner1[0] = static_cast(j1 & 1); corner1[1] = static_cast((j1 & 2) >> 1); corner1[2] = static_cast((j1 & 4) >> 2); Real invDenom = ((Real)1) / (F[j0] - F[j1]); for (int k = 0; k < 3; ++k) { Real numer = F[j0] * corner1[k] - F[j1] * corner0[k]; mesh.vertices[i][k] = numer * invDenom; } } return true; } // Extract the triangle mesh approximating F = 0 for all the voxels in // a 3D image. The input image must be stored in a 1-dimensional // array with lexicographical order; that is, image[i] corresponds to // voxel location (x,y,z) where i = x + bound0 * (y + bound1 * z). // The output 'indices' consists indices.size()/3 triangles, each a // triple of indices into 'vertices' bool Extract(Real level, std::vector>& vertices, std::vector& indices) const { vertices.clear(); indices.clear(); for (int z = 0; z + 1 < mImage.GetDimension(2); ++z) { for (int y = 0; y + 1 < mImage.GetDimension(1); ++y) { for (int x = 0; x + 1 < mImage.GetDimension(0); ++x) { std::array corners; mImage.GetCorners(x, y, z, corners); std::array F; for (int k = 0; k < 8; ++k) { F[k] = mImage[corners[k]] - level; } Mesh mesh; if (Extract(F, mesh)) { int vbase = static_cast(vertices.size()); for (int i = 0; i < mesh.topology.numVertices; ++i) { Vector3 position = mesh.vertices[i]; position[0] += static_cast(x); position[1] += static_cast(y); position[2] += static_cast(z); vertices.push_back(position); } for (int i = 0; i < mesh.topology.numTriangles; ++i) { for (int j = 0; j < 3; ++j) { indices.push_back(vbase + mesh.topology.itriple[i][j]); } } } else { vertices.clear(); indices.clear(); return false; } } } } return true; } // The extraction has duplicate vertices on edges shared by voxels. // This function will eliminate the duplication. void MakeUnique(std::vector>& vertices, std::vector& indices) const { std::vector> outVertices; std::vector outIndices; UniqueVerticesTriangles>(vertices, indices, outVertices, outIndices); vertices = std::move(outVertices); indices = std::move(outIndices); } // The extraction does not use any topological information about the // level surface. The triangles can be a mixture of clockwise-ordered // and counterclockwise-ordered. This function is an attempt to give // the triangles a consistent ordering by selecting a normal in // approximately the same direction as the average gradient at the // vertices (when sameDir is true), or in the opposite direction (when // sameDir is false). This might not always produce a consistent // order, but is fast. A consistent order can be computed if you // build a table of vertex, edge, and face adjacencies, but the // resulting data structure is somewhat expensive to process to // reorient triangles. void OrientTriangles(std::vector> const& vertices, std::vector& indices, bool sameDir) const { int const numTriangles = static_cast(indices.size() / 3); int* triangle = indices.data(); for (int t = 0; t < numTriangles; ++t, triangle += 3) { // Get triangle vertices. Vector3 v0 = vertices[triangle[0]]; Vector3 v1 = vertices[triangle[1]]; Vector3 v2 = vertices[triangle[2]]; // Construct triangle normal based on current orientation. Vector3 edge1 = v1 - v0; Vector3 edge2 = v2 - v0; Vector3 normal = Cross(edge1, edge2); // Get the image gradient at the vertices. Vector3 gradient0 = GetGradient(v0); Vector3 gradient1 = GetGradient(v1); Vector3 gradient2 = GetGradient(v2); // Compute the average gradient. Vector3 gradientAvr = (gradient0 + gradient1 + gradient2) / (Real)3; // Compute the dot product of normal and average gradient. Real dot = Dot(gradientAvr, normal); // Choose triangle orientation based on gradient direction. if (sameDir) { if (dot < (Real)0) { // Wrong orientation, reorder it. std::swap(triangle[1], triangle[2]); } } else { if (dot > (Real)0) { // Wrong orientation, reorder it. std::swap(triangle[1], triangle[2]); } } } } // Compute vertex normals for the mesh. void ComputeNormals(std::vector> const& vertices, std::vector const& indices, std::vector>& normals) const { // Maintain a running sum of triangle normals at each vertex. int const numVertices = static_cast(vertices.size()); normals.resize(numVertices); Vector3 zero = Vector3::Zero(); std::fill(normals.begin(), normals.end(), zero); int const numTriangles = static_cast(indices.size() / 3); int const* current = indices.data(); for (int i = 0; i < numTriangles; ++i) { int i0 = *current++; int i1 = *current++; int i2 = *current++; Vector3 v0 = vertices[i0]; Vector3 v1 = vertices[i1]; Vector3 v2 = vertices[i2]; // Construct triangle normal. Vector3 edge1 = v1 - v0; Vector3 edge2 = v2 - v0; Vector3 normal = Cross(edge1, edge2); // Maintain the sum of normals at each vertex. normals[i0] += normal; normals[i1] += normal; normals[i2] += normal; } // The normal vector storage was used to accumulate the sum of // triangle normals. Now these vectors must be rescaled to be // unit length. for (auto& normal : normals) { Normalize(normal); } } protected: Vector3 GetGradient(Vector3 position) const { int x = static_cast(std::floor(position[0])); if (x < 0 || x >= mImage.GetDimension(0) - 1) { return Vector3::Zero(); } int y = static_cast(std::floor(position[1])); if (y < 0 || y >= mImage.GetDimension(1) - 1) { return Vector3::Zero(); } int z = static_cast(std::floor(position[2])); if (z < 0 || z >= mImage.GetDimension(2) - 1) { return Vector3::Zero(); } position[0] -= static_cast(x); position[1] -= static_cast(y); position[2] -= static_cast(z); Real oneMX = (Real)1 - position[0]; Real oneMY = (Real)1 - position[1]; Real oneMZ = (Real)1 - position[2]; // Get image values at corners of voxel. std::array corners; mImage.GetCorners(x, y, z, corners); Real f000 = mImage[corners[0]]; Real f100 = mImage[corners[1]]; Real f010 = mImage[corners[2]]; Real f110 = mImage[corners[3]]; Real f001 = mImage[corners[4]]; Real f101 = mImage[corners[5]]; Real f011 = mImage[corners[6]]; Real f111 = mImage[corners[7]]; Vector3 gradient; Real tmp0 = oneMY * (f100 - f000) + position[1] * (f110 - f010); Real tmp1 = oneMY * (f101 - f001) + position[1] * (f111 - f011); gradient[0] = oneMZ * tmp0 + position[2] * tmp1; tmp0 = oneMX * (f010 - f000) + position[0] * (f110 - f100); tmp1 = oneMX * (f011 - f001) + position[0] * (f111 - f101); gradient[1] = oneMZ * tmp0 + position[2] * tmp1; tmp0 = oneMX * (f001 - f000) + position[0] * (f101 - f100); tmp1 = oneMX * (f011 - f010) + position[0] * (f111 - f110); gradient[2] = oneMY * tmp0 + position[1] * tmp1; return gradient; } Image3 const& mImage; }; }