• ### Announcements

GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Followers 0

# A C++ code to smooth (and fix cracks in) meshes generated by the standard, original Marching Cubes algorithm

## 6 posts in this topic

Nice work! This could surely come in handy at some point.

1

##### Share on other sites

Yes, I am using the plain vanilla, ancient, original MC that produces topological inconsistencies (writing that makes it seem worse than it is, because, really, the cracks generally seem to be just that; cracks, not gaping holes; each boundary consists of an even number of edges where the majority of the angle is distributed amongst all but two of the vertices) -- I've once tried the version with topological guarantees / case ambiguity resolution by Lewiner, et al. back in the mid-200x's, but I went with the version from Bourke's site in the end because it does a relatively decent job with relatively minimal headache.

I updated the first post to indicate that I'm using the standard, original MC algorithm (see: P. Bourke's 'Polygonising a scalar field').

If anyone has a full, cost-free, public domain C++ implementation of an adaptive MC algorithm to share, I'd be more than happy to absorb it into my toolkit -- perhaps GD could consider the possibility of adding in a 'recipes' section of the site to gather all of these kinds of things into one nicely organized spot. Apparently I implemented one when I was experimenting back in the mid-200x's and I forgot.

In any case, the major point of the post was mesh smoothing -- if anyone has any kind of implementation of Taubin smoothing that uses 'Fujiwara' (scale-dependent) or curvature normal (cotan) weighting, but doesn't destroy the mesh, that would be nice too. I've had no luck with these; the literature is abundant (to the point where it's conflicting).

Edited by taby
0

##### Share on other sites

Nice work! This could surely come in handy at some point.

Thank you. I hope it comes it handy.

The paper 'Geometric Signal Processing on Polygonal Meshes' by G. Taubin mentions a few edge weight schemes:

- constant unit weight: w_ij = 1

- inverse edge length weight: w_ij = 1 / |e_ij|

- curvature normal-esque (cotan) weight: w_ij = 1 / tan(theta_ij0) + 1 / tan(theta_ij1)

The code in the zip file from the first post uses the constant unit weight scheme (see indexed_mesh::laplace_smooth()).

I didn't put the other schemes into the code zip file because I'm not sure if I'm implementing them correctly, but I'll include them here in case anyone wishes to play with them to see if/where I'm going wrong.

Here's the code to replace the laplace_smooth function with the inverse edge length weight scheme:

void indexed_mesh::inverse_edge_length_smooth(const float scale)
{
vector<vertex_3> displacements(vertices.size(), vertex_3(0, 0, 0));

// Get per-vertex displacement.
for(size_t i = 0; i < vertices.size(); i++)
{
// Skip rogue vertices (which were probably made rogue during a previous
// attempt to fix mesh cracks).
if(0 == vertex_to_vertex_indices[i].size())
continue;

vector<float> weights(vertex_to_vertex_indices[i].size(), 0.0f);

// Calculate weights based on inverse edge lengths.
for(size_t j = 0; j < vertex_to_vertex_indices[i].size(); j++)
{
size_t neighbour_j = vertex_to_vertex_indices[i][j];

float edge_length = vertices[i].distance(vertices[neighbour_j]);

if(0 == edge_length)
edge_length = numeric_limits<float>::epsilon();

weights[j] = 1.0f / edge_length;
}

// Normalize the weights so that they sum up to 1.
float s = 0;

for(size_t j = 0; j < weights.size(); j++)
s += weights[j];

if(0 == s)
s = numeric_limits<float>::epsilon();

for(size_t j = 0; j < weights.size(); j++)
weights[j] /= s;

// Sum the displacements.
for(size_t j = 0; j < vertex_to_vertex_indices[i].size(); j++)
{
size_t neighbour_j = vertex_to_vertex_indices[i][j];
displacements[i] += (vertices[neighbour_j] - vertices[i])*weights[j];
}
}

// Apply per-vertex displacement.
for(size_t i = 0; i < vertices.size(); i++)
vertices[i] += displacements[i]*scale;
}

Here's the code to replace the laplace_smooth function with the curvature normal-esque weight scheme (it works most times, but no guarantees):

void indexed_mesh::curvature_normal_smooth(const float scale)
{
vector<vertex_3> displacements(vertices.size(), vertex_3(0, 0, 0));

// Get per-vertex displacement.
for(size_t i = 0; i < vertices.size(); i++)
{
if(0 == vertex_to_vertex_indices[i].size())
continue;

vector<float> weights(vertex_to_vertex_indices[i].size(), 0.0f);

size_t angle_error = 0;

// For each vertex pair (ie. each edge),
// calculate weight based on the two opposing angles (ie. curvature normal scheme).
for(size_t j = 0; j < vertex_to_vertex_indices[i].size(); j++)
{
size_t angle_count = 0;

size_t neighbour_j = vertex_to_vertex_indices[i][j];

// Find out which two triangles are shared by the edge.
for(size_t k = 0; k < vertex_to_triangle_indices[i].size(); k++)
{
for(size_t l = 0; l < vertex_to_triangle_indices[neighbour_j].size(); l++)
{
size_t tri0_index = vertex_to_triangle_indices[i][k];
size_t tri1_index = vertex_to_triangle_indices[neighbour_j][l];

// This will occur twice per edge.
if(tri0_index == tri1_index)
{
// Find the third vertex in this triangle (the vertex that doesn't belong to the edge).
for(size_t m = 0; m < 3; m++)
{
// This will occur once per triangle.
if(triangles[tri0_index].vertex_indices[m] != i && triangles[tri0_index].vertex_indices[m] != neighbour_j)
{
size_t opp_vert_index = triangles[tri0_index].vertex_indices[m];

// Get the angle opposite of the edge.
vertex_3 a = vertices[i] - vertices[opp_vert_index];
vertex_3 b = vertices[neighbour_j] - vertices[opp_vert_index];
a.normalize();
b.normalize();

float dot = a.dot(b);

if(-1 > dot)
dot = -1;
else if(1 < dot)
dot = 1;

float angle = acosf(dot);

// Curvature normal weighting.
float slope = tanf(angle);

if(0 == slope)
slope = numeric_limits<float>::epsilon();

// Note: Some weights will be negative, due to obtuse triangles.
// You may wish to do weights[j] += fabsf(1.0f / slope); here.
weights[j] += 1.0f / slope;

angle_count++;

break;
}
}

// Since we found a triangle match, we can skip to the first vertex's next triangle.
break;
}
}
} // End of: Find out which two triangles are shared by the vertex pair.

if(angle_count != 2)
angle_error++;

} // End of: For each vertex pair (ie. each edge).

if(angle_error != 0)
{
cout << "Warning: Vertex " << i << " belongs to " << angle_error << " edges that do not belong to two triangles (" << vertex_to_vertex_indices[i].size() - angle_error << " edges were OK)." << endl;
cout << "Your mesh probably has cracks or holes in it." << endl;
}

// Normalize the weights so that they sum up to 1.
float s = 0;

// Note: Some weights will be negative, due to obtuse triangles.
// You may wish to do s += fabsf(weights[j]); here.
for(size_t j = 0; j < weights.size(); j++)
s += weights[j];

if(0 == s)
s = numeric_limits<float>::epsilon();

for(size_t j = 0; j < weights.size(); j++)
weights[j] /= s;

// Sum the displacements.
for(size_t j = 0; j < vertex_to_vertex_indices[i].size(); j++)
{
size_t neighbour_j = vertex_to_vertex_indices[i][j];

displacements[i] += (vertices[neighbour_j] - vertices[i])*weights[j];
}
}

// To do: Find out why there are cases where displacement is much, much, much larger than all edge lengths put together.

// Apply per-vertex displacement.
for(size_t i = 0; i < vertices.size(); i++)
vertices[i] += displacements[i]*scale;
}
Edited by taby
0

##### Share on other sites

Another simple smoothing algorithm to try is where the scale is multiplied by a factor related to each vertex's angle deficit (curvature), ie.:

vertex[i] += displacement[i]*scale*((2pi - total_angle[i])/(2pi)).

This should smooth out spikes and pits, but leave ridges, valleys and flat regions relatively untouched -- which seems ideal for the task at hand.

Not sure what the algorithm's called, nor how much it would differ from the curvature normal weighting, but will try later today.

Update:  implemented as suggested, it doesn't work entirely as expected, but will try other things related to it.

Edited by taby
1

## Create an account

Register a new account