diff --git a/opensubdiv/far/topologyRefiner.h b/opensubdiv/far/topologyRefiner.h index ef19b584..0e057fdb 100644 --- a/opensubdiv/far/topologyRefiner.h +++ b/opensubdiv/far/topologyRefiner.h @@ -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); + } } } } diff --git a/opensubdiv/sdc/options.h b/opensubdiv/sdc/options.h index 38c47864..268539c3 100644 --- a/opensubdiv/sdc/options.h +++ b/opensubdiv/sdc/options.h @@ -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; diff --git a/opensubdiv/vtr/fvarLevel.cpp b/opensubdiv/vtr/fvarLevel.cpp index bf27c816..0a5b2b83 100644 --- a/opensubdiv/vtr/fvarLevel.cpp +++ b/opensubdiv/vtr/fvarLevel.cpp @@ -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"); } diff --git a/opensubdiv/vtr/fvarLevel.h b/opensubdiv/vtr/fvarLevel.h index 68cbcd8d..51db8dc1 100644 --- a/opensubdiv/vtr/fvarLevel.h +++ b/opensubdiv/vtr/fvarLevel.h @@ -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 _faceVertValues; // matches face-verts of level (16*N) + // Per-face (matches face-verts of corresponding level): + std::vector _faceVertValues; // Per-edge: - std::vector _edgeTags; // 1 per edge (2*N) + std::vector _edgeTags; // Per-vertex: - std::vector _vertSiblingCounts; // 1 per vertex (1*N) - std::vector _vertSiblingOffsets; // 1 per vertex (4*N) - std::vector _vertFaceSiblings; // matches face-verts of level (4*N) + std::vector _vertSiblingCounts; + std::vector _vertSiblingOffsets; + std::vector _vertFaceSiblings; // Per-value: - std::vector _vertValueIndices; // variable per vertex (4*M>N) - std::vector _vertValueTags; // variable per vertex (1*M>N) + std::vector _vertValueIndices; + std::vector _vertValueTags; + std::vector _vertValueCreaseEnds; }; // diff --git a/opensubdiv/vtr/fvarRefinement.cpp b/opensubdiv/vtr/fvarRefinement.cpp index b98d075f..8cb9010c 100644 --- a/opensubdiv/vtr/fvarRefinement.cpp +++ b/opensubdiv/vtr/fvarRefinement.cpp @@ -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 diff --git a/opensubdiv/vtr/fvarRefinement.h b/opensubdiv/vtr/fvarRefinement.h index 65ef80f6..6a95c911 100644 --- a/opensubdiv/vtr/fvarRefinement.h +++ b/opensubdiv/vtr/fvarRefinement.h @@ -79,6 +79,7 @@ public: void propagateEdgeTags(); void propagateValueTags(); + void propagateValueCreases(); public: