// // Copyright 2014 DreamWorks Animation LLC. // // 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. // #ifndef OPENSUBDIV3_SDC_SCHEME_H #define OPENSUBDIV3_SDC_SCHEME_H #include "../version.h" #include "../sdc/types.h" #include "../sdc/options.h" #include "../sdc/crease.h" #include #include #include namespace OpenSubdiv { namespace OPENSUBDIV_VERSION { namespace Sdc { /// /// \brief Scheme is a class template which provides all implementation for the /// subdivision schemes supported by OpenSubdiv through specializations of the /// methods of each. An instance of Scheme includes a set of Options /// that will dictate the variable aspects of its behavior. /// /// The primary purpose of Scheme is to provide the mask weights for vertices /// generated by subdivision. Methods to determine the masks are given topological /// neighborhoods from which to compute the appropriate weights for neighboring /// components. While these neighborhoods may require sharpness values for /// creasing, the computation of subdivided crease values is independent of the /// scheme type and is also made available through the Crease class. /// /// Mask queries are assisted by two utility classes -- a Neighborhood class /// defining the set of relevant data in the topological neighborhood of the vertex /// being subdivided, and a Mask class into which the associated mask weights will /// be stored. Depending on where and how these queries are used, more or less /// information may be available. See the details of the Neighborhood classes as /// appropriate initialization of them is critical. It is generally best to /// initialize them with what data is known and accessible for immediate and /// efficient retrieval, but subclasses can be created to gather it lazily if /// desired. /// template class Scheme { public: Scheme() : _options() { } Scheme(Options const& options) : _options(options) { } Options GetOptions() const { return _options; } void SetOptions(const Options& newOptions) { _options = newOptions; } /// /// \brief Face-vertex masks - trivial for all current schemes /// template void ComputeFaceVertexMask(FACE const& faceNeighborhood, MASK& faceVertexMask) const; /// /// \brief Edge-vertex masks /// If known, the Rule for the edge and/or the derived vertex can be specified to /// accelerate the computation (though the Rule for the parent is trivially determined). /// In particular, knowing the child rule can avoid the need to subdivide the sharpness /// of the edge to see if it is a transitional crease that warrants fractional blending. /// /// Whether to use the "Rules" in this interface is really debatable -- the parent Rule /// is really based on the edge and its sharpness, while the child Rule is technically /// based on the neighborhood of the child vertex, but it can be deduced from the two /// child edges' sharpness. So the Crease methods used to compute these rules differ /// from those for the vertex-vertex mask. Perhaps a simple pair of new methods for /// Crease should be added specific to the edge-vertex case, i.e. one that takes a /// single sharpness (for the parent rule) and one that takes a pair (for the child). /// template void ComputeEdgeVertexMask(EDGE const& edgeNeighborhood, MASK& edgeVertexMask, Crease::Rule parentRule = Crease::RULE_UNKNOWN, Crease::Rule childRule = Crease::RULE_UNKNOWN) const; /// /// \brief Vertex-vertex masks /// If known, a single Rule or pair of Rules can be specified (indicating a crease /// transition) to accelerate the computation. Either no Rules, the first, or both should /// be specified. Specification of only the first Rule implies it to be true for both /// (wish the compiler would allow such default value specification), i.e. no transition. /// The case of knowing the parent Rule but deferring determination of the child Rule to /// this method is not supported. /// template void ComputeVertexVertexMask(VERTEX const& vertexNeighborhood, MASK& vertexVertexMask, Crease::Rule parentRule = Crease::RULE_UNKNOWN, Crease::Rule childRule = Crease::RULE_UNKNOWN) const; /// /// \brief Limit masks for vertices -- position and tangents /// These presume that a vertex is suitably isolated for its limit to be well-defined /// and, unlike the refinement masks, the subdivision Rule for the vertex (presumably at /// its last level of refinement) is required rather than being optional. In the /// presence of semi-sharp creasing that has not decayed to zero, the limit is neither /// sharp nor smooth -- in such cases the Rule specified by the caller determines the /// result. /// /// For tangent masks, the direction of the first tangent (T1) is oriented towards the /// leading edge of the vertex, i.e. the first incident edge of the vertex (beginning /// the set of incident edges in counter-clockwise order). The second tangent (T2) lies /// within the tangent plane such that its normal can be computed as T1 x T2. So for a /// boundary vertex, T1 will point along the boundary in the direction of the leading /// edge while T2 points inward across the limit surface. /// /// As for magnitude, no assumptions should be made of the magnitudes of the resulting /// tangent vectors. Common formulae often factor out scale factors that contribute to /// magnitude. While some attempt has been made to make magnitudes more consistent /// between regular corners, boundaries and the interior, the same has not been done at /// irregular vertices -- at least not yet. This may be addressed in future, as having /// consistent magnitudes returned here can aid in the construction of patches from /// limit positions and tangents. /// template void ComputeVertexLimitMask(VERTEX const& vertexNeighborhood, MASK& positionMask, Crease::Rule vertexRule) const; template void ComputeVertexLimitMask(VERTEX const& vertexNeighborhood, MASK& positionMask, MASK& tangent1Mask, MASK& tangent2Mask, Crease::Rule vertexRule) const; // // Static methods defining traits/properties of the scheme: // static Split GetTopologicalSplitType(); static int GetRegularFaceSize(); static int GetRegularVertexValence(); static int GetLocalNeighborhoodSize(); protected: // // Supporting internal methods -- optionally implemented, depending on specialization: // // Subdivision/refinement masks -- two for edge-vertices and three for vertex-vertices: // template void assignCreaseMaskForEdge(EDGE const& edge, MASK& mask) const; template void assignSmoothMaskForEdge(EDGE const& edge, MASK& mask) const; template void assignCornerMaskForVertex(VERTEX const& edge, MASK& mask) const; template void assignCreaseMaskForVertex(VERTEX const& edge, MASK& mask, int const creaseEnds[2]) const; template void assignSmoothMaskForVertex(VERTEX const& edge, MASK& mask) const; // // Limit masks for position and tangents at vertices -- three cases for each: // template void assignCornerLimitMask(VERTEX const& vertex, MASK& pos) const; template void assignCreaseLimitMask(VERTEX const& vertex, MASK& pos, int const creaseEnds[2]) const; template void assignSmoothLimitMask(VERTEX const& vertex, MASK& pos) const; template void assignCornerLimitTangentMasks(VERTEX const& vertex, MASK& tan1, MASK& tan2) const; template void assignCreaseLimitTangentMasks(VERTEX const& vertex, MASK& tan1, MASK& tan2, int const creaseEnds[2]) const; template void assignSmoothLimitTangentMasks(VERTEX const& vertex, MASK& tan1, MASK& tan2) const; private: Options _options; protected: // // Internal implementation support: // // We need a local "mask" class to be declared locally within the vertex-vertex mask query // to hold one of the two possible masks required and to combine the local mask with the mask // the caller provides. It has been parameterized by so that a version compatible // with the caller's mask class is created. // template class LocalMask { public: typedef WEIGHT Weight; public: LocalMask(Weight* v, Weight* e, Weight* f) : _vWeights(v), _eWeights(e), _fWeights(f) { } ~LocalMask() { } public: // // Methods required for general mask assignments and queries: // int GetNumVertexWeights() const { return _vCount; } int GetNumEdgeWeights() const { return _eCount; } int GetNumFaceWeights() const { return _fCount; } void SetNumVertexWeights(int count) { _vCount = count; } void SetNumEdgeWeights( int count) { _eCount = count; } void SetNumFaceWeights( int count) { _fCount = count; } Weight const& VertexWeight(int index) const { return _vWeights[index]; } Weight const& EdgeWeight( int index) const { return _eWeights[index]; } Weight const& FaceWeight( int index) const { return _fWeights[index]; } Weight& VertexWeight(int index) { return _vWeights[index]; } Weight& EdgeWeight( int index) { return _eWeights[index]; } Weight& FaceWeight( int index) { return _fWeights[index]; } bool AreFaceWeightsForFaceCenters() const { return _fWeightsForCenters; } void SetFaceWeightsForFaceCenters(bool on) { _fWeightsForCenters = on; } public: // // Additional methods -- mainly the blending method for vertex-vertex masks: // template inline void CombineVertexVertexMasks(Weight thisCoeff, Weight dstCoeff, USER_MASK& dst) const { // // This implementation is convoluted by the potential sparsity of each mask. Since // it is specific to a vertex-vertex mask, we are guaranteed to have exactly one // vertex-weight for both masks, but the edge- and face-weights are optional. The // child mask (the "source") should have a superset of the weights of the parent // (the "destination") given its reduced sharpness, so we fortunately don't need to // test all permutations. // dst.VertexWeight(0) = dstCoeff * dst.VertexWeight(0) + thisCoeff * this->VertexWeight(0); int edgeWeightCount = this->GetNumEdgeWeights(); if (edgeWeightCount) { if (dst.GetNumEdgeWeights() == 0) { dst.SetNumEdgeWeights(edgeWeightCount); for (int i = 0; i < edgeWeightCount; ++i) { dst.EdgeWeight(i) = thisCoeff * this->EdgeWeight(i); } } else { for (int i = 0; i < edgeWeightCount; ++i) { dst.EdgeWeight(i) = dstCoeff * dst.EdgeWeight(i) + thisCoeff * this->EdgeWeight(i); } } } int faceWeightCount = this->GetNumFaceWeights(); if (faceWeightCount) { // // If combining face weights, be sure their interpretation (i.e. face-centers // or opposite vertices) is properly set in the destination mask: // if (dst.GetNumFaceWeights() == 0) { dst.SetNumFaceWeights(faceWeightCount); dst.SetFaceWeightsForFaceCenters(this->AreFaceWeightsForFaceCenters()); for (int i = 0; i < faceWeightCount; ++i) { dst.FaceWeight(i) = thisCoeff * this->FaceWeight(i); } } else { assert(this->AreFaceWeightsForFaceCenters() == dst.AreFaceWeightsForFaceCenters()); for (int i = 0; i < faceWeightCount; ++i) { dst.FaceWeight(i) = dstCoeff * dst.FaceWeight(i) + thisCoeff * this->FaceWeight(i); } } } } private: Weight* _vWeights; Weight* _eWeights; Weight* _fWeights; int _vCount; int _eCount; int _fCount; bool _fWeightsForCenters; }; }; // // Crease and corner masks are common to most schemes -- the rest need to be provided // for each Scheme specialization. // template template inline void Scheme::assignCreaseMaskForEdge(EDGE const&, MASK& mask) const { mask.SetNumVertexWeights(2); mask.SetNumEdgeWeights(0); mask.SetNumFaceWeights(0); mask.SetFaceWeightsForFaceCenters(false); mask.VertexWeight(0) = 0.5f; mask.VertexWeight(1) = 0.5f; } template template inline void Scheme::assignCornerMaskForVertex(VERTEX const&, MASK& mask) const { mask.SetNumVertexWeights(1); mask.SetNumEdgeWeights(0); mask.SetNumFaceWeights(0); mask.SetFaceWeightsForFaceCenters(false); mask.VertexWeight(0) = 1.0f; } // // The computation of a face-vertex mask is trivial and consistent for all schemes: // template template void Scheme::ComputeFaceVertexMask(FACE const& face, MASK& mask) const { int vertCount = face.GetNumVertices(); mask.SetNumVertexWeights(vertCount); mask.SetNumEdgeWeights(0); mask.SetNumFaceWeights(0); mask.SetFaceWeightsForFaceCenters(false); typename MASK::Weight vWeight = 1.0f / (typename MASK::Weight) vertCount; for (int i = 0; i < vertCount; ++i) { mask.VertexWeight(i) = vWeight; } } // // The computation of an edge-vertex mask requires inspection of sharpness values to // determine if smooth or a crease, and also to detect and apply a transition from a // crease to smooth. Using the protected methods to assign the specific masks (only // two -- smooth or crease) this implementation should serve all non-linear schemes // (currently Catmark and Loop) and only needs to be specialized for Bilinear to // trivialize it to the crease case. // // The implementation here is slightly complicated by combining two scenarios into a // single implementation -- either the caller knows the parent and child rules and // provides them, or they don't and the Rules have to be determined from sharpness // values. Both cases include quick return once the parent is determined to be // smooth or the child a crease, leaving the transitional case remaining. // // The overall process is as follows: // // - quickly detect the most common specified or detected Smooth case and return // - quickly detect a full Crease by child Rule assignment and return // - determine from sharpness if unspecified child is a crease -- return if so // - compute smooth mask for child and combine with crease from parent // // Usage of the parent Rule here allows some misuse in that only three of five possible // assignments are legitimate for the parent and four for the child (Dart being only // valid for the child and Corner for neither). Results are undefined in these cases. // template template void Scheme::ComputeEdgeVertexMask(EDGE const& edge, MASK& mask, Crease::Rule parentRule, Crease::Rule childRule) const { // // If the parent was specified or determined to be Smooth, we can quickly return // with a Smooth mask. Otherwise the parent is a crease -- if the child was // also specified to be a crease, we can quickly return with a Crease mask. // if ((parentRule == Crease::RULE_SMOOTH) || ((parentRule == Crease::RULE_UNKNOWN) && (edge.GetSharpness() <= 0.0f))) { assignSmoothMaskForEdge(edge, mask); return; } if (childRule == Crease::RULE_CREASE) { assignCreaseMaskForEdge(edge, mask); return; } // // We have a Crease on the parent and the child was either specified as Smooth // or was not specified at all -- deal with the unspecified case first (again // returning a Crease mask if the child is also determined to be a Crease) and // continue if we have a transition to Smooth. // // Note when qualifying the child that if the parent sharpness > 1.0, regardless // of the creasing method, whether the child sharpness values decay to zero is // irrelevant -- the fractional weight for such a case (the value of the parent // sharpness) is > 1.0, and when clamped to 1 effectively yields a full crease. // if (childRule == Crease::RULE_UNKNOWN) { Crease crease(_options); bool childIsCrease = false; if (parentRule == Crease::RULE_CREASE) { // Child unknown as default value but parent Rule specified as Crease childIsCrease = true; } else if (edge.GetSharpness() >= 1.0f) { // Sharpness >= 1.0 always a crease -- see note above childIsCrease = true; } else if (crease.IsUniform()) { // Sharpness < 1.0 is guaranteed to decay to 0.0 for Uniform child edges childIsCrease = false; } else { // Sharpness <= 1.0 does not necessarily decay to 0.0 for both child edges... float cEdgeSharpness[2]; edge.GetChildSharpnesses(crease, cEdgeSharpness); childIsCrease = (cEdgeSharpness[0] > 0.0f) && (cEdgeSharpness[1] > 0.0f); } if (childIsCrease) { assignCreaseMaskForEdge(edge, mask); return; } } // // We are now left with the Crease-to-Smooth case -- compute the Smooth mask // for the child and augment it with the transitional Crease of the parent. // // A general combination of separately assigned masks here (as done in the vertex- // vertex case) is overkill -- trivially combine the 0.5f vertex coefficient for // the Crease of the parent with the vertex weights and attenuate the face weights // accordingly. // assignSmoothMaskForEdge(edge, mask); typedef typename MASK::Weight Weight; Weight pWeight = edge.GetSharpness(); Weight cWeight = 1.0f - pWeight; mask.VertexWeight(0) = pWeight * 0.5f + cWeight * mask.VertexWeight(0); mask.VertexWeight(1) = pWeight * 0.5f + cWeight * mask.VertexWeight(1); int faceCount = mask.GetNumFaceWeights(); for (int i = 0; i < faceCount; ++i) { mask.FaceWeight(i) *= cWeight; } } // // The computation of a vertex-vertex mask requires inspection of creasing sharpness values // to determine what subdivision Rules apply to the parent and its child vertex, and also to // detect and apply a transition between two differing Rules. Using the protected methods to // assign specific masks, this implementation should serve all non-linear schemes (currently // Catmark and Loop) and only needs to be specialized for Bilinear to remove all unnecessary // complexity relating to creasing, Rules, etc. // // The implementation here is slightly complicated by combining two scenarios into one -- // either the caller knows the parent and child rules and provides them, or they don't and // the Rules have to be determined from sharpness values. Even when the Rules are known and // provided though, there are cases where the parent and child sharpness values need to be // identified, so accounting for the unknown Rules too is not much of an added complication. // // The benefit of supporting specified Rules is that they can often be trivially // determined from context (e.g. a vertex derived from a face at a previous level will always // be smooth) rather than more generally, and at greater cost, inspecting neighboring and // they are often the same for parent and child. // // The overall process is as follows: // // - quickly detect the most common Smooth case when specified and return // - determine if sharpness for parent is required and gather if so // - if unspecified, determine the parent rule // - assign mask for the parent rule -- returning if Smooth/Dart // - return if child rule matches parent // - gather sharpness for child to determine or combine child rule // - if unspecified, determine the child rule, returning if it matches parent // - assign local mask for child rule // - combine local child mask with the parent mask // // Remember -- if the parent rule is specified but the child is not, this implies only one // of the two optional rules was specified and is meant to indicate there is no transition, // so the child rule should be assigned to be the same (wish the compiler would allow this // in default value assignment). // template template void Scheme::ComputeVertexVertexMask(VERTEX const& vertex, MASK& mask, Crease::Rule pRule, Crease::Rule cRule) const { // Quick assignment and return for the most common case: if ((pRule == Crease::RULE_SMOOTH) || (pRule == Crease::RULE_DART)) { assignSmoothMaskForVertex(vertex, mask); return; } // If unspecified, assign the child rule to match the parent rule if specified: if ((cRule == Crease::RULE_UNKNOWN) && (pRule != Crease::RULE_UNKNOWN)) { cRule = pRule; } int valence = vertex.GetNumEdges(); // // Determine if we need the parent edge sharpness values -- identify/gather if so // and use it to compute the parent rule if unspecified: // float * pEdgeSharpnessBuffer = (float *)alloca(valence*sizeof(float)), * pEdgeSharpness = 0, pVertexSharpness = 0.0f; bool requireParentSharpness = (pRule == Crease::RULE_UNKNOWN) || (pRule == Crease::RULE_CREASE) || (pRule != cRule); if (requireParentSharpness) { pVertexSharpness = vertex.GetSharpness(); pEdgeSharpness = vertex.GetSharpnessPerEdge(pEdgeSharpnessBuffer); if (pRule == Crease::RULE_UNKNOWN) { pRule = Crease(_options).DetermineVertexVertexRule(pVertexSharpness, valence, pEdgeSharpness); } } if ((pRule == Crease::RULE_SMOOTH) || (pRule == Crease::RULE_DART)) { assignSmoothMaskForVertex(vertex, mask); return; // As done on entry, we can return immediately if parent is Smooth/Dart } else if (pRule == Crease::RULE_CREASE) { int creaseEnds[2]; Crease(_options).GetSharpEdgePairOfCrease(pEdgeSharpness, valence, creaseEnds); assignCreaseMaskForVertex(vertex, mask, creaseEnds); } else { assignCornerMaskForVertex(vertex, mask); } if (cRule == pRule) return; // // Identify/gather child sharpness to combine masks for the two differing Rules: // Crease crease(_options); float * cEdgeSharpnessBuffer = (float *)alloca(valence*sizeof(float)), * cEdgeSharpness = vertex.GetChildSharpnessPerEdge(crease, cEdgeSharpnessBuffer), cVertexSharpness = vertex.GetChildSharpness(crease); if (cRule == Crease::RULE_UNKNOWN) { cRule = crease.DetermineVertexVertexRule(cVertexSharpness, valence, cEdgeSharpness); if (cRule == pRule) return; } // // Initialize a local child mask, compute the fractional weight from parent and child // sharpness values and combine the two masks: // typedef typename MASK::Weight Weight; Weight * cMaskWeights = (Weight *)alloca((1 + 2 * valence)*sizeof(Weight)); LocalMask cMask(cMaskWeights, cMaskWeights + 1, cMaskWeights + 1 + valence); if ((cRule == Crease::RULE_SMOOTH) || (cRule == Crease::RULE_DART)) { assignSmoothMaskForVertex(vertex, cMask); } else if (cRule == Crease::RULE_CREASE) { int creaseEnds[2]; Crease(_options).GetSharpEdgePairOfCrease(cEdgeSharpness, valence, creaseEnds); assignCreaseMaskForVertex(vertex, cMask, creaseEnds); } else { assignCornerMaskForVertex(vertex, cMask); } Weight pWeight = crease.ComputeFractionalWeightAtVertex(pVertexSharpness, cVertexSharpness, valence, pEdgeSharpness, cEdgeSharpness); Weight cWeight = 1.0f - pWeight; cMask.CombineVertexVertexMasks(cWeight, pWeight, mask); } // // The computation of limit masks for vertices: // template template void Scheme::ComputeVertexLimitMask(VERTEX const& vertex, MASK& mask, Crease::Rule rule) const { if ((rule == Crease::RULE_SMOOTH) || (rule == Crease::RULE_DART)) { assignSmoothLimitMask(vertex, mask); } else if (rule == Crease::RULE_CREASE) { float * edgeSharpness = (float *)alloca(vertex.GetNumEdges() * sizeof(float)); vertex.GetSharpnessPerEdge(edgeSharpness); int creaseEnds[2]; Crease(_options).GetSharpEdgePairOfCrease(edgeSharpness, vertex.GetNumEdges(), creaseEnds); assignCreaseLimitMask(vertex, mask, creaseEnds); } else { assignCornerLimitMask(vertex, mask); } } template template void Scheme::ComputeVertexLimitMask(VERTEX const& vertex, MASK& posMask, MASK& tan1Mask, MASK& tan2Mask, Crease::Rule rule) const { if ((rule == Crease::RULE_SMOOTH) || (rule == Crease::RULE_DART)) { assignSmoothLimitMask(vertex, posMask); assignSmoothLimitTangentMasks(vertex, tan1Mask, tan2Mask); } else if (rule == Crease::RULE_CREASE) { float * edgeSharpness = (float *)alloca(vertex.GetNumEdges() * sizeof(float)); vertex.GetSharpnessPerEdge(edgeSharpness); int creaseEnds[2]; Crease(_options).GetSharpEdgePairOfCrease(edgeSharpness, vertex.GetNumEdges(), creaseEnds); assignCreaseLimitMask(vertex, posMask, creaseEnds); assignCreaseLimitTangentMasks(vertex, tan1Mask, tan2Mask, creaseEnds); } else { assignCornerLimitMask(vertex, posMask); assignCornerLimitTangentMasks(vertex, tan1Mask, tan2Mask); } } } // end namespace sdc } // end namespace OPENSUBDIV_VERSION using namespace OPENSUBDIV_VERSION; } // end namespace OpenSubdiv #endif /* OPENSUBDIV3_SDC_SCHEME_H */