void Mesher::eval_zero_crossings(Vec3f* v0, Vec3f* v1, unsigned count) { float p[count]; for (unsigned i=0; i < count; ++i) p[i] = 0.5; float step = 0.25; Region dummy; dummy.X = ex; dummy.Y = ey; dummy.Z = ez; dummy.voxels = count; for (int iteration=0; iteration < 8; ++iteration) { // Load new data into the x, y, z arrays. for (unsigned i=0; i < count; i++) { dummy.X[i] = v0[i][0] * (1 - p[i]) + v1[i][0] * p[i]; dummy.Y[i] = v0[i][1] * (1 - p[i]) + v1[i][1] * p[i]; dummy.Z[i] = v0[i][2] * (1 - p[i]) + v1[i][2] * p[i]; } float* out = eval_r(tree, dummy); for (unsigned i=0; i < count; i++) if (out[i] < 0) p[i] += step; else if (out[i] > 0) p[i] -= step; step /= 2; } }
static void region8(MathTree* tree, Region region, uint8_t** img) { float *X = malloc(region.voxels*sizeof(float)), *Y = malloc(region.voxels*sizeof(float)), *Z = malloc(region.voxels*sizeof(float)); // Copy the X, Y, Z vectors into a flattened matrix form. int q = 0; for (int k = region.nk - 1; k >= 0; --k) { for (int j = 0; j < region.nj; ++j) { for (int i = 0; i < region.ni; ++i) { X[q] = region.X[i]; Y[q] = region.Y[j]; Z[q] = region.Z[k]; q++; } } } region.X = X; region.Y = Y; region.Z = Z; float* result = eval_r(tree, region); // Free the allocated matrices free(X); free(Y); free(Z); for (int k = region.nk - 1; k >= 0; --k) { uint8_t L = region.L[k+1] >> 8; for (int j = 0; j < region.nj; ++j) { int row = j + region.jmin; for (int i = 0; i < region.ni; ++i) { int col = i + region.imin; if (*(result++) < 0 && img[row][col] < L) { img[row][col] = L; } } } } }
// Evaluates a region voxel-by-voxel, storing the output in the data // member of the tristate struct. bool Mesher::load_packed(const Region& r) { // Only load the packed matrix if we have few enough voxels. const unsigned voxels = (r.ni+1) * (r.nj+1) * (r.nk+1); if (voxels >= MIN_VOLUME) return false; // We've already run interval evaluation for this region // (at the beginning of triangulate_region), so here we'll // just disable inactive nodes. disable_nodes(tree); // Flatten a 3D region into a 1D list of points that // touches every point in the region, one by one. int q = 0; for (unsigned k=0; k <= r.nk; ++k) { for (unsigned j=0; j <= r.nj; ++j) { for (unsigned i=0; i <= r.ni; ++i) { X[q] = r.X[i]; Y[q] = r.Y[j]; Z[q] = r.Z[k]; q++; } } } // Make a dummy region that has the newly-flattened point arrays as the // X, Y, Z coordinate data arrays (so that we can run eval_r on it). packed.imin = r.imin; packed.jmin = r.jmin; packed.kmin = r.kmin; packed.ni = r.ni; packed.nj = r.nj; packed.nk = r.nk; packed.X = X; packed.Y = Y; packed.Z = Z; packed.voxels = voxels; // Run eval_r and copy the data out memcpy(data, eval_r(tree, packed), voxels * sizeof(float)); has_data = true; return true; }
// Estimate the normals of a set of points. std::list<Vec3f> Mesher::get_normals(const std::list<Vec3f>& points) { // Find epsilon as the single shortest side length divided by 10. float epsilon = INFINITY; auto j = points.begin(); j++; for (auto i = points.begin(); j != points.end(); ++i, ++j) { if (j != points.end()) { auto d = *j - *i; epsilon = fmin(epsilon, d.norm() / 100.0f); } } // We'll be evaluating a dummy region to numerically estimate gradients Region dummy; dummy.voxels = points.size() * 7; if (dummy.voxels >= MIN_VOLUME) std::cerr << "Error: too many normals to calculate at once!" << std::endl; dummy.X = nx; dummy.Y = ny; dummy.Z = nz; // Load position data into the dummy region int i=0; for (auto v : points) { dummy.X[i] = v[0]; dummy.X[i+1] = v[0] + epsilon; dummy.X[i+2] = v[0]; dummy.X[i+3] = v[0]; dummy.X[i+4] = v[0] - epsilon; dummy.X[i+5] = v[0]; dummy.X[i+6] = v[0]; dummy.Y[i] = v[1]; dummy.Y[i+1] = v[1]; dummy.Y[i+2] = v[1] + epsilon; dummy.Y[i+3] = v[1]; dummy.Y[i+4] = v[1]; dummy.Y[i+5] = v[1] - epsilon; dummy.Y[i+6] = v[1]; dummy.Z[i] = v[2]; dummy.Z[i+1] = v[2]; dummy.Z[i+2] = v[2]; dummy.Z[i+3] = v[2] + epsilon; dummy.Z[i+4] = v[2]; dummy.Z[i+5] = v[2]; dummy.Z[i+6] = v[2] - epsilon; i += 7; } float* out = eval_r(tree, dummy); // Extract normals from the evaluated data. std::list<Vec3f> normals; i = 0; for (auto v : points) { const float dx = (out[i+1] - out[i]) - (out[i+4] - out[i]); const float dy = (out[i+2] - out[i]) - (out[i+5] - out[i]); const float dz = (out[i+3] - out[i]) - (out[i+6] - out[i]); normals.push_back(Vec3f(dx, dy, dz).normalized()); i += 7; } return normals; }
_STATIC_ void triangulate_voxel(MathTree* tree, const Region r, float** const verts, unsigned* const count, unsigned* const allocated, Region packed, const float* data) { // If we can calculate all of the points in this region with a single // eval_r call, then do so. This large chunk will be used in future // recursive calls to make things more efficient. const unsigned voxels = (r.ni+1)*(r.nj+1)*(r.nk+1); if (!data && voxels < MIN_VOLUME) { packed = r; // Copy the X, Y, Z vectors into a flattened matrix form. packed.X = malloc(voxels*sizeof(float)); packed.Y = malloc(voxels*sizeof(float)); packed.Z = malloc(voxels*sizeof(float)); int q = 0; for (int k = 0; k <= r.nk; ++k) { for (int j = 0; j <= r.nj; ++j) { for (int i = 0; i <= r.ni; ++i) { packed.X[q] = r.X[i]; packed.Y[q] = r.Y[j]; packed.Z[q] = r.Z[k]; q++; } } } // Update the voxel count packed.voxels = voxels; data = eval_r(tree, packed); // Free the allocated matrices free(packed.X); free(packed.Y); free(packed.Z); } // If we have greater than one voxel, subdivide and recurse. if (r.voxels > 1) { Region octants[8]; const uint8_t split = octsect(r, octants); for (int i=0; i < 8; ++i) { if (split & (1 << i)) { triangulate_voxel(tree, octants[i], verts, count, allocated, packed, data); } } return; } // Find corner distance values for this voxel float d[8]; for (int i=0; i < 8; ++i) { // Figure out where this bit of data lives in the larger eval_r array. const unsigned index = (r.imin - packed.imin + ((i & 4) ? r.ni : 0)) + (r.jmin - packed.jmin + ((i & 2) ? r.nj : 0)) * (packed.ni+1) + (r.kmin - packed.kmin + ((i & 1) ? r.nk : 0)) * (packed.ni+1) * (packed.nj+1); d[i] = data[index]; } // Loop over the six tetrahedra that make up a voxel cell for (int t=0; t < 6; ++t) { // Find vertex positions for this tetrahedron uint8_t vertices[] = {0, 7, VERTEX_LOOP[t], VERTEX_LOOP[t+1]}; // Figure out which of the sixteen possible combinations // we're currently experiencing. uint8_t lookup = 0; for (int v=3; v>=0; --v) { lookup = (lookup << 1) + (d[vertices[v]] < 0); } // Iterate over (up to) two triangles in this tetrahedron for (int i=0; i < 2; ++i) { if (EDGE_MAP[lookup][i][0][0] == -1) break; // Do we need to allocate more space for incoming vertices? // If so, double the buffer size. if ((*count) + 9 >= (*allocated)) { *allocated *= 2; *verts = realloc(*verts, sizeof(float)*(*allocated)); } // ...and insert vertices into the mesh. for (int v=0; v < 3; ++v) { const Vec3f vertex = zero_crossing(d, vertices[EDGE_MAP[lookup][i][v][0]], vertices[EDGE_MAP[lookup][i][v][1]]); (*verts)[(*count)++] = vertex.x * (r.X[1] - r.X[0]) + r.X[0]; (*verts)[(*count)++] = vertex.y * (r.Y[1] - r.Y[0]) + r.Y[0]; (*verts)[(*count)++] = vertex.z * (r.Z[1] - r.Z[0]) + r.Z[0]; } } } }