This code is used to create a cartoon outline effect for any unanimated mesh (animated mesh support coming up soon). It works by moving the vertices out by their normals, and flipping the faces.
Depending on the mesh, it may take a while to combine duplicate vertices (I'm not terribly good at optimization). Without it though, it ends up looking like this:
Animated mesh support coming up as soon as I get the hang of the ISkinnedMesh interface. Anyway, here's the code:
Code: Select all
struct indexRedirect {
int oldIndex;
int newIndex;
};
struct vertCombine {
irr::core::vector3df totalPos;
irr::core::vector3df totalNormal;
int count;
};
// Adds a toon outline to a mesh. Works by copying the mesh, and expanding by the normals. It then flips the faces.
irr::scene::IMesh* createToonOutlineMesh(irr::scene::IMesh* mesh, float size, irr::video::SColor color) {
irr::scene::SMesh* toonMesh = new irr::scene::SMesh();
// Copy the mesh buffers
for (int i = 0; i < mesh->getMeshBufferCount(); i++) {
irr::scene::SMeshBuffer* sourceBuffer = (irr::scene::SMeshBuffer*)mesh->getMeshBuffer(i);
irr::scene::SMeshBuffer* destBuffer = new irr::scene::SMeshBuffer();
irr::video::S3DVertex* copyVertices = new irr::video::S3DVertex[sourceBuffer->getVertexCount()];
for (int j = 0; j < sourceBuffer->getVertexCount(); j++) {
copyVertices[j] = sourceBuffer->Vertices[j];
}
irr::u16* copyIndices = new irr::u16[sourceBuffer->getIndexCount()];
for (int j = 0; j < sourceBuffer->getIndexCount(); j++) {
copyIndices[j] = sourceBuffer->Indices[j];
}
// Find duplicate vertices, then add an indexRedirect so indices can be updated and the vertices can later be collapsed
irr::video::S3DVertex* sourceVertices = (irr::video::S3DVertex*)sourceBuffer->getVertices();
vertCombine* combiners = new vertCombine[sourceBuffer->getVertexCount()];
irr::core::array<indexRedirect> redirects;
bool *check = new bool[sourceBuffer->getVertexCount()];
for (int j = 0; j < sourceBuffer->getVertexCount(); j++) {
check[j] = true;
combiners[j].totalPos = sourceVertices[j].Pos;
combiners[j].totalNormal = sourceVertices[j].Normal;
combiners[j].count = 1;
}
for (int j = 0; j < sourceBuffer->getVertexCount(); j++) {
if (check[j]) {
// Check for vertices that are very close
for (int k = j; k < sourceBuffer->getVertexCount(); k++) {
if (check[k]) {
if (sourceVertices[j].Pos.getDistanceFromSQ(sourceVertices[k].Pos) <= irr::core::ROUNDING_ERROR_f32*irr::core::ROUNDING_ERROR_f32) {
check[k] = false;
indexRedirect redir;
redir.newIndex = j;
redir.oldIndex = k;
redirects.push_back(redir);
}
}
}
}
}
delete [] check;
// Combine vertices
for (int j = 0; j < redirects.size(); j++) {
combiners[redirects[j].newIndex].totalPos += sourceVertices[redirects[j].oldIndex].Pos;
combiners[redirects[j].newIndex].totalNormal += sourceVertices[redirects[j].oldIndex].Normal;
combiners[redirects[j].newIndex].count++;
}
for (int j = 0; j < redirects.size(); j++) {
copyVertices[redirects[j].newIndex].Pos = combiners[redirects[j].newIndex].totalPos / combiners[redirects[j].newIndex].count;
copyVertices[redirects[j].newIndex].Normal = combiners[redirects[j].newIndex].totalNormal / combiners[redirects[j].newIndex].count;
copyVertices[redirects[j].newIndex].Normal.normalize();
}
// After combining the vertices, fix indices so they no longer reference dead vertices
for (int j = 0; j < redirects.size(); j++) {
for (int k = 0; k < sourceBuffer->getIndexCount(); k++) {
if (copyIndices[k] == redirects[j].oldIndex) {
copyIndices[k] = redirects[j].newIndex;
}
}
}
delete [] combiners;
// Now expand the mesh, moving the vertices along their normals
for (int j = 0; j < sourceBuffer->getVertexCount(); j++) {
copyVertices[j].Color = color;
copyVertices[j].Pos += copyVertices[j].Normal*size;
}
destBuffer->append(copyVertices,sourceBuffer->getVertexCount(),copyIndices,sourceBuffer->getIndexCount());
destBuffer->recalculateBoundingBox();
// Now set the material
destBuffer->Material = destBuffer->Material;
destBuffer->Material.Lighting = false;
destBuffer->Material.BackfaceCulling = false;
destBuffer->Material.FrontfaceCulling = true;
destBuffer->Material.DiffuseColor = color;
destBuffer->Material.ColorMaterial = irr::video::ECM_DIFFUSE_AND_AMBIENT;
destBuffer->Material.TextureLayer[0].Texture = 0;
toonMesh->addMeshBuffer(destBuffer);
}
toonMesh->recalculateBoundingBox();
return (irr::scene::IMesh*)toonMesh;
}