396 lines
16 KiB
Plaintext
396 lines
16 KiB
Plaintext
#include <vector>
|
|
#include <list>
|
|
#include <set>
|
|
#include <limits>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cassert>
|
|
|
|
#include <osg/Geometry>
|
|
#include <osg/Array>
|
|
#include <osg/Notify>
|
|
#include <osgAnimation/MorphGeometry>
|
|
#include <osgUtil/MeshOptimizers>
|
|
|
|
#include "GeometryUniqueVisitor"
|
|
#include "TriangleMeshGraph"
|
|
|
|
|
|
// Smoothing steps:
|
|
//
|
|
// 1. compute the vertex/triangles graph
|
|
// 2. compute triangle normals (vertexTriangles::addTriangle)
|
|
// 3. determine *piecewise* one-ring for each *unique* vertex (TriangleMeshGraph::vertexOneRing)
|
|
// Each piece of the one-ring contains triangles that are neighbors and do not share a sharp edge
|
|
// 4. for each one-ring piece sum the triangle normals (TriangleMeshSmoother::computeVertexNormals)
|
|
// 5. if the vertex has been processed already: duplicate and update triangles
|
|
// otherwise set the normal
|
|
//
|
|
// **triangle normals are normalized but weighted by their area when cumulated over the ring**
|
|
|
|
class TriangleMeshSmoother {
|
|
public:
|
|
enum SmoothingMode {
|
|
recompute = 1 << 0,
|
|
diagnose = 1 << 1,
|
|
smooth_flipped = 1 << 2,
|
|
smooth_all = 1 << 3
|
|
};
|
|
|
|
|
|
class DuplicateVertex : public osg::ArrayVisitor {
|
|
public:
|
|
unsigned int _i;
|
|
unsigned int _end;
|
|
|
|
DuplicateVertex(unsigned int i): _i(i), _end(i)
|
|
{}
|
|
|
|
template <class ARRAY>
|
|
void apply_imp(ARRAY& array) {
|
|
_end = array.size();
|
|
array.push_back(array[_i]);
|
|
}
|
|
|
|
virtual void apply(osg::ByteArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::ShortArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::IntArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::UByteArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::UShortArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::UIntArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::FloatArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::DoubleArray& array) { apply_imp(array); }
|
|
|
|
virtual void apply(osg::Vec2Array& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec3Array& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec4Array& array) { apply_imp(array); }
|
|
|
|
virtual void apply(osg::Vec2bArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec3bArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec4bArray& array) { apply_imp(array); }
|
|
|
|
virtual void apply(osg::Vec2sArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec3sArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec4sArray& array) { apply_imp(array); }
|
|
|
|
virtual void apply(osg::Vec2iArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec3iArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec4iArray& array) { apply_imp(array); }
|
|
|
|
virtual void apply(osg::Vec2dArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec3dArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec4dArray& array) { apply_imp(array); }
|
|
|
|
virtual void apply(osg::Vec2ubArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec3ubArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec4ubArray& array) { apply_imp(array); }
|
|
|
|
virtual void apply(osg::Vec2usArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec3usArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec4usArray& array) { apply_imp(array); }
|
|
|
|
virtual void apply(osg::Vec2uiArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec3uiArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::Vec4uiArray& array) { apply_imp(array); }
|
|
|
|
virtual void apply(osg::MatrixfArray& array) { apply_imp(array); }
|
|
virtual void apply(osg::MatrixdArray& array) { apply_imp(array); }
|
|
};
|
|
|
|
public:
|
|
TriangleMeshSmoother(osg::Geometry& geometry, float creaseAngle, bool comparePosition=false, int mode=diagnose):
|
|
_geometry(geometry),
|
|
_creaseAngle(creaseAngle),
|
|
_graph(0),
|
|
_mode(mode)
|
|
{
|
|
if(!_geometry.getVertexArray() || !_geometry.getVertexArray()->getNumElements()) {
|
|
return;
|
|
}
|
|
|
|
osgUtil::SharedArrayOptimizer deduplicator;
|
|
deduplicator.findDuplicatedUVs(geometry);
|
|
|
|
// duplicate shared arrays as it isn't safe to duplicate vertices when arrays are shared.
|
|
if (geometry.containsSharedArrays()) {
|
|
geometry.duplicateSharedArrays();
|
|
}
|
|
|
|
if(!_geometry.getNormalArray() || _geometry.getNormalArray()->getNumElements() != _geometry.getVertexArray()->getNumElements()) {
|
|
_geometry.setNormalArray(new osg::Vec3Array(_geometry.getVertexArray()->getNumElements()), osg::Array::BIND_PER_VERTEX);
|
|
}
|
|
|
|
// build a unifier to consider deduplicated vertex indices
|
|
_graph = new TriangleMeshGraph(_geometry, comparePosition);
|
|
|
|
unsigned int nbTriangles = 0;
|
|
for(unsigned int i = 0 ; i < _geometry.getNumPrimitiveSets() ; ++ i) {
|
|
osg::PrimitiveSet* primitive = _geometry.getPrimitiveSet(i);
|
|
|
|
if(!primitive || !primitive->getNumIndices()) {
|
|
continue;
|
|
}
|
|
else if(primitive->getMode() > osg::PrimitiveSet::TRIANGLES) {
|
|
OSG_INFO << "[smoother] Cannot smooth geometry '" << _geometry.getName()
|
|
<< "' due to not tessellated primitives" << std::endl;
|
|
return;
|
|
}
|
|
else if(primitive->getMode() == osg::PrimitiveSet::TRIANGLES) {
|
|
nbTriangles += primitive->getNumIndices() / 3;
|
|
}
|
|
}
|
|
_triangles.reserve(nbTriangles);
|
|
|
|
// collect all buffers that are BIND_PER_VERTEX for eventual vertex duplication
|
|
addArray(_geometry.getVertexArray());
|
|
addArray(_geometry.getColorArray());
|
|
addArray(_geometry.getSecondaryColorArray());
|
|
addArray(_geometry.getFogCoordArray());
|
|
for(unsigned int i = 0; i < _geometry.getNumTexCoordArrays(); ++ i) {
|
|
addArray(_geometry.getTexCoordArray(i));
|
|
}
|
|
for(unsigned int i = 0; i < _geometry.getNumVertexAttribArrays(); ++ i) {
|
|
addArray(_geometry.getVertexAttribArray(i));
|
|
}
|
|
|
|
switch(_mode) {
|
|
case recompute:
|
|
computeVertexNormals();
|
|
break;
|
|
case smooth_all:
|
|
smoothVertexNormals(true, true);
|
|
break;
|
|
case smooth_flipped:
|
|
smoothVertexNormals(true, false);
|
|
break;
|
|
case diagnose:
|
|
smoothVertexNormals(false, false);
|
|
break;
|
|
};
|
|
|
|
// deduplicate UVs array that were only shared within the geometry
|
|
deduplicator.deduplicateUVs(geometry);
|
|
}
|
|
|
|
~TriangleMeshSmoother() {
|
|
if(_graph) {
|
|
delete _graph;
|
|
}
|
|
}
|
|
|
|
protected:
|
|
unsigned int duplicateVertex(unsigned int index) {
|
|
DuplicateVertex duplicate(index);
|
|
for(ArrayVector::iterator array = _vertexArrays.begin(); array != _vertexArrays.end(); ++ array) {
|
|
(*array)->accept(duplicate);
|
|
}
|
|
#if 0
|
|
OSG_INFO << "[normals] [[TriangleMeshSmoother]] vertex " << index
|
|
<< " duplicated => " << duplicate._end << std::endl;
|
|
#endif
|
|
_graph->add(duplicate._end, index);
|
|
return duplicate._end;
|
|
}
|
|
|
|
void smoothVertexNormals(bool fix=true, bool force=false) {
|
|
_vertexArrays.clear(); // make sure we do not change vertex count
|
|
bool flipped = false;
|
|
|
|
osg::Vec3Array* normals = dynamic_cast<osg::Vec3Array*>(_geometry.getNormalArray());
|
|
for(unsigned int index = 0 ; index < _geometry.getVertexArray()->getNumElements() ; ++ index) {
|
|
std::vector<IndexVector> oneRing = _graph->vertexOneRing(_graph->unify(index), _creaseAngle);
|
|
osg::Vec3f smoothedNormal(0.f, 0.f, 0.f);
|
|
|
|
// sum normals for each cluster in the one-ring
|
|
for(std::vector<IndexVector>::iterator cluster = oneRing.begin() ; cluster != oneRing.end() ; ++ cluster) {
|
|
smoothedNormal += cumulateTriangleNormals(*cluster);
|
|
}
|
|
|
|
float length = smoothedNormal.normalize();
|
|
if(length > 0.) {
|
|
if(force || smoothedNormal * normals->at(index) < 1.e-6) {
|
|
flipped = true;
|
|
if(fix) {
|
|
(*normals)[index] = smoothedNormal;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(flipped) {
|
|
OSG_WARN << std::endl << "Warning: [smoothVertexNormals] [[normals]] Geometry '" << _geometry.getName() << "' ";
|
|
switch(_mode) {
|
|
case diagnose:
|
|
OSG_WARN << "has some flipped normals; please check that the shading is correct" << std::endl;
|
|
OSG_WARN << "Monitor: normal.invalid" << std::endl;
|
|
break;
|
|
case smooth_flipped:
|
|
OSG_WARN << "has some flipped normals that have been fixed" << std::endl;
|
|
OSG_WARN << "Monitor: normal.smooth_flipped" << std::endl;
|
|
break;
|
|
case smooth_all:
|
|
OSG_WARN << "normals have all been smoothed" << std::endl;
|
|
OSG_WARN << "Monitor: normal.smooth_all" << std::endl;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void computeVertexNormals() {
|
|
osg::Vec3Array* normals = new osg::Vec3Array(osg::Array::BIND_PER_VERTEX,
|
|
_geometry.getVertexArray()->getNumElements());
|
|
addArray(normals);
|
|
|
|
for(unsigned int i = 0 ; i < normals->getNumElements() ; ++ i) {
|
|
(*normals)[i].set(0.f, 0.f, 0.f);
|
|
}
|
|
|
|
for(VertexIterator uniqueIndex = _graph->begin() ; uniqueIndex != _graph->end() ; ++ uniqueIndex) {
|
|
unsigned int index = uniqueIndex->_index;
|
|
std::set<unsigned int> processed;
|
|
|
|
std::vector<IndexVector> oneRing = _graph->vertexOneRing(index, _creaseAngle);
|
|
for(std::vector<IndexVector>::iterator cluster = oneRing.begin() ; cluster != oneRing.end() ; ++ cluster) {
|
|
osg::Vec3f clusterNormal = cumulateTriangleNormals(*cluster);
|
|
clusterNormal.normalize();
|
|
|
|
std::set<unsigned int> duplicates;
|
|
for(IndexVector::const_iterator tri = cluster->begin() ; tri != cluster->end() ; ++ tri) {
|
|
const Triangle& triangle = _graph->triangle(*tri);
|
|
|
|
if(_graph->unify(triangle.v1()) == index) {
|
|
duplicates.insert(triangle.v1());
|
|
}
|
|
else if(_graph->unify(triangle.v2()) == index) {
|
|
duplicates.insert(triangle.v2());
|
|
}
|
|
else if(_graph->unify(triangle.v3()) == index) {
|
|
duplicates.insert(triangle.v3());
|
|
}
|
|
}
|
|
|
|
for(std::set<unsigned int>::iterator vertex = duplicates.begin() ; vertex != duplicates.end() ; ++ vertex) {
|
|
if(processed.find(*vertex) == processed.end()) {
|
|
// vertex not yet processed
|
|
(*normals)[*vertex] = clusterNormal;
|
|
processed.insert(*vertex);
|
|
}
|
|
else {
|
|
// vertex already processed in a previous cluster: need to duplicate
|
|
unsigned int duplicate = duplicateVertex(*vertex);
|
|
replaceVertexIndexInTriangles(*cluster, *vertex, duplicate);
|
|
(*normals)[duplicate] = clusterNormal;
|
|
|
|
processed.insert(duplicate);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_geometry.setNormalArray(normals, osg::Array::BIND_PER_VERTEX);
|
|
updateGeometryPrimitives();
|
|
|
|
OSG_WARN << std::endl <<"Warning: [computeVertexNormals] [[normals]] Geometry '" << _geometry.getName()
|
|
<< "' normals have been recomputed" << std::endl;
|
|
OSG_WARN << "Monitor: normal.recompute" << std::endl;
|
|
}
|
|
|
|
osg::Vec3f cumulateTriangleNormals(const IndexVector& triangles) const {
|
|
osg::Vec3f normal;
|
|
normal.set(0.f, 0.f, 0.f);
|
|
for(IndexVector::const_iterator triangle = triangles.begin() ; triangle != triangles.end() ; ++ triangle) {
|
|
const Triangle& t = _graph->triangle(*triangle);
|
|
normal += (t._normal * t._area);
|
|
}
|
|
return normal;
|
|
}
|
|
|
|
void replaceVertexIndexInTriangles(const IndexVector& triangles, unsigned int oldIndex, unsigned int newIndex) {
|
|
for(IndexVector::const_iterator tri = triangles.begin() ; tri != triangles.end() ; ++ tri) {
|
|
Triangle& triangle = _graph->triangle(*tri);
|
|
if(triangle.v1() == oldIndex) {
|
|
triangle.v1() = newIndex;
|
|
}
|
|
else if(triangle.v2() == oldIndex) {
|
|
triangle.v2() = newIndex;
|
|
}
|
|
else if(triangle.v3() == oldIndex) {
|
|
triangle.v3() = newIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
void addArray(osg::Array* array) {
|
|
if (array && array->getBinding() == osg::Array::BIND_PER_VERTEX) {
|
|
_vertexArrays.push_back(array);
|
|
}
|
|
}
|
|
|
|
void updateGeometryPrimitives() {
|
|
osg::Geometry::PrimitiveSetList primitives;
|
|
for(unsigned int i = 0 ; i < _geometry.getNumPrimitiveSets() ; ++ i) {
|
|
osg::PrimitiveSet* primitive = _geometry.getPrimitiveSet(i);
|
|
if(primitive && primitive->getMode() < osg::PrimitiveSet::TRIANGLES) {
|
|
primitives.push_back(primitive);
|
|
}
|
|
}
|
|
|
|
osg::DrawElementsUInt* triangles = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES);
|
|
for(unsigned int i = 0 ; i < _graph->getNumTriangles() ; ++ i) {
|
|
const Triangle& triangle = _graph->triangle(i);
|
|
triangles->push_back(triangle.v1());
|
|
triangles->push_back(triangle.v2());
|
|
triangles->push_back(triangle.v3());
|
|
}
|
|
primitives.push_back(triangles);
|
|
|
|
_geometry.setPrimitiveSetList(primitives);
|
|
}
|
|
|
|
|
|
osg::Geometry& _geometry;
|
|
float _creaseAngle;
|
|
TriangleMeshGraph* _graph;
|
|
TriangleVector _triangles;
|
|
ArrayVector _vertexArrays;
|
|
int _mode; // smooth or recompute normals
|
|
};
|
|
|
|
|
|
|
|
class SmoothNormalVisitor : public GeometryUniqueVisitor {
|
|
public:
|
|
SmoothNormalVisitor(float creaseAngle, bool comparePosition=false):
|
|
GeometryUniqueVisitor("SmoothNormalVisitor"),
|
|
_creaseAngle(creaseAngle),
|
|
_comparePosition(comparePosition)
|
|
{}
|
|
|
|
void process(osg::Geometry& geometry) {
|
|
if(!geometry.getNormalArray()) {
|
|
TriangleMeshSmoother(geometry, _creaseAngle, _comparePosition, TriangleMeshSmoother::recompute);
|
|
}
|
|
else {
|
|
TriangleMeshSmoother(geometry, _creaseAngle, _comparePosition, TriangleMeshSmoother::diagnose);
|
|
}
|
|
}
|
|
|
|
void process(osgAnimation::MorphGeometry& morphGeometry) {
|
|
TriangleMeshSmoother(morphGeometry, 0, true, TriangleMeshSmoother::smooth_all);
|
|
osgAnimation::MorphGeometry::MorphTargetList targets = morphGeometry.getMorphTargetList();
|
|
for(osgAnimation::MorphGeometry::MorphTargetList::iterator target = targets.begin() ; target != targets.end() ; ++ target) {
|
|
// check normal orientation using the same primitives as parent geometry
|
|
osg::Geometry::PrimitiveSetList& primitives = target->getGeometry()->getPrimitiveSetList();
|
|
target->getGeometry()->setPrimitiveSetList(morphGeometry.getPrimitiveSetList());
|
|
|
|
TriangleMeshSmoother(*target->getGeometry(), 0, true, TriangleMeshSmoother::smooth_all);
|
|
|
|
target->getGeometry()->setPrimitiveSetList(primitives);
|
|
}
|
|
}
|
|
|
|
protected:
|
|
float _creaseAngle;
|
|
bool _comparePosition;
|
|
};
|