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];
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
// 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,
// 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
// 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
// easily passed down by value.
//
// Questions:
// Should the individual enum's be nested within the class or independent?
//
// Note:
// A case can be made that the CreaseMethod enum is better defined as part of the
// Crease class, but the goal is to try and put them all in one place. We could define
// it there and aggregate it into Options here, but we need to be careful about the
// possibility of circular dependencies (nesting types in classes inhibits forward
// declaration).
// ALPHA NOTES:
// 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
// of any RenderMan legacy for future use. Details are noted below:
// "CreasingMethod"
// - note the change from the default "Normal" method to "Uniform"
// "VVarBoundaryInterpolation"
// - both name and enumerations being reconsidered
// - 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 {
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 {
VVAR_BOUNDARY_NONE = 0,
VVAR_BOUNDARY_EDGE_ONLY,
VVAR_BOUNDARY_EDGE_AND_CORNER
};
enum FVarBoundaryInterpolation {
FVAR_BOUNDARY_BILINEAR = 0,
FVAR_BOUNDARY_EDGE_ONLY,
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 {
CREASE_UNIFORM = 0,
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 {
TRI_SUB_NORMAL = 0,
TRI_SUB_OLD,
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 {
NON_MANIFOLD_NONE = 0,
NON_MANIFOLD_SMOOTH,
@ -112,11 +111,11 @@ public:
// Trivial constructor and destructor:
Options() : _vvarBoundInterp(VVAR_BOUNDARY_NONE),
_fvarBoundInterp(FVAR_BOUNDARY_BILINEAR),
_nonManInterp(NON_MANIFOLD_NONE),
_creasingMethod(CREASE_UNIFORM),
_triangleSub(TRI_SUB_NORMAL),
_hbrCompatible(false) { }
_fvarBoundInterp(FVAR_BOUNDARY_BILINEAR),
_nonManInterp(NON_MANIFOLD_NONE),
_creasingMethod(CREASE_UNIFORM),
_triangleSub(TRI_SUB_NORMAL),
_hbrCompatible(false) { }
~Options() { }
//
@ -148,7 +147,7 @@ public:
private:
// Bitfield members:
unsigned int _vvarBoundInterp : 2;
unsigned int _fvarBoundInterp : 2;
unsigned int _fvarBoundInterp : 3;
unsigned int _nonManInterp : 2;
unsigned int _creasingMethod : 2;
unsigned int _triangleSub : 2;

View File

@ -49,7 +49,7 @@ namespace Vtr {
// Simple (for now) constructor and destructor:
//
FVarLevel::FVarLevel(Level const& level) :
_level(level), _isLinear(false), _valueCount(0) {
_level(level), _isLinear(false), _hasSmoothBoundaries(false), _valueCount(0) {
}
FVarLevel::~FVarLevel() {
@ -86,111 +86,62 @@ FVarLevel::resizeComponents() {
//
// 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
// Initialize the component tags once all face-values have been assigned...
//
// WAIT -- consider the following vertex-oriented alternative...
// - 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
// Constructing the mapping between vertices and their face-varying values involves:
//
// Implications:
// - both the edge-traversal and the vertex traversal want to compare indices
// - pros/cons for vertex traversal:
// - pro: 1/2 as many verts as edges
// - pro: directly identifies adjacent face-vert values (via local index)
// - CON: knowledge of discts edges absent (cts at vertex, discts end of edge)
// - pros/cons for edge traversal:
// - 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
// - iteration through all vertices to mark edge discontinuities and classify
// - allocation of vectors mapping vertices to their multiple (sibling) values
// - iteration through all vertices and their distinct values to tag topologically
//
// Once values have been identified for each vertex and tagged, refinement propagates
// the tags to child values using more simplified logic (child values inherit the
// topology of their parent) and no futher analysis is required.
//
void
FVarLevel::completeTopologyFromFaceValues() {
//
// REMEMBER!!!
// .... that "mismatches" may also occur where the topology matches, but options
// 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.
// Assign some members and local variables based on the interpolation options (the
// members reflect queries that are made elsewhere):
//
_isLinear = (_options.GetFVarBoundaryInterpolation() == Sdc::Options::FVAR_BOUNDARY_BILINEAR);
bool geomCornersAreSharp = (_options.GetVVarBoundaryInterpolation() == Sdc::Options::VVAR_BOUNDARY_EDGE_AND_CORNER);
bool fvarCornersAreSharp = (_options.GetFVarBoundaryInterpolation() != Sdc::Options::FVAR_BOUNDARY_EDGE_ONLY);
bool fvarPropagateCorner = false;
_hasSmoothBoundaries = (_options.GetFVarBoundaryInterpolation() != Sdc::Options::FVAR_BOUNDARY_BILINEAR) &&
(_options.GetFVarBoundaryInterpolation() != Sdc::Options::FVAR_BOUNDARY_ALWAYS_SHARP);
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
// than iterating through the edges because:
// Its awkward and potentially inefficient to try and accomplish everything in one
// 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
// adjacent pairs of face-varying values much more efficient
// So this first pass will allocate/initialize the overall structure of the topology.
// 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
// needs to be aware of such a discontinuity, we mark the opposite vertex for any
// edge that is found to be discts.
// - assigns the number of siblings for each of the N vertices
// - determining the total number of siblings M in the process
// - 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
// face-verts and the vert-faces. These are both 0-initialized and only updated when
// discontinuities are encountered.
// The second pass initializes remaining members based on the total number of siblings
// M after allocating appropriate vectors dependent on M.
//
// Still looking or opportunities to economize effort between the two passes...
//
ValueTag valueTagMatch(false);
ValueTag valueTagMismatch(true);
@ -200,9 +151,9 @@ FVarLevel::completeTopologyFromFaceValues() {
int const maxValence = _level.getMaxValence();
Index * indexBuffer = (Index *)alloca(maxValence*sizeof(Index));
int * valueBuffer = (int *)alloca(maxValence*sizeof(int));
Sibling * siblingBuffer = (Sibling *)alloca(maxValence*sizeof(Sibling));
Index * indexBuffer = (Index *)alloca(maxValence*sizeof(Index));
int * valueBuffer = (int *)alloca(maxValence*sizeof(int));
Sibling * siblingBuffer = (Sibling *)alloca(maxValence*sizeof(Sibling));
int * uniqueValues = valueBuffer;
@ -226,7 +177,7 @@ FVarLevel::completeTopologyFromFaceValues() {
uniqueValues[uniqueValueCount++] = vValues[0];
vSiblings[0] = 0;
bool vIsBoundary = (vEdges.size() != vFaces.size());
bool vIsBoundary = _level._vertTags[vIndex]._boundary;
for (int i = vIsBoundary; i < vFaces.size(); ++i) {
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:
//
@ -297,16 +263,21 @@ FVarLevel::completeTopologyFromFaceValues() {
}
//
// Allocate space for the values -- we already have tags for those corresponding to each
// vertex (and those tags have been updated above), so append tags indicating the mismatch
// for all of the sibling values detected:
// Now that we know the total number of additional sibling values (M values in addition
// to the N vertex values) allocate space to accomodate all N + M values. Note that we
// 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);
_vertValueTags.resize(totalValueCount, valueTagMismatch);
if (_hasSmoothBoundaries) {
_vertValueCreaseEnds.resize(totalValueCount * 2);
}
//
// Now a second pass through the vertices to identify the local face-varying topology
// in more detail -- tagging each FVar value associated with the vertex:
// Now the second pass through the vertices to identify the local face-varying topology
// 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.
// 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
// each will indicate refinement rules for its children.
//
for (int vIndex = 0; vIndex < _level.getNumVertices(); ++vIndex) {
ValueTag& vTag = _vertValueTags[vIndex];
ValueTag valueTagCorner = valueTagMismatch;
valueTagCorner._crease = false;
ValueTag valueTagCrease = valueTagMismatch;
valueTagCrease._crease = true;
for (int vIndex = 0; vIndex < _level.getNumVertices(); ++vIndex) {
IndexArray const vFaces = _level.getVertexFaces(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,
// 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:
//
if (!vTag._mismatch) {
if (!_vertValueTags[vIndex]._mismatch) {
if (_level.getDepth() == 0) {
_vertValueIndices[vIndex] = _faceVertValues[_level.getOffsetOfFaceVertices(vFaces[0]) + vInFace[0]];
} else {
@ -356,16 +316,8 @@ FVarLevel::completeTopologyFromFaceValues() {
// be appended to the set of 1-per-vertex.
//
// 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.
//
// 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:
// this vertex and tag each according to its more localized topology, i.e. is
// it a boundary/crease or a corner.
//
IndexArray const vEdges = _level.getVertexEdges(vIndex);
LocalIndexArray const vInEdge = _level.getVertexEdgeLocalIndices(vIndex);
@ -373,56 +325,155 @@ FVarLevel::completeTopologyFromFaceValues() {
int vSiblingCount = _vertSiblingCounts[vIndex];
int vSiblingOffset = _vertSiblingOffsets[vIndex];
SiblingArray const vFaceSiblings = getVertexFaceSiblings(vIndex);
//
// Gather the unique set of values and relevant topological information for each
// as we go...
// Assign the value indices for the vertex and all of its siblings and determine
// the "valence" for each value (i.e. the number of faces in which it occurs):
//
int vvCount = 1 + vSiblingCount;
int * vvIndices = indexBuffer;
//int vvSrcFaces[vvCount];
Index lastValue = _faceVertValues[_level.getOffsetOfFaceVertices(vFaces[0]) + vInFace[0]];
vvCount = 1;
vvIndices[0] = lastValue;
//vvSrcFaces[0] = 0;
int * vSiblingValences = indexBuffer;
vSiblingValences[0] = 1;
for (int i = 1; i <= vSiblingCount; ++i) {
vSiblingValences[i] = 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) {
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)) ||
(std::find(vvIndices, vvIndices + vvCount, nextValue) == vvIndices + vvCount);
if (nextValueIsNew) {
vvIndices[vvCount] = nextValue;
//vvSrcFaces[vvCount] = i;
vvCount ++;
//
// Test for conditions that make all vertex values sharp and tag all as corners when so:
//
bool sharpenAllValuesSinceOneCornerPresent = false;
if (sharpenAllIfAnyCorner) {
for (int i = 0; i <= vSiblingCount; ++i) {
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;
valueTag._mismatch = true;
valueTag._corner = true;
valueTag._crease = false;
for (int i = 0; i <= vSiblingCount; ++i) {
Index valueIndex = (i == 0) ? vIndex : (vSiblingOffset + i - 1);
_vertValueIndices[vIndex] = vvIndices[0];
_vertValueTags[vIndex] = valueTag;
ValueTag& valueTag = _vertValueTags[valueIndex];
for (int i = 0; i < vSiblingCount; ++i) {
_vertValueIndices[vSiblingOffset + i] = vvIndices[1 + i];
_vertValueTags[vSiblingOffset + i] = valueTag;
bool isDisjoint = (vSiblingValences[i] == 0);
bool isCorner = (vSiblingValences[i] == 1);
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");
//print();
//printf("validating...\n");
//assert(validate());
// printf("completed fvar topology...\n");
// print();
// printf("validating...\n");
// 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) {
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");
}

View File

@ -116,20 +116,7 @@ public:
typedef unsigned char ValueTagSize;
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 (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).
ValueTagSize _crease : 1; // value is a crease, otherwise a corner
};
public:
@ -155,18 +142,24 @@ public:
// Queries per vertex (and its potential sibling values):
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 getVertexValue(Index vIndex, Sibling sibling = 0) const;
// int getNumVertexSiblings(Index vIndex) const;
// IndexArray const getVertexSiblingValues(Index vIndex) 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:
void getEdgeFaceValues(Index eIndex, int fIncToEdge, Index valuesPerVert[2]) 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:
//
@ -189,128 +182,33 @@ public:
public:
Level const & _level;
//
// 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.
//
// Options vary between channels:
Sdc::Options _options;
bool _isLinear;
int _valueCount;
bool _isLinear;
bool _hasSmoothBoundaries;
int _valueCount;
//
// Members that distinguish the face-varying "topology" from the Level to
// which the data set is associated.
// Vectors recording face-varying topology -- values-per-face, which edges
// 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
// a set of per-face-vertex indices -- analogous to vertices -- which are
// 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-face (matches face-verts of corresponding level):
std::vector<Index> _faceVertValues;
// Per-edge:
std::vector<ETag> _edgeTags; // 1 per edge (2*N)
std::vector<ETag> _edgeTags;
// Per-vertex:
std::vector<Sibling> _vertSiblingCounts; // 1 per vertex (1*N)
std::vector<int> _vertSiblingOffsets; // 1 per vertex (4*N)
std::vector<Sibling> _vertFaceSiblings; // matches face-verts of level (4*N)
std::vector<Sibling> _vertSiblingCounts;
std::vector<int> _vertSiblingOffsets;
std::vector<Sibling> _vertFaceSiblings;
// Per-value:
std::vector<Index> _vertValueIndices; // variable per vertex (4*M>N)
std::vector<ValueTag> _vertValueTags; // variable per vertex (1*M>N)
std::vector<Index> _vertValueIndices;
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:
//
_child->_options = _parent->_options;
_child->_isLinear = _parent->_isLinear;
_child->_options = _parent->_options;
_child->_isLinear = _parent->_isLinear;
_child->_hasSmoothBoundaries = _parent->_hasSmoothBoundaries;
//
// It's difficult to know immediately how many child values arise from the
@ -91,6 +92,9 @@ FVarRefinement::applyRefinement() {
propagateEdgeTags();
propagateValueTags();
if (_child->_hasSmoothBoundaries) {
propagateValueCreases();
}
//
// The refined face-values are technically redundant as they can be constructed
@ -102,7 +106,7 @@ FVarRefinement::applyRefinement() {
_child->initializeFaceValuesFromFaceVertices();
}
//printf("Refinement to level %d:\n", _child->getDepth());
//printf("FVar refinement to level %d:\n", _child->getDepth());
//_child->print();
//printf("Validating refinement to level %d:\n", _child->getDepth());
@ -164,6 +168,9 @@ FVarRefinement::trimAndFinalizeChildValues() {
_child->_valueCount = _child->getNumVertices() + _childSiblingFromEdgeCount + _childSiblingFromVertCount;
_child->_vertValueTags.resize(_child->_valueCount);
if (_child->_hasSmoothBoundaries) {
_child->_vertValueCreaseEnds.resize(2 * _child->_valueCount);
}
_childValueParentSource.resize(_child->_valueCount);
@ -338,19 +345,32 @@ FVarRefinement::propagateValueTags() {
// - if complete, trivially propagated/inherited
// - if incomplete, need to map to child subset
//
//
// Values from face-vertices -- all match:
//
FVarLevel::ValueTag valTagMatch(false);
FVarLevel::ValueTag valTagMismatch(true);
valTagMismatch._corner = true;
Index cVert = 0;
for (cVert = 0; cVert < _refinement._childVertFromFaceCount; ++cVert) {
_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) {
Index pEdge = _refinement.getChildVertexParentIndex(cVert);
bool pEdgeIsSplit = _parent->_edgeTags[pEdge]._mismatch;
FVarLevel::ValueTag const& cValueTag = pEdgeIsSplit ? valTagMismatch : valTagMatch;
FVarLevel::ValueTag const& cValueTag = pEdgeIsSplit ? valTagSplitEdge: valTagMatch;
_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) {
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 OPENSUBDIV_VERSION

View File

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