// // 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/patchTableFactory.h" #include "../far/error.h" #include "../far/ptexIndices.h" #include "../far/topologyRefiner.h" #include "../vtr/level.h" #include "../vtr/refinement.h" #include "../far/endCapBSplineBasisPatchFactory.h" #include "../far/endCapGregoryBasisPatchFactory.h" #include "../far/endCapLegacyGregoryPatchFactory.h" #include #include #include namespace OpenSubdiv { namespace OPENSUBDIV_VERSION { namespace { // // A convenience container for the different types of feature adaptive patches // template struct PatchTypes { TYPE R, // regular patch 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; 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; } // Counts the number of arrays required to store each type of patch used // in the primitive int getNumPatchArrays() const { int result=0; if (R) ++result; if (G) ++result; if (GB) ++result; if (GP) ++result; return result; } }; typedef PatchTypes PatchCVPointers; typedef PatchTypes PatchParamPointers; typedef PatchTypes SharpnessIndexPointers; typedef PatchTypes PatchFVarOffsets; typedef PatchTypes PatchFVarPointers; } // namespace anon namespace Far { void PatchTableFactory::PatchFaceTag::clear() { std::memset(this, 0, sizeof(*this)); } void PatchTableFactory::PatchFaceTag::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; _boundaryMask = boundaryEdgeMask; 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 PatchTableFactory::PatchFaceTag::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); _boundaryMask = boundaryVertexMask; 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; } } // // Trivial anonymous helper functions: // static inline void offsetAndPermuteIndices(Far::Index const indices[], int count, Far::Index offset, int const permutation[], Far::Index result[]) { // The patch vertices for boundary and corner patches // are assigned index values even though indices will // be undefined along boundary and corner edges. // When the resulting patch table is going to be used // as indices for drawing, it is convenient for invalid // indices to be replaced with known good values, such // as the first un-permuted index, which is the index // of the first vertex of the patch face. Far::Index knownGoodIndex = indices[0]; if (permutation) { for (int i = 0; i < count; ++i) { if (permutation[i] < 0) { result[i] = offset + knownGoodIndex; } else { 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)); } } // // 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, PatchTableFactory::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 table. 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 PatchTableFactory::AdaptiveContext { public: AdaptiveContext(TopologyRefiner const & refiner, Options options); TopologyRefiner const & refiner; Options const options; // The patch table being created PatchTable * table; public: // // Vertex // // 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 PatchTableFactory::AdaptiveContext::AdaptiveContext( TopologyRefiner const & ref, Options opts) : refiner(ref), options(opts), table(0), fvarChannelCursor(ref, opts) { fvarPatchValues.resize(fvarChannelCursor.size()); } void PatchTableFactory::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 PatchTableFactory::AdaptiveContext::RequiresFVarPatches() const { return not fvarPatchValues.empty(); } // // Reserves tables based on the contents of the PatchArrayVector in the PatchTable: // void PatchTableFactory::allocateVertexTables(PatchTable * table, int /* nlevels */, bool hasSharpness) { int ncvs = 0, npatches = 0; for (int i=0; iGetNumPatchArrays(); ++i) { npatches += table->GetNumPatches(i); ncvs += table->GetNumControlVertices(i); } if (ncvs==0 or npatches==0) return; table->_patchVerts.resize( ncvs ); table->_paramTable.resize( npatches ); if (hasSharpness) { table->_sharpnessIndices.resize( npatches, Vtr::INDEX_INVALID ); } } // // Allocate face-varying tables // void PatchTableFactory::allocateFVarChannels(TopologyRefiner const & refiner, Options options, int npatches, PatchTable * table) { assert(options.generateFVarTables and refiner.GetNumFVarChannels()>0 and npatches>0 and table); // 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; } table->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); table->setFVarPatchChannelLinearInterpolation(fvc.pos(), interpolation); int nverts = 0; if (interpolation==Sdc::Options::FVAR_LINEAR_ALL) { PatchDescriptor::Type type = options.triangulateQuads ? PatchDescriptor::TRIANGLES : PatchDescriptor::QUADS; table->setFVarPatchChannelPatchesType(fvc.pos(), type); nverts = npatches * PatchDescriptor::GetNumFVarControlVertices(type); } table->allocateChannelValues(fvc.pos(), npatches, nverts); } } // gather face-varying patch points int PatchTableFactory::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; PatchTable * table = context.table; 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::internal::Level const & vtxLevel = refiner.getLevel(level); Vtr::internal::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::internal::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::internal::Level::VTag fvarVertTags[4]; Vtr::internal::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::internal::Level::ETag fvarEdgeTags[4]; ConstIndexArray faceEdges = vtxLevel.getFaceEdges(faceIndex); Vtr::internal::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._isSingleCrease) { fvarPatchType = PatchDescriptor::REGULAR; } Vtr::Array patchTypes = table->getFVarPatchTypes(fvc.pos()); assert(not patchTypes.empty()); patchTypes[fofss] = fvarPatchType; int const * permutation = 0; // Gather the verts FVar values int orientationIndex = fvarPatchTag._boundaryIndex; if (fvarPatchType == PatchDescriptor::REGULAR) { if (fvarPatchTag._boundaryCount == 0) { static int const permuteRegular[16] = { 5, 6, 7, 8, 4, 0, 1, 9, 15, 3, 2, 10, 14, 13, 12, 11 }; permutation = permuteRegular; vtxLevel.gatherQuadRegularInteriorPatchPoints(faceIndex, patchVerts, orientationIndex, *fvc); } else if (fvarPatchTag._boundaryCount == 1) { // Expand boundary patch vertices and rotate to restore correct orientation. static int const permuteBoundary[4][16] = { { -1, -1, -1, -1, 11, 3, 0, 4, 10, 2, 1, 5, 9, 8, 7, 6 }, { 9, 10, 11, -1, 8, 2, 3, -1, 7, 1, 0, -1, 6, 5, 4, -1 }, { 6, 7, 8, 9, 5, 1, 2, 10, 4, 0, 3, 11, -1, -1, -1, -1 }, { -1, 4, 5, 6, -1, 0, 1, 7, -1, 3, 2, 8, -1, 11, 10, 9 } }; permutation = permuteBoundary[orientationIndex]; vtxLevel.gatherQuadRegularBoundaryPatchPoints(faceIndex, patchVerts, orientationIndex, *fvc); } else if (fvarPatchTag._boundaryCount == 2) { // Expand corner patch vertices and rotate to restore correct orientation. static int const permuteCorner[4][16] = { { -1, -1, -1, -1, -1, 0, 1, 4, -1, 3, 2, 5, -1, 8, 7, 6 }, { -1, -1, -1, -1, 8, 3, 0, -1, 7, 2, 1, -1, 6, 5, 4, -1 }, { 6, 7, 8, -1, 5, 2, 3, -1, 4, 1, 0, -1, -1, -1, -1, -1 }, { -1, 4, 5, 6, -1, 1, 2, 7, -1, 0, 3, 8, -1, -1, -1, -1 } }; permutation = permuteCorner[orientationIndex]; vtxLevel.gatherQuadRegularCornerPatchPoints(faceIndex, patchVerts, orientationIndex, *fvc); } else { assert(fvarPatchTag._boundaryCount >=0 && fvarPatchTag._boundaryCount <= 2); } } 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::internal::Refinement const& refinement = refiner.getRefinement(i-1); Vtr::internal::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) depth, nonquad, (unsigned short) boundaryMask, (unsigned short) transitionMask); return ++coord; } // // 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; } // // 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)... // PatchTable * PatchTableFactory::Create(TopologyRefiner const & refiner, Options options) { if (refiner.IsUniform()) { return createUniform(refiner, options); } else { return createAdaptive(refiner, options); } } PatchTable * PatchTableFactory::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.GetMaxValence(), maxlevel = refiner.GetMaxLevel(), firstlevel = options.generateAllLevels ? 0 : maxlevel, nlevels = maxlevel-firstlevel+1; PtexIndices ptexIndices(refiner); 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 table and allocate and initialize its members. // PatchTable * table = new PatchTable(maxvalence); table->_numPtexFaces = ptexIndices.GetNumFaces(); table->reservePatchArrays(nlevels); PatchDescriptor desc(ptype); // generate patch arrays for (int level=firstlevel, poffset=0, voffset=0; level<=maxlevel; ++level) { TopologyLevel const & refLevel = refiner.GetLevel(level); int npatches = refLevel.GetNumFaces(); if (refiner.HasHoles()) { for (int i = npatches - 1; i >= 0; --i) { npatches -= refLevel.IsFaceHole(i); } } assert(npatches>=0); if (options.triangulateQuads) npatches *= 2; if (level>=firstlevel) { table->pushPatchArray(desc, npatches, &voffset, &poffset, 0); } } // Allocate various tables allocateVertexTables( table, 0, /*hasSharpness=*/false ); FVarChannelCursor fvc(refiner, options); bool generateFVarPatches = (options.generateFVarTables and fvc.size()>0); if (generateFVarPatches) { int npatches = table->GetNumPatchesTotal(); allocateFVarChannels(refiner, options, npatches, table); assert(fvc.size() == table->GetNumFVarChannels()); } // // Now populate the patches: // Index * iptr = &table->_patchVerts[0]; PatchParam * pptr = &table->_paramTable[0]; Index ** fptr = 0; Index levelVertOffset = options.generateAllLevels ? 0 : refiner.GetLevel(0).GetNumVertices(); 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()] = table->getFVarPatchesValues(fvc.pos()).begin(); } } for (int level=1; level<=maxlevel; ++level) { TopologyLevel const & refLevel = refiner.GetLevel(level); int nfaces = refLevel.GetNumFaces(); 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.GetLevel(level).GetNumVertices(); for (fvc=fvc.begin(); fvc!=fvc.end(); ++fvc) { levelFVarVertOffsets[fvc.pos()] += refiner.GetLevel(level).GetNumFVarValues(fvc.pos()); } } } return table; } PatchTable * PatchTableFactory::createAdaptive(TopologyRefiner const & refiner, Options options) { assert(not refiner.IsUniform()); PtexIndices ptexIndices(refiner); 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 table and allocate and initialize its members based on // the inventory of patches determined above: // int maxValence = refiner.GetMaxValence(); context.table = new PatchTable(maxValence); // Populate the patch array descriptors context.table->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.table->_numPtexFaces = ptexIndices.GetNumFaces(); // Allocate various tables bool hasSharpness = context.options.useSingleCreasePatch; allocateVertexTables(context.table, 0, hasSharpness); if (context.RequiresFVarPatches()) { int npatches = context.table->GetNumPatchesTotal(); allocateFVarChannels(refiner, options, npatches, context.table); // 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); } // // Now populate the patches: // populateAdaptivePatches(context, ptexIndices); return context.table; } // // 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 PatchTableFactory::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::internal::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::internal::Refinement const * refinement = 0; Vtr::internal::Refinement::SparseTag const * refinedFaceTags = 0; if (levelIndex < refiner.GetMaxLevel()) { refinement = &refiner.getRefinement(levelIndex); refinedFaceTags = &refinement->getParentFaceSparseTag(0); } for (int faceIndex = 0; faceIndex < level->getNumFaces(); ++faceIndex) { PatchFaceTag & patchTag = levelPatchTags[faceIndex]; patchTag.clear(); patchTag._hasPatch = false; if (level->isFaceHole(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::internal::Refinement::SparseTag refinedFaceTag = refinedFaceTags ? refinedFaceTags[faceIndex] : Vtr::internal::Refinement::SparseTag(); if (refinedFaceTag._selected) { continue; } Vtr::ConstIndexArray fVerts = level->getFaceVertices(faceIndex); assert(fVerts.size() == 4); Vtr::internal::Level::VTag compFaceVertTag = level->getFaceCompositeVTag(fVerts); if (compFaceVertTag._incomplete) { continue; } // // We have a quad that will be represented as a B-spline or end cap 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). // // NOTE on patches around non-manifold vertices: // In most cases 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::internal::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->getEdgeTag(fEdges[0])._boundary) << 0) | ((level->getEdgeTag(fEdges[1])._boundary) << 1) | ((level->getEdgeTag(fEdges[2])._boundary) << 2) | ((level->getEdgeTag(fEdges[3])._boundary) << 3); if (hasNonManifoldVertex) { int nonManEdgeMask = ((level->getEdgeTag(fEdges[0])._nonManifold) << 0) | ((level->getEdgeTag(fEdges[1])._nonManifold) << 1) | ((level->getEdgeTag(fEdges[2])._nonManifold) << 2) | ((level->getEdgeTag(fEdges[3])._nonManifold) << 3); boundaryEdgeMask |= nonManEdgeMask; } if (boundaryEdgeMask) { patchTag.assignBoundaryPropertiesFromEdgeMask(boundaryEdgeMask); } else { int boundaryVertMask = ((level->getVertexTag(fVerts[0])._boundary) << 0) | ((level->getVertexTag(fVerts[1])._boundary) << 1) | ((level->getVertexTag(fVerts[2])._boundary) << 2) | ((level->getVertexTag(fVerts[3])._boundary) << 3); if (hasNonManifoldVertex) { int nonManVertMask = ((level->getVertexTag(fVerts[0])._nonManifold) << 0) | ((level->getVertexTag(fVerts[1])._nonManifold) << 1) | ((level->getVertexTag(fVerts[2])._nonManifold) << 2) | ((level->getVertexTag(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->getVertexTag(fVerts[0])._xordinary) { xordCount++; xordVertex = 0; } if (level->getVertexTag(fVerts[1])._xordinary) { xordCount++; xordVertex = 1; } if (level->getVertexTag(fVerts[2])._xordinary) { xordCount++; xordVertex = 2; } if (level->getVertexTag(fVerts[3])._xordinary) { xordCount++; xordVertex = 3; } if (xordCount == 1) { // We require the vertex opposite the xordinary vertex be interior: if (not level->getVertexTag(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) { if (patchTag._boundaryCount == 0) { context.patchInventory.R++; } else if (patchTag._boundaryCount == 1) { context.patchInventory.R++; } else { context.patchInventory.R++; } } else { // select endcap patchtype switch(context.options.GetEndCapType()) { case Options::ENDCAP_GREGORY_BASIS: context.patchInventory.GP++; break; case Options::ENDCAP_BSPLINE_BASIS: context.patchInventory.R++; break; case Options::ENDCAP_LEGACY_GREGORY: if (patchTag._boundaryCount == 0) { context.patchInventory.G++; } else { context.patchInventory.GB++; } break; case Options::ENDCAP_BILINEAR_BASIS: // not implemented yet assert(false); break; default: // no endcap break; } } } 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 PatchTableFactory::populateAdaptivePatches( AdaptiveContext & context, PtexIndices const & ptexIndices) { TopologyRefiner const & refiner = context.refiner; PatchTable * table = context.table; // // 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) = table->getPatchArrayVertices(arrayIndex).begin(); pptrs.getValue(desc) = table->getPatchParams(arrayIndex).begin(); if (context.options.useSingleCreasePatch) { sptrs.getValue(desc) = table->getSharpnessIndices(arrayIndex); } if (context.RequiresFVarPatches()) { Index & offsets = fofss.getValue(desc); offsets = table->getPatchIndex(arrayIndex, 0); // XXXX manuelk this stuff will go away as we use offsets from FVarPatchChannel FVarChannelCursor & fvc = context.fvarChannelCursor; assert(fvc.size() == table->GetNumFVarChannels()); Index ** fptr = (Index **)alloca(fvc.size()*sizeof(Index *)); for (fvc=fvc.begin(); fvc!=fvc.end(); ++fvc) { Index pidx = table->getPatchIndex(arrayIndex, 0); int ofs = pidx * 4; fptr[fvc.pos()] = &table->getFVarPatchesValues(fvc.pos())[ofs]; } fptrs.getValue(desc) = fptr; } } // // 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*sizeof(int)); memset(levelFVarVertOffsets, 0, nchannels*sizeof(int)); } // endcap factories // XXX EndCapBSplineBasisPatchFactory *endCapBSpline = NULL; EndCapGregoryBasisPatchFactory *endCapGregoryBasis = NULL; EndCapLegacyGregoryPatchFactory *endCapLegacyGregory = NULL; switch(context.options.GetEndCapType()) { case Options::ENDCAP_GREGORY_BASIS: endCapGregoryBasis = new EndCapGregoryBasisPatchFactory( refiner, context.options.shareEndCapPatchPoints); break; case Options::ENDCAP_BSPLINE_BASIS: endCapBSpline = new EndCapBSplineBasisPatchFactory(refiner); break; case Options::ENDCAP_LEGACY_GREGORY: endCapLegacyGregory = new EndCapLegacyGregoryPatchFactory(refiner); break; default: break; } for (int i = 0; i < refiner.GetNumLevels(); ++i) { Vtr::internal::Level const * level = &refiner.getLevel(i); const PatchFaceTag * levelPatchTags = &context.patchTags[levelFaceOffset]; for (int faceIndex = 0; faceIndex < level->getNumFaces(); ++faceIndex) { if (level->isFaceHole(faceIndex)) { continue; } const PatchFaceTag& patchTag = levelPatchTags[faceIndex]; if (not patchTag._hasPatch) { continue; } if (patchTag._isRegular) { Index patchVerts[16]; int bIndex = patchTag._boundaryIndex; int boundaryMask = patchTag._boundaryMask; int transitionMask = patchTag._transitionMask; if (!patchTag._isSingleCrease) { int const * permutation = 0; if (patchTag._boundaryCount == 0) { static int const permuteRegular[16] = { 5, 6, 7, 8, 4, 0, 1, 9, 15, 3, 2, 10, 14, 13, 12, 11 }; permutation = permuteRegular; level->gatherQuadRegularInteriorPatchPoints(faceIndex, patchVerts, bIndex); } else if (patchTag._boundaryCount == 1) { // Expand boundary patch vertices and rotate to restore correct orientation. static int const permuteBoundary[4][16] = { { -1, -1, -1, -1, 11, 3, 0, 4, 10, 2, 1, 5, 9, 8, 7, 6 }, { 9, 10, 11, -1, 8, 2, 3, -1, 7, 1, 0, -1, 6, 5, 4, -1 }, { 6, 7, 8, 9, 5, 1, 2, 10, 4, 0, 3, 11, -1, -1, -1, -1 }, { -1, 4, 5, 6, -1, 0, 1, 7, -1, 3, 2, 8, -1, 11, 10, 9 } }; permutation = permuteBoundary[bIndex]; level->gatherQuadRegularBoundaryPatchPoints(faceIndex, patchVerts, bIndex); } else if (patchTag._boundaryCount == 2) { // Expand corner patch vertices and rotate to restore correct orientation. static int const permuteCorner[4][16] = { { -1, -1, -1, -1, -1, 0, 1, 4, -1, 3, 2, 5, -1, 8, 7, 6 }, { -1, -1, -1, -1, 8, 3, 0, -1, 7, 2, 1, -1, 6, 5, 4, -1 }, { 6, 7, 8, -1, 5, 2, 3, -1, 4, 1, 0, -1, -1, -1, -1, -1 }, { -1, 4, 5, 6, -1, 1, 2, 7, -1, 0, 3, 8, -1, -1, -1, -1 } }; permutation = permuteCorner[bIndex]; level->gatherQuadRegularCornerPatchPoints(faceIndex, patchVerts, bIndex); } else { assert(patchTag._boundaryCount >=0 && patchTag._boundaryCount <= 2); } offsetAndPermuteIndices(patchVerts, 16, levelVertOffset, permutation, iptrs.R); iptrs.R += 16; pptrs.R = computePatchParam(refiner, ptexIndices, i, faceIndex, boundaryMask, transitionMask, pptrs.R); // XXX: sharpness will be integrated into patch param soon. if (sptrs.R) *sptrs.R++ = assignSharpnessIndex(0, table->_sharpnessValues); fofss.R += gatherFVarData(context, i, faceIndex, levelFaceOffset, /*rotation*/0, levelFVarVertOffsets, fofss.R, fptrs.R); } else { 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.R); int creaseEdge = (bIndex+2)%4; float sharpness = level->getEdgeSharpness((level->getFaceEdges(faceIndex)[creaseEdge])); sharpness = std::min(sharpness, (float)(context.options.maxIsolationLevel-i)); // rotate transition mask to align with crease edge transitionMask = ((((transitionMask << 4) | transitionMask) >> bIndex)) & 0xf; iptrs.R += 16; pptrs.R = computePatchParam(refiner, ptexIndices, i, faceIndex, /*boundary*/0, transitionMask, pptrs.R); if (sptrs.R) *sptrs.R++ = assignSharpnessIndex(sharpness, table->_sharpnessValues); fofss.R += gatherFVarData(context, i, faceIndex, levelFaceOffset, bIndex, levelFVarVertOffsets, fofss.R, fptrs.R); } } else { // emit end patch. end patch should be in the max level (until we implement DFAS) assert(i==refiner.GetMaxLevel()); // switch endcap patchtype by option switch(context.options.GetEndCapType()) { case Options::ENDCAP_GREGORY_BASIS: { // note: this call will be moved into vtr::level. ConstIndexArray cvs = endCapGregoryBasis->GetPatchPoints( level, faceIndex, levelPatchTags, levelVertOffset); for (int j = 0; j < cvs.size(); ++j) iptrs.GP[j] = cvs[j]; iptrs.GP += cvs.size(); pptrs.GP = computePatchParam( refiner, ptexIndices, i, faceIndex, /*boundary*/0, /*transition*/0, pptrs.GP); if (sptrs.R) *sptrs.R++ = assignSharpnessIndex(0, table->_sharpnessValues); fofss.GP += gatherFVarData(context, i, faceIndex, levelFaceOffset, 0, levelFVarVertOffsets, fofss.GP, fptrs.GP); break; } case Options::ENDCAP_BSPLINE_BASIS: { ConstIndexArray cvs = endCapBSpline->GetPatchPoints( level, faceIndex, levelPatchTags, levelVertOffset); for (int j = 0; j < cvs.size(); ++j) iptrs.R[j] = cvs[j]; iptrs.R += cvs.size(); pptrs.R = computePatchParam( refiner, ptexIndices, i, faceIndex, /*boundary*/0, /*transition*/0, pptrs.R); if (sptrs.R) *sptrs.R++ = assignSharpnessIndex(0, table->_sharpnessValues); fofss.R += gatherFVarData(context, i, faceIndex, levelFaceOffset, 0, levelFVarVertOffsets, fofss.R, fptrs.R); break; } case Options::ENDCAP_LEGACY_GREGORY: { ConstIndexArray cvs = endCapLegacyGregory->GetPatchPoints( level, faceIndex, levelPatchTags, levelVertOffset); if (patchTag._boundaryCount == 0) { for (int j = 0; j < cvs.size(); ++j) iptrs.G[j] = cvs[j]; iptrs.G += cvs.size(); pptrs.G = computePatchParam( refiner, ptexIndices, i, faceIndex, /*boundary*/0, /*transition*/0, pptrs.G); if (sptrs.R) *sptrs.R++ = assignSharpnessIndex(0, table->_sharpnessValues); fofss.G += gatherFVarData(context, i, faceIndex, levelFaceOffset, 0, levelFVarVertOffsets, fofss.G, fptrs.G); } else { for (int j = 0; j < cvs.size(); ++j) iptrs.GB[j] = cvs[j]; iptrs.GB += cvs.size(); pptrs.GB = computePatchParam( refiner, ptexIndices, i, faceIndex, /*boundary*/0, /*transition*/0, pptrs.GB); if (sptrs.R) *sptrs.R++ = assignSharpnessIndex(0, table->_sharpnessValues); fofss.GB += gatherFVarData(context, i, faceIndex, levelFaceOffset, 0, levelFVarVertOffsets, fofss.GB, fptrs.GB); } break; } case Options::ENDCAP_BILINEAR_BASIS: // not implemented yet assert(false); break; default: // no endcap break; } } } levelFaceOffset += level->getNumFaces(); levelVertOffset += level->getNumVertices(); if (context.RequiresFVarPatches()) { int nchannels = refiner.GetNumFVarChannels(); for (int channel=0; channelgetNumFVarValues(channel); } } } // finalize end patches switch(context.options.GetEndCapType()) { case Options::ENDCAP_GREGORY_BASIS: table->_localPointStencils = endCapGregoryBasis->CreateVertexStencilTable(); table->_localPointVaryingStencils = endCapGregoryBasis->CreateVaryingStencilTable(); delete endCapGregoryBasis; break; case Options::ENDCAP_BSPLINE_BASIS: table->_localPointStencils = endCapBSpline->CreateVertexStencilTable(); table->_localPointVaryingStencils = endCapBSpline->CreateVaryingStencilTable(); delete endCapBSpline; break; case Options::ENDCAP_LEGACY_GREGORY: endCapLegacyGregory->Finalize( table->GetMaxValence(), &table->_quadOffsetsTable, &table->_vertexValenceTable); delete endCapLegacyGregory; break; default: break; } 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 (table->GetFVarChannelLinearInterpolation(fvc.pos())!=Sdc::Options::FVAR_LINEAR_ALL) { table->setBicubicFVarPatchChannelValues(fvc.pos(), context.fvarPatchSize, context.fvarPatchValues[fvc.pos()]); } } } } } // end namespace Far } // end namespace OPENSUBDIV_VERSION } // end namespace OpenSubdiv