Added support for face-varying boundary interpolation options:

- "propagate corners" added as new enumeration to Sdc::Options
    - topology tags within FVar channel initialized and propagated
    - face-varying Interpolate() method updated to deal with creases
This commit is contained in:
barfowl 2014-09-29 18:46:33 -07:00
parent aacd43a09b
commit 606e8fc1b9
6 changed files with 396 additions and 340 deletions

View File

@ -1149,7 +1149,16 @@ TopologyRefiner::faceVaryingInterpolateChildVertsFromVerts(
U & vdst = dst[cVertValue]; U & vdst = dst[cVertValue];
vdst.Clear(); vdst.Clear();
vdst.AddWithWeight(src[pVertValue], 1.0); if (parentFVar.isValueCorner(parentFVar.getVertexValueIndex(vert, pSibling))) {
vdst.AddWithWeight(src[pVertValue], 1.0f);
} else {
Index pEndValues[2];
parentFVar.getVertexCreaseEndValues(vert, pSibling, pEndValues);
vdst.AddWithWeight(src[pEndValues[0]], 0.125f);
vdst.AddWithWeight(src[pEndValues[1]], 0.125f);
vdst.AddWithWeight(src[pVertValue], 0.75f);
}
} }
} }
} }

View File

@ -36,72 +36,71 @@ namespace Sdc {
// scheme to affect the shape of the limit surface. These differ from approximations // scheme to affect the shape of the limit surface. These differ from approximations
// that may be applied at a higher level, i.e. options to limit the level of feature // that may be applied at a higher level, i.e. options to limit the level of feature
// adaptive subdivision, options to ignore fractional creasing, or creasing entirely, // adaptive subdivision, options to ignore fractional creasing, or creasing entirely,
// etc. These options define a particular limit surface. // etc. These options define the shape of a particular limit surface, including the
// "shape" of primitive variable data associated with it.
// //
// The intent is that these sets of options be defined at a high-level and propagated // The intent is that these sets of options be defined at a high-level and propagated
// into the lowest-level computation in support of each subdivision scheme. Ideally // into the lowest-level computation in support of each subdivision scheme. Ideally
// it remains a set of bit-fields (essentially an int) and so remains light weight and // it remains a set of bit-fields (essentially an int) and so remains light weight and
// easily passed down by value. // easily passed down by value.
// //
// Questions: // ALPHA NOTES:
// Should the individual enum's be nested within the class or independent? // Several of these options are being reconsidered in light of the divergence of
// // OSD 3.0 from Hbr. In some cases the options can be expressed more clearly and free
// Note: // of any RenderMan legacy for future use. Details are noted below:
// A case can be made that the CreaseMethod enum is better defined as part of the // "CreasingMethod"
// Crease class, but the goal is to try and put them all in one place. We could define // - note the change from the default "Normal" method to "Uniform"
// it there and aggregate it into Options here, but we need to be careful about the // "VVarBoundaryInterpolation"
// possibility of circular dependencies (nesting types in classes inhibits forward // - both name and enumerations being reconsidered
// declaration). // - the "VVar" prefix was misguided on my part (barfowl)
// - "boundary interpolation" is a potential misnomer as it only affects corners
// - its effect is to sharpen edges/corners, but edges are always sharpened
// - the "None" case serves no purpose (and would be discouraged)
// "FVarBoundaryInterpolation" and the bool "propagate corners"
// - as above, both name and enumerations being reconsidered
// - again "boundary interpolation" a misnomer interior interpolation affected
// - consider "FVarInterpolation" or "FVarLinearInterpolation" which by default
// (0) is linear throughout and specifies where FVar interpolation is to be
// Linear instead of following the assigned scheme. Since the default (0) is
// completely (bi)linear, each option successively removes linear features
// making the last fully smooth:
// FVAR_LINEAR_ALL,
// FVAR_LINEAR_EDGE_AND_CORNER,
// FVAR_LINEAR_CORNER,
// FVAR_LINEAR_CORNER_PROPAGATE (incorporates "propagate corners")
// FVAR_LINEAR_NONE,
// - the "propgate corners" option only applied to one "interpolation option"
// (formerly EDGE_AND_CORNER, now LINEAR_CORNER) and so has been added as a
// new fifth choice.
// "TriangleSubdivision":
// - hoping we can get rid of this due to lack of interest/use
// - specific to Catmark and only at level 0
// "NonManifoldInterpolation":
// - hoping we can get rid of this due to lack of interest/use
// //
class Options { class Options {
public: public:
// XXXX
// Manuel suggested "VertexBoundaryInterpolation" here, but when used, that sounded
// too much like boundary interpolation specific to a vertex -- I went with the VVar
// and FVar naming here instead (abbreviating the FaceVaryingBoundaryInterpolation
// that was suggested)...
//
enum VVarBoundaryInterpolation { enum VVarBoundaryInterpolation {
VVAR_BOUNDARY_NONE = 0, VVAR_BOUNDARY_NONE = 0,
VVAR_BOUNDARY_EDGE_ONLY, VVAR_BOUNDARY_EDGE_ONLY,
VVAR_BOUNDARY_EDGE_AND_CORNER VVAR_BOUNDARY_EDGE_AND_CORNER
}; };
enum FVarBoundaryInterpolation { enum FVarBoundaryInterpolation {
FVAR_BOUNDARY_BILINEAR = 0, FVAR_BOUNDARY_BILINEAR = 0,
FVAR_BOUNDARY_EDGE_ONLY, FVAR_BOUNDARY_EDGE_ONLY,
FVAR_BOUNDARY_EDGE_AND_CORNER, FVAR_BOUNDARY_EDGE_AND_CORNER,
FVAR_BOUNDARY_ALWAYS_SHARP FVAR_BOUNDARY_ALWAYS_SHARP,
FVAR_BOUNDARY_EDGE_AND_CORNER_PROP
}; };
//
// Tony has expressed a preference of UNIFORM vs NORMAL here, which diverges from
// Hbr/RenderMan, but makes a lot more sense as it allows us to distinguish between
// uniform and non-uniform creasing computations (with uniform being trivial).
//
enum CreasingMethod { enum CreasingMethod {
CREASE_UNIFORM = 0, CREASE_UNIFORM = 0,
CREASE_CHAIKIN CREASE_CHAIKIN
}; };
//
// Is it possible to get rid of this entirely? It is specific to Catmark, seems to
// be little used and only applies to the first level of subdivision. Getting rid
// of the code that supports this (though it is localized) would be a relief...
//
enum TriangleSubdivision { enum TriangleSubdivision {
TRI_SUB_NORMAL = 0, TRI_SUB_NORMAL = 0,
TRI_SUB_OLD, TRI_SUB_OLD,
TRI_SUB_NEW TRI_SUB_NEW
}; };
//
// This is speculative for now and included for illustration purposes -- the simplest
// set of interpolation rules for non-manifold features is to make them infinitely
// sharp, which fits into existing evaluation schemes. Allowing them to be smooth is
// less well-defined and requires additional cases in the masks to properly support.
//
enum NonManifoldInterpolation { enum NonManifoldInterpolation {
NON_MANIFOLD_NONE = 0, NON_MANIFOLD_NONE = 0,
NON_MANIFOLD_SMOOTH, NON_MANIFOLD_SMOOTH,
@ -112,11 +111,11 @@ public:
// Trivial constructor and destructor: // Trivial constructor and destructor:
Options() : _vvarBoundInterp(VVAR_BOUNDARY_NONE), Options() : _vvarBoundInterp(VVAR_BOUNDARY_NONE),
_fvarBoundInterp(FVAR_BOUNDARY_BILINEAR), _fvarBoundInterp(FVAR_BOUNDARY_BILINEAR),
_nonManInterp(NON_MANIFOLD_NONE), _nonManInterp(NON_MANIFOLD_NONE),
_creasingMethod(CREASE_UNIFORM), _creasingMethod(CREASE_UNIFORM),
_triangleSub(TRI_SUB_NORMAL), _triangleSub(TRI_SUB_NORMAL),
_hbrCompatible(false) { } _hbrCompatible(false) { }
~Options() { } ~Options() { }
// //
@ -148,7 +147,7 @@ public:
private: private:
// Bitfield members: // Bitfield members:
unsigned int _vvarBoundInterp : 2; unsigned int _vvarBoundInterp : 2;
unsigned int _fvarBoundInterp : 2; unsigned int _fvarBoundInterp : 3;
unsigned int _nonManInterp : 2; unsigned int _nonManInterp : 2;
unsigned int _creasingMethod : 2; unsigned int _creasingMethod : 2;
unsigned int _triangleSub : 2; unsigned int _triangleSub : 2;

View File

@ -49,7 +49,7 @@ namespace Vtr {
// Simple (for now) constructor and destructor: // Simple (for now) constructor and destructor:
// //
FVarLevel::FVarLevel(Level const& level) : FVarLevel::FVarLevel(Level const& level) :
_level(level), _isLinear(false), _valueCount(0) { _level(level), _isLinear(false), _hasSmoothBoundaries(false), _valueCount(0) {
} }
FVarLevel::~FVarLevel() { FVarLevel::~FVarLevel() {
@ -86,111 +86,62 @@ FVarLevel::resizeComponents() {
// //
// Initialize the component tags once all face-values have been assigned: // Initialize the component tags once all face-values have been assigned...
// - edge traversal:
// - finding and testing the values at matching ends of the edge
// - marking the bitfields according to the result
// - is it worth marking vertices as "mismatch"?
// - need more vertex analysis later
// - will only be marking discts verts
// - no iteration of edge-verts, unlike vert-edges
// - (consider) marking incident faces as "mismatch"
// - vertex traversal:
// - mismatches previously identified in edge traversal:
// - should be able to skip vertices that match
// - need to identify number/index of values for each
// - should be able to identify topology per value during iteration:
// - one "in a row" will be a corner, "crease" otherwise
// //
// WAIT -- consider the following vertex-oriented alternative... // Constructing the mapping between vertices and their face-varying values involves:
// - vertex traversal:
// - iterate through all successive face-vert values:
// - each successive mismatch identifies an edge discty at one end
// - mark the edge mismatch
// - mark the discts end (which is trivially known from local index)
// - "spans" of values identifies their topology
// - beware disjoint spans -- effectively corners
// - set of unique values gathered in process
// * NOTE - does not capture the dart/boundary case
// - single value consistent around vertex -- no discts edge detected
// - remember this is not a Dart but a true boundary, i.e. a Crease
// - could mark vertex when marking edge, but order-dependent:
// - if just need to mark mismatch, could be sufficient:
// - one value but mismatch implies Dart
// - single value may be at the center of multiple discts edges:
// - value becomes Corner then instead of Crease
// //
// Implications: // - iteration through all vertices to mark edge discontinuities and classify
// - both the edge-traversal and the vertex traversal want to compare indices // - allocation of vectors mapping vertices to their multiple (sibling) values
// - pros/cons for vertex traversal: // - iteration through all vertices and their distinct values to tag topologically
// - pro: 1/2 as many verts as edges //
// - pro: directly identifies adjacent face-vert values (via local index) // Once values have been identified for each vertex and tagged, refinement propagates
// - CON: knowledge of discts edges absent (cts at vertex, discts end of edge) // the tags to child values using more simplified logic (child values inherit the
// - pros/cons for edge traversal: // topology of their parent) and no futher analysis is required.
// - pro: can mark incident verts and faces mismatched in process
// - CON: 2x as many edges as verts
// - CON: requires search to find edge in each adjacent face
// - vertex traversal seems preferable, but CON is significant:
// - could need to test "other end of each edge":
// - unfortunate in that pair-wise test succeeding now slowed down
// in ALL cases -- even when everything matches
// - could mark "other end vertex" when marking any edge discts:
// - would result in "revisiting" a vertex
// - what are implications of identifying opposite end-splits later?
// - currently matched becomes mismatched, crease
// - single mismatched becomes mismatched, corner
// - multiple mismatched is awkward to deal with:
// - spans that may be crease fragmented -- how?
// - depending on span size may produce:
// - two Creases, two Corners, one of each...
// Best of both worlds:
// - double vertex traversal:
// - first intializes edge tags and marks opposite vertex mismatched
// - may also identify value-per-vertex counts
// - second (sparse) will analyze local topology
// //
void void
FVarLevel::completeTopologyFromFaceValues() { FVarLevel::completeTopologyFromFaceValues() {
// //
// REMEMBER!!! // Assign some members and local variables based on the interpolation options (the
// .... that "mismatches" may also occur where the topology matches, but options // members reflect queries that are made elsewhere):
// specify different treatment, e.g. boundary and corner interpolation rules. So we
// should inspect both the VVar and FVar boundary rules and determine when to mark
// such features as mismatched.
//
// Note that this affects typical exterior boundaries in addition to corners -- a
// geometrically smooth disk may end up with piecewise linear UV boundaries.
//
// Not sure what the precedence is here, particuarly "propagate corner" vs the choice
// of "fvar boundary interpolation" -- will need to check with Hbr.
// //
_isLinear = (_options.GetFVarBoundaryInterpolation() == Sdc::Options::FVAR_BOUNDARY_BILINEAR); _isLinear = (_options.GetFVarBoundaryInterpolation() == Sdc::Options::FVAR_BOUNDARY_BILINEAR);
bool geomCornersAreSharp = (_options.GetVVarBoundaryInterpolation() == Sdc::Options::VVAR_BOUNDARY_EDGE_AND_CORNER); _hasSmoothBoundaries = (_options.GetFVarBoundaryInterpolation() != Sdc::Options::FVAR_BOUNDARY_BILINEAR) &&
bool fvarCornersAreSharp = (_options.GetFVarBoundaryInterpolation() != Sdc::Options::FVAR_BOUNDARY_EDGE_ONLY); (_options.GetFVarBoundaryInterpolation() != Sdc::Options::FVAR_BOUNDARY_ALWAYS_SHARP);
bool fvarPropagateCorner = false;
fvarCornersAreSharp = fvarPropagateCorner ? geomCornersAreSharp : fvarCornersAreSharp; bool geomCornersAreSmooth = (_options.GetVVarBoundaryInterpolation() != Sdc::Options::VVAR_BOUNDARY_EDGE_AND_CORNER);
bool fvarCornersAreSharp = (_options.GetFVarBoundaryInterpolation() != Sdc::Options::FVAR_BOUNDARY_EDGE_ONLY);
bool mismatchCorners = (fvarCornersAreSharp != geomCornersAreSharp); bool makeCornersSharp = geomCornersAreSmooth && fvarCornersAreSharp;
bool sharpenAllIfAnyCorner = (_options.GetFVarBoundaryInterpolation() == Sdc::Options::FVAR_BOUNDARY_EDGE_AND_CORNER_PROP);
// //
// Iterate through the vertices first to identify discts edges -- this is better done // Its awkward and potentially inefficient to try and accomplish everything in one
// than iterating through the edges because: // pass over the vertices...
// //
// - there are typically twice as many edges as vertices // Make a first pass through the vertices to identify discts edges and to determine
// the number of values-per-vertex for subsequent allocation. The presence of a
// discts edge warrants marking vertices at BOTH ends as having mismatched topology
// wrt the vertices (part of why full topological analysis is deferred).
// //
// - the ordering of incident faces of a vertex make retrieval/comparison of // So this first pass will allocate/initialize the overall structure of the topology.
// adjacent pairs of face-varying values much more efficient // Given N vertices and M (as yet unknown) sibling values, the first pass achieves
// the following:
// //
// Since an edge may be discts at only one end, but the vertex at the cts end still // - assigns the number of siblings for each of the N vertices
// needs to be aware of such a discontinuity, we mark the opposite vertex for any // - determining the total number of siblings M in the process
// edge that is found to be discts. // - assigns the sibling offsets for each of the N vertices
// - assigns the vert-value tags (partially) for the first N vertices (matches or not)
// - initializes the vert-face siblings for all N vertices
// and
// - tags any incident edges as discts
// //
// In the process, we will initialize the "sibling" values associated with both the // The second pass initializes remaining members based on the total number of siblings
// face-verts and the vert-faces. These are both 0-initialized and only updated when // M after allocating appropriate vectors dependent on M.
// discontinuities are encountered. //
// Still looking or opportunities to economize effort between the two passes...
// //
ValueTag valueTagMatch(false); ValueTag valueTagMatch(false);
ValueTag valueTagMismatch(true); ValueTag valueTagMismatch(true);
@ -200,9 +151,9 @@ FVarLevel::completeTopologyFromFaceValues() {
int const maxValence = _level.getMaxValence(); int const maxValence = _level.getMaxValence();
Index * indexBuffer = (Index *)alloca(maxValence*sizeof(Index)); Index * indexBuffer = (Index *)alloca(maxValence*sizeof(Index));
int * valueBuffer = (int *)alloca(maxValence*sizeof(int)); int * valueBuffer = (int *)alloca(maxValence*sizeof(int));
Sibling * siblingBuffer = (Sibling *)alloca(maxValence*sizeof(Sibling)); Sibling * siblingBuffer = (Sibling *)alloca(maxValence*sizeof(Sibling));
int * uniqueValues = valueBuffer; int * uniqueValues = valueBuffer;
@ -226,7 +177,7 @@ FVarLevel::completeTopologyFromFaceValues() {
uniqueValues[uniqueValueCount++] = vValues[0]; uniqueValues[uniqueValueCount++] = vValues[0];
vSiblings[0] = 0; vSiblings[0] = 0;
bool vIsBoundary = (vEdges.size() != vFaces.size()); bool vIsBoundary = _level._vertTags[vIndex]._boundary;
for (int i = vIsBoundary; i < vFaces.size(); ++i) { for (int i = vIsBoundary; i < vFaces.size(); ++i) {
int iPrev = i ? (i - 1) : (vFaces.size() - 1); int iPrev = i ? (i - 1) : (vFaces.size() - 1);
@ -277,6 +228,21 @@ FVarLevel::completeTopologyFromFaceValues() {
} }
} }
//
// While we've tagged the vertex as having mismatched FVar topology in the presence of
// any discts edges, we also need to account for different treatment of vertices along
// geometric boundaries if the FVar interpolation rules affect them:
//
if (vIsBoundary && !_vertValueTags[vIndex]._mismatch) {
if (vFaces.size() == 1) {
if (makeCornersSharp) {
_vertValueTags[vIndex]._mismatch = true;
}
} else if (!_hasSmoothBoundaries) {
_vertValueTags[vIndex]._mismatch = true;
}
}
// //
// Make note of any extra values that will be added after one-per-vertex: // Make note of any extra values that will be added after one-per-vertex:
// //
@ -297,16 +263,21 @@ FVarLevel::completeTopologyFromFaceValues() {
} }
// //
// Allocate space for the values -- we already have tags for those corresponding to each // Now that we know the total number of additional sibling values (M values in addition
// vertex (and those tags have been updated above), so append tags indicating the mismatch // to the N vertex values) allocate space to accomodate all N + M values. Note that we
// for all of the sibling values detected: // we already have tags for the first N partially initialized (matching or not) so just
// append tags for the additional M sibling values (all M initialized as mismatched).
// //
_vertValueIndices.resize(totalValueCount); _vertValueIndices.resize(totalValueCount);
_vertValueTags.resize(totalValueCount, valueTagMismatch); _vertValueTags.resize(totalValueCount, valueTagMismatch);
if (_hasSmoothBoundaries) {
_vertValueCreaseEnds.resize(totalValueCount * 2);
}
// //
// Now a second pass through the vertices to identify the local face-varying topology // Now the second pass through the vertices to identify the local face-varying topology
// in more detail -- tagging each FVar value associated with the vertex: // in more detail -- tagging each FVar value associated with each vertex:
// //
// REMEMBER -- we want a ValueTag for each instance of the value at each vertex, i.e. // REMEMBER -- we want a ValueTag for each instance of the value at each vertex, i.e.
// per vertex-value. At level 0, given user input, the number of vertex-values may // per vertex-value. At level 0, given user input, the number of vertex-values may
@ -315,33 +286,22 @@ FVarLevel::completeTopologyFromFaceValues() {
// of vertex-values is what we need for refinement to "unweld" them, and the tag for // of vertex-values is what we need for refinement to "unweld" them, and the tag for
// each will indicate refinement rules for its children. // each will indicate refinement rules for its children.
// //
for (int vIndex = 0; vIndex < _level.getNumVertices(); ++vIndex) { ValueTag valueTagCorner = valueTagMismatch;
ValueTag& vTag = _vertValueTags[vIndex]; valueTagCorner._crease = false;
ValueTag valueTagCrease = valueTagMismatch;
valueTagCrease._crease = true;
for (int vIndex = 0; vIndex < _level.getNumVertices(); ++vIndex) {
IndexArray const vFaces = _level.getVertexFaces(vIndex); IndexArray const vFaces = _level.getVertexFaces(vIndex);
LocalIndexArray const vInFace = _level.getVertexFaceLocalIndices(vIndex); LocalIndexArray const vInFace = _level.getVertexFaceLocalIndices(vIndex);
//
// At this point, mismatches have only been determined by the presence of edges
// that are discontinuous. We may still have boundary/corner vertices that match
// topologically, but need to be treated differently (i.e. they do not match) due
// to boundary interpolation rules. So update the "matched" status of boundary
// vertices before continuing to the general treatment for all cases:
//
bool vIsBoundary = _level._vertTags[vIndex]._boundary;
if (vIsBoundary && !vTag._mismatch) {
bool vIsCorner = (vFaces.size() == 1);
if (vIsCorner) {
vTag._mismatch = mismatchCorners;
}
}
// //
// If the face-varying topology around this vertex matches the vertex topology, // If the face-varying topology around this vertex matches the vertex topology,
// there is little more to do -- except for level 0 where there may not be a // there is little more to do -- except for level 0 where there may not be a
// 1-to-1 correspondence between given values and vertex-values: // 1-to-1 correspondence between given values and vertex-values:
// //
if (!vTag._mismatch) { if (!_vertValueTags[vIndex]._mismatch) {
if (_level.getDepth() == 0) { if (_level.getDepth() == 0) {
_vertValueIndices[vIndex] = _faceVertValues[_level.getOffsetOfFaceVertices(vFaces[0]) + vInFace[0]]; _vertValueIndices[vIndex] = _faceVertValues[_level.getOffsetOfFaceVertices(vFaces[0]) + vInFace[0]];
} else { } else {
@ -356,16 +316,8 @@ FVarLevel::completeTopologyFromFaceValues() {
// be appended to the set of 1-per-vertex. // be appended to the set of 1-per-vertex.
// //
// When the topology does not match, we need to gather the unique values around // When the topology does not match, we need to gather the unique values around
// this vertex and tag each according to its more localized topology. // this vertex and tag each according to its more localized topology, i.e. is
// // it a boundary/crease or a corner.
// Special cases to consider:
// - one mismatched value: should be a crease (corner if linear or boundary?)
// - one value per face-vert: all will be corners (hard or smooth?)
//
// The general case will involve identifying "spans" via discts edges. Any value
// that has more than one instance split over multiple spans must be a hard corner.
//
// For now, make each unique value a hard corner to get something working:
// //
IndexArray const vEdges = _level.getVertexEdges(vIndex); IndexArray const vEdges = _level.getVertexEdges(vIndex);
LocalIndexArray const vInEdge = _level.getVertexEdgeLocalIndices(vIndex); LocalIndexArray const vInEdge = _level.getVertexEdgeLocalIndices(vIndex);
@ -373,56 +325,155 @@ FVarLevel::completeTopologyFromFaceValues() {
int vSiblingCount = _vertSiblingCounts[vIndex]; int vSiblingCount = _vertSiblingCounts[vIndex];
int vSiblingOffset = _vertSiblingOffsets[vIndex]; int vSiblingOffset = _vertSiblingOffsets[vIndex];
SiblingArray const vFaceSiblings = getVertexFaceSiblings(vIndex);
// //
// Gather the unique set of values and relevant topological information for each // Assign the value indices for the vertex and all of its siblings and determine
// as we go... // the "valence" for each value (i.e. the number of faces in which it occurs):
// //
int vvCount = 1 + vSiblingCount; int * vSiblingValences = indexBuffer;
int * vvIndices = indexBuffer; vSiblingValences[0] = 1;
//int vvSrcFaces[vvCount]; for (int i = 1; i <= vSiblingCount; ++i) {
vSiblingValences[i] = 0;
Index lastValue = _faceVertValues[_level.getOffsetOfFaceVertices(vFaces[0]) + vInFace[0]]; }
vvCount = 1;
vvIndices[0] = lastValue;
//vvSrcFaces[0] = 0;
int vSiblingIndex = 1;
Index * vertValueSiblingIndices = &_vertValueIndices[vSiblingOffset];
_vertValueIndices[vIndex] = _faceVertValues[_level.getOffsetOfFaceVertices(vFaces[0]) + vInFace[0]];
for (int i = 1; i < vFaces.size(); ++i) { for (int i = 1; i < vFaces.size(); ++i) {
Index nextValue = _faceVertValues[_level.getOffsetOfFaceVertices(vFaces[i]) + vInFace[i]]; if (vFaceSiblings[i] == vSiblingIndex) {
*vertValueSiblingIndices++ = _faceVertValues[_level.getOffsetOfFaceVertices(vFaces[i]) + vInFace[i]];
vSiblingIndex++;
}
vSiblingValences[vFaceSiblings[i]]++;
}
assert(vSiblingIndex == (1 + vSiblingCount));
if (lastValue != nextValue) { //
bool nextValueIsNew = (vvCount == 1) || ((vvCount == 2) && (vvIndices[0] != nextValue)) || // Test for conditions that make all vertex values sharp and tag all as corners when so:
(std::find(vvIndices, vvIndices + vvCount, nextValue) == vvIndices + vvCount); //
if (nextValueIsNew) { bool sharpenAllValuesSinceOneCornerPresent = false;
vvIndices[vvCount] = nextValue; if (sharpenAllIfAnyCorner) {
//vvSrcFaces[vvCount] = i; for (int i = 0; i <= vSiblingCount; ++i) {
vvCount ++; if (vSiblingValences[i] == 1) {
sharpenAllValuesSinceOneCornerPresent = true;
break;
} }
} }
lastValue = nextValue;
} }
assert(vvCount == (1 + vSiblingCount)); bool makeAllVertexValuesSharp = !_hasSmoothBoundaries ||
sharpenAllValuesSinceOneCornerPresent ||
_level._vertTags[vIndex]._nonManifold;
if (makeAllVertexValuesSharp) {
_vertValueTags[vIndex] = valueTagCorner;
for (int i = 0; i < vSiblingCount; ++i) {
_vertValueTags[vSiblingOffset + i] = valueTagCorner;
}
continue;
}
// //
// Assign tags to the vertex' primary value and sibling values: // Inspect each vertex value and tag as corner or crease accordingly:
// //
ValueTag valueTag; for (int i = 0; i <= vSiblingCount; ++i) {
valueTag._mismatch = true; Index valueIndex = (i == 0) ? vIndex : (vSiblingOffset + i - 1);
valueTag._corner = true;
valueTag._crease = false;
_vertValueIndices[vIndex] = vvIndices[0]; ValueTag& valueTag = _vertValueTags[valueIndex];
_vertValueTags[vIndex] = valueTag;
for (int i = 0; i < vSiblingCount; ++i) { bool isDisjoint = (vSiblingValences[i] == 0);
_vertValueIndices[vSiblingOffset + i] = vvIndices[1 + i]; bool isCorner = (vSiblingValences[i] == 1);
_vertValueTags[vSiblingOffset + i] = valueTag; if ((isCorner && fvarCornersAreSharp) || isDisjoint) {
valueTag = valueTagCorner;
continue;
}
//
// We have a crease value -- identify the two neighboring values:
//
// This should really be done more efficiently in a single iteration
// in conjunction with determining valence, etc., e.g. identify the
// first and last face for each continuous sector. This is only done
// once for the cage and the results propagated through refinement,
// so revisit if it appears to take more time than it should.
//
LocalIndex * endFaces = &_vertValueCreaseEnds[2 * valueIndex];
if (isCorner) {
// A smooth corner -- all three values are in the same face:
int j;
for (j = 0; vFaceSiblings[j] != i; ++j) ;
endFaces[0] = j;
endFaces[1] = j;
} else if (vSiblingCount == 0) {
// Single value -- a mismatched boundary or interior dart:
if (vEdges.size() > vFaces.size()) {
endFaces[0] = 0;
endFaces[1] = vFaces.size() - 1;
} else {
for (int j = 0; j < vEdges.size(); ++j) {
if (_edgeTags[vEdges[j]]._mismatch) {
endFaces[0] = j;
endFaces[1] = (j ? j : vFaces.size()) - 1;
break;
}
}
}
} else if ((i == 0) && (vFaceSiblings[vFaceSiblings.size() - 1] == 0)) {
// The "span" wraps around the start of an interior vertex:
int j;
for (j = vFaceSiblings.size() - 1; (vFaceSiblings[j] == 0); --j) ;
endFaces[0] = j + 1;
for (j = 0; (vFaceSiblings[j] == 0); ++j) ;
endFaces[1] = j - 1;
} else {
int j;
for (j = 0; vFaceSiblings[j] != i; ++j) ;
endFaces[0] = j;
for (j = vFaceSiblings.size() - 1; vFaceSiblings[j] != i; --j) ;
endFaces[1] = j;
}
valueTag = valueTagCrease;
} }
} }
//printf("completed topology...\n"); // printf("completed fvar topology...\n");
//print(); // print();
//printf("validating...\n"); // printf("validating...\n");
//assert(validate()); // assert(validate());
}
//
// Values tagged as creases have their two "end values" identified relative to the incident
// faces of the vertex for compact storage and quick retrieval. This methods identifies the
// values for the two ends of such a crease value:
//
void
FVarLevel::getVertexCreaseEndValues(Index vIndex, Sibling vSibling, Index endValues[2]) const
{
int creaseEndOffset = 2 * getVertexValueIndex(vIndex, vSibling);
LocalIndex vertFace0 = _vertValueCreaseEnds[creaseEndOffset];
LocalIndex vertFace1 = _vertValueCreaseEnds[creaseEndOffset + 1];
IndexArray const vFaces = _level.getVertexFaces(vIndex);
LocalIndexArray const vInFace = _level.getVertexFaceLocalIndices(vIndex);
LocalIndex endInFace0 = vInFace[vertFace0];
LocalIndex endInFace1 = vInFace[vertFace1];
if (_level.getDepth() > 0) {
endInFace0 = (endInFace0 + 1) % 4;
endInFace1 = (endInFace1 + 3) % 4;
} else {
// Avoid the costly % N for potential N-sided faces at level 0...
endInFace0++;
if (endInFace0 == _level.getNumFaceVertices(vFaces[vertFace0])) {
endInFace0 = 0;
}
endInFace1 = (endInFace1 ? endInFace1 : _level.getNumFaceVertices(vFaces[vertFace1])) - 1;
}
endValues[0] = _faceVertValues[_level.getOffsetOfFaceVertices(vFaces[vertFace0]) + endInFace0];
endValues[1] = _faceVertValues[_level.getOffsetOfFaceVertices(vFaces[vertFace1]) + endInFace1];
} }
// //
@ -562,6 +613,12 @@ FVarLevel::print() const {
for (int j = 0; j < sCount; ++j) { for (int j = 0; j < sCount; ++j) {
printf("%4d", _vertValueIndices[sOffset + j]); printf("%4d", _vertValueIndices[sOffset + j]);
} }
if (sCount) {
printf(", crease =%4d", _vertValueTags[i]._crease);
for (int j = 0; j < sCount; ++j) {
printf("%4d", _vertValueTags[sOffset + j]._crease);
}
}
printf("\n"); printf("\n");
} }

View File

@ -116,20 +116,7 @@ public:
typedef unsigned char ValueTagSize; typedef unsigned char ValueTagSize;
ValueTagSize _mismatch : 1; // local FVar topology does not match ValueTagSize _mismatch : 1; // local FVar topology does not match
ValueTagSize _corner : 1; // value is a corner (unlike its vertex) ValueTagSize _crease : 1; // value is a crease, otherwise a corner
ValueTagSize _crease : 1; // value is a crease (unlike its vertex)
// Note that the corner/crease distinction will take into account the boundary
// interpolation options and other topological factors. For example, a value
// on a Crease that is set to have Linear interpolation rules can be set to be a
// Corner for interpolation purposes, and conversely a Corner value with smooth
// corner rules will be set to be a Crease.
// Since the Crease is the only non-trivial case, want to store more information
// here for Crease so that we can quickly identify the values involved when it
// comes time to interpolate (though considering vertex interpolation, the set
// of edges is searched to identify the two corresponding to the crease when the
// mask is computed, so perhaps this effort is unwarranted).
}; };
public: public:
@ -155,18 +142,24 @@ public:
// Queries per vertex (and its potential sibling values): // Queries per vertex (and its potential sibling values):
bool vertexTopologyMatches(Index vIndex) const { return !_vertValueTags[vIndex]._mismatch; } bool vertexTopologyMatches(Index vIndex) const { return !_vertValueTags[vIndex]._mismatch; }
int getNumVertexValues(Index vIndex) const; int getNumVertexValues(Index vIndex) const;
Index getVertexValueIndex(Index vIndex, Sibling sibling = 0) const; Index getVertexValueIndex(Index vIndex, Sibling sibling = 0) const;
Index getVertexValue(Index vIndex, Sibling sibling = 0) const; Index getVertexValue(Index vIndex, Sibling sibling = 0) const;
// int getNumVertexSiblings(Index vIndex) const;
// IndexArray const getVertexSiblingValues(Index vIndex) const;
SiblingArray const getVertexFaceSiblings(Index faceIndex) const; SiblingArray const getVertexFaceSiblings(Index faceIndex) const;
// Queries specific to values:
bool isValueCrease(Index valueIndex) const { return _vertValueTags[valueIndex]._crease; }
bool isValueCorner(Index valueIndex) const { return !_vertValueTags[valueIndex]._crease; }
// Higher-level topological queries, i.e. values in a neighborhood: // Higher-level topological queries, i.e. values in a neighborhood:
void getEdgeFaceValues(Index eIndex, int fIncToEdge, Index valuesPerVert[2]) const; void getEdgeFaceValues(Index eIndex, int fIncToEdge, Index valuesPerVert[2]) const;
void getVertexEdgeValues(Index vIndex, Index valuesPerEdge[]) const; void getVertexEdgeValues(Index vIndex, Index valuesPerEdge[]) const;
void getVertexCreaseEndValues(Index vIndex, Sibling sibling, Index endValues[2]) const;
// Currently the sibling value storage and indexing is being reconsidered...
// int getNumVertexSiblings(Index vIndex) const;
// IndexArray const getVertexSiblingValues(Index vIndex) const;
// Non-const methods -- modifiers to be protected: // Non-const methods -- modifiers to be protected:
// //
@ -189,128 +182,33 @@ public:
public: public:
Level const & _level; Level const & _level;
// // Options vary between channels:
// It's a little bit unclear at present how FVarBoundaryInterpolation works.
// I would have though the default would be to inherit the same interpolation
// rules from the geometry, but I don't see an enumeration for that -- so if
// that is desirable, an explicit internal initialization/assignment will be
// warranted.
//
// Remember that the VVarBoundaryInterpolation has three enums that can now
// be reduced to two, so some revision to FVarBoundaryInterpolation may also
// be considered.
//
// Options are stored locally here and they may vary between channels. By
// default the options member is initialized from whatever contains it --
// which may apply a common set to all channels or vary them individually.
//
Sdc::Options _options; Sdc::Options _options;
bool _isLinear;
int _valueCount; bool _isLinear;
bool _hasSmoothBoundaries;
int _valueCount;
// //
// Members that distinguish the face-varying "topology" from the Level to // Vectors recording face-varying topology -- values-per-face, which edges
// which the data set is associated. // are discts wrt the FVar data, the one-to-many mapping between vertices and
// their sibling values, etc. We use 8-bit "local indices" where possible.
// //
// Like the geometric topology, the face-varying topology is specified by // Per-face (matches face-verts of corresponding level):
// a set of per-face-vertex indices -- analogous to vertices -- which are std::vector<Index> _faceVertValues;
// referred to as "values". Additional vectors associated with vertices
// are constructed to identify the set of values incident each vertex.
// There is typically a single value associated with each vertex but also
// a set of "sibling" values when the face-varying values around a vertex
// are not all the same. The neighborhood of each vertex is expressed in
// terms of these local sibling indices, i.e. local indices typically 0,
// 1 or generally very low, and not exceeding the limit we use for vertex
// valence (and N-sided faces).
//
// As the unique values are identified and associated with each vertex,
// the local neighborhood is also inspected and each value "tagged" to
// indicate its topology. Foremost is a bit indicating whether the value
// "matches" the topology of its vertex (which can only occur when there
// is only one value for that vertex), but additionally bits are added to
// describe the topological neighborhood to indicate how it and all of its
// refined descendants should be treated.
//
// Tags are associated with the "vertex values", i.e. each instance of a
// value for each vertex. When a vertex has more than one value associated
// with it, the subdivision rules applicable to each are independent and
// fixed throughout refinement.
//
// Level 0 as a special case:
// Given that users can specify the input values arbitrarily, it is
// necessary to add a little extra to accomodate this.
// For subsequent levels of refinement, the values associated with
// each vertex are exclusive to that vertex, e.g. if vertex V has values
// A and B incident to it, no other vertex will share A and B. Any child
// values of A and B are then local to the child of V.
// This is not the case in level 0. Considering a quad mesh there
// may be only 4 values for the corners of a unit square, and all vertices
// share combinations of those values. In the
//
// Notes on memory usage:
// The largest contributor to memory here is the set of face-values,
// which matches the size of the geometric face-vertices, i.e. typically
// 4 ints per face. It turns out this can be reconstructed from the rest,
// so whether it should always be updated/retained vs computed when needed
// is up for debate (right now it is constructed from the other members in
// a method as the last step in refinement).
// The most critical vector stores the sibling index for the values
// incident each vertex, i.e. it is the same size as the set of vert-faces
// (typically 4 ints per vertex) but a fraction of the size since we use
// 8-bit indices for the siblings.
// The rest are typically an integer or two per vertex or value and
// 8-bit tags per edge and value.
// The bulk of the memory usage is in the face-values, and these are
// not needed for refinement.
//
// Memory cost (bytes) for members (N verts, N faces, 2*N edges, M > N value):
// 16*N per-face-vert values
// 2*N per-edge tags
// N per-vertex sibling counts
// 4*N per-vertex sibling offsets
// 4*N per-vert-face siblings
// 4*M per-value indices (redundant after level 0)
// M per-value tags
// - total: 27*N + 5*M
// - consider size of M:
// - worst case M = 4*N at level 0, but M -> N as level increases
// - typical size may be M ~= 1.5*N, so say M = 8/5 * N
// - current examples indicate far less: 1.1*N to 1.2*N
// - so M = 6/5 * N may be more realistic
// - total = 35*N, i.e. 8-9 ints-per-face (or per-vertex)
// * roughly an extra int for each face-vertex index
// - bare minimum (*):
// - 21*N:
// - 16*N face values specified as input
// - 4*N for some kind of vertex-to-value index/offset/mapping
// - N for some kind of vertex/match indication tag
// * assuming face-values retained in user-specified form
// - compute on-demand by vert-face-siblings reduces by 12*N
// - note typical UV data size of M = N*8/5 values:
// - float[2] for each value -> data = 8*M = 13*N
// - potentially redundant:
// - 6*N (4*M) value indices for level > 0
// - possible extras:
// - 2*M (3*N) for the 2 ends of each value that is a crease
// - allocated for all M vertex-values
// - only populated for those tagged as creases
// ? how quickly can we look this up instead?
//
// Per-face:
std::vector<Index> _faceVertValues; // matches face-verts of level (16*N)
// Per-edge: // Per-edge:
std::vector<ETag> _edgeTags; // 1 per edge (2*N) std::vector<ETag> _edgeTags;
// Per-vertex: // Per-vertex:
std::vector<Sibling> _vertSiblingCounts; // 1 per vertex (1*N) std::vector<Sibling> _vertSiblingCounts;
std::vector<int> _vertSiblingOffsets; // 1 per vertex (4*N) std::vector<int> _vertSiblingOffsets;
std::vector<Sibling> _vertFaceSiblings; // matches face-verts of level (4*N) std::vector<Sibling> _vertFaceSiblings;
// Per-value: // Per-value:
std::vector<Index> _vertValueIndices; // variable per vertex (4*M>N) std::vector<Index> _vertValueIndices;
std::vector<ValueTag> _vertValueTags; // variable per vertex (1*M>N) std::vector<ValueTag> _vertValueTags;
std::vector<LocalIndex> _vertValueCreaseEnds;
}; };
// //

View File

@ -77,8 +77,9 @@ FVarRefinement::applyRefinement() {
// //
// Transfer basic properties from the parent to child level: // Transfer basic properties from the parent to child level:
// //
_child->_options = _parent->_options; _child->_options = _parent->_options;
_child->_isLinear = _parent->_isLinear; _child->_isLinear = _parent->_isLinear;
_child->_hasSmoothBoundaries = _parent->_hasSmoothBoundaries;
// //
// It's difficult to know immediately how many child values arise from the // It's difficult to know immediately how many child values arise from the
@ -91,6 +92,9 @@ FVarRefinement::applyRefinement() {
propagateEdgeTags(); propagateEdgeTags();
propagateValueTags(); propagateValueTags();
if (_child->_hasSmoothBoundaries) {
propagateValueCreases();
}
// //
// The refined face-values are technically redundant as they can be constructed // The refined face-values are technically redundant as they can be constructed
@ -102,7 +106,7 @@ FVarRefinement::applyRefinement() {
_child->initializeFaceValuesFromFaceVertices(); _child->initializeFaceValuesFromFaceVertices();
} }
//printf("Refinement to level %d:\n", _child->getDepth()); //printf("FVar refinement to level %d:\n", _child->getDepth());
//_child->print(); //_child->print();
//printf("Validating refinement to level %d:\n", _child->getDepth()); //printf("Validating refinement to level %d:\n", _child->getDepth());
@ -164,6 +168,9 @@ FVarRefinement::trimAndFinalizeChildValues() {
_child->_valueCount = _child->getNumVertices() + _childSiblingFromEdgeCount + _childSiblingFromVertCount; _child->_valueCount = _child->getNumVertices() + _childSiblingFromEdgeCount + _childSiblingFromVertCount;
_child->_vertValueTags.resize(_child->_valueCount); _child->_vertValueTags.resize(_child->_valueCount);
if (_child->_hasSmoothBoundaries) {
_child->_vertValueCreaseEnds.resize(2 * _child->_valueCount);
}
_childValueParentSource.resize(_child->_valueCount); _childValueParentSource.resize(_child->_valueCount);
@ -338,19 +345,32 @@ FVarRefinement::propagateValueTags() {
// - if complete, trivially propagated/inherited // - if complete, trivially propagated/inherited
// - if incomplete, need to map to child subset // - if incomplete, need to map to child subset
// //
//
// Values from face-vertices -- all match:
//
FVarLevel::ValueTag valTagMatch(false); FVarLevel::ValueTag valTagMatch(false);
FVarLevel::ValueTag valTagMismatch(true);
valTagMismatch._corner = true;
Index cVert = 0; Index cVert = 0;
for (cVert = 0; cVert < _refinement._childVertFromFaceCount; ++cVert) { for (cVert = 0; cVert < _refinement._childVertFromFaceCount; ++cVert) {
_child->_vertValueTags[cVert] = valTagMatch; _child->_vertValueTags[cVert] = valTagMatch;
} }
//
// Values from edge-vertices -- for edges that are split, tag as mismatched and tag
// as corner or crease depending on the presence of creases in the parent:
//
FVarLevel::ValueTag valTagMismatch(true);
FVarLevel::ValueTag valTagCrease(true);
valTagCrease._crease = true;
FVarLevel::ValueTag& valTagSplitEdge = _parent->_hasSmoothBoundaries ? valTagCrease : valTagMismatch;
for (int i = 0; i < _refinement._childVertFromEdgeCount; ++i, ++cVert) { for (int i = 0; i < _refinement._childVertFromEdgeCount; ++i, ++cVert) {
Index pEdge = _refinement.getChildVertexParentIndex(cVert); Index pEdge = _refinement.getChildVertexParentIndex(cVert);
bool pEdgeIsSplit = _parent->_edgeTags[pEdge]._mismatch; bool pEdgeIsSplit = _parent->_edgeTags[pEdge]._mismatch;
FVarLevel::ValueTag const& cValueTag = pEdgeIsSplit ? valTagMismatch : valTagMatch; FVarLevel::ValueTag const& cValueTag = pEdgeIsSplit ? valTagSplitEdge: valTagMatch;
_child->_vertValueTags[cVert] = cValueTag; _child->_vertValueTags[cVert] = cValueTag;
@ -362,6 +382,11 @@ FVarRefinement::propagateValueTags() {
} }
} }
} }
//
// Values from vertex-vertices -- inherit tags from parent values when complete
// otherwise (not yet supported) need to identify the parent value for each child:
//
for (int i = 0; i < _refinement._childVertFromVertCount; ++i, ++cVert) { for (int i = 0; i < _refinement._childVertFromVertCount; ++i, ++cVert) {
assert(!_refinement._childVertexTag[cVert]._incomplete); assert(!_refinement._childVertexTag[cVert]._incomplete);
@ -380,6 +405,73 @@ FVarRefinement::propagateValueTags() {
} }
} }
void
FVarRefinement::propagateValueCreases() {
assert(_child->_hasSmoothBoundaries);
// Skip child vertices from faces:
Index cVert = _refinement._childVertFromFaceCount;
//
// For each child vertex from an edge that has FVar values and is complete, initialize
// the crease-ends for those values tagged as smooth boundaries:
//
for (int i = 0; i < _refinement._childVertFromEdgeCount; ++i, ++cVert) {
if (!_child->_vertValueTags[cVert]._mismatch) continue;
if (_refinement._childVertexTag[cVert]._incomplete) continue;
if (_child->_vertValueTags[cVert]._crease) {
LocalIndex * sibling0Ends = &_child->_vertValueCreaseEnds[2 * cVert];
sibling0Ends[0] = 0;
sibling0Ends[1] = 1;
}
int vSiblingCount = _child->_vertSiblingCounts[cVert];
if (vSiblingCount) {
int cOffset = _child->_vertSiblingOffsets[cVert];
if (_child->_vertValueTags[cOffset]._crease) {
LocalIndex * sibling1Ends = &_child->_vertValueCreaseEnds[2 * cOffset];
sibling1Ends[0] = 2;
sibling1Ends[1] = 3;
}
}
}
//
// For each child vertex from a vertex that has FVar values and is complete, initialize
// the crease-ends for those values tagged as smooth boundaries:
//
for (int i = 0; i < _refinement._childVertFromVertCount; ++i, ++cVert) {
if (!_child->_vertValueTags[cVert]._mismatch) continue;
if (_refinement._childVertexTag[cVert]._incomplete) continue;
Index pVert = _refinement.getChildVertexParentIndex(cVert);
if (_child->_vertValueTags[cVert]._crease) {
LocalIndex * pSiblingEnds = &_parent->_vertValueCreaseEnds[2 * pVert];
LocalIndex * cSiblingEnds = &_child->_vertValueCreaseEnds[2 * cVert];
cSiblingEnds[0] = pSiblingEnds[0];
cSiblingEnds[1] = pSiblingEnds[1];
}
int vSiblingCount = _child->_vertSiblingCounts[cVert];
if (vSiblingCount) {
int cSiblingOffset = _child->_vertSiblingOffsets[cVert];
int pSiblingOffset = _parent->_vertSiblingOffsets[pVert];
LocalIndex * pSiblingEnds = &_parent->_vertValueCreaseEnds[2 * pSiblingOffset];
LocalIndex * cSiblingEnds = &_child->_vertValueCreaseEnds[2 * cSiblingOffset];
for (int j = 0; j < vSiblingCount; ++j, cSiblingEnds += 2, pSiblingEnds += 2) {
if (_child->_vertValueTags[cSiblingOffset + j]._crease) {
cSiblingEnds[0] = pSiblingEnds[0];
cSiblingEnds[1] = pSiblingEnds[1];
}
}
}
}
}
} // end namespace Vtr } // end namespace Vtr
} // end namespace OPENSUBDIV_VERSION } // end namespace OPENSUBDIV_VERSION

View File

@ -79,6 +79,7 @@ public:
void propagateEdgeTags(); void propagateEdgeTags();
void propagateValueTags(); void propagateValueTags();
void propagateValueCreases();
public: public: