OpenSubdiv/opensubdiv/sdc/crease.h
jcowles 392e5e8bed Remove #pragma once
While this may be worth revisiting, we should first quantify the benefits and
identify the compilers that support it. Ultimately, we may never use pragma
once in favor of strictly using standard C++.
2015-05-20 09:59:18 -07:00

248 lines
11 KiB
C++

//
// 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_CREASE_H
#define OPENSUBDIV3_SDC_CREASE_H
#include "../version.h"
#include "../sdc/options.h"
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Sdc {
///
/// \brief Types, constants and utilities related to semi-sharp creasing -- whose implementation
/// is independent of the subdivision scheme.
///
/// Crease is intended to be a light-weight, trivially constructed class that computes
/// crease-related properties -- typically sharpness values and associated interpolation
/// weights. An instance of Crease is defined with a set of options that include current
/// and future variations that will impact computations involving sharpness values.
///
/// The Crease methods do not use topological neighborhoods as input. The methods here
/// rely more on the sharpness values and less on the topology, so we choose to work directly
/// with the sharpness values. We also follow the trend of using primitive arrays in the
/// interface to encourage local gathering for re-use.
///
/// Note on the need for and use of sharpness values:
/// In general, mask queries rely on the sharpness values. The common case of a smooth
/// vertex, when known, avoids the need to inspect them, but unless the rules are well understood,
/// users will be expected to provided them -- particularly when they expect the mask queries
/// to do all of the work (just determining if a vertex is smooth will require inspection of
/// incident edge sharpness).
/// Mask queries will occassionally require the subdivided sharpness values around the
/// child vertex. So users will be expected to either provide them up front when known, or to be
/// gathered on demand. Any implementation of subdivision with creasing cannot avoid subdividing
/// the sharpness values first, so keeping them available for re-use is a worthwhile consideration.
///
class Crease {
public:
//@{
/// Constants and related queries of sharpness values:
///
static float const SHARPNESS_SMOOTH; // = 0.0f, do we really need this?
static float const SHARPNESS_INFINITE; // = 10.0f;
static bool IsSmooth(float sharpness) { return sharpness <= SHARPNESS_SMOOTH; }
static bool IsSharp(float sharpness) { return sharpness > SHARPNESS_SMOOTH; }
static bool IsInfinite(float sharpness) { return sharpness >= SHARPNESS_INFINITE; }
static bool IsSemiSharp(float sharpness) { return (SHARPNESS_SMOOTH < sharpness) && (sharpness < SHARPNESS_INFINITE); }
//@}
///
/// Enum for the types of subdivision rules applied based on sharpness values (note these
/// correspond to Hbr's vertex "mask"). The values are assigned to bit positions as it is
/// useful to use bitwise operations to inspect collections of vertices (i.e. all of the
/// vertices incident a particular face).
///
enum Rule {
RULE_UNKNOWN = 0,
RULE_SMOOTH = (1 << 0),
RULE_DART = (1 << 1),
RULE_CREASE = (1 << 2),
RULE_CORNER = (1 << 3)
};
public:
Crease() : _options() { }
Crease(Options const& options) : _options(options) { }
~Crease() { }
bool IsUniform() const { return _options.GetCreasingMethod() == Options::CREASE_UNIFORM; }
//@{
/// Optional sharp features:
/// Since options treat certain topological features as infinitely sharp -- boundaries
/// or (in future) nonmanifold features -- sharpness values should be adjust before use.
/// The following methods will adjust (by return) specific values according to the options
/// applied.
///
float SharpenBoundaryEdge(float edgeSharpness) const;
float SharpenBoundaryVertex(float edgeSharpness) const;
// For future consideration
//float SharpenNonManifoldEdge(float edgeSharpness) const;
//float SharpenNonManifoldVertex(float edgeSharpness) const;
//@}
//@{
/// Sharpness subdivision:
/// The computation of a Uniform subdivided sharpness value is as follows:
/// - Smooth edges or verts stay Smooth
/// - Sharp edges or verts stay Sharp
/// - semi-sharp edges or verts are decremented by 1.0
/// but for Chaikin (and potentially future non-uniform schemes that improve upon it) the
/// computation is more involved. In the case of edges in particular, the sharpness of a
/// child edge is determined by the sharpness in the neighborhood of the end vertex
/// corresponding to the child. For this reason, an alternative to subdividing sharpness
/// that computes all child edges around a vertex is given.
///
float SubdivideUniformSharpness(float vertexOrEdgeSharpness) const;
float SubdivideVertexSharpness(float vertexSharpness) const;
float SubdivideEdgeSharpnessAtVertex(float edgeSharpness,
int incidentEdgeCountAtEndVertex,
float const* edgeSharpnessAroundEndVertex) const;
void SubdivideEdgeSharpnessesAroundVertex(int incidentEdgeCountAtVertex,
float const* incidentEdgeSharpnessAroundVertex,
float* childEdgesSharpnessAroundVertex) const;
//@}
//@{
/// Rule determination:
/// Mask queries do not require the Rule to be known, it can be determined from
/// the information provided, but it is generally more efficient when the Rule is known
/// and provided. In particular, the Smooth case dominates and is known to be applicable
/// based on the origin of the vertex without inspection of sharpness.
///
Rule DetermineVertexVertexRule(float vertexSharpness,
int incidentEdgeCount,
float const* incidentEdgeSharpness) const;
Rule DetermineVertexVertexRule(float vertexSharpness,
int sharpEdgeCount) const;
//@}
/// \brief Transitional weighting:
/// When the rules applicable to a parent vertex and its child differ, one or more
/// sharpness values has "decayed" to zero. Both rules are then applicable and blended
/// by a weight between 0 and 1 that reflects the transition. Most often this will be
/// a single sharpness value that decays from within the interval [0,1] to zero -- and
/// the weight to apply is exactly that sharpness value -- but more than one may decay,
/// and values > 1 may also decay to 0 in a single step while others within [0,1] may
/// remain > 0.
/// So to properly determine a transitional weight, sharpness values for both the
/// parent and child must be inspected, combined and clamped accordingly.
///
float ComputeFractionalWeightAtVertex(float vertexSharpness,
float childVertexSharpness,
int incidentEdgeCount,
float const* incidentEdgeSharpness,
float const* childEdgesSharpness) const;
void GetSharpEdgePairOfCrease(float const * incidentEdgeSharpness,
int incidentEdgeCount,
int sharpEdgePair[2]) const;
// Would these really help? Maybe only need Rules for the vertex-vertex case...
//
// Rule DetermineEdgeVertexRule(float parentEdgeSharpness) const;
// Rule DetermineEdgeVertexRule(float childEdge1Sharpness, float childEdge2Sharpness) const;
protected:
float decrementSharpness(float sharpness) const;
private:
Options _options;
};
//
// Inline declarations:
//
inline float
Crease::SharpenBoundaryEdge(float /* edgeSharpness */) const {
//
// Despite the presence of the BOUNDARY_NONE option, boundary edges are always sharpened.
// Much of the code relies on sharpess to indicate boundaries to avoid the more complex
// topological inspection
//
return SHARPNESS_INFINITE;
}
inline float
Crease::SharpenBoundaryVertex(float vertexSharpness) const {
return (_options.GetVtxBoundaryInterpolation() == Options::VTX_BOUNDARY_EDGE_AND_CORNER) ?
SHARPNESS_INFINITE : vertexSharpness;
}
inline float
Crease::decrementSharpness(float sharpness) const {
if (IsSmooth(sharpness)) return Crease::SHARPNESS_SMOOTH; // redundant but most common
if (IsInfinite(sharpness)) return Crease::SHARPNESS_INFINITE;
if (sharpness > 1.0f) return (sharpness - 1.0f);
return Crease::SHARPNESS_SMOOTH;
}
inline float
Crease::SubdivideUniformSharpness(float vertexOrEdgeSharpness) const {
return decrementSharpness(vertexOrEdgeSharpness);
}
inline float
Crease::SubdivideVertexSharpness(float vertexSharpness) const {
return decrementSharpness(vertexSharpness);
}
inline void
Crease::GetSharpEdgePairOfCrease(float const * incidentEdgeSharpness, int incidentEdgeCount,
int sharpEdgePair[2]) const {
// Only to be called when a crease is present at a vertex -- exactly two sharp
// edges are expected here:
//
sharpEdgePair[0] = 0;
while (IsSmooth(incidentEdgeSharpness[sharpEdgePair[0]])) ++ sharpEdgePair[0];
sharpEdgePair[1] = incidentEdgeCount - 1;
while (IsSmooth(incidentEdgeSharpness[sharpEdgePair[1]])) -- sharpEdgePair[1];
}
} // end namespace sdc
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_SDC_CREASE_H */