//
//   Copyright 2014 DreamWorks Animation LLC.
//
//   Licensed under the terms set forth in the LICENSE.txt file available at
//   https://opensubdiv.org/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 occasionally 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) non-manifold features -- sharpness values should be adjusted 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 sharpness 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 */
