Skip to content

Commit b1fe0a6

Browse files
authored
Merge pull request #1357 from danieldresser-ie/weightedNormals
Add calculateUniformNormals/VertexNormals/FaceVaryingNormals
2 parents 7994df4 + eee9960 commit b1fe0a6

File tree

9 files changed

+999
-55
lines changed

9 files changed

+999
-55
lines changed

include/IECoreScene/MeshAlgo.h

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,43 @@ namespace MeshAlgo
5454
/// If provided, this will be periodically checked, and cancel the calculation with an exception
5555
/// if the canceller has been triggered ( indicating the result is no longer needed )
5656

57-
/// Calculate the normals of a mesh primitive.
57+
// Enum for specifying how to weight normal calculation
58+
enum class NormalWeighting
59+
{
60+
Equal, // Equal weight to each vertex, simple, not very good
61+
Angle, // Weight based on angle where each face touches vertex - best consistent results
62+
Area // Weight based on area of each face connected to vertex - may yield good results on hard
63+
// surface models with edge bevels, where you want to preserve the flatness of large faces
64+
};
65+
66+
/// Calculate the normals of a mesh primitive, based on a given position primitive variable.
67+
///
68+
/// NOTE : Uses tbb internally - in order to integrate with a program using tbb, should be placed inside a
69+
/// this_task_arena::isolate or other mechanism to protect from stealing outer tasks.
70+
71+
/// Uniform normals are simple - just compute the average normal across the face.
72+
IECORESCENE_API PrimitiveVariable calculateUniformNormals( const MeshPrimitive *mesh, const std::string &position = "P", const IECore::Canceller *canceller = nullptr );
73+
74+
/// Vertex normals require averaging the normals of all adjacent faces, so we take "weighting" to determine
75+
/// how they are averaged.
76+
IECORESCENE_API PrimitiveVariable calculateVertexNormals( const MeshPrimitive *mesh, NormalWeighting weighting, const std::string &position = "P", const IECore::Canceller *canceller = nullptr );
77+
78+
/// With face varying normals, we can average adjacent faces to produce a smooth corner, or create a faceted
79+
/// corner. So we take both a weighting mode, and "thresholdAngle", which give a cutoff in degrees - faces
80+
/// which meet at less than this angle will be treated as faceted instead of smooth.
81+
IECORESCENE_API PrimitiveVariable calculateFaceVaryingNormals( const MeshPrimitive *mesh, NormalWeighting weighting, float thresholdAngle, const std::string &position = "P", const IECore::Canceller *canceller = nullptr );
82+
83+
/// Old form where no weighting method is specified - computes vertex normals using fast but inaccurate method
84+
/// Deprecated.
5885
IECORESCENE_API PrimitiveVariable calculateNormals( const MeshPrimitive *mesh, PrimitiveVariable::Interpolation interpolation = PrimitiveVariable::Vertex, const std::string &position = "P", const IECore::Canceller *canceller = nullptr );
5986

6087
/// TODO: remove this compatibility function:
6188
IECORESCENE_API std::pair<PrimitiveVariable, PrimitiveVariable> calculateTangents( const MeshPrimitive *mesh, const std::string &uvSet = "uv", bool orthoTangents = true, const std::string &position = "P" );
6289
/// Calculate the surface tangent vectors of a mesh primitive based on UV information
6390
IECORESCENE_API std::pair<PrimitiveVariable, PrimitiveVariable> calculateTangentsFromUV( const MeshPrimitive *mesh, const std::string &uvSet = "uv", const std::string &position = "P", bool orthoTangents = true, bool leftHanded = false, const IECore::Canceller *canceller = nullptr );
64-
/// Calculate the surface tangent vectors of a mesh primitive based on the first neighbor edge
91+
/// Calculate the surface tangent vectors of a mesh primitive based on the first neighbor edge.
92+
/// Note that "first" is defined by the edge that comes first in the vertexIds list:
93+
/// if a tri is stored as [ 0, 1, 2 ], then this is interpreted as the edges <0,1>, <1,2> and <2,0>, in that order.
6594
IECORESCENE_API std::pair<PrimitiveVariable, PrimitiveVariable> calculateTangentsFromFirstEdge( const MeshPrimitive *mesh, const std::string &position = "P", const std::string &normal = "N", bool orthoTangents = true, bool leftHanded = false, const IECore::Canceller *canceller = nullptr );
6695
/// Calculate the surface tangent vectors of a mesh primitive based on the primitives centroid
6796
IECORESCENE_API std::pair<PrimitiveVariable, PrimitiveVariable> calculateTangentsFromPrimitiveCentroid( const MeshPrimitive *mesh, const std::string &position = "P", const std::string &normal = "N", bool orthoTangents = true, bool leftHanded = false, const IECore::Canceller *canceller = nullptr );
@@ -95,6 +124,9 @@ IECORESCENE_API void reorderVertices( MeshPrimitive *mesh, int id0, int id1, int
95124
/// Distributes points over a mesh using an IECore::PointDistribution in UV space
96125
/// and mapping it to 3d space. It gives a fairly even distribution regardless of
97126
/// vertex spacing, provided the UVs are well layed out.
127+
///
128+
/// NOTE : Uses tbb internally - in order to integrate with a program using tbb, should be placed inside a
129+
/// this_task_arena::isolate or other mechanism to protect from stealing outer tasks.
98130
IECORESCENE_API PointsPrimitivePtr distributePoints( const MeshPrimitive *mesh, float density = 100.0, const Imath::V2f &offset = Imath::V2f( 0 ), const std::string &densityMask = "density", const std::string &uvSet = "uv", const std::string &refPosition = "P", const IECore::StringAlgo::MatchPattern &primitiveVariables = "", const IECore::Canceller *canceller = nullptr );
99131

100132
/// Split the input mesh in to N meshes based on the N unique values contained in a segment primitive variable.
@@ -163,16 +195,32 @@ IECORESCENE_API std::vector<MeshPrimitivePtr> segment( const MeshPrimitive *mesh
163195

164196
/// Merge the input meshes into a single mesh.
165197
/// Any PrimitiveVariables that exist will be combined or extended using a default value.
198+
///
199+
/// NOTE : Uses tbb internally - in order to integrate with a program using tbb, should be placed inside a
200+
/// this_task_arena::isolate or other mechanism to protect from stealing outer tasks.
166201
IECORESCENE_API MeshPrimitivePtr merge( const std::vector<const MeshPrimitive *> &meshes, const IECore::Canceller *canceller = nullptr );
167202

168203
/// Generate a new triangulated MeshPrimitive
204+
///
205+
/// NOTE : Uses tbb internally - in order to integrate with a program using tbb, should be placed inside a
206+
/// this_task_arena::isolate or other mechanism to protect from stealing outer tasks.
169207
IECORESCENE_API MeshPrimitivePtr triangulate( const MeshPrimitive *mesh, const IECore::Canceller *canceller = nullptr );
170208

171209
/// Generate a list of connected vertices per vertex
172210
/// The first vector contains a flat list of all the indices of the connected neighbor vertices.
173-
/// The second one holds an offset index for every vertex. Note that the offset indices vector skips the first offset index (since it's 0)
211+
/// The second one holds an offset index for every vertex. Note that the offset indices vector skips the
212+
/// first offset index (since it's 0).
213+
/// Note also that the resulting data vector has a capacity larger than it's size ( due to the storage required
214+
/// for the implementation of this function ). If you are keeping this result persistently, you may want to call
215+
/// result.second->writable().shrink_to_fit() in order to reduce long term memory use, however this reallocation
216+
/// is just a waste of time if you are using the result to compute something else and then discarding it.
174217
IECORESCENE_API std::pair<IECore::IntVectorDataPtr, IECore::IntVectorDataPtr> connectedVertices( const IECoreScene::MeshPrimitive *mesh, const IECore::Canceller *canceller = nullptr );
175218

219+
/// Generate a list of face vertices which point to each vertex
220+
/// The first vector contains a flat list of all the indices of the connected face vertices.
221+
/// The second one holds offset indices for every vertex, as above.
222+
IECORESCENE_API std::pair<IECore::IntVectorDataPtr, IECore::IntVectorDataPtr> correspondingFaceVertices( const IECoreScene::MeshPrimitive *mesh, const IECore::Canceller *canceller = nullptr );
223+
176224
} // namespace MeshAlgo
177225

178226
} // namespace IECoreScene

include/IECoreScene/PolygonVertexIterator.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ class PolygonVertexIterator
5353
public :
5454

5555
typedef std::forward_iterator_tag iterator_category;
56-
typedef typename VertexValueIterator::value_type value_type;
57-
typedef typename VertexValueIterator::difference_type difference_type;
58-
typedef typename VertexValueIterator::pointer pointer;
59-
typedef typename VertexValueIterator::reference reference;
56+
typedef typename std::iterator_traits<VertexValueIterator>::value_type value_type;
57+
typedef typename std::iterator_traits<VertexValueIterator>::difference_type difference_type;
58+
typedef typename std::iterator_traits<VertexValueIterator>::pointer pointer;
59+
typedef typename std::iterator_traits<VertexValueIterator>::reference reference;
6060

6161
/// Uninitialised.
6262
PolygonVertexIterator();

src/IECoreScene/MeshAlgoConnectedVertices.cpp

Lines changed: 115 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,62 +38,145 @@ using namespace std;
3838
using namespace IECore;
3939
using namespace IECoreScene;
4040

41+
namespace {
42+
43+
inline void linearInsert( int *list, int val )
44+
{
45+
while( *list != -1 )
46+
{
47+
if( *list == val )
48+
{
49+
return;
50+
}
51+
list++;
52+
}
53+
*list = val;
54+
}
55+
56+
} // namespace
57+
4158
pair<IntVectorDataPtr, IntVectorDataPtr> MeshAlgo::connectedVertices( const MeshPrimitive *mesh, const Canceller *canceller )
4259
{
4360
size_t numVertices = mesh->variableData< V3fVectorData >( "P", PrimitiveVariable::Vertex )->readable().size();
4461
const vector<int> &numVerticesPerFace = mesh->verticesPerFace()->readable();
4562
const vector<int> &vertexIds = mesh->vertexIds()->readable();
4663

47-
vector< set<int> > neighbors( numVertices );
64+
IntVectorDataPtr offsetsData = new IntVectorData();
65+
vector<int> &offsets = offsetsData->writable();
66+
offsets.resize( numVertices, 0 );
4867

49-
int currentVertOffset = 0;
68+
Canceller::check( canceller );
69+
70+
// Start initializing the offsets vector by storing the maximum number of possible neighbours each
71+
// vertex could have. Every time a vertex appears in the vertex id list, that means it's part of
72+
// a polygon, and has two more edges connecting it to two other vertices. In the common case,
73+
// the number we arrive at by this method is twice as high as needed, because in a manifold mesh,
74+
// every edge appears in the face list twice.
75+
for( int i : vertexIds )
76+
{
77+
offsets[ i ] += 2;
78+
}
79+
80+
Canceller::check( canceller );
81+
82+
// Convert the neighbour counts into offsets to the start of each list of possible neighbours,
83+
// by storing a running total
84+
int totalPossibleNeighbours = 0;
85+
for( int &o : offsets )
86+
{
87+
int count = o;
88+
o = totalPossibleNeighbours;
89+
totalPossibleNeighbours += count;
90+
}
91+
92+
// Allocate storage for all possible neighbours, and collect neighbours from every face.
93+
// On a manifold mesh, only half the storage for each vertex will be used, because every
94+
// vertex pair occurs in two separate faces. ( Unused element will be left at -1 )
95+
Canceller::check( canceller );
96+
IntVectorDataPtr neighbourListData = new IntVectorData();
97+
std::vector<int> &neighbourList = neighbourListData->writable();
98+
neighbourList.resize( totalPossibleNeighbours, -1 );
99+
int faceStart = 0;
50100
for ( auto &vertsPerFace : numVerticesPerFace )
51101
{
52102
Canceller::check( canceller );
53103

54104
for ( int i = 0; i < vertsPerFace; ++i)
55105
{
56-
const int &faceVert = vertexIds[ currentVertOffset + i ];
57-
const int &faceVertNext = vertexIds[ currentVertOffset + ( i + 1 ) % vertsPerFace ];
58-
neighbors[ faceVert ].insert( faceVertNext );
59-
neighbors[ faceVertNext ].insert( faceVert );
106+
int faceVert = vertexIds[ faceStart + i ];
107+
int faceVertNext = vertexIds[ faceStart + ( i + 1 ) % vertsPerFace ];
108+
linearInsert( &neighbourList[ offsets[faceVert] ], faceVertNext );
109+
linearInsert( &neighbourList[ offsets[faceVertNext] ], faceVert );
60110
}
61-
currentVertOffset += vertsPerFace;
111+
faceStart += vertsPerFace;
62112
}
63113

64-
int neighborCount = 0;
65-
int cancelTestCounter = 0;
66-
for ( auto &n : neighbors )
114+
// Compact the neighbourList to contain only used vertices by removing any -1 values,
115+
// and update offsets accordingly - we also convert the offsets from pointing to the
116+
// start of the lists to the end of the lists at the same time.
117+
int usedOutputIndex = 0;
118+
for( int i = 0; i < (int)offsets.size(); i++ )
67119
{
68-
if( cancelTestCounter++ == 1000 )
120+
Canceller::check( canceller );
121+
int end = i < (int)offsets.size() - 1 ? offsets[i + 1] : totalPossibleNeighbours;
122+
int neighbourIndex = offsets[i];
123+
while( neighbourIndex < end && neighbourList[neighbourIndex] != -1 )
69124
{
70-
Canceller::check( canceller );
71-
cancelTestCounter = 0;
125+
neighbourList[usedOutputIndex++] = neighbourList[neighbourIndex++];
72126
}
73-
neighborCount += n.size();
127+
offsets[i] = usedOutputIndex;
74128
}
129+
neighbourList.resize( usedOutputIndex );
75130

76-
IntVectorDataPtr offsets = new IntVectorData();
77-
IntVectorDataPtr neighborList = new IntVectorData();
78-
vector<int> &offsetsW = offsets->writable();
79-
vector<int> &neighborListW = neighborList->writable();
131+
// It would be a little simpler to just call neighbourList.shrink_to_fit() here so the output would always
132+
// be exactly sized right, but this reallocation costs about 10% of our performance, so instead I guess
133+
// we'll just document that you may want to call shrink_to_fit() if you're keeping this data around.
134+
135+
return pair<IntVectorDataPtr, IntVectorDataPtr>( neighbourListData, offsetsData );
136+
}
137+
138+
pair<IntVectorDataPtr, IntVectorDataPtr> MeshAlgo::correspondingFaceVertices( const MeshPrimitive *mesh, const Canceller *canceller )
139+
{
140+
size_t numVertices = mesh->variableData< V3fVectorData >( "P", PrimitiveVariable::Vertex )->readable().size();
141+
const vector<int> &vertexIds = mesh->vertexIds()->readable();
80142

81-
neighborListW.resize( neighborCount, -1 );
82-
offsetsW.resize( neighbors.size(), -1 );
143+
IntVectorDataPtr offsetsData = new IntVectorData();
144+
vector<int> &offsets = offsetsData->writable();
145+
Canceller::check( canceller );
146+
offsets.resize( numVertices, 0 );
83147

84-
int ix = 0;
85-
for ( size_t i = 0; i < neighbors.size(); ++i )
148+
// Start initializing the offsets vector by storing the number of face vertices
149+
for( int i : vertexIds )
86150
{
87-
if( i % 1000 == 0 )
88-
{
89-
Canceller::check( canceller );
90-
}
91-
for ( auto &n : neighbors[ i ] )
92-
{
93-
neighborListW[ ix++ ] = n;
94-
}
95-
offsetsW[ i ] = ix;
151+
offsets[ i ]++;
152+
}
153+
154+
Canceller::check( canceller );
155+
156+
// Convert the counts into offsets to the start of each list of face vertices
157+
int countFaceVertices = 0;
158+
for( int &o : offsets )
159+
{
160+
int count = o;
161+
o = countFaceVertices;
162+
countFaceVertices += count;
163+
}
164+
165+
Canceller::check( canceller );
166+
IntVectorDataPtr faceVerticesData = new IntVectorData();
167+
vector<int> &faceVertices = faceVerticesData->writable();
168+
Canceller::check( canceller );
169+
faceVertices.resize( countFaceVertices );
170+
171+
// Now run through all faces, storing face vertex indices in the new list. We increment the offset
172+
// for each face vertex we store, meaning that the indices start out pointing to the beginning of the
173+
// list for each vertex, and end up pointing at the end of the list for each vertex.
174+
for( unsigned int i = 0; i < vertexIds.size(); i++ )
175+
{
176+
int vert = vertexIds[ i ];
177+
faceVertices[ offsets[ vert ] ] = i;
178+
offsets[ vert ] ++;
96179
}
97180

98-
return pair<IntVectorDataPtr, IntVectorDataPtr>( neighborList, offsets );
181+
return pair<IntVectorDataPtr, IntVectorDataPtr>( faceVerticesData, offsetsData );
99182
}

0 commit comments

Comments
 (0)