// // Copyright 2013 Pixar // // Licensed under the Apache License, Version 2.0 (the "Apache License") // with the following modification; you may not use this file except in // compliance with the Apache License and the following modification to it: // Section 6. Trademarks. is deleted and replaced with: // // 6. Trademarks. This License does not grant permission to use the trade // names, trademarks, service marks, or product names of the Licensor // and its affiliates, except as required to comply with Section 4(c) of // the License and to reproduce the content of the NOTICE file. // // You may obtain a copy of the Apache License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the Apache License with the above modification is // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the Apache License for the specific // language governing permissions and limitations under the Apache License. // #include "../far/patchTablesFactory.h" #include "../far/gregoryBasis.h" #include "../far/topologyRefiner.h" #include "../vtr/level.h" #include "../vtr/refinement.h" #include #include #include namespace OpenSubdiv { namespace OPENSUBDIV_VERSION { namespace { // // A convenience container for the different types of feature adaptive patches // template struct PatchTypes { static const int NUM_TRANSITIONS=6, NUM_ROTATIONS=4; TYPE R[NUM_TRANSITIONS], // regular patch S[NUM_TRANSITIONS][NUM_ROTATIONS], // single-crease patch B[NUM_TRANSITIONS][NUM_ROTATIONS], // boundary patch (4 rotations) C[NUM_TRANSITIONS][NUM_ROTATIONS], // corner patch (4 rotations) G, // gregory patch GB, // gregory boundary patch GP; // gregory basis patch PatchTypes() { std::memset(this, 0, sizeof(PatchTypes)); } // Returns the number of patches based on the patch type in the descriptor TYPE & getValue( Far::PatchDescriptor desc ) { switch (desc.GetType()) { case Far::PatchDescriptor::REGULAR : return R[desc.GetPattern()]; case Far::PatchDescriptor::SINGLE_CREASE : return S[desc.GetPattern()][desc.GetRotation()]; case Far::PatchDescriptor::BOUNDARY : return B[desc.GetPattern()][desc.GetRotation()]; case Far::PatchDescriptor::CORNER : return C[desc.GetPattern()][desc.GetRotation()]; case Far::PatchDescriptor::GREGORY : return G; case Far::PatchDescriptor::GREGORY_BOUNDARY : return GB; case Far::PatchDescriptor::GREGORY_BASIS : return GP; default : assert(0); } // can't be reached (suppress compiler warning) return R[0]; } // Counts the number of arrays required to store each type of patch used // in the primitive int getNumPatchArrays() const { int result=0; for (int i=0; i<6; ++i) { if (R[i]) ++result; for (int j=0; j<4; ++j) { if (S[i][j]) ++result; if (B[i][j]) ++result; if (C[i][j]) ++result; } } if (G) ++result; if (GB) ++result; if (GP) ++result; return result; } // Returns true if there's any single-crease patch bool hasSingleCreasedPatches() const { for (int i=0; i<6; ++i) { for (int j=0; j<4; ++j) { if (S[i][j]) return true; } } return false; } }; typedef PatchTypes PatchCVPointers; typedef PatchTypes PatchParamPointers; typedef PatchTypes SharpnessIndexPointers; typedef PatchTypes PatchFVarOffsets; typedef PatchTypes PatchFVarPointers; // // A simple struct containing all information gathered about a face that is relevant // to constructing a patch for it (some of these enums should probably be defined more // as part of PatchTables) // // Like the HbrFace::AdaptiveFlags, this struct aggregates all of the face tags // supporting feature adaptive refinement. For now it is not used elsewhere and can // remain local to this implementation, but we may want to move it into a header of // its own if it has greater use later. // // Note that several properties being assigned here attempt to do so given a 4-bit // mask of properties at the edges or vertices of the quad. Still not sure exactly // what will be done this way, but the goal is to create lookup tables (of size 16 // for the 4 bits) to quickly determine was is needed, rather than iteration and // branching on the edges or vertices. // struct PatchFaceTag { public: // The HBR_ADAPTIVE TransitionType from -- now named to more clearly // reflect the number and orientation of transitional edges. Note that the values // assigned here need to match the intended purpose to remain consistent with Hbr: enum TransitionType { NONE = 0, TRANS_ONE = 1, TRANS_TWO_ADJ = 2, TRANS_THREE = 3, TRANS_ALL = 4, TRANS_TWO_OPP = 5 }; public: unsigned int _hasPatch : 1; unsigned int _isRegular : 1; unsigned int _isTransitional : 1; unsigned int _transitionType : 3; unsigned int _transitionRot : 2; unsigned int _boundaryIndex : 2; unsigned int _boundaryCount : 3; unsigned int _hasBoundaryEdge : 3; unsigned int _isSingleCrease : 1; void clear() { std::memset(this, 0, sizeof(*this)); } void assignBoundaryPropertiesFromEdgeMask(int boundaryEdgeMask) { // // The number of rotations to apply for boundary or corner patches varies on both // where the boundary/corner occurs and whether boundary or corner -- so using a // 4-bit mask should be sufficient to quickly determine all cases: // // Note that we currently expect patches with multiple boundaries to have already // been isolated, so asserts are applied for such unexpected cases. // // Is the compiler going to build the 16-entry lookup table here, or should we do // it ourselves? // _hasBoundaryEdge = true; switch (boundaryEdgeMask) { case 0x0: _boundaryCount = 0, _boundaryIndex = 0, _hasBoundaryEdge = false; break; // no boundaries case 0x1: _boundaryCount = 1, _boundaryIndex = 0; break; // boundary edge 0 case 0x2: _boundaryCount = 1, _boundaryIndex = 1; break; // boundary edge 1 case 0x3: _boundaryCount = 2, _boundaryIndex = 1; break; // corner/crease vertex 1 case 0x4: _boundaryCount = 1, _boundaryIndex = 2; break; // boundary edge 2 case 0x5: assert(false); break; // N/A - opposite boundary edges case 0x6: _boundaryCount = 2, _boundaryIndex = 2; break; // corner/crease vertex 2 case 0x7: assert(false); break; // N/A - three boundary edges case 0x8: _boundaryCount = 1, _boundaryIndex = 3; break; // boundary edge 3 case 0x9: _boundaryCount = 2, _boundaryIndex = 0; break; // corner/crease vertex 0 case 0xa: assert(false); break; // N/A - opposite boundary edges case 0xb: assert(false); break; // N/A - three boundary edges case 0xc: _boundaryCount = 2, _boundaryIndex = 3; break; // corner/crease vertex 3 case 0xd: assert(false); break; // N/A - three boundary edges case 0xe: assert(false); break; // N/A - three boundary edges case 0xf: assert(false); break; // N/A - all boundaries default: assert(false); break; } } void assignBoundaryPropertiesFromVertexMask(int boundaryVertexMask) { // // This is strictly needed for the irregular case when a vertex is a boundary in // the presence of no boundary edges -- an extra-ordinary face with only one corner // on the boundary. // // Its unclear at this point if patches with more than one such vertex are supported // (if so, how do we deal with rotations) so for now we only allow one such vertex // and assert for all other cases. // assert(_hasBoundaryEdge == false); switch (boundaryVertexMask) { case 0x0: _boundaryCount = 0; break; // no boundaries case 0x1: _boundaryCount = 1, _boundaryIndex = 0; break; // boundary vertex 0 case 0x2: _boundaryCount = 1, _boundaryIndex = 1; break; // boundary vertex 1 case 0x3: assert(false); break; case 0x4: _boundaryCount = 1, _boundaryIndex = 2; break; // boundary vertex 2 case 0x5: assert(false); break; case 0x6: assert(false); break; case 0x7: assert(false); break; case 0x8: _boundaryCount = 1, _boundaryIndex = 3; break; // boundary vertex 3 case 0x9: assert(false); break; case 0xa: assert(false); break; case 0xb: assert(false); break; case 0xc: assert(false); break; case 0xd: assert(false); break; case 0xe: assert(false); break; case 0xf: assert(false); break; default: assert(false); break; } } void assignTransitionRotationForCorner(int transitionEdgeMask) { // // Corner transition patches have only two interior edges that may be transitional. // // Either both are transitional (TRANS_TWO_ADJ) with only a single possible orientation, // or only one is transitional (TRANS_ONE) with two possibilities. The former case is // trivial. For the latter, use the known corner index to identify one of the two // possible transition masks and test to determine between the two cases. // if (_transitionType == TRANS_ONE) { int const edgeMaskPerCorner[] = { 4, 8, 1, 2 }; _transitionRot = 1 + (edgeMaskPerCorner[_boundaryIndex] != transitionEdgeMask); } else { _transitionRot = 1; } } void assignTransitionRotationForBoundary(int transitionEdgeMask) { // // Boundary transition patches have three interior edges that may be transitional. // // The case of all three transitional (TRANS_THREE) has only one orientation, while the // case of two opposite transitional edges (TRANS_TWO_OPP) also has only one orientation. // So both of these are trivially handled. // // The case of a single transitional edge (TRANS_ONE) or one transitional edge (TRANS_TWO_ADJ) // both have multiple orientations -- three for TRANS_ONE and two for TRANS_TWO_ADJ. Each is // handled separately: // if (_transitionType == TRANS_ONE) { if (transitionEdgeMask == (1 << ((_boundaryIndex + 2) % 4))) { _transitionRot = 2; } else if (transitionEdgeMask == (1 << ((_boundaryIndex + 1) % 4))) { _transitionRot = 1; } else { _transitionRot = 3; } // XXXX manuelk mirror this rotation to match shader idiosyncracies _transitionRot = (4-_transitionRot)%4; } else if (_transitionType == TRANS_TWO_ADJ) { int const edgeMaskPerBoundary[] = { 6, 12, 9, 3 }; _transitionRot = 1 + (edgeMaskPerBoundary[_boundaryIndex] == transitionEdgeMask); } else if (_transitionType == TRANS_THREE) { _transitionRot = 0; } else { _transitionRot = 1; } } void assignTransitionRotationForSingleCrease(int transitionEdgeMask) { // // Single crease transition patches. // // rotate edgemask by boundaryIndex to align the creased edge // transitionEdgeMask = ((transitionEdgeMask >> _boundaryIndex) | (transitionEdgeMask << (4-_boundaryIndex))) % 16; /* edgemask type : rotation to match to shader 0000 0 : NONE : 0 0001 1 : ONE : 0 0010 2 : ONE : 3 0011 3 : TWO_ADJ : 3 0100 4 : ONE : 2 0101 5 : TWO_OPP : 0 0110 6 : TWO_ADJ : 2 0111 7 : THREE : 1 (needs verify) 1000 8 : ONE : 1 1001 9 : TWO_ADJ : 0 1010 10 : TWO_OPP : 1 1011 11 : THREE : 2 (needs verify) 1100 12 : TWO_ADJ : 1 1101 13 : THREE : 3 1110 14 : THREE : 0 (needs verify) 1111 15 : ALL : 0 */ static int transitionRots[16] = {0, 0, 3, 3, 2, 0, 2, 1, 1, 0, 1, 2, 1, 3, 0, 0 }; _transitionRot = transitionRots[transitionEdgeMask]; } void assignTransitionPropertiesFromEdgeMask(int transitionEdgeMask) { // // Note the transition rotations will be a function of the boundary rotations, and // so boundary rotations/index should have been previously assigned: // // As with the boundary rotation case, consider retrieving values from static 16- // entry lookup tables if possible (depending on the function involving boundary // rotations)... // _isTransitional = (transitionEdgeMask != 0); switch (transitionEdgeMask) { case 0x0: _transitionType = NONE; break; // no transitions case 0x1: _transitionType = TRANS_ONE; break; // single edge 0 case 0x2: _transitionType = TRANS_ONE; break; // single edge 1 case 0x3: _transitionType = TRANS_TWO_ADJ; break; // two adjacent edges, 0 and 1 case 0x4: _transitionType = TRANS_ONE; break; // single edge 2 case 0x5: _transitionType = TRANS_TWO_OPP; break; // two opposite edges, 0 and 2 case 0x6: _transitionType = TRANS_TWO_ADJ; break; // two adjacent edges, 1 and 2 case 0x7: _transitionType = TRANS_THREE; break; // three edges, all but 3 case 0x8: _transitionType = TRANS_ONE; break; // single edge 3 case 0x9: _transitionType = TRANS_TWO_ADJ; break; // two adjacent edges, 3 and 0 case 0xa: _transitionType = TRANS_TWO_OPP; break; // two opposite edges, 1 and 3 case 0xb: _transitionType = TRANS_THREE; break; // three edges, all but 2 case 0xc: _transitionType = TRANS_TWO_ADJ; break; // two adjacent edges, 2 and 3 case 0xd: _transitionType = TRANS_THREE; break; // three edges, all but 1 case 0xe: _transitionType = TRANS_THREE; break; // three edges, all but 0 case 0xf: _transitionType = TRANS_ALL; break; // all edges default: assert(false); break; } // May need another switch/lookup table here or combine it with the above -- the // results below are a function of both transition and boundary properties... if (transitionEdgeMask == 0) { _transitionRot = 0; } else if (_boundaryCount == 0 and _isSingleCrease) { assignTransitionRotationForSingleCrease(transitionEdgeMask); } else if (_boundaryCount == 0) { // XXXX manuelk Rotations are mostly a direct map of the transitionEdgeMask // Except for: // - TRANS_TWO_ADJ that has rotation { 1, 2, 0, 3 } // - TRANS_THREE that has rotation { 3, 2, 1, 0 } // (matching shader idiosyncracies) static unsigned char transitionRots[16] = {0, 0, 1, 1, 2, 0, 2, 3, 3, 0, 1, 2, 3, 1, 0, 0}; _transitionRot = transitionRots[transitionEdgeMask]; } else if (_boundaryCount == 1) { assignTransitionRotationForBoundary(transitionEdgeMask); } else if (_boundaryCount == 2) { assignTransitionRotationForCorner(transitionEdgeMask); } } }; typedef std::vector PatchTagVector; // // Trivial anonymous helper functions: // inline void offsetAndPermuteIndices(Far::Index const indices[], int count, Far::Index offset, int const permutation[], Far::Index result[]) { if (permutation) { for (int i = 0; i < count; ++i) { result[i] = offset + indices[permutation[i]]; } } else if (offset) { for (int i = 0; i < count; ++i) { result[i] = offset + indices[i]; } } else { std::memcpy(result, indices, count * sizeof(Far::Index)); } } } // namespace anon namespace Far { // // Face-varying channel cursor // // This cursors allows to iterate over a set of selected face-varying channels. // If client-code specifies an optional sub-set of the list of channels carried // by the TopologyRefiner, the cursor can traverse this list and return both its // current position in the sub-set and the original index of the corresponding // channel in the TopologyRefiner. // class FVarChannelCursor { public: FVarChannelCursor(TopologyRefiner const & refiner, PatchTablesFactory::Options options) { if (options.generateFVarTables) { // If client-code does not select specific channels, default to all // the channels in the refiner. if (options.numFVarChannels==-1) { _numChannels = refiner.GetNumFVarChannels(); _channelIndices = 0; } else { assert(options.numFVarChannels<=refiner.GetNumFVarChannels()); _numChannels = options.numFVarChannels; _channelIndices = options.fvarChannelIndices; } } else { _numChannels = 0; } _currentChannel = this->begin(); } // Increment cursor FVarChannelCursor & operator++() { ++_currentChannel; return *this; } // Assign a position to a cursor FVarChannelCursor & operator = (int currentChannel) { _currentChannel = currentChannel; return *this; } // Compare cursor positions bool operator != (int pos) { return _currentChannel < pos; } // Return FVar channel index in the TopologyRefiner list // XXXX use something better than dereferencing operator maybe ? int operator*() { assert(_currentChannel<_numChannels); // If the cursor is iterating over a sub-set of channels, return the // channel index from the sub-set, otherwise use the current cursor // position as channel index. return _channelIndices ? _channelIndices[_currentChannel] : _currentChannel; } int pos() { return _currentChannel; } int begin() { return 0; } int end() { return _numChannels; } int size() { return _numChannels; } private: int _numChannels, // total number of channels _currentChannel; // current cursor position int const * _channelIndices; // list of selected channel indices }; // // Adaptive Context // // Helper class aggregating transient contextual data structures during the // creation of feature adaptive patch tables. The structure simplifies // the function prototypes of high-level private methods in the factory. // This helps keeping the factory class stateless. // // Note : struct members are not re-entrant nor are they intended to be ! // struct PatchTablesFactory::AdaptiveContext { public: AdaptiveContext(TopologyRefiner const & refiner, Options options); TopologyRefiner const & refiner; Options const options; // The patch tables being created PatchTables * tables; public: // // Vertex // // True if the factory needs to create "legacy" Gregory patches (GREGORY, // GREGORY_BOUNDARY types) bool RequiresLegacyGregoryPatches() const; // True if the factory needs to create Gregory patches (GREGORY_BASIS type) bool RequiresGregoryBasisPatches() const; // Counters accumulating each type of patches during topology traversal PatchTypes patchInventory; // Bit tags accumulating patch attributes during topology traversal PatchTagVector patchTags; public: // // Face-varying // // True if face-varying patches need to be generated for this topology bool RequiresFVarPatches() const; // A cursor to iterate through the face-varying channels requested // by client-code FVarChannelCursor fvarChannelCursor; // Allocate temporary space to store face-varying values : because we do // not know yet the types of each patch, we pre-emptively allocate // non-sparse arrays for each channel. Patches are assumed to have a maximum // of fvarPatchSize CVs). void AllocateFVarPatchValues(int npatches); static const int fvarPatchSize = 16; // We need temporary storage space to accumulate fvar values as we sort the // vertices of the adapative cubic patches. FVar patch types do not match // vertex patch types, and unfortunately we cannot generate offsets for a // given patch until we have traversed the entire adaptive hierarchy. Instead // of incurring another full hierarchy traversal, we store the FVar values // in a temporary array with patches of fixed size. Once the values have been // populated (in the correct sorted order), we copy them in the final sparse // vectors and generate offsets. std::vector > fvarPatchValues; }; // Constructor PatchTablesFactory::AdaptiveContext::AdaptiveContext( TopologyRefiner const & ref, Options opts) : refiner(ref), options(opts), tables(0), fvarChannelCursor(ref, opts) { fvarPatchValues.resize(fvarChannelCursor.size()); } void PatchTablesFactory::AdaptiveContext::AllocateFVarPatchValues(int npatches) { FVarChannelCursor & fvc = fvarChannelCursor; for (fvc=fvc.begin(); fvc!=fvc.end(); ++fvc) { Sdc::Options::FVarLinearInterpolation interpolation = refiner.GetFVarLinearInterpolation(*fvc); // the LINEAR_ALL rule can populate values immediately (all quads) so // we do not need this temporary storage if (interpolation != Sdc::Options::FVAR_LINEAR_ALL) { fvarPatchValues[fvc.pos()].resize(npatches*fvarPatchSize); } } } bool PatchTablesFactory::AdaptiveContext::RequiresLegacyGregoryPatches() const { return (patchInventory.G>0) or (patchInventory.GB>0); } bool PatchTablesFactory::AdaptiveContext::RequiresGregoryBasisPatches() const { return (patchInventory.GP>0); } bool PatchTablesFactory::AdaptiveContext::RequiresFVarPatches() const { return not fvarPatchValues.empty(); } // // Reserves tables based on the contents of the PatchArrayVector in the PatchTables: // void PatchTablesFactory::allocateVertexTables(PatchTables * tables, int /* nlevels */, bool hasSharpness) { int ncvs = 0, npatches = 0; for (int i=0; iGetNumPatchArrays(); ++i) { npatches += tables->GetNumPatches(i); ncvs += tables->GetNumControlVertices(i); } if (ncvs==0 or npatches==0) return; tables->_patchVerts.resize( ncvs ); tables->_paramTable.resize( npatches ); if (hasSharpness) { tables->_sharpnessIndices.resize( npatches, Vtr::INDEX_INVALID ); } } // // Allocate face-varying tables // void PatchTablesFactory::allocateFVarChannels(TopologyRefiner const & refiner, Options options, int npatches, PatchTables * tables) { assert(options.generateFVarTables and refiner.GetNumFVarChannels()>0 and npatches>0 and tables); // Create a channel cursor to iterate over client-selected channels or // default to the channels found in the TopologyRefiner FVarChannelCursor fvc(refiner, options); if (fvc.size()==0) { return; } tables->allocateFVarPatchChannels(fvc.size()); // Iterate with the cursor to initialize each channel for (fvc=fvc.begin(); fvc!=fvc.end(); ++fvc) { Sdc::Options::FVarLinearInterpolation interpolation = refiner.GetFVarLinearInterpolation(*fvc); tables->setFVarPatchChannelLinearInterpolation(fvc.pos(), interpolation); int nverts = 0; if (interpolation==Sdc::Options::FVAR_LINEAR_ALL) { PatchDescriptor::Type type = options.triangulateQuads ? PatchDescriptor::TRIANGLES : PatchDescriptor::QUADS; tables->setFVarPatchChannelPatchesType(fvc.pos(), type); nverts = npatches * PatchDescriptor::GetNumFVarControlVertices(type); } tables->allocateChannelValues(fvc.pos(), npatches, nverts); } } // gather face-varying patch points inline int PatchTablesFactory::gatherFVarData(AdaptiveContext & context, int level, Index faceIndex, Index levelFaceOffset, int rotation, Index const * levelFVarVertOffsets, Index fofss, Index ** fptrs) { if (not context.RequiresFVarPatches()) { return 0; } TopologyRefiner const & refiner = context.refiner; PatchTables * tables = context.tables; assert((levelFaceOffset + faceIndex)<(int)context.patchTags.size()); PatchFaceTag & vertexPatchTag = context.patchTags[levelFaceOffset + faceIndex]; Index patchVerts[context.fvarPatchSize]; // Iterate over valid FVar channels (if any) FVarChannelCursor & fvc = context.fvarChannelCursor; for (fvc=fvc.begin(); fvc!=fvc.end(); ++fvc) { Vtr::Level const & vtxLevel = refiner.getLevel(level); Vtr::FVarLevel const & fvarLevel = vtxLevel.getFVarLevel(*fvc); if (refiner.GetFVarLinearInterpolation(*fvc)!=Sdc::Options::FVAR_LINEAR_ALL) { // // Bi-cubic patches // // If the face-varying topology matches the vertex topology (which should be the // dominant case), we can use the patch tag for the original vertex patch -- // quickly check the composite tag for the face-varying values at the corners: // PatchFaceTag fvarPatchTag = vertexPatchTag; ConstIndexArray faceVerts = vtxLevel.getFaceVertices(faceIndex), fvarValues = fvarLevel.getFaceValues(faceIndex); Vtr::FVarLevel::ValueTag compFVarTagsForFace = fvarLevel.getFaceCompositeValueTag(fvarValues, faceVerts); if (compFVarTagsForFace.isMismatch()) { // At least one of the corner vertices has differing topology in FVar space, // so we need to perform similar analysis to what was done to determine the // face's original patch tag to determine the face-varying patch tag here. // // Recall how that patch tag is initialized: // - a "composite" (bitwise-OR) tag of the face's VTags is taken // - if determined to be on a boundary, a "boundary mask" is built and // passed to the PatchFaceTag to determine boundary orientation // - when necessary, a "composite" tag for the face's ETags is inspected // - special case for "single-crease patch" // - special case for "approx smooth corner with regular patch" // // Note differences here (simplifications): // - we don't need to deal with the single-crease patch case: // - if vertex patch was single crease the mismatching FVar patch // cannot be // - the fvar patch cannot become single-crease patch as only sharp // (discts) edges are introduced, which are now boundary edges // - the "approx smooth corner with regular patch" case was ignored: // - its unclear if it should persist for the vertex patch // // As was the case with the vertex patch, since we are creating a patch it // is assumed that all required isolation has occurred. For example, a // regular patch at level 0 that has a FVar patch with too many boundaries // (or local xordinary vertices) is going to cause trouble here... // // // Gather the VTags for the four corners of the FVar patch (these are the VTag // of each vertex merged with the FVar tag of its value) while computing the // composite VTag: // Vtr::Level::VTag fvarVertTags[4]; Vtr::Level::VTag compFVarVTag = fvarLevel.getFaceCompositeValueAndVTag(fvarValues, faceVerts, fvarVertTags); // // Clear/re-initialize the FVar patch tag and compute the appropriate boundary // masks if boundary orientation is necessary: // fvarPatchTag.clear(); fvarPatchTag._hasPatch = true; fvarPatchTag._isRegular = not compFVarVTag._xordinary; if (compFVarVTag._boundary) { Vtr::Level::ETag fvarEdgeTags[4]; ConstIndexArray faceEdges = vtxLevel.getFaceEdges(faceIndex); Vtr::Level::ETag compFVarETag = fvarLevel.getFaceCompositeCombinedEdgeTag(faceEdges, fvarEdgeTags); if (compFVarETag._boundary) { int boundaryEdgeMask = (fvarEdgeTags[0]._boundary << 0) | (fvarEdgeTags[1]._boundary << 1) | (fvarEdgeTags[2]._boundary << 2) | (fvarEdgeTags[3]._boundary << 3); fvarPatchTag.assignBoundaryPropertiesFromEdgeMask(boundaryEdgeMask); } else { int boundaryVertMask = (fvarVertTags[0]._boundary << 0) | (fvarVertTags[1]._boundary << 1) | (fvarVertTags[2]._boundary << 2) | (fvarVertTags[3]._boundary << 3); fvarPatchTag.assignBoundaryPropertiesFromVertexMask(boundaryVertMask); } } } // // Determine and assign the type of the patch // PatchDescriptor::Type fvarPatchType = PatchDescriptor::REGULAR; if (not fvarPatchTag._isRegular) { // because we do not want to have to generate vertex-valence // & quad-offset tables for each fvar channel, we default to // Gregory-basis type patchs only (and use stencils to // compute the 20 cvs basis) fvarPatchType = context.options.useFVarQuadEndCaps ? PatchDescriptor::QUADS : PatchDescriptor::GREGORY_BASIS; } else if (fvarPatchTag._boundaryCount > 1) { fvarPatchType = PatchDescriptor::CORNER; } else if (fvarPatchTag._boundaryCount == 1) { fvarPatchType = PatchDescriptor::BOUNDARY; } else if (fvarPatchTag._isSingleCrease) { fvarPatchType = PatchDescriptor::REGULAR; } Vtr::Array patchTypes = tables->getFVarPatchTypes(fvc.pos()); assert(not patchTypes.empty()); patchTypes[fofss] = fvarPatchType; int const * permutation = 0; // Gather the verts FVar values // XXXX Patch verts should be rotated to match boundary / corner // edges. Transition patterns should not be a concern, however // we need to match parametric space, so this may need to be // revisited... int orientationIndex = fvarPatchTag._boundaryIndex; if (fvarPatchType == PatchDescriptor::REGULAR) { static int const permuteRegular[16] = { 5, 6, 7, 8, 4, 0, 1, 9, 15, 3, 2, 10, 14, 13, 12, 11 }; vtxLevel.gatherQuadRegularInteriorPatchPoints(faceIndex, patchVerts, orientationIndex, *fvc); permutation = permuteRegular; } else if (fvarPatchType == PatchDescriptor::CORNER) { static int const permuteCorner[9] = { 8, 3, 0, 7, 2, 1, 6, 5, 4 }; vtxLevel.gatherQuadRegularCornerPatchPoints(faceIndex, patchVerts, orientationIndex, *fvc); permutation = permuteCorner; } else if (fvarPatchType == PatchDescriptor::BOUNDARY) { static int const permuteBoundary[12] = { 11, 3, 0, 4, 10, 2, 1, 5, 9, 8, 7, 6 }; vtxLevel.gatherQuadRegularBoundaryPatchPoints(faceIndex, patchVerts, orientationIndex, *fvc); permutation = permuteBoundary; } else if (fvarPatchType == PatchDescriptor::QUADS) { vtxLevel.gatherQuadLinearPatchPoints(faceIndex, patchVerts, orientationIndex, *fvc); permutation = 0; } else if (fvarPatchType == PatchDescriptor::GREGORY_BASIS) { // XXXX // Gregory basis patch : we need to gather the vertices and // generate the stencil. We can use the index in the vertex // patch array to index the stencils. assert(0); } else { // note : we do not plan on supporting direct evaluation types // of Gregory patches, because they requre extremely inefficient // quad-offset and vertex-valence data structures. assert(0); } int nverts = PatchDescriptor::GetNumFVarControlVertices(fvarPatchType); assert(nverts <= context.fvarPatchSize); offsetAndPermuteIndices(patchVerts, nverts, levelFVarVertOffsets[fvc.pos()], permutation, &context.fvarPatchValues[fvc.pos()][fofss*context.fvarPatchSize]); } else { // // Bi-linear patches // ConstIndexArray fvarValues = fvarLevel.getFaceValues(faceIndex); // Store verts values directly in non-sparse context channel arrays for (int vert=0; vert 0; --i) { Vtr::Refinement const& refinement = refiner.getRefinement(i-1); Vtr::Level const& parentLevel = refiner.getLevel(i-1); Vtr::Index parentFaceIndex = refinement.getChildFaceParentFace(faceIndex); childIndexInParent = refinement.getChildFaceInParentFace(faceIndex); if (parentLevel.getFaceVertices(parentFaceIndex).size() == 4) { switch ( childIndexInParent ) { case 0 : break; case 1 : { u+=ofs; } break; case 2 : { u+=ofs; v+=ofs; } break; case 3 : { v+=ofs; } break; } ofs = (unsigned short)(ofs << 1); } else { nonquad = true; // If the root face is not a quad, we need to offset the ptex index // CCW to match the correct child face Vtr::ConstIndexArray children = refinement.getFaceChildFaces(parentFaceIndex); for (int j=0; jSet(ptexIndex, (short)u, (short)v, (unsigned char) rotation, (unsigned char) depth, nonquad); return ++coord; } #ifdef ENDCAP_TOPOPOLGY // XXXX manuelk work in progress for end-cap topology gathering // // Populates the topology table used by Gregory-basis patches // // Note : 'faceIndex' values are expected to be sorted in ascending order !!! // Note 2: this code attempts to identify basis vertices shared along // gregory patch edges static int gatherGregoryBasisTopology(Vtr::Level const& level, Index faceIndex, int numVertices, PatchFaceTag const * levelPatchTags, bool skip[0], std::vector & basisIndices, PatchTables::PTable & topology) { assert(not topology.empty()); Index * dest = &topology[basisIndices.size()*20]; assert(Vtr::INDEX_INVALID==0xFFFFFFFF); memset(dest, 0xFF, 20*sizeof(Index)); IndexArray fedges = level.getFaceEdges(faceIndex); assert(fedges.size()==4); for (int i=0; i<4; ++i) { Index edge = fedges[i], adjface = 0; { // Gather adjacent faces IndexArray adjfaces = level.getEdgeFaces(edge); for (int i=0; i=0 and srcBasisIdx<(int)basisIndices.size()); // Copy the indices of CVs from the face on the other side of the // shared edge static int const gregoryEdgeVerts[4][4] = { { 0, 1, 7, 5}, { 5, 6, 12, 10}, {10, 11, 17, 15}, {15, 16, 2, 0} }; Index * src = &topology[srcBasisIdx*20]; for (int j=0; j<4; ++j) { dest[i*4+j] = src[gregoryEdgeVerts[aedge][j]]; } skip[i] = true; } else { skip[i] = false; } } for (int i=0; i<20; ++i) { if (dest[i]==Vtr::INDEX_INVALID) { dest[i] = numVertices++; } } basisIndices.push_back(faceIndex); return numVertices; } #endif // // Indexing sharpnesses // inline int assignSharpnessIndex(float sharpness, std::vector & sharpnessValues) { // linear search for (int i=0; i<(int)sharpnessValues.size(); ++i) { if (sharpnessValues[i] == sharpness) { return i; } } sharpnessValues.push_back(sharpness); return (int)sharpnessValues.size()-1; } // // Populate the quad-offsets table used by Gregory patches // void PatchTablesFactory::getQuadOffsets( Vtr::Level const& level, Index faceIndex, unsigned int offsets[]) { Vtr::ConstIndexArray fVerts = level.getFaceVertices(faceIndex); for (int i = 0; i < 4; ++i) { Vtr::Index vIndex = fVerts[i]; Vtr::ConstIndexArray vFaces = level.getVertexFaces(vIndex), vEdges = level.getVertexEdges(vIndex); int thisFaceInVFaces = -1; for (int j = 0; j < vFaces.size(); ++j) { if (faceIndex == vFaces[j]) { thisFaceInVFaces = j; break; } } assert(thisFaceInVFaces != -1); Index vOffsets[2]; vOffsets[0] = thisFaceInVFaces; vOffsets[1] = (thisFaceInVFaces + 1)%vEdges.size(); // we have to use the number of incident edges to modulo the local index // because there could be 2 consecutive edges in the face belonging to // the Gregory patch. offsets[i] = vOffsets[0] | (vOffsets[1] << 8); } } // // We should be able to use a single Create() method for both the adaptive and uniform // cases. In the past, more additional arguments were passed to the uniform version, // but that may no longer be necessary (see notes in the uniform version below)... // PatchTables * PatchTablesFactory::Create( TopologyRefiner const & refiner, Options options ) { if (refiner.IsUniform()) { return createUniform(refiner, options); } else { return createAdaptive(refiner, options); } } PatchTables * PatchTablesFactory::createUniform(TopologyRefiner const & refiner, Options options) { assert(refiner.IsUniform()); // ensure that triangulateQuads is only set for quadrilateral schemes options.triangulateQuads &= (refiner.GetSchemeType()==Sdc::SCHEME_BILINEAR or refiner.GetSchemeType()==Sdc::SCHEME_CATMARK); int maxvalence = refiner.getLevel(0).getMaxValence(), maxlevel = refiner.GetMaxLevel(), firstlevel = options.generateAllLevels ? 0 : maxlevel, nlevels = maxlevel-firstlevel+1; PatchDescriptor::Type ptype = PatchDescriptor::NON_PATCH; if (options.triangulateQuads) { ptype = PatchDescriptor::TRIANGLES; } else { switch (refiner.GetSchemeType()) { case Sdc::SCHEME_BILINEAR : case Sdc::SCHEME_CATMARK : ptype = PatchDescriptor::QUADS; break; case Sdc::SCHEME_LOOP : ptype = PatchDescriptor::TRIANGLES; break; } } assert(ptype!=PatchDescriptor::NON_PATCH); // // Create the instance of the tables and allocate and initialize its members. // PatchTables * tables = new PatchTables(maxvalence); tables->_numPtexFaces = refiner.GetNumPtexFaces(); tables->reservePatchArrays(nlevels); PatchDescriptor desc(ptype, PatchDescriptor::NON_TRANSITION, 0); // generate patch arrays for (int level=firstlevel, poffset=0, voffset=0; level<=maxlevel; ++level) { int npatches = refiner.GetNumFaces(level); if (refiner.HasHoles()) { npatches -= refiner.GetNumHoles(level); } assert(npatches>=0); if (options.triangulateQuads) npatches *= 2; if (level>=firstlevel) { tables->pushPatchArray(desc, npatches, &voffset, &poffset, 0); } } // Allocate various tables allocateVertexTables( tables, 0, /*hasSharpness=*/false ); bool generateFVarPatches=false; FVarChannelCursor fvc(refiner, options); if (options.generateFVarTables and fvc.size()>0) { int npatches = tables->GetNumPatchesTotal(); allocateFVarChannels(refiner, options, npatches, tables); assert(fvc.size() == tables->GetNumFVarChannels()); } // // Now populate the patches: // Index * iptr = &tables->_patchVerts[0]; PatchParam * pptr = &tables->_paramTable[0]; Index ** fptr = 0; Index levelVertOffset = options.generateAllLevels ? 0 : refiner.GetNumVertices(0); Index * levelFVarVertOffsets = 0; if (generateFVarPatches) { levelFVarVertOffsets = (Index *)alloca(fvc.size()*sizeof(Index)); memset(levelFVarVertOffsets, 0, fvc.size()*sizeof(Index)); fptr = (Index **)alloca(fvc.size()*sizeof(Index *)); for (fvc=fvc.begin(); fvc!=fvc.end(); ++fvc) { fptr[fvc.pos()] = tables->getFVarPatchesValues(fvc.pos()).begin(); } } for (int level=1; level<=maxlevel; ++level) { int nfaces = refiner.GetNumFaces(level); if (level>=firstlevel) { for (int face=0; facegetFVarPatchesValues(fvc.pos()).size()); fptr[fvc.pos()][vert] = levelFVarVertOffsets[fvc.pos()] + fvalues[vert]; } fptr[fvc.pos()]+=fvalues.size(); } } if (options.triangulateQuads) { // Triangulate the quadrilateral: {v0,v1,v2,v3} -> {v0,v1,v2},{v3,v0,v2}. *iptr = *(iptr - 4); // copy v0 index ++iptr; *iptr = *(iptr - 3); // copy v2 index ++iptr; *pptr = *(pptr - 1); // copy first patch param ++pptr; if (generateFVarPatches) { for (fvc=fvc.begin(); fvc!=fvc.end(); ++fvc) { *fptr[fvc.pos()] = *(fptr[fvc.pos()]-4); // copy fv0 index ++fptr[fvc.pos()]; *fptr[fvc.pos()] = *(fptr[fvc.pos()]-3); // copy fv2 index ++fptr[fvc.pos()]; } } } } } if (options.generateAllLevels) { levelVertOffset += refiner.GetNumVertices(level); for (fvc=fvc.begin(); fvc!=fvc.end(); ++fvc) { levelFVarVertOffsets[fvc.pos()] += refiner.GetNumFVarValues(level, fvc.pos()); } } } return tables; } PatchTables * PatchTablesFactory::createAdaptive(TopologyRefiner const & refiner, Options options) { assert(not refiner.IsUniform()); AdaptiveContext context(refiner, options); // // First identify the patches -- accumulating the inventory patches for all of the // different types and information about the patch for each face: // identifyAdaptivePatches(context); // // Create the instance of the tables and allocate and initialize its members based on // the inventory of patches determined above: // int maxValence = refiner.getLevel(0).getMaxValence(); context.tables = new PatchTables(maxValence); // Populate the patch array descriptors context.tables->reservePatchArrays(context.patchInventory.getNumPatchArrays()); // Sort through the inventory and push back non-empty patch arrays ConstPatchDescriptorArray const & descs = PatchDescriptor::GetAdaptivePatchDescriptors(Sdc::SCHEME_CATMARK); int voffset=0, poffset=0, qoffset=0; for (int i=0; ipushPatchArray(desc, context.patchInventory.getValue(desc), &voffset, &poffset, &qoffset ); } context.tables->_numPtexFaces = refiner.GetNumPtexFaces(); // Allocate various tables bool hasSharpness = context.patchInventory.hasSingleCreasedPatches(); allocateVertexTables(context.tables, 0, hasSharpness); if (context.RequiresFVarPatches()) { int npatches = context.tables->GetNumPatchesTotal(); allocateFVarChannels(refiner, options, npatches, context.tables); // Reserve temporary non-sparse storage for non-linear fvar channels. // FVar Values for these channels are copied into the final // FVarPatchChannel after the second traversal happens within the call to // populateAdaptivePatches() context.AllocateFVarPatchValues(npatches); } // Specifics for Gregory patches if (context.RequiresLegacyGregoryPatches()) { context.tables->_quadOffsetsTable.resize( context.patchInventory.G*4 + context.patchInventory.GB*4 ); } // // Now populate the patches: // populateAdaptivePatches(context); return context.tables; } // // Identify all patches required for faces at all levels -- accumulating the number of patches // for each type, and retaining enough information for the patch for each face to populate it // later with no additional analysis. // void PatchTablesFactory::identifyAdaptivePatches(AdaptiveContext & context) { TopologyRefiner const & refiner = context.refiner; // // Iterate through the levels of refinement to inspect and tag components with information // relative to patch generation. We allocate all of the tags locally and use them to // populate the patches once a complete inventory has been taken and all tables appropriately // allocated and initialized: // // The first Level may have no Refinement if it is the only level -- similarly the last Level // has no Refinement, so a single level is effectively the last, but with less information // available in some cases, as it was not generated by refinement. // context.patchTags.resize(refiner.GetNumFacesTotal()); PatchFaceTag * levelPatchTags = &context.patchTags[0]; for (int levelIndex = 0; levelIndex < refiner.GetNumLevels(); ++levelIndex) { Vtr::Level const * level = &refiner.getLevel(levelIndex); // // Given components at Level[i], we need to be looking at Refinement[i] -- and not // [i-1] -- because the Refinement has transitional information for its parent edges // and faces. // // For components in this level, we want to determine: // - what Edges are "transitional" (already done in Refinement for parent) // - what Faces are "transitional" (already done in Refinement for parent) // - what Faces are "complete" (applied to this Level in previous refinement) // Vtr::Refinement const * refinement = 0; Vtr::Refinement::SparseTag const * refinedFaceTags = 0; if (levelIndex < refiner.GetMaxLevel()) { refinement = &refiner.getRefinement(levelIndex); refinedFaceTags = &refinement->_parentFaceTag[0]; } for (int faceIndex = 0; faceIndex < level->getNumFaces(); ++faceIndex) { PatchFaceTag & patchTag = levelPatchTags[faceIndex]; patchTag.clear(); patchTag._hasPatch = false; if (level->isHole(faceIndex)) { continue; } // // This face does not warrant a patch under the following conditions: // // - the face was fully refined into child faces // - the face is not a quad (should have been refined, so assert) // - the face is not "complete" // // The first is trivially determined, and the second is really redundant. The // last -- "incompleteness" -- indicates a face that exists to support the limit // of some neighboring component, and which does not have its own neighborhood // fully defined for its limit. If any child vertex of a vertex of this face is // "incomplete" (and all are tagged) the face must be "incomplete", so get the // "composite" tag which combines bits for all vertices: // Vtr::Refinement::SparseTag refinedFaceTag = refinedFaceTags ? refinedFaceTags[faceIndex] : Vtr::Refinement::SparseTag(); if (refinedFaceTag._selected) { continue; } Vtr::ConstIndexArray fVerts = level->getFaceVertices(faceIndex); assert(fVerts.size() == 4); Vtr::Level::VTag compFaceVertTag = level->getFaceCompositeVTag(fVerts); if (compFaceVertTag._incomplete) { continue; } // // We have a quad that will be represented as a B-spline or Gregory patch. Use // the "composite" tag again to quickly determine if any vertex is irregular, on // a boundary, non-manifold, etc. // // Inspect the edges for boundaries and transitional edges and pack results into // 4-bit masks. We detect boundary edges rather than vertices as we hope to // replace the mask in future with one for infinitely sharp edges -- allowing // us to detect regular patches and avoid isolation. We still need to account // for the irregular/xordinary case when a corner vertex is a boundary but there // are no boundary edges. // // As for transition detection, assign the transition properties (even if 0) as // their rotations override boundary rotations (when no transition) // // NOTE on patches around non-manifold vertices: // In most the use of regular boundary or corner patches is what we want, // but in some, i.e. when a non-manifold vertex is infinitely sharp, using // such patches will create some discontinuities. At this point non-manifold // support is still evolving and is not strictly defined, so this is left to // a later date to resolve. // // NOTE on infinitely sharp (hard) edges: // We should be able to adapt this later to detect hard (inf-sharp) edges // rather than just boundary edges -- there is a similar tag per edge. That // should allow us to generate regular patches for interior hard features. // bool hasBoundaryVertex = compFaceVertTag._boundary; bool hasNonManifoldVertex = compFaceVertTag._nonManifold; bool hasXOrdinaryVertex = compFaceVertTag._xordinary; patchTag._hasPatch = true; patchTag._isRegular = not hasXOrdinaryVertex or hasNonManifoldVertex; // single crease patch optimization if (context.options.useSingleCreasePatch and not hasXOrdinaryVertex and not hasBoundaryVertex and not hasNonManifoldVertex) { Vtr::ConstIndexArray fEdges = level->getFaceEdges(faceIndex); Vtr::Level::ETag compFaceETag = level->getFaceCompositeETag(fEdges); if (compFaceETag._semiSharp or compFaceETag._infSharp) { float sharpness = 0; int rotation = 0; if (level->isSingleCreasePatch(faceIndex, &sharpness, &rotation)) { // cap sharpness to the max isolation level float cappedSharpness = std::min(sharpness, (float)(context.options.maxIsolationLevel - levelIndex)); if (cappedSharpness > 0) { patchTag._isSingleCrease = true; patchTag._boundaryIndex = (rotation + 2) % 4; } } } } // Identify boundaries for both regular and xordinary patches -- non-manifold // edges and vertices are interpreted as boundaries for regular patches if (hasBoundaryVertex or hasNonManifoldVertex) { Vtr::ConstIndexArray fEdges = level->getFaceEdges(faceIndex); int boundaryEdgeMask = ((level->_edgeTags[fEdges[0]]._boundary) << 0) | ((level->_edgeTags[fEdges[1]]._boundary) << 1) | ((level->_edgeTags[fEdges[2]]._boundary) << 2) | ((level->_edgeTags[fEdges[3]]._boundary) << 3); if (hasNonManifoldVertex) { int nonManEdgeMask = ((level->_edgeTags[fEdges[0]]._nonManifold) << 0) | ((level->_edgeTags[fEdges[1]]._nonManifold) << 1) | ((level->_edgeTags[fEdges[2]]._nonManifold) << 2) | ((level->_edgeTags[fEdges[3]]._nonManifold) << 3); boundaryEdgeMask |= nonManEdgeMask; } if (boundaryEdgeMask) { patchTag.assignBoundaryPropertiesFromEdgeMask(boundaryEdgeMask); } else { int boundaryVertMask = ((level->_vertTags[fVerts[0]]._boundary) << 0) | ((level->_vertTags[fVerts[1]]._boundary) << 1) | ((level->_vertTags[fVerts[2]]._boundary) << 2) | ((level->_vertTags[fVerts[3]]._boundary) << 3); if (hasNonManifoldVertex) { int nonManVertMask = ((level->_vertTags[fVerts[0]]._nonManifold) << 0) | ((level->_vertTags[fVerts[1]]._nonManifold) << 1) | ((level->_vertTags[fVerts[2]]._nonManifold) << 2) | ((level->_vertTags[fVerts[3]]._nonManifold) << 3); boundaryVertMask |= nonManVertMask; } patchTag.assignBoundaryPropertiesFromVertexMask(boundaryVertMask); } } // XXXX (barfowl) -- why are we approximating a smooth x-ordinary corner with // a sharp corner patch? The boundary/corner points of the regular patch are // not even made colinear to make it smoother. Something historical here... // // So this treatment may become optional in future and is bracketed with a // condition now for that reason. We approximate x-ordinary smooth corners // with regular B-spline patches instead of using a Gregory patch. The smooth // corner must be properly isolated from any other irregular vertices (as it // will be at any level > 1) otherwise the Gregory patch is necessary. // // This flag to be initialized with a future option... ? bool approxSmoothCornerWithRegularPatch = true; if (approxSmoothCornerWithRegularPatch) { if (!patchTag._isRegular and (patchTag._boundaryCount == 2)) { // We may have a sharp corner opposite/adjacent an xordinary vertex -- // need to make sure there is only one xordinary vertex and that it // is the corner vertex. if (levelIndex > 1) { patchTag._isRegular = true; } else { int xordVertex = 0; int xordCount = 0; if (level->_vertTags[fVerts[0]]._xordinary) { xordCount++; xordVertex = 0; } if (level->_vertTags[fVerts[1]]._xordinary) { xordCount++; xordVertex = 1; } if (level->_vertTags[fVerts[2]]._xordinary) { xordCount++; xordVertex = 2; } if (level->_vertTags[fVerts[3]]._xordinary) { xordCount++; xordVertex = 3; } if (xordCount == 1) { // We require the vertex opposite the xordinary vertex be interior: if (not level->_vertTags[fVerts[(xordVertex + 2) % 4]]._boundary) { patchTag._isRegular = true; } } } } } // // Now that all boundary features have have been identified and tagged, assign // the transition type for the patch before taking inventory. // // Identify and increment counts for regular patches (both non-transitional and // transitional) and extra-ordinary patches (always non-transitional): // patchTag.assignTransitionPropertiesFromEdgeMask(refinedFaceTag._transitional); if (patchTag._isRegular) { int transIndex = patchTag._transitionType; int transRot = patchTag._transitionRot; if (!patchTag._isSingleCrease and patchTag._boundaryCount == 0) { context.patchInventory.R[transIndex]++; } else if (patchTag._isSingleCrease and patchTag._boundaryCount == 0) { context.patchInventory.S[transIndex][transRot]++; } else if (patchTag._boundaryCount == 1) { context.patchInventory.B[transIndex][transRot]++; } else { context.patchInventory.C[transIndex][transRot]++; } } else { // if end-cap patches use a stencils-driven basis, we don't need // to track regular / boundary cases if (not context.options.adaptiveStencilTables) { if (patchTag._boundaryCount == 0) { context.patchInventory.G++; } else { context.patchInventory.GB++; } } else { context.patchInventory.GP++; } } } levelPatchTags += level->getNumFaces(); } } // // Populate all adaptive patches now that the tables to hold data for them have been allocated. // We need the inventory (counts per patch type) and the patch tags per face that were previously // idenified. // void PatchTablesFactory::populateAdaptivePatches(AdaptiveContext & context) { TopologyRefiner const & refiner = context.refiner; PatchTables * tables = context.tables; // // Setup convenience pointers at the beginning of each patch array for each // table (patches, ptex) // PatchCVPointers iptrs; PatchParamPointers pptrs; PatchFVarOffsets fofss; PatchFVarPointers fptrs; SharpnessIndexPointers sptrs; ConstPatchDescriptorArray const & descs = PatchDescriptor::GetAdaptivePatchDescriptors(Sdc::SCHEME_CATMARK); for (int i=0; ifindPatchArray(desc); if (arrayIndex==Vtr::INDEX_INVALID) { continue; } iptrs.getValue(desc) = tables->getPatchArrayVertices(arrayIndex).begin(); pptrs.getValue(desc) = tables->getPatchParams(arrayIndex).begin(); if (context.patchInventory.hasSingleCreasedPatches()) { sptrs.getValue(desc) = tables->getSharpnessIndices(arrayIndex); } if (context.RequiresFVarPatches()) { Index & offsets = fofss.getValue(desc); offsets = tables->getPatchIndex(arrayIndex, 0); // XXXX manuelk this stuff will go away as we use offsets from FVarPatchChannel FVarChannelCursor & fvc = context.fvarChannelCursor; assert(fvc.size() == tables->GetNumFVarChannels()); Index ** fptr = (Index **)alloca(fvc.size()*sizeof(Index *)); for (fvc=fvc.begin(); fvc!=fvc.end(); ++fvc) { Index pidx = tables->getPatchIndex(arrayIndex, 0); int ofs = pidx * 4; fptr[fvc.pos()] = &tables->getFVarPatchesValues(fvc.pos())[ofs]; } fptrs.getValue(desc) = fptr; } } unsigned int * quad_G_C0_P = context.patchInventory.G>0 ? &tables->_quadOffsetsTable[0] : 0, * quad_G_C1_P = context.patchInventory.GB>0 ? &tables->_quadOffsetsTable[context.patchInventory.G*4] : 0; std::vector gregoryVertexFlags; // // To avoid gathering vertex neighborhoods for all vertices, identify vertices involved in // gregory patches as the faces are traversed, to be gathered later: // bool hasGregoryPatches = context.RequiresLegacyGregoryPatches() or context.RequiresGregoryBasisPatches(); GregoryBasisFactory * gregoryStencilsFactory = 0; #ifdef ENDCAP_TOPOPOLGY int numGregoryBasisVertices=0; std::vector gregoryBasisIndices; #endif if (hasGregoryPatches) { StencilTables const * adaptiveStencils = context.options.adaptiveStencilTables; if (adaptiveStencils and context.RequiresGregoryBasisPatches()) { assert(not context.RequiresLegacyGregoryPatches()); int maxvalence = refiner.getLevel(0).getMaxValence(), npatches = context.patchInventory.GP; gregoryStencilsFactory = new GregoryBasisFactory(refiner, *adaptiveStencils, npatches, maxvalence); #ifdef ENDCAP_TOPOPOLGY gregoryBasisIndices.reserve(npatches); tables->_endcapTopology.resize(npatches*20); #endif } gregoryVertexFlags.resize(refiner.GetNumVerticesTotal(), false); } // // Now iterate through the faces for all levels and populate the patches: // int levelFaceOffset = 0, levelVertOffset = 0; int * levelFVarVertOffsets = 0; if (context.RequiresFVarPatches()) { int nchannels = refiner.GetNumFVarChannels(); levelFVarVertOffsets = (int *)alloca(nchannels); memset(levelFVarVertOffsets, 0, nchannels*sizeof(int)); } for (int i = 0; i < refiner.GetNumLevels(); ++i) { Vtr::Level const * level = &refiner.getLevel(i); const PatchFaceTag * levelPatchTags = &context.patchTags[levelFaceOffset]; for (int faceIndex = 0; faceIndex < level->getNumFaces(); ++faceIndex) { if (level->isHole(faceIndex)) { continue; } const PatchFaceTag& patchTag = levelPatchTags[faceIndex]; if (not patchTag._hasPatch) { continue; } if (patchTag._isRegular) { Index patchVerts[16]; int tIndex = patchTag._transitionType; int rIndex = patchTag._transitionRot; int bIndex = patchTag._boundaryIndex; if (!patchTag._isSingleCrease and patchTag._boundaryCount == 0) { int const permuteInterior[16] = { 5, 6, 7, 8, 4, 0, 1, 9, 15, 3, 2, 10, 14, 13, 12, 11 }; level->gatherQuadRegularInteriorPatchPoints(faceIndex, patchVerts, rIndex); offsetAndPermuteIndices(patchVerts, 16, levelVertOffset, permuteInterior, iptrs.R[tIndex]); iptrs.R[tIndex] += 16; pptrs.R[tIndex] = computePatchParam(refiner, i, faceIndex, rIndex, pptrs.R[tIndex]); fofss.R[tIndex] += gatherFVarData(context, i, faceIndex, levelFaceOffset, rIndex, levelFVarVertOffsets, fofss.R[tIndex], fptrs.R[tIndex]); } else { // For the boundary and corner cases, the Hbr code makes some adjustments to the // rotations here from the way they were defined earlier. That raises questions // as to the purpose of the earlier assignments and their naming. I'd prefer to // label the sets of rotations for their intended purpose, and to compute and // assign them earlier for use here with no adjustment. // // Non-transition case: // rot = 0; // outside switch // f->_adaptiveFlags.brots = (f->_adaptiveFlags.rots + 1) % 4; // Transition case: // rot = f->_adaptiveFlags.brots; // is this now same as transition rots? // // Both cases of "rot" above are now handled with the "transition rotation" -- still // not clear what the purpose of the other is. Need to look into usage of these // adaptive-flag rotations in: // getOneRing, computePatchParam, computeFVarData // It may be that a separate "face rotation" flag is warranted if we need something // else dependent on the boundary orientation. // if (patchTag._isSingleCrease and patchTag._boundaryCount==0) { int const permuteInterior[16] = { 5, 6, 7, 8, 4, 0, 1, 9, 15, 3, 2, 10, 14, 13, 12, 11 }; level->gatherQuadRegularInteriorPatchPoints(faceIndex, patchVerts, bIndex); offsetAndPermuteIndices(patchVerts, 16, levelVertOffset, permuteInterior, iptrs.S[tIndex][rIndex]); int creaseEdge = (bIndex+2)%4; float sharpness = level->getEdgeSharpness((level->getFaceEdges(faceIndex)[creaseEdge])); sharpness = std::min(sharpness, (float)(context.options.maxIsolationLevel-i)); iptrs.S[tIndex][rIndex] += 16; pptrs.S[tIndex][rIndex] = computePatchParam(refiner, i, faceIndex, bIndex, pptrs.S[tIndex][rIndex]); *sptrs.S[tIndex][rIndex]++ = assignSharpnessIndex(sharpness, tables->_sharpnessValues); fofss.S[tIndex][rIndex] += gatherFVarData(context, i, faceIndex, levelFaceOffset, bIndex, levelFVarVertOffsets, fofss.S[tIndex][rIndex], fptrs.S[tIndex][rIndex]); } else if (patchTag._boundaryCount == 1) { int const permuteBoundary[12] = { 11, 3, 0, 4, 10, 2, 1, 5, 9, 8, 7, 6 }; level->gatherQuadRegularBoundaryPatchPoints(faceIndex, patchVerts, bIndex); offsetAndPermuteIndices(patchVerts, 12, levelVertOffset, permuteBoundary, iptrs.B[tIndex][rIndex]); iptrs.B[tIndex][rIndex] += 12; pptrs.B[tIndex][rIndex] = computePatchParam(refiner, i, faceIndex, bIndex, pptrs.B[tIndex][rIndex]); fofss.B[tIndex][rIndex] += gatherFVarData(context, i, faceIndex, levelFaceOffset, bIndex, levelFVarVertOffsets, fofss.B[tIndex][rIndex], fptrs.B[tIndex][rIndex]); } else { int const permuteCorner[9] = { 8, 3, 0, 7, 2, 1, 6, 5, 4 }; level->gatherQuadRegularCornerPatchPoints(faceIndex, patchVerts, bIndex); offsetAndPermuteIndices(patchVerts, 9, levelVertOffset, permuteCorner, iptrs.C[tIndex][rIndex]); bIndex = (bIndex+3)%4; iptrs.C[tIndex][rIndex] += 9; pptrs.C[tIndex][rIndex] = computePatchParam(refiner, i, faceIndex, bIndex, pptrs.C[tIndex][rIndex]); fofss.C[tIndex][rIndex] += gatherFVarData(context, i, faceIndex, levelFaceOffset, bIndex, levelFVarVertOffsets, fofss.C[tIndex][rIndex], fptrs.C[tIndex][rIndex]); } } } else { if (gregoryStencilsFactory) { // Gregory basis end-cap (20 CVs - no quad-offsets / valence tables) assert(i==refiner.GetMaxLevel()); // Gregory Boundary Patch (4 CVs 0-ring for varying interpolation) Vtr::ConstIndexArray faceVerts = level->getFaceVertices(faceIndex); for (int j = 0; j < 4; ++j) { iptrs.GP[j] = faceVerts[j] + levelVertOffset; gregoryVertexFlags[iptrs.GP[j]] = true; } iptrs.GP += 4; #ifdef ENDCAP_TOPOPOLGY bool edgeSkip[4]; numGregoryBasisVertices = gatherGregoryBasisTopology(*level, faceIndex, numGregoryBasisVertices, levelPatchTags, edgeSkip, gregoryBasisIndices, tables->_endcapTopology); #endif gregoryStencilsFactory->AddPatchBasis(faceIndex); pptrs.GP = computePatchParam(refiner, i, faceIndex, 0, pptrs.GP); fofss.GP += gatherFVarData(context, i, faceIndex, levelFaceOffset, 0, levelFVarVertOffsets, fofss.GP, fptrs.GP); } else { if (patchTag._boundaryCount == 0) { // Gregory Regular Patch (4 CVs + quad-offsets / valence tables) Vtr::ConstIndexArray faceVerts = level->getFaceVertices(faceIndex); for (int j = 0; j < 4; ++j) { iptrs.G[j] = faceVerts[j] + levelVertOffset; gregoryVertexFlags[iptrs.G[j]] = true; } iptrs.G += 4; getQuadOffsets(*level, faceIndex, quad_G_C0_P); quad_G_C0_P += 4; pptrs.G = computePatchParam(refiner, i, faceIndex, 0, pptrs.G); fofss.G += gatherFVarData(context, i, faceIndex, levelFaceOffset, 0, levelFVarVertOffsets, fofss.G, fptrs.G); } else { // Gregory Boundary Patch (4 CVs + quad-offsets / valence tables) Vtr::ConstIndexArray faceVerts = level->getFaceVertices(faceIndex); for (int j = 0; j < 4; ++j) { iptrs.GB[j] = faceVerts[j] + levelVertOffset; gregoryVertexFlags[iptrs.GB[j]] = true; } iptrs.GB += 4; getQuadOffsets(*level, faceIndex, quad_G_C1_P); quad_G_C1_P += 4; //int bIndex = (patchTag._boundaryIndex+1)%4; pptrs.GB = computePatchParam(refiner, i, faceIndex, 0, pptrs.GB); fofss.GB += gatherFVarData(context, i, faceIndex, levelFaceOffset, 0, levelFVarVertOffsets, fofss.GB, fptrs.GB); } } } } levelFaceOffset += level->getNumFaces(); levelVertOffset += level->getNumVertices(); if (context.RequiresFVarPatches()) { int nchannels = refiner.GetNumFVarChannels(); for (int channel=0; channelgetNumFVarValues(channel); } } } if (gregoryStencilsFactory) { tables->_endcapStencilTables = gregoryStencilsFactory->CreateStencilTables(); delete gregoryStencilsFactory; } if (context.RequiresFVarPatches()) { // Compress & copy FVar values from context into FVarPatchChannel // sparse array, generate offsets FVarChannelCursor & fvc = context.fvarChannelCursor; for (fvc=fvc.begin(); fvc!=fvc.end(); ++fvc) { if (tables->GetFVarChannelLinearInterpolation(fvc.pos())!=Sdc::Options::FVAR_LINEAR_ALL) { tables->setBicubicFVarPatchChannelValues(fvc.pos(), context.fvarPatchSize, context.fvarPatchValues[fvc.pos()]); } } } // // Now deal with the "vertex valence" table for Gregory patches -- this table contains the one-ring // of vertices around each vertex. Currently it is extremely wasteful for the following reasons: // - it allocates 2*maxvalence+1 for ALL vertices // - it initializes the one-ring for ALL vertices // We use the full size expected (not sure what else relies on that) but we avoiding initializing // the vast majority of vertices that are not associated with gregory patches -- by having previously // marked those that are associated above and skipping all others. // if (context.RequiresLegacyGregoryPatches()) { const int SizePerVertex = 2*tables->_maxValence + 1; std::vector & vTable = tables->_vertexValenceTable; vTable.resize(refiner.GetNumVerticesTotal() * SizePerVertex); int vOffset = 0; int levelLast = refiner.GetMaxLevel(); for (int i = 0; i <= levelLast; ++i) { Vtr::Level const * level = &refiner.getLevel(i); if (i == levelLast) { int vTableOffset = vOffset * SizePerVertex; for (int vIndex = 0; vIndex < level->getNumVertices(); ++vIndex) { int* vTableEntry = &vTable[vTableOffset]; // // If not marked as a vertex of a gregory patch, just set to 0 to ignore. Otherwise // gather the one-ring around the vertex and set its resulting size (note the negative // size used to distinguish between boundary/interior): // //if (!gregoryVertexFlags[vIndex + vOffset]) { vTableEntry[0] = 0; //} else { int * ringDest = vTableEntry + 1, ringSize = level->gatherQuadRegularRingAroundVertex(vIndex, ringDest); for (int j = 0; j < ringSize; ++j) { ringDest[j] += vOffset; } if (ringSize & 1) { // boundary vertex : duplicate boundary vertex index // and store negative valence. ringSize++; vTableEntry[ringSize]=vTableEntry[ringSize-1]; vTableEntry[0] = -ringSize/2; } else { vTableEntry[0] = ringSize/2; } //} vTableOffset += SizePerVertex; } } vOffset += level->getNumVertices(); } } } } // end namespace Far } // end namespace OPENSUBDIV_VERSION } // end namespace OpenSubdiv