Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
#
# Copyright 2021 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# source & headers
set(SOURCE_FILES
faceSurface.cpp
faceTopology.cpp
faceVertex.cpp
hash.cpp
irregularPatchBuilder.cpp
parameterization.cpp
patchTree.cpp
patchTreeBuilder.cpp
refinerSurfaceFactory.cpp
regularPatchBuilder.cpp
surface.cpp
surfaceData.cpp
surfaceFactory.cpp
surfaceFactoryCache.cpp
tessellation.cpp
vertexDescriptor.cpp
)
set(PRIVATE_HEADER_FILES
faceSurface.h
faceTopology.h
faceVertex.h
faceVertexSubset.h
hash.h
irregularPatchBuilder.h
patchTree.h
patchTreeBuilder.h
pointOperations.h
regularPatchBuilder.h
vertexTag.h
)
set(PUBLIC_HEADER_FILES
irregularPatchType.h
limits.h
parameterization.h
refinerSurfaceFactory.h
surface.h
surfaceData.h
surfaceFactory.h
surfaceFactoryMeshAdapter.h
surfaceFactoryCache.h
tessellation.h
vertexDescriptor.h
)
set(DOXY_HEADER_FILES ${PUBLIC_HEADER_FILES})
include_directories("${CMAKE_CURRENT_BINARY_DIR}")
if (NOT NO_LIB)
# Compile objs first for both the CPU and GPU libs -----
add_library(bfr_obj
OBJECT
${SOURCE_FILES}
${PRIVATE_HEADER_FILES}
${PUBLIC_HEADER_FILES}
)
set_target_properties(bfr_obj
PROPERTIES
FOLDER "opensubdiv"
)
endif()
#-------------------------------------------------------------------------------
osd_add_doxy_headers( "${DOXY_HEADER_FILES}" )
install(
FILES
${PUBLIC_HEADER_FILES}
DESTINATION
"${CMAKE_INCDIR_BASE}/bfr"
PERMISSIONS
OWNER_READ
GROUP_READ
WORLD_READ )
#-------------------------------------------------------------------------------

View File

@@ -0,0 +1,540 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../bfr/faceSurface.h"
#include <algorithm>
#include <cstring>
#include <cstdio>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// Initialization utilities for both vertex and face-varying surfaces:
//
void
FaceSurface::preInitialize(FaceTopology const & faceTopology,
Index const faceIndices[]) {
//
// Initialize members, allocate subsets for the corners and clear
// tags combining features of all corners:
//
_topology = &faceTopology;
_indices = faceIndices;
_isFaceVarying = false;
_matchesVertex = false;
_corners.SetSize(GetFaceSize());
_combinedTag.Clear();
}
void
FaceSurface::postInitialize() {
//
// Determine if the surface is regular and if not, filter options
// that are not being used (to avoid them falsely indicating that
// two similar surfaces are different):
//
_isRegular = isRegular();
_optionsInEffect = GetSdcOptionsAsAssigned();
if (!_isRegular) {
reviseSdcOptionsInEffect();
}
}
//
// Initializers for vertex and face-varying surfaces:
//
void
FaceSurface::Initialize(FaceTopology const & vtxTopology,
Index const vtxIndices[]) {
preInitialize(vtxTopology, vtxIndices);
_isFaceVarying = false;
// WIP - we could reduce the subset by seeking delimiting inf-sharp
// edges, but not in the presence of a dart
bool useInfSharpSubsets = _topology->GetTag().HasInfSharpEdges() &&
!_topology->GetTag().HasInfSharpDarts();
//
// For each corner, identify the manifold subset containing the face
// and sharpen according to the vertex boundary interpolation option
// if warranted. Meanwhile, accumulate the combined set of tags for
// all corners:
//
for (int corner = 0; corner < GetFaceSize(); ++corner) {
FaceVertex const & vtxTop = GetCornerTopology(corner);
FaceVertexSubset & vtxSub = _corners[corner];
vtxTop.GetVertexSubset(&vtxSub);
if (vtxSub.IsBoundary() && !vtxSub.IsSharp()) {
sharpenBySdcVtxBoundaryInterpolation(&vtxSub, vtxTop);
}
if (useInfSharpSubsets && vtxTop.GetTag().HasInfSharpEdges()) {
// WIP - potentially reduce to a smaller subset here
}
_combinedTag.Combine(vtxSub.GetTag());
}
postInitialize();
}
void
FaceSurface::Initialize(FaceSurface const & vtxSurface,
Index const fvarIndices[]) {
preInitialize(*vtxSurface._topology, fvarIndices);
_isFaceVarying = true;
//
// For each corner, find the face-varying subset of the vertex subset
// and sharpen according to the face-varying interpolation option if
// warranted. Meanwhile, accumulate the combined set of tags for all
// corners, and whether the face-varying topology matches the vertex
// for all corners:
//
for (int corner = 0; corner < GetFaceSize(); ++corner) {
FaceVertex const & vtxTop = GetCornerTopology(corner);
FaceVertexSubset const & vtxSub = vtxSurface.GetCornerSubset(corner);
FaceVertexSubset & fvarSub = _corners[corner];
vtxTop.FindFaceVaryingSubset(&fvarSub, fvarIndices, vtxSub);
if (fvarSub.IsBoundary() && !fvarSub.IsSharp()) {
sharpenBySdcFVarLinearInterpolation(&fvarSub, fvarIndices,
vtxSub, vtxTop);
}
_combinedTag.Combine(fvarSub.GetTag());
_matchesVertex = _matchesVertex && fvarSub.ShapeMatchesSuperset(vtxSub);
fvarIndices += vtxTop.GetNumFaceVertices();
}
postInitialize();
}
//
// Minor methods supporting initialization:
//
bool
FaceSurface::isRegular() const {
//
// Immediate reject features from the combined tags (semi-sharp
// vertices, any sharp edges, any irregular face sizes) before
// testing valence and topology at each corner:
//
if (_combinedTag.HasSharpEdges() ||
_combinedTag.HasSemiSharpVertices() ||
_combinedTag.HasIrregularFaceSizes()) {
return false;
}
//
// If no boundaries, the interior case can be quickly determined:
//
if (!_combinedTag.HasBoundaryVertices()) {
if (_combinedTag.HasInfSharpVertices()) return false;
if (GetRegFaceSize() == 4) {
// Can use bitwise-OR here for reg valence of 4:
return (_corners[0].GetNumFaces() |
_corners[1].GetNumFaces() |
_corners[2].GetNumFaces() |
_corners[3].GetNumFaces()) == 4;
} else {
return (_corners[0].GetNumFaces() == 6) &&
(_corners[1].GetNumFaces() == 6) &&
(_corners[2].GetNumFaces() == 6);
}
}
//
// Test all corners for appropriate interior or boundary valence:
//
int regInteriorValence = (GetRegFaceSize() == 4) ? 4 : 6;
int regBoundaryValence = (regInteriorValence / 2);
for (int i = 0; i < GetFaceSize(); ++i) {
FaceVertexSubset const & corner = _corners[i];
if (corner.IsSharp()) {
if (corner.GetNumFaces() != 1) return false;
} else if (corner.IsBoundary()) {
if (corner.GetNumFaces() != regBoundaryValence) return false;
} else {
if (corner.GetNumFaces() != regInteriorValence) return false;
}
}
return true;
}
void
FaceSurface::reviseSdcOptionsInEffect() {
//
// "Override" (ignore, set to default) any options not affecting
// the shape of the limit surface. The boundary and face-varying
// interpolation options are fixed/ignored for all cases. Whether
// other options have an effect depends on the topology present.
//
// This is done, in part, to make accurate comparisons between
// the topologies of two surfaces. For example, the presence of
// differing creasing methods should not lead to two topologically
// identical surfaces with no creasing being considered different.
//
// This is to be used on construction on irregular surfaces AFTER
// the combined tags have been determined.
//
assert(!_isRegular);
MultiVertexTag const & tags = _combinedTag;
Sdc::Options & options = _optionsInEffect;
// Boundary and face-varying interpolation fixed/ignored for all:
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
options.SetFVarLinearInterpolation( Sdc::Options::FVAR_LINEAR_ALL);
// Crease-method ignored when no semi-sharp creasing:
if (options.GetCreasingMethod() != Sdc::Options::CREASE_UNIFORM) {
if (!tags.HasSemiSharpEdges() && !tags.HasSemiSharpVertices()) {
options.SetCreasingMethod(Sdc::Options::CREASE_UNIFORM);
}
}
// Catmark triangle smoothing ignored if not Catmark with triangles:
if (options.GetTriangleSubdivision() != Sdc::Options::TRI_SUB_CATMARK) {
// This is slightly stronger than necessary -- will keep the
// tri-smooth setting if Catmark and any non-quads:
if ((GetSdcScheme() != Sdc::SCHEME_CATMARK) ||
!tags.HasIrregularFaceSizes()) {
options.SetTriangleSubdivision(Sdc::Options::TRI_SUB_CATMARK);
}
}
// Non-default values of any future options will warrant attention
}
//
// Internal methods to apply the Sdc boundary interpolation options for
// vertex and face-varying topology:
//
void
FaceSurface::sharpenBySdcVtxBoundaryInterpolation(FaceVertexSubset * vtxSub,
FaceVertex const & vtxTop) const {
assert(vtxSub->IsBoundary() && !vtxSub->IsSharp());
//
// Sharpen according to Sdc::Options::VtxBoundaryInterpolation:
//
// Remember vertex boundary interpolation is applied based on the
// full topology of the vertex not a particular subset (e.g. we can
// have a smooth corner in a subset delimited by inf-sharp edges).
// And edges are all implicitly sharpened -- leaving only corners to
// be sharpened -- making the EDGE_ONLY and EDGE_AND_CORNER names
// somewhat misleading.
//
bool isSharp = false;
switch (_topology->_schemeOptions.GetVtxBoundaryInterpolation()) {
case Sdc::Options::VTX_BOUNDARY_NONE:
// Nothing to do, as the name suggests
break;
case Sdc::Options::VTX_BOUNDARY_EDGE_ONLY:
// Edges are implicitly sharpened -- nothing more to do
break;
case Sdc::Options::VTX_BOUNDARY_EDGE_AND_CORNER:
// Edges are implicitly sharpened -- sharpen any corners
isSharp = (vtxTop.GetNumFaces() == 1);
break;
default:
assert("Unknown value for Sdc::Options::VtxBoundaryInterpolation" == 0);
break;
}
if (isSharp) {
vtxTop.SharpenSubset(vtxSub);
}
}
namespace fvar_plus {
//
// This local namespace includes a few utilities for dealing solely
// with the CORNERS_PLUS1 and PLUS2 face-varying interpolation options.
//
// These "plus" options differ from the others in that the behavior
// within a face-varying subset is influenced by factors outside the
// subset, i.e. the presence of external face-varying indices or sharp
// edges.
//
typedef FaceSurface::Index Index;
//
// If more than two distinct face-varying subsets are present, the
// corner is sharpened regardless of any other conditions -- leaving
// cases of only one or two subsets to be dealt with.
//
bool
hasMoreThanTwoFVarSubsets(FaceVertex const & top,
Index const fvarIndices[]) {
Index indexCorner = top.GetFaceIndexAtCorner(fvarIndices);
Index indexOther = -1;
int numOtherEdgesDiscts = 1;
//
// Iterate through the faces and return if more than two unique
// fvar indices encountered, or more than two discts edges are
// found in the only other subset:
//
int numFaces = top.GetNumFaces();
for (int face = 0; face < numFaces; ++face) {
Index index = top.GetFaceIndexAtCorner(face, fvarIndices);
// Matches the corner's subset -- skip:
if (index == indexCorner) continue;
// Does not match corner's subset or the other subset -- done:
if ((indexOther >= 0) && (index != indexOther)) return true;
// Matches the "other" subset -- check for discontinuity
// between this face and the next:
indexOther = index;
int faceNext = top.GetFaceNext(face);
numOtherEdgesDiscts += (faceNext < 0) ||
!top.FaceIndicesMatchAcrossEdge(face, faceNext, fvarIndices);
if (numOtherEdgesDiscts > 2) return true;
}
return false;
}
//
// Two face-varying subsets are said to have "dependent sharpness"
// when the sharpness of one influences the other. This is applied
// when one subset has no sharp interior edges while the other does.
//
// NOTE that while these match the behavior of Far, it is unclear if
// Far's conditions are what was intended (need to compare to Hbr).
// If both subsets have a semi-sharp interior edge, the largest of
// the two should probably influence the other -- as is the case as
// one of those semi-sharp edges becomes inf-sharp.
//
bool
hasDependentSharpness(FaceVertex const & topology,
FaceVertexSubset const & subset) {
return ((topology.GetNumFaces() - subset.GetNumFaces()) > 1) &&
topology.GetTag().HasSharpEdges() &&
!subset.GetTag().HasSharpEdges();
}
//
// After the conditions for dependent sharpness have been confirmed,
// retrieve the desired value. The result is the maximum sharpness
// of interior edges that are outside the subset -- and do not lie
// on the seams between the two subsets.
//
float
getDependentSharpness(FaceVertex const & top,
FaceVertexSubset const & subset) {
// Identify the first and last faces of the subset -- to be
// skipped when searching for the largest interior sharp edge:
int firstFace = top.GetFaceFirst(subset);
int lastFace = top.GetFaceLast(subset);
// Skip the face or its neighbor with the shared leading edge:
int firstFacePrev = top.GetFacePrevious(firstFace);
int lastFaceNext = top.GetFaceNext(lastFace);
firstFace = (firstFacePrev < 0) ? -1 : firstFace;
lastFace = (lastFaceNext < 0) ? -1 : lastFaceNext;
// Search for largest interior sharp edge using leading edges:
float sharp = 0.0f;
for (int i = 0; i < top.GetNumFaces(); ++i) {
if (top.GetFacePrevious(i) >= 0) {
if ((i != firstFace) && (i != lastFace)) {
sharp = std::max(sharp, top.GetFaceEdgeSharpness(2*i));
}
}
}
// Must exceed vert sharpness to have any effect, otherwise ignore:
return (sharp > top.GetVertexSharpness()) ? sharp : 0.0f;
}
}
//
// The main method for affecting face-varying subsets according to the
// face-varying interpolation options. Most of these are trivial, with
// only the LINEAR_CORNERS_PLUS* cases requiring much effort.
//
void
FaceSurface::sharpenBySdcFVarLinearInterpolation(FaceVertexSubset * fvarSub,
Index const fvarIndices[],
FaceVertexSubset const & vtxSub,
FaceVertex const & vtxTop) const {
assert(fvarSub->IsBoundary() && !fvarSub->IsSharp());
// Each option applies rules to make the corner "linear", i.e. sharp:
bool isSharp = false;
switch (_topology->_schemeOptions.GetFVarLinearInterpolation()) {
case Sdc::Options::FVAR_LINEAR_NONE:
// Nothing to do, as the name suggests
break;
case Sdc::Options::FVAR_LINEAR_CORNERS_ONLY:
// Sharpen corners only:
isSharp = (fvarSub->GetNumFaces() == 1);
break;
case Sdc::Options::FVAR_LINEAR_CORNERS_PLUS1:
//
// Sharpen corners with more than two disjoint face-varying subsets
// and apply "dependent sharpness" (see above) when necessary:
//
isSharp = (fvarSub->GetNumFaces() == 1) ||
fvar_plus::hasMoreThanTwoFVarSubsets(vtxTop, fvarIndices);
if (!isSharp && fvar_plus::hasDependentSharpness(vtxTop, *fvarSub)) {
// Sharpen if sharp edges of other subset affects this one
vtxTop.SharpenSubset(fvarSub,
fvar_plus::getDependentSharpness(vtxTop, *fvarSub));
}
break;
case Sdc::Options::FVAR_LINEAR_CORNERS_PLUS2:
//
// Sharpen as with "plus1" above, in addition to sharpening both
// concave corners and darts.
//
// In other words, the only situations unsharpened are when either
// the face-varying and vertex subsets exactly match, or there
// are two fvar subsets that both have two or more faces (and no
// dependent sharpness between them).
//
isSharp = (fvarSub->GetNumFaces() == 1) ||
fvar_plus::hasMoreThanTwoFVarSubsets(vtxTop, fvarIndices);
if (!isSharp) {
// Distinguish by the number of faces outside the subset:
int numOtherFaces = vtxSub.GetNumFaces() - fvarSub->GetNumFaces();
if (numOtherFaces == 0) {
// Sharpen if a dart was created from a periodic vertex
isSharp = !vtxSub.IsBoundary();
} else if (numOtherFaces == 1) {
// Sharpen this concave corner since other subset is a corner
isSharp = true;
} else {
// Sharpen if sharp edges of other subset affects this one
if (fvar_plus::hasDependentSharpness(vtxTop, *fvarSub)) {
vtxTop.SharpenSubset(fvarSub,
fvar_plus::getDependentSharpness(vtxTop, *fvarSub));
}
}
}
break;
case Sdc::Options::FVAR_LINEAR_BOUNDARIES:
// Sharpen all boundaries:
isSharp = true;
break;
case Sdc::Options::FVAR_LINEAR_ALL:
assert("Unexpected FVarLinearInterpolation == FVAR_LINEAR_ALL" == 0);
break;
default:
assert("Unknown value for Sdc::Options::FVarLinearInterpolation" == 0);
break;
}
if (isSharp) {
vtxTop.SharpenSubset(fvarSub);
}
}
//
// Miscellaneous methods for debugging:
//
void
FaceSurface::print(bool printVerts) const {
MultiVertexTag const & tag = _combinedTag;
printf(" FaceTopology:\n");
printf(" face size = %d\n", _topology->GetFaceSize());
printf(" num-face-verts = %d\n", _topology->GetNumFaceVertices());
printf(" Properties:\n");
printf(" is regular = %d\n", IsRegular());
printf(" Combined tags:\n");
printf(" inf-sharp verts = %d\n", tag.HasInfSharpVertices());
printf(" semi-sharp verts = %d\n", tag.HasSemiSharpVertices());
printf(" inf-sharp edges = %d\n", tag.HasInfSharpEdges());
printf(" semi-sharp edges = %d\n", tag.HasSemiSharpEdges());
printf(" inf-sharp darts = %d\n", tag.HasInfSharpDarts());
printf(" unsharp boundary = %d\n", tag.HasNonSharpBoundary());
printf(" irregular faces = %d\n", tag.HasIrregularFaceSizes());
printf(" unordered verts = %d\n", tag.HasUnOrderedVertices());
if (printVerts) {
Index const * indices = _indices;
for (int i = 0; i < GetFaceSize(); ++i) {
FaceVertex const & top = GetCornerTopology(i);
FaceVertexSubset const & sub = GetCornerSubset(i);
printf(" corner %d:\n", i);
printf(" topology: num faces = %d, boundary = %d\n",
top.GetNumFaces(), top.GetTag().IsBoundary());
printf(" subset: num faces = %d, boundary = %d\n",
sub.GetNumFaces(), sub.IsBoundary());
printf(" num before = %d, num after = %d\n",
sub._numFacesBefore, sub._numFacesAfter);
printf(" face-vert indices:\n");
for (int j = 0, n = 0; j < top.GetNumFaces(); ++j) {
printf(" face %d: ", j);
int S = top.GetFaceSize(j);
for (int k = 0; k < S; ++k, ++n) {
printf("%3d", indices[n]);
}
printf("\n");
}
indices += top.GetNumFaceVertices();
}
}
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv

View File

@@ -0,0 +1,198 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_FACE_SURFACE_H
#define OPENSUBDIV3_BFR_FACE_SURFACE_H
#include "../version.h"
#include "../bfr/faceTopology.h"
#include "../bfr/faceVertex.h"
#include "../vtr/stackBuffer.h"
#include "../vtr/types.h"
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// The FaceSurface class combines references to several other classes and
// data to provide a complete description of the limit surface of a face.
//
// It is a simple aggregate of four sets of data:
//
// - an instance of FaceTopology with all topological information
// - a set of FaceVertexSubsets for topological extent of each corner
// - a set of indices associated with all vertices of FaceTopology
// - a subset of the Sdc::Options that actually affects the surface
//
// with a few additional members summarizing features of these. The full
// set of topology and corresponding indices are provided on construction
// and the rest are initialized as member variables.
//
// FaceSurfaces are constructed/initialized in two ways:
//
// - for the vertex topology of a face, initialization requires:
// - an instance of FaceTopology
// - vertex indices associated with the FaceTopology (though in
// some cases the vertex indices are not necessary)
//
// - for the face-varying topology of a face:
// - an instance of FaceSurface capturing the vertex topology
// - face-varying indices associated with the vertex topology
//
// Once initialized, other than a few simple queries, it serves solely
// as a container to be passed to other classes to assemble into regular
// or irregular surfaces.
//
class FaceSurface {
public:
typedef FaceTopology::Index Index;
public:
// Constructors for vertex and face-varying surfaces:
FaceSurface();
FaceSurface(FaceTopology const & vtxTopology, Index const vtxIndices[]);
FaceSurface(FaceSurface const & vtxSurface, Index const fvarIndices[]);
~FaceSurface() { }
bool IsInitialized() const;
void Initialize(FaceTopology const & vtxTopology, Index const vtxInds[]);
void Initialize(FaceSurface const & vtxSurface, Index const fvarInds[]);
// Main public methods to distinguish surface and topology:
bool IsRegular() const { return _isRegular; }
bool FVarTopologyMatchesVertex() const { return _matchesVertex; }
// Debugging:
void print(bool printVerts = false) const;
public:
// Public access to the main members:
FaceTopology const & GetTopology() const { return *_topology; }
FaceVertexSubset const * GetSubsets() const { return _corners; }
Index const * GetIndices() const { return _indices; }
MultiVertexTag GetTag() const { return _combinedTag; }
public:
// Additional public access to data used by builder classes:
int GetFaceSize() const;
int GetRegFaceSize() const;
Sdc::SchemeType GetSdcScheme() const;
Sdc::Options GetSdcOptionsInEffect() const;
Sdc::Options GetSdcOptionsAsAssigned() const;
FaceVertex const & GetCornerTopology(int corner) const;
FaceVertexSubset const & GetCornerSubset(int corner) const;
int GetNumIndices() const;
private:
// Internal methods:
void preInitialize(FaceTopology const & topology, Index const indices[]);
void postInitialize();
bool isRegular() const;
void reviseSdcOptionsInEffect();
// Methods to apply specified interpolation options to the corners:
void sharpenBySdcVtxBoundaryInterpolation(
FaceVertexSubset * vtxSubsetPtr,
FaceVertex const & cornerTopology) const;
void sharpenBySdcFVarLinearInterpolation(
FaceVertexSubset * fvarSubsetPtr,
Index const fvarIndices[],
FaceVertexSubset const & vtxSubset,
FaceVertex const & cornerTopology) const;
private:
typedef Vtr::internal::StackBuffer<FaceVertexSubset,8,true> CornerArray;
FaceTopology const * _topology;
Index const * _indices;
CornerArray _corners;
// Members reflecting the effective subset of topology and options:
MultiVertexTag _combinedTag;
Sdc::Options _optionsInEffect;
unsigned int _isFaceVarying : 1;
unsigned int _matchesVertex : 1;
unsigned int _isRegular : 1;
};
//
// Inline constructors:
//
inline
FaceSurface::FaceSurface() : _topology(0), _indices(0) {
}
inline
FaceSurface::FaceSurface(FaceTopology const & vtxTop, Index const vIndices[]) {
Initialize(vtxTop, vIndices);
}
inline
FaceSurface::FaceSurface(FaceSurface const & vtxSurf, Index const fvIndices[]) {
Initialize(vtxSurf, fvIndices);
}
//
// Inline accessors:
//
inline bool
FaceSurface::IsInitialized() const {
return _topology != 0;
}
inline int
FaceSurface::GetFaceSize() const {
return _topology->GetFaceSize();
}
inline int
FaceSurface::GetRegFaceSize() const {
return _topology->GetRegFaceSize();
}
inline Sdc::SchemeType
FaceSurface::GetSdcScheme() const {
return _topology->_schemeType;
}
inline Sdc::Options
FaceSurface::GetSdcOptionsInEffect() const {
return _optionsInEffect;
}
inline Sdc::Options
FaceSurface::GetSdcOptionsAsAssigned() const {
return _topology->_schemeOptions;
}
inline FaceVertex const &
FaceSurface::GetCornerTopology(int corner) const {
return _topology->GetTopology(corner);
}
inline FaceVertexSubset const &
FaceSurface::GetCornerSubset(int corner) const {
return _corners[corner];
}
inline int
FaceSurface::GetNumIndices() const {
return _topology->GetNumFaceVertices();
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_FACE_SURFACE_H */

View File

@@ -0,0 +1,155 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../bfr/faceTopology.h"
#include "../sdc/crease.h"
#include <cstring>
#include <cstdio>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// Constructor needs the same Sdc scheme/options as the SurfaceFactory
// to support internal work -- may need to figure another way to assign
// these if we later need a default constructor for some purpose...
//
FaceTopology::FaceTopology(Sdc::SchemeType schemeType,
Sdc::Options schemeOptions) :
_schemeType(schemeType),
_schemeOptions(schemeOptions),
_regFaceSize(Sdc::SchemeTypeTraits::GetRegularFaceSize(schemeType)),
_isInitialized(false) {
}
//
// Main initialize/finalize used by base factory to delimit assignment:
//
void
FaceTopology::Initialize(int faceSize) {
_faceSize = faceSize;
_numFaceVertsTotal = 0;
_combinedTag.Clear();
_isInitialized = true;
_isFinalized = false;
_corner.SetSize(faceSize);
}
void
FaceTopology::Finalize() {
//
// Inspect all corner vertex topologies -- accumulating the presence
// of irregular features for the face and assigning other internal
// members used to assemble the limit surface:
//
// WIP - potentially want to identify presence of degenerate faces
// below too, i.e. face size < 3. A subclass may specify these in
// an ordered set and that would mess up some of the topological
// traversals. In such case, we can initialize the vertex subset
// to excludes such faces -- treating their edges as non-manifold.
//
// Probably need to add yet another bit per vertex here to know when
// to process an otherwise simple manifold ring, i.e. hasDegenFaces
//
assert(_isInitialized);
for (int i = 0; i < _faceSize; ++i) {
FaceVertex & cTop = GetTopology(i);
_combinedTag.Combine(cTop.GetTag());
_numFaceVertsTotal += cTop.GetNumFaceVertices();
}
_isFinalized = true;
}
void
FaceTopology::ResolveUnOrderedCorners(Index const fvIndices[]) {
//
// Inspect and deal with any corner that did not have its incident
// faces specified in counter-clockwise order (and so which may be
// non-manifold). The face-vertex indices are required for the
// corner to identify the connectivity between them for later use:
//
// Be sure to reset the combined tags as resolution of ordering
// will also detect non-manifold (or manifold) features, e.g.
// sharpening or the presence of boundaries edges.
//
_combinedTag.Clear();
for (int i = 0; i < _faceSize; ++i) {
FaceVertex & cTop = GetTopology(i);
if (cTop.GetTag().IsUnOrdered()) {
cTop.ConnectUnOrderedFaces(fvIndices);
}
_combinedTag.Combine(cTop.GetTag());
fvIndices += cTop.GetNumFaceVertices();
}
}
void
FaceTopology::print(Index const faceVertIndices[]) const {
MultiVertexTag const & tag = _combinedTag;
printf("FaceTopology:\n");
printf(" face size = %d\n", _faceSize);
printf(" num-face-verts = %d\n", _numFaceVertsTotal);
printf(" Tags:\n");
printf(" inf-sharp verts = %d\n", tag.HasInfSharpVertices());
printf(" semi-sharp verts = %d\n", tag.HasSemiSharpVertices());
printf(" inf-sharp edges = %d\n", tag.HasInfSharpEdges());
printf(" semi-sharp edges = %d\n", tag.HasSemiSharpEdges());
printf(" inf-sharp darts = %d\n", tag.HasInfSharpDarts());
printf(" unsharp boundary = %d\n", tag.HasNonSharpBoundary());
printf(" irregular faces = %d\n", tag.HasIrregularFaceSizes());
printf(" unordered verts = %d\n", tag.HasUnOrderedVertices());
if (faceVertIndices) {
Index const * cornerFaceVertIndices = faceVertIndices;
for (int i = 0; i < _faceSize; ++i) {
printf(" corner %d:\n", i);
FaceVertex const & cTop = GetTopology(i);
printf(" topology: num faces = %d, boundary = %d\n",
cTop.GetNumFaces(), cTop.GetTag().IsBoundary());
printf(" face-vert indices:\n");
for (int j = 0, n = 0; j < cTop.GetNumFaces(); ++j) {
printf(" face %d: ", j);
int S = cTop.GetFaceSize(j);
for (int k = 0; k < S; ++k, ++n) {
printf("%3d", cornerFaceVertIndices[n]);
}
printf("\n");
}
cornerFaceVertIndices += cTop.GetNumFaceVertices();
}
}
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv

View File

@@ -0,0 +1,102 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_FACE_TOPOLOGY_H
#define OPENSUBDIV3_BFR_FACE_TOPOLOGY_H
#include "../version.h"
#include "../bfr/faceVertex.h"
#include "../vtr/stackBuffer.h"
#include "../sdc/types.h"
#include "../sdc/options.h"
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// The FaceTopology class describes the full topological neighborhood
// around a base face of a mesh, which includes everything topologically
// necessary to define the limit surface for that face.
//
// It is used solely by the base SurfaceFactory class -- first partially
// populated by subclasses before being inspected and augmented to help
// assemble the limit surface for the face. Its members include some of
// the members of the SurfaceFactory class (e.g. the subdivision scheme
// and options) to make them more available for its purposes.
//
// The primary component of FaceTopology is an array of instances of
// the FaceVertex class (one for each vertex of the face), which is a
// lightweight wrapper around the public VertexDescriptor class that is
// populated by subclasses of SurfaceFactory.
//
// FaceTopology is one of three key components in defining the limit
// surface around a face. The others are a set of FaceVertexSubsets (one
// for each FaceVertex) that specify the subset of the neighborhood of
// the corners of the face that actually do contribute to its surface,
// and the indices associated with vertices that FaceTopology describes
// (which become the control points of the limit surface).
//
class FaceTopology {
public:
typedef FaceVertex::Index Index;
public:
FaceTopology(Sdc::SchemeType schemeType,
Sdc::Options schemeOptions);
~FaceTopology() { }
void Initialize(int faceSize);
void Finalize();
public:
Sdc::SchemeType GetSchemeType() const { return _schemeType; }
Sdc::Options GetSchemeOptions() const { return _schemeOptions; }
int GetFaceSize() const { return _faceSize; }
int GetRegFaceSize() const { return _regFaceSize; }
FaceVertex & GetTopology(int i) { return _corner[i]; }
FaceVertex const & GetTopology(int i) const { return _corner[i]; }
MultiVertexTag const GetTag() const { return _combinedTag; }
int GetNumFaceVertices() const { return _numFaceVertsTotal; }
int GetNumFaceVertices(int i) const{return _corner[i].GetNumFaceVertices();}
// Methods to test for and resolve unordered corners of the face:
bool HasUnOrderedCorners() const { return GetTag().HasUnOrderedVertices(); }
void ResolveUnOrderedCorners(Index const faceVertexIndices[]);
// Debugging...
void print(Index const faceVertIndices[]) const;
public:
Sdc::SchemeType _schemeType;
Sdc::Options _schemeOptions;
int _faceSize;
int _regFaceSize;
int _numFaceVertsTotal;
MultiVertexTag _combinedTag;
unsigned short _isInitialized : 1;
unsigned short _isFinalized : 1;
Vtr::internal::StackBuffer<FaceVertex,4> _corner;
};
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_FACE_TOPOLOGY_H */

View File

@@ -0,0 +1,988 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../bfr/faceVertex.h"
#include "../sdc/crease.h"
#include "../vtr/array.h"
#include "../vtr/stackBuffer.h"
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <map>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// Main initialize and finalize methods used to bracket the assignment
// by clients to the VertexDescriptor member:
//
void
FaceVertex::Initialize(int faceSize, int regFaceSize) {
_commonFaceSize = (short) faceSize;
_regFaceSize = (unsigned char) regFaceSize;
_numFaceVerts = 0;
_isExpInfSharp = false;
_isExpSemiSharp = false;
_isImpInfSharp = false;
_isImpSemiSharp = false;
_vDesc._isValid = false;
_vDesc._isInitialized = false;
}
void
FaceVertex::Finalize(int faceInVertex) {
assert(_vDesc._isFinalized);
_faceInRing = (short) faceInVertex;
//
// Initialize members from the VertexDescriptor:
//
if (!_vDesc.HasIncidentFaceSizes()) {
// Common face size was previously initialized to the face size
_numFaceVerts = _vDesc._numFaces * _commonFaceSize;
} else {
_commonFaceSize = 0;
// Recall face sizes are available as differences between offsets:
_numFaceVerts = _vDesc._faceSizeOffsets[_vDesc._numFaces];
}
// Vertex sharpness:
_isExpInfSharp = Sdc::Crease::IsInfinite(_vDesc._vertSharpness);
_isExpSemiSharp = Sdc::Crease::IsSemiSharp(_vDesc._vertSharpness);
//
// Initialize tags from VertexDescriptor and other members
//
// Note that not all tags can be assigned at this point if the vertex
// is defined by a set of unordered faces. In such cases, the tags
// will be assigned later when the connectivity between incident faces
// is determined. Those that can be assigned regardless of ordering
// are set here -- splitting the assignment of those remaining between
// ordered and unordered cases.
//
_tag.Clear();
_tag._unCommonFaceSizes = _vDesc.HasIncidentFaceSizes();
_tag._irregularFaceSizes = (_commonFaceSize != _regFaceSize);
_tag._infSharpVerts = _isExpInfSharp;
_tag._semiSharpVerts = _isExpSemiSharp;
_tag._unOrderedFaces = !_vDesc.IsManifold();
if (_vDesc.IsManifold()) {
finalizeOrderedTags();
}
}
void
FaceVertex::finalizeOrderedTags() {
//
// A vertex with a set of ordered faces is required to be manifold:
//
_tag._unOrderedFaces = false;
_tag._nonManifoldVerts = false;
_tag._boundaryVerts = _vDesc.IsBoundary();
_tag._boundaryNonSharp = _vDesc.IsBoundary();
//
// Assign tags (and other members) affected by edge sharpness:
//
if (_vDesc.HasEdgeSharpness()) {
float const * sharpness = &_vDesc._faceEdgeSharpness[0];
// Detect unsharpened boundary edges:
bool isBoundary = _tag._boundaryVerts;
if (isBoundary) {
int last = 2 * _vDesc._numFaces - 1;
_tag._boundaryNonSharp =
!Sdc::Crease::IsInfinite(sharpness[0]) ||
!Sdc::Crease::IsInfinite(sharpness[last]);
}
// Detect interior inf-sharp and semi-sharp edges:
int numInfSharpEdges = 0;
int numSemiSharpEdges = 0;
for (int i = isBoundary; i < _vDesc._numFaces; ++i ) {
if (Sdc::Crease::IsInfinite(sharpness[2*i])) {
++ numInfSharpEdges;
} else if (Sdc::Crease::IsSharp(sharpness[2*i])) {
++ numSemiSharpEdges;
}
}
// Mark the presence of interior sharp edges:
_tag._infSharpEdges = (numInfSharpEdges > 0);
_tag._semiSharpEdges = (numSemiSharpEdges > 0);
_tag._infSharpDarts = (numInfSharpEdges == 1) && !isBoundary;
// Detect edges effectively making the vertex sharp -- note that
// a vertex can be both explicitly and implicitly sharp (e.g. low
// semi-sharp vertex value with a higher semi-sharp edge):
int numInfSharpTotal = numInfSharpEdges + isBoundary * 2;
if (numInfSharpTotal > 2) {
_isImpInfSharp = true;
} else if ((numInfSharpTotal + numSemiSharpEdges) > 2) {
_isImpSemiSharp = true;
}
// Mark the vertex inf-sharp if implicitly inf-sharp:
if (!_isExpInfSharp && _isImpInfSharp) {
_tag._infSharpVerts = true;
_tag._semiSharpVerts = false;
}
}
}
bool
FaceVertex::HasImplicitVertexSharpness() const {
return _isImpInfSharp || _isImpSemiSharp;
}
float
FaceVertex::GetImplicitVertexSharpness() const {
if (_isImpInfSharp) {
return Sdc::Crease::SHARPNESS_INFINITE;
}
assert(_isImpSemiSharp);
//
// Since this will be applied at an inf-sharp crease, there will be
// two inf-sharp edges in addition to the semi-sharp, so we only
// need find the max of the semi-sharp edges and whatever explicit
// vertex sharpness may have been assigned. Iterate through all
// faces and inspect the sharpness of each leading interior edge:
//
float sharpness = GetVertexSharpness();
for (int i = 0; i < GetNumFaces(); ++i) {
if (GetFacePrevious(i) >= 0) {
sharpness = std::max(sharpness, GetFaceEdgeSharpness(2*i));
}
}
return sharpness;
}
//
// Methods to initialize and/or find subsets of the corner's topology:
//
int
FaceVertex::initCompleteSubset(Subset * subsetPtr) const {
Subset & subset = *subsetPtr;
//
// Initialize with tags and assign the extent:
//
int numFaces = GetNumFaces();
subset.Initialize(GetTag());
subset._numFacesTotal = (short) numFaces;
if (isInterior()) {
subset._numFacesBefore = 0;
subset._numFacesAfter = (short)(numFaces - 1);
} else if (isOrdered()) {
subset._numFacesBefore = _faceInRing;
subset._numFacesAfter = (short)(numFaces - 1 - subset._numFacesBefore);
} else {
// Unordered faces -- boundary needs to identify its orientation:
subset._numFacesAfter = 0;
for (int f = GetFaceNext(_faceInRing); f >= 0; f = GetFaceNext(f)) {
++ subset._numFacesAfter;
}
subset._numFacesBefore = (short)(numFaces - 1 - subset._numFacesAfter);
}
return subset._numFacesTotal;
}
int
FaceVertex::findConnectedSubsetExtent(Subset * subsetPtr) const {
Subset & subset = *subsetPtr;
//
// Initialize with tags and mark manifold:
//
subset.Initialize(GetTag());
subset._tag._nonManifoldVerts = false;
// Add faces to the dflt single face extent by seeking forward/backward:
int fStart = _faceInRing;
for (int f = GetFaceNext(fStart); f >= 0; f = GetFaceNext(f)) {
if (f == fStart) {
// Periodic -- tag as such and return:
subset.SetBoundary(false);
return subset._numFacesTotal;
}
subset._numFacesAfter ++;
subset._numFacesTotal ++;
}
for (int f = GetFacePrevious(fStart); f >= 0; f = GetFacePrevious(f)) {
subset._numFacesBefore ++;
subset._numFacesTotal ++;
}
subset.SetBoundary(true);
return subset._numFacesTotal;
}
int
FaceVertex::GetVertexSubset(Subset * subsetPtr) const {
//
// The subset from a manifold vertex is trivially complete (ordered
// or not), but for non-manifold cases we need to search and update
// the tags according to the content of the subset:
//
if (isManifold()) {
initCompleteSubset(subsetPtr);
} else {
findConnectedSubsetExtent(subsetPtr);
adjustSubsetTags(subsetPtr);
// And if on a non-manifold crease, test for implicit sharpness:
if (!subsetPtr->IsSharp() && HasImplicitVertexSharpness()) {
SharpenSubset(subsetPtr, GetImplicitVertexSharpness());
}
}
return subsetPtr->_numFacesTotal;
}
int
FaceVertex::findFVarSubsetExtent(Subset const & vtxSub,
Subset * fvarSubsetPtr,
Index const fvarIndices[]) const {
Subset & fvarSub = *fvarSubsetPtr;
//
// Initialize with tags and declare as a boundary to start:
//
fvarSub.Initialize(vtxSub._tag);
fvarSub.SetBoundary(true);
if (vtxSub._numFacesTotal == 1) return 1;
//
// Inspect/gather faces "after" (counter-clockwise order from) the
// corner face. If we arrive back at the corner face, a periodic
// set is complete, but check the continuity of the seam and apply
// before returning:
//
int cornerFace = _faceInRing;
int numFacesAfterToVisit = vtxSub._numFacesAfter;
if (numFacesAfterToVisit) {
int thisFace = cornerFace;
int nextFace = GetFaceNext(thisFace);
for (int i = 0; i < numFacesAfterToVisit; ++i) {
if (!FaceIndicesMatchAcrossEdge(thisFace, nextFace, fvarIndices)) {
break;
}
++ fvarSub._numFacesAfter;
++ fvarSub._numFacesTotal;
thisFace = nextFace;
nextFace = GetFaceNext(thisFace);
}
if (nextFace == cornerFace) {
assert(vtxSub._numFacesBefore == 0);
if (FaceIndicesMatchAtEdgeEnd(thisFace, cornerFace, fvarIndices)) {
fvarSub.SetBoundary(false);
}
return fvarSub._numFacesTotal;
}
}
//
// Inspect/gather faces "before" (clockwise order from) the corner
// face. Include any faces "after" in the case of a periodic vertex
// that was interrupted by a discontinuity above:
//
int numFacesBeforeToVisit = vtxSub._numFacesBefore;
if (!vtxSub.IsBoundary()) {
numFacesBeforeToVisit += vtxSub._numFacesAfter - fvarSub._numFacesAfter;
}
if (numFacesBeforeToVisit) {
int thisFace = cornerFace;
int prevFace = GetFacePrevious(thisFace);
for (int i = 0; i < numFacesBeforeToVisit; ++i) {
if (!FaceIndicesMatchAcrossEdge(prevFace, thisFace, fvarIndices)) {
break;
}
++ fvarSub._numFacesBefore;
++ fvarSub._numFacesTotal;
thisFace = prevFace;
prevFace = GetFacePrevious(thisFace);
}
}
return fvarSub._numFacesTotal;
}
int
FaceVertex::FindFaceVaryingSubset(Subset * fvarSubsetPtr,
Index const fvarIndices[],
Subset const & vtxSub) const {
Subset & fvarSub = *fvarSubsetPtr;
//
// Find the face-varying extent and update the tags if its topology
// is a true subset of the vertex. Also reset the sharpness in this
// case as the rules for the FVar interpolation options (applied
// later) take precedence over those of the vertex:
//
findFVarSubsetExtent(vtxSub, &fvarSub, fvarIndices);
bool fvarTopologyMatchesVertex = fvarSub.ExtentMatchesSuperset(vtxSub);
if (!fvarTopologyMatchesVertex) {
if (fvarSub.IsSharp()) {
UnSharpenSubset(&fvarSub);
}
adjustSubsetTags(&fvarSub, &vtxSub);
}
// Sharpen if the vertex is non-manifold:
if (!fvarSub.IsSharp() && !isManifold()) {
SharpenSubset(&fvarSub);
}
// Sharpen if the face-varying value is non-manifold, i.e. if there
// are any occurrences of the corner FVar index outside the subset:
if (!fvarSub.IsSharp() && (fvarSub.GetNumFaces() < vtxSub.GetNumFaces())) {
Index fvarMatch = GetFaceIndexAtCorner(fvarIndices);
int numMatches = 0;
for (int i = 0; i < GetNumFaces(); ++i) {
numMatches += (GetFaceIndexAtCorner(i, fvarIndices) == fvarMatch);
if (numMatches > fvarSub.GetNumFaces()) {
SharpenSubset(&fvarSub);
break;
}
}
}
return fvarSub.GetNumFaces();
}
//
// Method to revise the tags for a subset of the corner, which may no
// longer include properties that trigger exceptional behavior:
//
void
FaceVertex::SharpenSubset(Subset * subset) const {
// Mark the subset sharp and ensure any related tags are also
// updated accordingly:
subset->_tag._infSharpVerts = true;
subset->_tag._semiSharpVerts = false;
}
void
FaceVertex::UnSharpenSubset(Subset * subset) const {
// Restore subset sharpness based on actual sharpness assignment:
subset->_tag._infSharpVerts = _isExpInfSharp;
subset->_tag._semiSharpVerts = _isExpSemiSharp;
}
void
FaceVertex::SharpenSubset(Subset * subset, float sharpness) const {
// Mark the subset according to sharpness value
if (sharpness > subset->_localSharpness) {
subset->_localSharpness = sharpness;
subset->_tag._infSharpVerts = Sdc::Crease::IsInfinite(sharpness);
subset->_tag._semiSharpVerts = Sdc::Crease::IsSemiSharp(sharpness);
}
}
bool
FaceVertex::subsetHasIrregularFaces(Subset const & subset) const {
assert(_tag.HasIrregularFaceSizes());
if (!_tag._unCommonFaceSizes) return true;
int f = GetFaceFirst(subset);
for (int i = 0; i < subset.GetNumFaces(); ++i, f = GetFaceNext(f)) {
if (GetFaceSize(f) != _regFaceSize) return true;
}
return false;
}
bool
FaceVertex::subsetHasInfSharpEdges(Subset const & subset) const {
assert(_tag.HasInfSharpEdges());
int n = subset.GetNumFaces();
if (n > 1) {
int f = GetFaceFirst(subset);
// Reduce number of faces to visit when inspecting trailing edges:
for (int i = subset.IsBoundary(); i < n; ++i, f = GetFaceNext(f)) {
if (IsFaceEdgeInfSharp(f, 1)) return true;
}
}
return false;
}
bool
FaceVertex::subsetHasSemiSharpEdges(Subset const & subset) const {
assert(_tag.HasSemiSharpEdges());
int n = subset.GetNumFaces();
if (n > 1) {
int f = GetFaceFirst(subset);
// Reduce number of faces to visit when inspecting trailing edges:
for (int i = subset.IsBoundary(); i < n; ++i, f = GetFaceNext(f)) {
if (IsFaceEdgeSemiSharp(f, 1)) return true;
}
}
return false;
}
void
FaceVertex::adjustSubsetTags(Subset * subset,
Subset const * superset) const {
VertexTag & subsetTag = subset->_tag;
// Adjust any tags related to boundary or sharpness status:
if (subsetTag.IsBoundary()) {
subsetTag._infSharpDarts = false;
}
if (subsetTag.IsInfSharp()) {
subsetTag._semiSharpVerts = false;
}
// Adjust for the presence of irregular faces or sharp edges if the
// subset is actually a proper subset of this entire corner or the
// optionally provided superset:
int numSuperFaces = superset ? superset->GetNumFaces() : GetNumFaces();
bool superBoundary = superset ? superset->IsBoundary() : isBoundary();
if ((subset->GetNumFaces() < numSuperFaces) ||
(subset->IsBoundary() != superBoundary)) {
if (subsetTag._irregularFaceSizes) {
subsetTag._irregularFaceSizes = subsetHasIrregularFaces(*subset);
}
if (subsetTag._infSharpEdges) {
subsetTag._infSharpEdges = subsetHasInfSharpEdges(*subset);
if (subsetTag._infSharpEdges && subset->IsBoundary()) {
SharpenSubset(subset);
}
}
if (subsetTag._semiSharpEdges) {
subsetTag._semiSharpEdges = subsetHasSemiSharpEdges(*subset);
}
}
}
//
// Main and supporting internal datatypes and methods to connect unordered
// faces and allow for topological traversals of the incident faces:
//
// The fundamental element of this process is the following definition of
// an Edge. It is lightweight and only stores a state (boundary, interior,
// or non-manifold) along with the one or two faces for a manifold edge.
// It is initialized as a boundary when first created and is then modified
// by adding additional incident faces.
//
struct FaceVertex::Edge {
// Empty constructor intentional since we over-allocate what we need:
Edge() { }
void clear() { std::memset(this, 0, sizeof(*this)); }
void Initialize(Index vtx) { clear(), endVertex = vtx; }
// Transition of state as incident faces are added:
void SetBoundary() { boundary = 1; }
void SetInterior() { boundary = 0, interior = 1; }
void SetNonManifold() { boundary = 0, interior = 0, nonManifold = 1; }
// Special cases forcing non-manifold
void SetDegenerate() { SetNonManifold(), degenerate = 1; }
void SetDuplicate() { SetNonManifold(), duplicate = 1; }
void SetSharpness(float sharpness) {
if (sharpness > 0.0f) {
if (Sdc::Crease::IsInfinite(sharpness)) {
infSharp = true;
} else {
semiSharp = true;
}
}
}
void SetFace(int newFace, bool newTrailing) {
trailing = newTrailing;
*(trailing ? &prevFace : &nextFace) = (short) newFace;
}
void AddFace(int newFace, bool newTrailing) {
// Update the state of the Edge based on the added incident face:
if (boundary) {
if (newTrailing == trailing) {
// Edge is reversed
SetNonManifold();
} else if (newFace == (trailing ? prevFace : nextFace)) {
// Edge is repeated in the face
SetNonManifold();
} else {
// Edge is manifold thus far -- promote to interior
SetInterior();
SetFace(newFace, newTrailing);
}
} else if (interior) {
// More than two incident faces -- make non-manifold
SetNonManifold();
}
}
Index endVertex;
unsigned short boundary : 1;
unsigned short interior : 1;
unsigned short nonManifold : 1;
unsigned short trailing : 1;
unsigned short degenerate : 1;
unsigned short duplicate : 1;
unsigned short infSharp : 1;
unsigned short semiSharp : 1;
short prevFace, nextFace;
};
void
FaceVertex::ConnectUnOrderedFaces(Index const fvIndices[]) {
//
// There are two transient sets of data needed here: a set of Edges
// that connect adjoining faces, and a set of indices (one for each
// of the 2*N face-edges) to identify the Edge for each face-edge.
//
// IMPORTANT -- since these later edge indices are of the same type
// and size as the internal face-edge neighbors, we'll use that array
// to avoid a separate declaration (and possible allocation) and will
// update it in place later.
//
int numFaceEdges = GetNumFaces() * 2;
_faceEdgeNeighbors.SetSize(numFaceEdges);
// Allocate and populate the edges and indices referring to them.
// Initialization fails to detect some "duplicate" edges in a face,
// so post-process to catch these before continuing:
Vtr::internal::StackBuffer<Edge,32,true> edges(numFaceEdges);
short * feEdges = &_faceEdgeNeighbors[0];
int numEdges = createUnOrderedEdges(edges, feEdges, fvIndices);
markDuplicateEdges(edges, feEdges, fvIndices);
// Use the connecting edges to assign neighboring faces (overwriting
// our edge indices) and finish initializing the tags retaining the
// properties of the corner:
assignUnOrderedFaceNeighbors(edges, feEdges);
finalizeUnOrderedTags(edges, numEdges, fvIndices);
}
//
// Identify a set of shared edges between unordered faces so that we can
// establish connections between them.
//
// The "face-edge edges" are really just half-edges that refer (by index)
// to potentially shared Edges. As Edges are created, these half-edges
// are made to refer to them, after which the state of the edge may change
// due to the presence or orientation of additional incident faces.
//
int
FaceVertex::createUnOrderedEdges(Edge edges[],
short feEdges[],
Index const fvIndices[]) const {
// Optional map to help construction for high valence:
typedef std::map<Index,int> EdgeMap;
EdgeMap edgeMap;
bool useMap = (GetNumFaces() > 16);
//
// Iterate through the face-edge pairs to find connecting edges:
//
Index vCorner = GetFaceIndexAtCorner(0, fvIndices);
int numFaceEdges = 2 * GetNumFaces();
int numEdges = 0;
// Don't rely on the tag yet to determine presence of sharpness:
bool hasSharpness = _vDesc.HasEdgeSharpness();
for (int feIndex = 0; feIndex < numFaceEdges; ++feIndex) {
Index vIndex = (feIndex & 1) ?
GetFaceIndexTrailing((feIndex >> 1), fvIndices) :
GetFaceIndexLeading( (feIndex >> 1), fvIndices);
int eIndex = -1;
if (vIndex != vCorner) {
if (useMap) {
EdgeMap::iterator eFound = edgeMap.find(vIndex);
if (eFound != edgeMap.end()) {
eIndex = eFound->second;
} else {
// Make sure to create the new edge below at this index
edgeMap[vIndex] = numEdges;
}
} else {
for (int j = 0; j < numEdges; ++j) {
if (edges[j].endVertex == vIndex) {
eIndex = j;
break;
}
}
}
// Update an existing edge or create a new one
if (eIndex >= 0) {
edges[eIndex].AddFace(feIndex >> 1, feIndex & 1);
} else {
// Index of the new (pre-allocated) edge:
eIndex = numEdges ++;
// Initialize a new edge as boundary (manifold)
Edge & E = edges[eIndex];
E.Initialize(vIndex);
E.SetBoundary();
E.SetFace(feIndex >> 1, feIndex & 1);
if (hasSharpness) {
E.SetSharpness(GetFaceEdgeSharpness(feIndex));
}
}
} else {
// If degenerate, create unique edge (non-manifold)
eIndex = numEdges++;
edges[eIndex].Initialize(vIndex);
edges[eIndex].SetDegenerate();
}
assert(eIndex >= 0);
feEdges[feIndex] = (short) eIndex;
}
return numEdges;
}
void
FaceVertex::markDuplicateEdges(Edge edges[],
short const feEdges[],
Index const fvIndices[]) const {
//
// The edge assignment thus far does not correctly detect the presence
// of all edges repeated or duplicated in the same face, e.g. for quad
// with vertices {A, B, A, C} the edge AB occurs both as AB and BA.
// When the face is oriented relative to corner B, we have {B, A, C, A}
// and edge BA will be detected as non-manifold -- but not from corner
// A or C.
//
// So look for repeated instances of the corner vertex in the face and
// inspect its neighbors to see if they match the leading or trailing
// edges.
//
// This is a trivial test for a quad: if the opposite vertex matches
// the corner vertex, both the leading and trailing edges will be
// duplicated and so can immediately be marked non-manifold. So deal
// with the common case of all neighboring quads separately.
//
if (_commonFaceSize == 3) return;
Index vCorner = fvIndices[0];
int numFaces = GetNumFaces();
if (_commonFaceSize == 4) {
Index const * fvOpposite = fvIndices + 2;
for (int face = 0; face < numFaces; ++face, fvOpposite += 4) {
if (*fvOpposite == vCorner) {
edges[feEdges[2*face ]].SetDuplicate();
edges[feEdges[2*face+1]].SetDuplicate();
}
}
} else {
Index const * fv = fvIndices;
for (int face = 0; face < numFaces; ++face) {
int faceSize = GetFaceSize(face);
if (faceSize == 4) {
if (fv[2] == vCorner) {
edges[feEdges[2*face ]].SetDuplicate();
edges[feEdges[2*face+1]].SetDuplicate();
}
} else {
for (int j = 2; j < (faceSize - 2); ++j) {
if (fv[j] == vCorner) {
if (fv[j-1] == fv[1])
edges[feEdges[2*face ]].SetDuplicate();
if (fv[j+1] == fv[faceSize-1])
edges[feEdges[2*face+1]].SetDuplicate();
}
}
}
fv += faceSize;
}
}
}
void
FaceVertex::assignUnOrderedFaceNeighbors(Edge const edges[],
short const feEdges[]) {
int numFaceEdges = 2 * GetNumFaces();
for (int i = 0; i < numFaceEdges; ++i) {
assert(feEdges[i] >= 0);
Edge const & E = edges[feEdges[i]];
bool edgeIsSingular = E.nonManifold || E.boundary;
if (edgeIsSingular) {
_faceEdgeNeighbors[i] = -1;
} else {
_faceEdgeNeighbors[i] = (i & 1) ? E.nextFace : E.prevFace;
}
}
}
void
FaceVertex::finalizeUnOrderedTags(Edge const edges[], int numEdges,
Index const fvIndices[]) {
//
// Summarize properties of the corner given the number and nature of
// the edges around its vertex and initialize remaining members or
// tags that depend on them.
//
// First, take inventory of relevant properties from the edges:
//
int numNonManifoldEdges = 0;
int numInfSharpEdges = 0;
int numSemiSharpEdges = 0;
int numSingularEdges = 0;
bool hasBoundaryEdges = false;
bool hasBoundaryEdgesNotSharp = false;
bool hasDegenerateEdges = false;
bool hasDuplicateEdges = false;
for (int i = 0; i < numEdges; ++i) {
Edge const & E = edges[i];
if (E.interior) {
numInfSharpEdges += E.infSharp;
numSemiSharpEdges += E.semiSharp;
} else if (E.boundary) {
hasBoundaryEdges = true;
hasBoundaryEdgesNotSharp |= !E.infSharp;
} else {
++ numNonManifoldEdges;
hasDegenerateEdges |= E.degenerate;
hasDuplicateEdges |= E.duplicate;
}
// Singular edges include all that are effectively inf-sharp:
numSingularEdges += E.nonManifold || E.boundary || E.infSharp;
}
//
// Next determine whether manifold or not. Some obvious tests quickly
// indicate if the corner is non-manifold, but ultimately it will be
// necessary to traverse the faces to confirm that they form a single
// connected set (e.g. two cones sharing their apex vertex may appear
// manifold to this point but as two connected sets are non-manifold).
//
bool isNonManifold = false;
bool isNonManifoldCrease = false;
if (numNonManifoldEdges) {
isNonManifold = true;
if (!hasDegenerateEdges && !hasDuplicateEdges && !hasBoundaryEdges) {
// Special crease case that avoids sharpening: two interior
// non-manifold edges radiating more than two sets of faces.
// This requires closer inspection to confirm, so make use of
// as many pre-conditions available here to avoid doing so:
isNonManifoldCrease = !_isExpInfSharp &&
(numNonManifoldEdges == 2) && (GetNumFaces() > numEdges) &&
testNonManifoldCrease(edges, numEdges, fvIndices);
}
} else {
// Mismatch between number of incident faces and edges:
isNonManifold = ((numEdges - GetNumFaces()) != (int)hasBoundaryEdges);
if (!isNonManifold) {
// If all faces are not connected, the set is non-manifold:
Subset subset;
int numFacesInSubset = findConnectedSubsetExtent(&subset);
if (numFacesInSubset < GetNumFaces()) {
isNonManifold = true;
}
}
}
//
// Assign tags and other members related to the inventory of edges
// (boundary status is relevant if non-manifold as it can affect
// the presence of the limit surface):
//
_tag._nonManifoldVerts = isNonManifold;
_tag._boundaryVerts = hasBoundaryEdges;
_tag._boundaryNonSharp = hasBoundaryEdgesNotSharp;
_tag._infSharpEdges = (numInfSharpEdges > 0);
_tag._semiSharpEdges = (numSemiSharpEdges > 0);
_tag._infSharpDarts = (numInfSharpEdges == 1) && !hasBoundaryEdges;
// Conditions effectively making the vertex sharp, include the usual
// excess of inf-sharp edges plus some non-manifold cases:
if ((numSingularEdges > 2) || (isNonManifold && !isNonManifoldCrease)) {
_isImpInfSharp = true;
} else if ((numSingularEdges + numSemiSharpEdges) > 2) {
_isImpSemiSharp = true;
}
// Mark the vertex inf-sharp if implicitly inf-sharp:
if (!_isExpInfSharp && _isImpInfSharp) {
_tag._infSharpVerts = true;
_tag._semiSharpVerts = false;
}
}
bool
FaceVertex::testNonManifoldCrease(Edge const edges[], int numEdges,
Index const fvIndices[]) const {
//
// Local struct that keeps track of face-corners remaining around
// a vertex as those visited as part of connected manifold subsets
// are removed during inspection:
//
struct FaceCornerArray {
typedef Vtr::internal::StackBuffer<int,32,true> MemberArray;
typedef Vtr::ConstArray<int> SearchArray;
MemberArray leading;
MemberArray trailing;
int size;
FaceCornerArray(int n) : leading(n), trailing(n), size(n) { }
int FindLeading(int vertex) const {
return SearchArray(leading, size).FindIndex(vertex);
}
int FindTrailing(int vertex) const {
return SearchArray(trailing, size).FindIndex(vertex);
}
void RemoveFace(int index) {
std::swap(leading[index], leading[size - 1]);
std::swap(trailing[index], trailing[size - 1]);
-- size;
}
int RemoveManifoldSubset(int startVertex, int endVertex) {
assert(startVertex != endVertex);
for (int nextVertex = startVertex; nextVertex != endVertex; ) {
int faceIndex = FindLeading(nextVertex);
if (faceIndex < 0) return (nextVertex == startVertex) ? 0 : -1;
nextVertex = trailing[faceIndex];
if (nextVertex == startVertex) return -1;
RemoveFace(faceIndex);
}
return 1;
}
};
//
// Identify the vertices at the ends of the two non-manifold edges
// of the potential crease:
//
int creaseEnd[2] = { -1, -1 };
for (int i = 0; i < numEdges; ++i) {
if (edges[i].nonManifold) {
creaseEnd[creaseEnd[0] >= 0] = edges[i].endVertex;
}
}
assert((creaseEnd[0] >= 0) && (creaseEnd[1] >= 0));
//
// Use the locally defined set of face-edge pairs to determine if
// the collection of faces around the vertex forms a non-manifold
// crease.
//
// First initialize the FaceCornerArray from the face vertices:
//
int numFaces = GetNumFaces();
FaceCornerArray faceCorners(numFaces);
for (int i = 0; i < numFaces; ++i) {
faceCorners.leading[i] = GetFaceIndexLeading( i, fvIndices);
faceCorners.trailing[i] = GetFaceIndexTrailing(i, fvIndices);
}
// Remove faces for manifold subsets in one direction:
int removed = 0;
do {
removed = faceCorners.RemoveManifoldSubset(creaseEnd[0], creaseEnd[1]);
if (removed < 0) {
return false;
}
} while (removed);
if (faceCorners.size == 0) return true;
// Remove faces for manifold subsets in the other direction:
do {
removed = faceCorners.RemoveManifoldSubset(creaseEnd[1], creaseEnd[0]);
if (removed < 0) {
return false;
}
} while (removed);
return (faceCorners.size == 0);
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv

View File

@@ -0,0 +1,382 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_FACE_VERTEX_H
#define OPENSUBDIV3_BFR_FACE_VERTEX_H
#include "../version.h"
#include "../bfr/vertexTag.h"
#include "../bfr/vertexDescriptor.h"
#include "../bfr/faceVertexSubset.h"
#include "../bfr/surfaceData.h"
#include "../sdc/crease.h"
#include <cassert>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// The FaceVertex class is the primary internal class for gathering all
// topological information around the corner of a face. As such, it
// wraps an instance of the public VertexDescriptor class populated by the
// Factory subclasses. It extends an instance of VertexDescriptor with
// additional topological information and methods to make it more widely
// available and useful to internal classes.
//
// One fundamental extension of FaceVertex is that it includes the
// location of the face in the ring of incident faces around the vertex.
// VertexDescriptor alone simply specifies the neighborhood of the
// vertex, but the FaceVertex provides context relative to the face for
// which all of this information is being gathered.
//
// Several instances of FaceVertex (one for each corner of a face)
// are necessary to fully define the limit surface for a face, but in
// many cases, only a subset of the FaceVertex's incident faces will
// actually contribute to the surface. A companion class for defining
// that subset is defined elsewhere.
//
class FaceVertex {
public:
typedef internal::SurfaceData::Index Index;
public:
FaceVertex() { }
~FaceVertex() { }
// Methods supporting construction/initialization (subclass required
// to populate VertexDescriptor between Initialize and Finalize):
void Initialize(int faceSize, int regFaceSize);
void Finalize(int faceInVertex);
VertexDescriptor & GetVertexDescriptor() { return _vDesc; }
void ConnectUnOrderedFaces(Index const faceVertexIndices[]);
public:
// Methods to initialize and find subsets:
typedef FaceVertexSubset Subset;
int GetVertexSubset(Subset * subset) const;
int FindFaceVaryingSubset(Subset * fvarSubset,
Index const fvarIndices[],
Subset const & vtxSubset) const;
// Methods to control sharpness of the corner in a subset:
void SharpenSubset(Subset * subset) const;
void SharpenSubset(Subset * subset, float sharpness) const;
void UnSharpenSubset(Subset * subset) const;
public:
//
// Public methods to query simple properties:
//
VertexTag GetTag() const { return _tag; }
int GetFace() const { return _faceInRing; }
int GetNumFaces() const { return _vDesc._numFaces; }
int GetNumFaceVertices() const { return _numFaceVerts; }
bool HasCommonFaceSize() const { return (_commonFaceSize > 0); }
int GetCommonFaceSize() const { return _commonFaceSize; }
public:
//
// Public methods to inspect incident faces and connected neighbors:
//
int GetFaceSize(int face) const;
// Get neighbors of a specific face (return -1 if unconnected):
int GetFaceNext( int face) const;
int GetFacePrevious(int face) const;
// Find faces relative to this face (require a known safe step size):
int GetFaceAfter (int stepForwardFromCornerFace) const;
int GetFaceBefore(int stepBackwardFromCornerFace) const;
// Get first and last faces of a subset:
int GetFaceFirst(Subset const & subset) const;
int GetFaceLast( Subset const & subset) const;
public:
//
// Public methods to access indices assigned to incident faces:
//
int GetFaceIndexOffset(int face) const;
Index GetFaceIndexAtCorner(Index const indices[]) const;
Index GetFaceIndexAtCorner(int face, Index const indices[]) const;
Index GetFaceIndexTrailing(int face, Index const indices[]) const;
Index GetFaceIndexLeading( int face, Index const indices[]) const;
bool FaceIndicesMatchAtCorner( int f1, int f2, Index const indices[])const;
bool FaceIndicesMatchAtEdgeEnd( int f1, int f2, Index const indices[])const;
bool FaceIndicesMatchAcrossEdge(int f1, int f2, Index const indices[])const;
public:
//
// Public methods for sharpness of the vertex or its incident edges:
//
float GetVertexSharpness() const;
float GetFaceEdgeSharpness(int faceEdge) const;
float GetFaceEdgeSharpness(int face, bool trailingEdge) const;
bool IsFaceEdgeSharp( int face, bool trailingEdge) const;
bool IsFaceEdgeInfSharp( int face, bool trailingEdge) const;
bool IsFaceEdgeSemiSharp(int face, bool trailingEdge) const;
bool HasImplicitVertexSharpness() const;
float GetImplicitVertexSharpness() const;
private:
// Internal convenience methods:
bool isOrdered() const { return _tag.IsOrdered(); }
bool isUnOrdered() const { return _tag.IsUnOrdered(); }
bool isBoundary() const { return _tag.IsBoundary(); }
bool isInterior() const { return _tag.IsInterior(); }
bool isManifold() const { return _tag.IsManifold(); }
int getConnectedFaceNext(int face) const;
int getConnectedFacePrev(int face) const;
private:
// Internal methods for assembling and managing subsets:
int initCompleteSubset(Subset * subset) const;
int findConnectedSubsetExtent(Subset * subset) const;
int findFVarSubsetExtent(Subset const & vtxSubset,
Subset * fvarSubset,
Index const fvarIndices[]) const;
void adjustSubsetTags(Subset * subset,
Subset const * superset = 0) const;
bool subsetHasInfSharpEdges( Subset const & subset) const;
bool subsetHasSemiSharpEdges(Subset const & subset) const;
bool subsetHasIrregularFaces(Subset const & subset) const;
private:
// Internal methods to connect a set of unordered faces (given their
// associated face-vertex indices) and assess the resulting topology:
struct Edge;
int createUnOrderedEdges(Edge edges[],
short faceEdgeIndices[],
Index const faceVertIndices[]) const;
void markDuplicateEdges(Edge edges[],
short const faceEdgeIndices[],
Index const faceVertIndices[]) const;
void assignUnOrderedFaceNeighbors(Edge const edges[],
short const faceEdgeIndices[]);
bool testNonManifoldCrease(Edge const edges[], int numEdges,
Index const faceVertIndices[]) const;
void finalizeUnOrderedTags(Edge const edges[], int numEdges,
Index const faceVertIndices[]);
// Ordered counterpart to the above method for finalizing tags
void finalizeOrderedTags();
private:
typedef Vtr::internal::StackBuffer<short,16,true> ShortBuffer;
// Private members:
VertexDescriptor _vDesc;
VertexTag _tag;
short _faceInRing;
short _commonFaceSize;
unsigned char _regFaceSize;
unsigned char _isExpInfSharp : 1;
unsigned char _isExpSemiSharp : 1;
unsigned char _isImpInfSharp : 1;
unsigned char _isImpSemiSharp : 1;
int _numFaceVerts;
ShortBuffer _faceEdgeNeighbors;
};
//
// Inline methods for inspecting/traversing incident faces of the vertex:
//
inline int
FaceVertex::GetFaceSize(int face) const {
return _commonFaceSize ? _commonFaceSize :
(_vDesc._faceSizeOffsets[face+1] - _vDesc._faceSizeOffsets[face]);
}
inline int
FaceVertex::getConnectedFaceNext(int face) const {
return _faceEdgeNeighbors[2*face + 1];
}
inline int
FaceVertex::getConnectedFacePrev(int face) const {
return _faceEdgeNeighbors[2*face];
}
inline int
FaceVertex::GetFaceNext(int face) const {
if (isUnOrdered()) {
return getConnectedFaceNext(face);
} else if (face < (_vDesc._numFaces - 1)) {
return face + 1;
} else {
return isBoundary() ? -1 : 0;
}
}
inline int
FaceVertex::GetFacePrevious(int face) const {
if (isUnOrdered()) {
return getConnectedFacePrev(face);
} else if (face) {
return face - 1;
} else {
return isBoundary() ? -1 : (_vDesc._numFaces - 1);
}
}
inline int
FaceVertex::GetFaceAfter(int step) const {
assert(step >= 0);
if (isOrdered()) {
return (_faceInRing + step) % _vDesc._numFaces;
} else if (step == 1) {
return getConnectedFaceNext(_faceInRing);
} else if (step == 2) {
return getConnectedFaceNext(getConnectedFaceNext(_faceInRing));
} else {
int face = _faceInRing;
for ( ; step > 0; --step) {
face = getConnectedFaceNext(face);
}
return face;
}
}
inline int
FaceVertex::GetFaceBefore(int step) const {
assert(step >= 0);
if (isOrdered()) {
return (_faceInRing - step + _vDesc._numFaces) % _vDesc._numFaces;
} else if (step == 1) {
return getConnectedFacePrev(_faceInRing);
} else if (step == 2) {
return getConnectedFacePrev(getConnectedFacePrev(_faceInRing));
} else {
int face = _faceInRing;
for ( ; step > 0; --step) {
face = getConnectedFacePrev(face);
}
return face;
}
}
inline int
FaceVertex::GetFaceFirst(Subset const & subset) const {
return GetFaceBefore(subset._numFacesBefore);
}
inline int
FaceVertex::GetFaceLast(Subset const & subset) const {
return GetFaceAfter(subset._numFacesAfter);
}
//
// Inline methods for accessing indices associated with incident faces:
//
inline int
FaceVertex::GetFaceIndexOffset(int face) const {
return _commonFaceSize ? (face * _commonFaceSize) :
_vDesc._faceSizeOffsets[face];
}
inline FaceVertex::Index
FaceVertex::GetFaceIndexAtCorner(Index const indices[]) const {
return indices[GetFaceIndexOffset(_faceInRing)];
}
inline FaceVertex::Index
FaceVertex::GetFaceIndexAtCorner(int face, Index const indices[]) const {
return indices[GetFaceIndexOffset(face)];
}
inline FaceVertex::Index
FaceVertex::GetFaceIndexLeading(int face, Index const indices[]) const {
return indices[GetFaceIndexOffset(face) + 1];
}
inline FaceVertex::Index
FaceVertex::GetFaceIndexTrailing(int face, Index const indices[]) const {
// It is safe to use "face+1" here for the last face:
return indices[GetFaceIndexOffset(face+1) - 1];
}
inline bool
FaceVertex::FaceIndicesMatchAtCorner(int facePrev, int faceNext,
Index const indices[]) const {
return GetFaceIndexAtCorner(facePrev, indices) ==
GetFaceIndexAtCorner(faceNext, indices);
}
inline bool
FaceVertex::FaceIndicesMatchAtEdgeEnd(int facePrev, int faceNext,
Index const indices[]) const {
return GetFaceIndexTrailing(facePrev, indices) ==
GetFaceIndexLeading(faceNext, indices);
}
inline bool
FaceVertex::FaceIndicesMatchAcrossEdge(int facePrev, int faceNext,
Index const indices[]) const {
return FaceIndicesMatchAtCorner (facePrev, faceNext, indices) &&
FaceIndicesMatchAtEdgeEnd(facePrev, faceNext, indices);
}
//
// Inline methods for accessing vertex and edge sharpness:
//
inline float
FaceVertex::GetVertexSharpness() const {
return _vDesc._vertSharpness;
}
inline float
FaceVertex::GetFaceEdgeSharpness(int faceEdge) const {
return _vDesc._faceEdgeSharpness[faceEdge];
}
inline float
FaceVertex::GetFaceEdgeSharpness(int face, bool trailing) const {
return _vDesc._faceEdgeSharpness[face*2 + trailing];
}
inline bool
FaceVertex::IsFaceEdgeSharp(int face, bool trailing) const {
return Sdc::Crease::IsSharp(_vDesc._faceEdgeSharpness[face*2+trailing]);
}
inline bool
FaceVertex::IsFaceEdgeInfSharp(int face, bool trailing) const {
return Sdc::Crease::IsInfinite(_vDesc._faceEdgeSharpness[face*2+trailing]);
}
inline bool
FaceVertex::IsFaceEdgeSemiSharp(int face, bool trailing) const {
return Sdc::Crease::IsSemiSharp(_vDesc._faceEdgeSharpness[face*2+trailing]);
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_FACE_VERTEX_H */

View File

@@ -0,0 +1,81 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_FACE_VERTEX_SUBSET_H
#define OPENSUBDIV3_BFR_FACE_VERTEX_SUBSET_H
#include "../version.h"
#include "../bfr/vertexTag.h"
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// FaceVertexSubset is a simple struct and companion of FaceVertex that
// identifies a subset of the topology around a corner. Such subsets are
// what ultimately define the limit surface around a face and so are used
// by higher level classes in conjunction with FaceVertex.
//
// WIP - this is simple enough to warrant a nested class in FaceVertex
// - it serves no purpose without a FaceVertex and the FaceVertex
// class has several methods to initialize/modify FaceVertexSubsets
//
struct FaceVertexSubset {
FaceVertexSubset() { }
void Initialize(VertexTag tag) {
_tag = tag;
_numFacesBefore = 0;
_numFacesAfter = 0;
_numFacesTotal = 1;
_localSharpness = 0.0f;
}
// Queries consistent with other classes:
VertexTag GetTag() const { return _tag; }
int GetNumFaces() const { return _numFacesTotal; }
// Simple get/set methods to avoid the tedious syntax of the tag:
bool IsBoundary() const { return _tag._boundaryVerts; }
bool IsSharp() const { return _tag._infSharpVerts; }
void SetBoundary(bool on) { _tag._boundaryVerts = on; }
void SetSharp(bool on) { _tag._infSharpVerts = on; }
// Methods comparing to a superset (not any arbitrary subset):
bool ExtentMatchesSuperset(FaceVertexSubset const & sup) const {
return (GetNumFaces() == sup.GetNumFaces()) &&
(IsBoundary() == sup.IsBoundary());
}
bool ShapeMatchesSuperset(FaceVertexSubset const & sup) const {
return ExtentMatchesSuperset(sup) &&
(IsSharp() == sup.IsSharp());
}
// Member tags containing boundary and sharp bits:
VertexTag _tag;
// Members defining the extent of the subset:
short _numFacesBefore;
short _numFacesAfter;
short _numFacesTotal;
// Member to override vertex sharpness (rarely used):
float _localSharpness;
};
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_FACE_VERTEX_SUBSET_H */

View File

@@ -0,0 +1,691 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//
// Functions for hashing to 32- and 64-bit integers. These are the same
// functions used in USD (pxr/base/arch/hash.*) under similar conditions.
//
#include "hash.h"
//
// SpookyHash: a 128-bit noncryptographic hash function
// By Bob Jenkins, public domain
// Oct 31 2010: alpha, framework + SpookyHash::Mix appears right
// Oct 31 2011: alpha again, Mix only good to 2^^69 but rest appears right
// Dec 31 2011: beta, improved Mix, tested it for 2-bit deltas
// Feb 2 2012: production, same bits as beta
// Feb 5 2012: adjusted definitions of uint* to be more portable
// Mar 30 2012: 3 bytes/cycle, not 4. Alpha was 4 but wasn't thorough enough.
// August 5 2012: SpookyV2 (different results)
//
// Up to 3 bytes/cycle for long messages. Reasonably fast for short messages.
// All 1 or 2 bit deltas achieve avalanche within 1% bias per output bit.
//
// This was developed for and tested on 64-bit x86-compatible processors.
// It assumes the processor is little-endian. There is a macro
// controlling whether unaligned reads are allowed (by default they are).
// This should be an equally good hash on big-endian machines, but it will
// compute different results on them than on little-endian machines.
//
// Google's CityHash has similar specs to SpookyHash, and CityHash is faster
// on new Intel boxes. MD4 and MD5 also have similar specs, but they are orders
// of magnitude slower. CRCs are two or more times slower, but unlike
// SpookyHash, they have nice math for combining the CRCs of pieces to form
// the CRCs of wholes. There are also cryptographic hashes, but those are even
// slower than MD5.
//
#include <cstring>
#include <cstddef>
// Local typedefs to match the original code:
typedef uint64_t uint64;
typedef uint32_t uint32;
typedef uint16_t uint16;
typedef uint8_t uint8;
namespace {
class SpookyHash
{
public:
//
// SpookyHash: hash a single message in one call, produce 128-bit output
//
static void Hash128(
const void *message, // message to hash
size_t length, // length of message in bytes
uint64 *hash1, // in/out: in seed 1, out hash value 1
uint64 *hash2); // in/out: in seed 2, out hash value 2
//
// Hash64: hash a single message in one call, return 64-bit output
//
static uint64 Hash64(
const void *message, // message to hash
size_t length, // length of message in bytes
uint64 seed) // seed
{
uint64 hash1 = seed;
Hash128(message, length, &hash1, &seed);
return hash1;
}
//
// Hash32: hash a single message in one call, produce 32-bit output
//
static uint32 Hash32(
const void *message, // message to hash
size_t length, // length of message in bytes
uint32 seed) // seed
{
uint64 hash1 = seed, hash2 = seed;
Hash128(message, length, &hash1, &hash2);
return (uint32)hash1;
}
#ifdef OPENSUBDIV3_BFR_HASH_INCLUDE_UNUSED_FUNCTIONS
//
// Init: initialize the context of a SpookyHash
//
void Init(
uint64 seed1, // any 64-bit value will do, including 0
uint64 seed2); // different seeds produce independent hashes
//
// Update: add a piece of a message to a SpookyHash state
//
void Update(
const void *message, // message fragment
size_t length); // length of message fragment in bytes
//
// Final: compute the hash for the current SpookyHash state
//
// This does not modify the state; you can keep updating it afterward
//
// The result is the same as if SpookyHash() had been called with
// all the pieces concatenated into one message.
//
void Final(
uint64 *hash1, // out only: first 64 bits of hash value.
uint64 *hash2); // out only: second 64 bits of hash value.
#endif
//
// left rotate a 64-bit value by k bytes
//
static inline uint64 Rot64(uint64 x, int k)
{
return (x << k) | (x >> (64 - k));
}
//
// This is used if the input is 96 bytes long or longer.
//
// The internal state is fully overwritten every 96 bytes.
// Every input bit appears to cause at least 128 bits of entropy
// before 96 other bytes are combined, when run forward or backward
// For every input bit,
// Two inputs differing in just that input bit
// Where "differ" means xor or subtraction
// And the base value is random
// When run forward or backwards one Mix
// I tried 3 pairs of each; they all differed by at least 212 bits.
//
static inline void Mix(
const uint64 *data,
uint64 &s0, uint64 &s1, uint64 &s2, uint64 &s3,
uint64 &s4, uint64 &s5, uint64 &s6, uint64 &s7,
uint64 &s8, uint64 &s9, uint64 &s10,uint64 &s11)
{
s0 += data[0]; s2 ^= s10; s11 ^= s0; s0 = Rot64(s0,11); s11 += s1;
s1 += data[1]; s3 ^= s11; s0 ^= s1; s1 = Rot64(s1,32); s0 += s2;
s2 += data[2]; s4 ^= s0; s1 ^= s2; s2 = Rot64(s2,43); s1 += s3;
s3 += data[3]; s5 ^= s1; s2 ^= s3; s3 = Rot64(s3,31); s2 += s4;
s4 += data[4]; s6 ^= s2; s3 ^= s4; s4 = Rot64(s4,17); s3 += s5;
s5 += data[5]; s7 ^= s3; s4 ^= s5; s5 = Rot64(s5,28); s4 += s6;
s6 += data[6]; s8 ^= s4; s5 ^= s6; s6 = Rot64(s6,39); s5 += s7;
s7 += data[7]; s9 ^= s5; s6 ^= s7; s7 = Rot64(s7,57); s6 += s8;
s8 += data[8]; s10 ^= s6; s7 ^= s8; s8 = Rot64(s8,55); s7 += s9;
s9 += data[9]; s11 ^= s7; s8 ^= s9; s9 = Rot64(s9,54); s8 += s10;
s10 += data[10]; s0 ^= s8; s9 ^= s10; s10 = Rot64(s10,22); s9 += s11;
s11 += data[11]; s1 ^= s9; s10 ^= s11; s11 = Rot64(s11,46); s10 += s0;
}
//
// Mix all 12 inputs together so that h0, h1 are a hash of them all.
//
// For two inputs differing in just the input bits
// Where "differ" means xor or subtraction
// And the base value is random, or a counting value starting at that bit
// The final result will have each bit of h0, h1 flip
// For every input bit,
// with probability 50 +- .3%
// For every pair of input bits,
// with probability 50 +- 3%
//
// This does not rely on the last Mix() call having already mixed some.
// Two iterations was almost good enough for a 64-bit result, but a
// 128-bit result is reported, so End() does three iterations.
//
static inline void EndPartial(
uint64 &h0, uint64 &h1, uint64 &h2, uint64 &h3,
uint64 &h4, uint64 &h5, uint64 &h6, uint64 &h7,
uint64 &h8, uint64 &h9, uint64 &h10,uint64 &h11)
{
h11+= h1; h2 ^= h11; h1 = Rot64(h1,44);
h0 += h2; h3 ^= h0; h2 = Rot64(h2,15);
h1 += h3; h4 ^= h1; h3 = Rot64(h3,34);
h2 += h4; h5 ^= h2; h4 = Rot64(h4,21);
h3 += h5; h6 ^= h3; h5 = Rot64(h5,38);
h4 += h6; h7 ^= h4; h6 = Rot64(h6,33);
h5 += h7; h8 ^= h5; h7 = Rot64(h7,10);
h6 += h8; h9 ^= h6; h8 = Rot64(h8,13);
h7 += h9; h10^= h7; h9 = Rot64(h9,38);
h8 += h10; h11^= h8; h10= Rot64(h10,53);
h9 += h11; h0 ^= h9; h11= Rot64(h11,42);
h10+= h0; h1 ^= h10; h0 = Rot64(h0,54);
}
static inline void End(
const uint64 *data,
uint64 &h0, uint64 &h1, uint64 &h2, uint64 &h3,
uint64 &h4, uint64 &h5, uint64 &h6, uint64 &h7,
uint64 &h8, uint64 &h9, uint64 &h10,uint64 &h11)
{
h0 += data[0]; h1 += data[1]; h2 += data[2]; h3 += data[3];
h4 += data[4]; h5 += data[5]; h6 += data[6]; h7 += data[7];
h8 += data[8]; h9 += data[9]; h10 += data[10]; h11 += data[11];
EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
}
//
// The goal is for each bit of the input to expand into 128 bits of
// apparent entropy before it is fully overwritten.
// n trials both set and cleared at least m bits of h0 h1 h2 h3
// n: 2 m: 29
// n: 3 m: 46
// n: 4 m: 57
// n: 5 m: 107
// n: 6 m: 146
// n: 7 m: 152
// when run forwards or backwards
// for all 1-bit and 2-bit diffs
// with diffs defined by either xor or subtraction
// with a base of all zeros plus a counter, or plus another bit, or random
//
static inline void ShortMix(uint64 &h0, uint64 &h1, uint64 &h2, uint64 &h3)
{
h2 = Rot64(h2,50); h2 += h3; h0 ^= h2;
h3 = Rot64(h3,52); h3 += h0; h1 ^= h3;
h0 = Rot64(h0,30); h0 += h1; h2 ^= h0;
h1 = Rot64(h1,41); h1 += h2; h3 ^= h1;
h2 = Rot64(h2,54); h2 += h3; h0 ^= h2;
h3 = Rot64(h3,48); h3 += h0; h1 ^= h3;
h0 = Rot64(h0,38); h0 += h1; h2 ^= h0;
h1 = Rot64(h1,37); h1 += h2; h3 ^= h1;
h2 = Rot64(h2,62); h2 += h3; h0 ^= h2;
h3 = Rot64(h3,34); h3 += h0; h1 ^= h3;
h0 = Rot64(h0,5); h0 += h1; h2 ^= h0;
h1 = Rot64(h1,36); h1 += h2; h3 ^= h1;
}
//
// Mix all 4 inputs together so that h0, h1 are a hash of them all.
//
// For two inputs differing in just the input bits
// Where "differ" means xor or subtraction
// And the base value is random, or a counting value starting at that bit
// The final result will have each bit of h0, h1 flip
// For every input bit,
// with probability 50 +- .3% (it is probably better than that)
// For every pair of input bits,
// with probability 50 +- .75% (the worst case is approximately that)
//
static inline void ShortEnd(uint64 &h0, uint64 &h1, uint64 &h2, uint64 &h3)
{
h3 ^= h2; h2 = Rot64(h2,15); h3 += h2;
h0 ^= h3; h3 = Rot64(h3,52); h0 += h3;
h1 ^= h0; h0 = Rot64(h0,26); h1 += h0;
h2 ^= h1; h1 = Rot64(h1,51); h2 += h1;
h3 ^= h2; h2 = Rot64(h2,28); h3 += h2;
h0 ^= h3; h3 = Rot64(h3,9); h0 += h3;
h1 ^= h0; h0 = Rot64(h0,47); h1 += h0;
h2 ^= h1; h1 = Rot64(h1,54); h2 += h1;
h3 ^= h2; h2 = Rot64(h2,32); h3 += h2;
h0 ^= h3; h3 = Rot64(h3,25); h0 += h3;
h1 ^= h0; h0 = Rot64(h0,63); h1 += h0;
}
private:
//
// Short is used for messages under 192 bytes in length
// Short has a low startup cost, the normal mode is good for long
// keys, the cost crossover is at about 192 bytes. The two modes were
// held to the same quality bar.
//
static void Short(
const void *message, // message (array of bytes, not necessarily
// aligned)
size_t length, // length of message (in bytes)
uint64 *hash1, // in/out: in the seed, out the hash value
uint64 *hash2); // in/out: in the seed, out the hash value
// number of uint64's in internal state
static const size_t sc_numVars = 12;
// size of the internal state
static const size_t sc_blockSize = sc_numVars*8;
// size of buffer of unhashed data, in bytes
static const size_t sc_bufSize = 2*sc_blockSize;
//
// sc_const: a constant which:
// * is not zero
// * is odd
// * is a not-very-regular mix of 1's and 0's
// * does not need any other special mathematical properties
//
static const uint64 sc_const = 0xdeadbeefdeadbeefLL;
#ifdef OPENSUBDIV3_BFR_HASH_INCLUDE_UNUSED_FUNCTIONS
uint64 m_data[2*sc_numVars]; // unhashed data, for partial messages
uint64 m_state[sc_numVars]; // internal state of the hash
size_t m_length; // total length of the input so far
uint8 m_remainder; // length of unhashed data stashed in m_data
#endif
};
#define ALLOW_UNALIGNED_READS 1
//
// short hash ... it could be used on any message,
// but it's used by Spooky just for short messages.
//
void SpookyHash::Short(
const void *message,
size_t length,
uint64 *hash1,
uint64 *hash2)
{
uint64 buf[2*sc_numVars];
union
{
const uint8 *p8;
uint32 *p32;
uint64 *p64;
size_t i;
} u;
u.p8 = (const uint8 *)message;
if (!ALLOW_UNALIGNED_READS && (u.i & 0x7))
{
memcpy(buf, message, length);
u.p64 = buf;
}
size_t remainder = length%32;
uint64 a=*hash1;
uint64 b=*hash2;
uint64 c=sc_const;
uint64 d=sc_const;
if (length > 15)
{
const uint64 *end = u.p64 + (length/32)*4;
// handle all complete sets of 32 bytes
for (; u.p64 < end; u.p64 += 4)
{
c += u.p64[0];
d += u.p64[1];
ShortMix(a,b,c,d);
a += u.p64[2];
b += u.p64[3];
}
//Handle the case of 16+ remaining bytes.
if (remainder >= 16)
{
c += u.p64[0];
d += u.p64[1];
ShortMix(a,b,c,d);
u.p64 += 2;
remainder -= 16;
}
}
// Handle the last 0..15 bytes, and its length
d += ((uint64)length) << 56;
switch (remainder)
{
case 15:
d += ((uint64)u.p8[14]) << 48;
// FALLTHRU
case 14:
d += ((uint64)u.p8[13]) << 40;
// FALLTHRU
case 13:
d += ((uint64)u.p8[12]) << 32;
// FALLTHRU
case 12:
d += u.p32[2];
c += u.p64[0];
break;
case 11:
d += ((uint64)u.p8[10]) << 16;
// FALLTHRU
case 10:
d += ((uint64)u.p8[9]) << 8;
// FALLTHRU
case 9:
d += (uint64)u.p8[8];
// FALLTHRU
case 8:
c += u.p64[0];
break;
case 7:
c += ((uint64)u.p8[6]) << 48;
// FALLTHRU
case 6:
c += ((uint64)u.p8[5]) << 40;
// FALLTHRU
case 5:
c += ((uint64)u.p8[4]) << 32;
// FALLTHRU
case 4:
c += u.p32[0];
break;
case 3:
c += ((uint64)u.p8[2]) << 16;
// FALLTHRU
case 2:
c += ((uint64)u.p8[1]) << 8;
// FALLTHRU
case 1:
c += (uint64)u.p8[0];
break;
case 0:
c += sc_const;
d += sc_const;
}
ShortEnd(a,b,c,d);
*hash1 = a;
*hash2 = b;
}
// do the whole hash in one call
void SpookyHash::Hash128(
const void *message,
size_t length,
uint64 *hash1,
uint64 *hash2)
{
if (length < sc_bufSize)
{
Short(message, length, hash1, hash2);
return;
}
uint64 h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11;
uint64 buf[sc_numVars];
uint64 *end;
union
{
const uint8 *p8;
uint64 *p64;
size_t i;
} u;
size_t remainder;
h0=h3=h6=h9 = *hash1;
h1=h4=h7=h10 = *hash2;
h2=h5=h8=h11 = sc_const;
u.p8 = (const uint8 *)message;
end = u.p64 + (length/sc_blockSize)*sc_numVars;
// handle all whole sc_blockSize blocks of bytes
if (ALLOW_UNALIGNED_READS || ((u.i & 0x7) == 0))
{
while (u.p64 < end)
{
Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
u.p64 += sc_numVars;
}
}
else
{
while (u.p64 < end)
{
memcpy(buf, u.p64, sc_blockSize);
Mix(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
u.p64 += sc_numVars;
}
}
// handle the last partial block of sc_blockSize bytes
remainder = (length - ((const uint8 *)end-(const uint8 *)message));
memcpy(buf, end, remainder);
memset(((uint8 *)buf)+remainder, 0, sc_blockSize-remainder);
((uint8 *)buf)[sc_blockSize-1] = static_cast<uint8>(remainder);
// do some final mixing
End(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
*hash1 = h0;
*hash2 = h1;
}
#ifdef OPENSUBDIV3_BFR_HASH_INCLUDE_UNUSED_FUNCTIONS
// init spooky state
void SpookyHash::Init(uint64 seed1, uint64 seed2)
{
m_length = 0;
m_remainder = 0;
m_state[0] = seed1;
m_state[1] = seed2;
}
// add a message fragment to the state
void SpookyHash::Update(const void *message, size_t length)
{
uint64 h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11;
size_t newLength = length + m_remainder;
uint8 remainder;
union
{
const uint8 *p8;
uint64 *p64;
size_t i;
} u;
const uint64 *end;
// Is this message fragment too short? If it is, stuff it away.
if (newLength < sc_bufSize)
{
memcpy(&((uint8 *)m_data)[m_remainder], message, length);
m_length = length + m_length;
m_remainder = (uint8)newLength;
return;
}
// init the variables
if (m_length < sc_bufSize)
{
h0=h3=h6=h9 = m_state[0];
h1=h4=h7=h10 = m_state[1];
h2=h5=h8=h11 = sc_const;
}
else
{
h0 = m_state[0];
h1 = m_state[1];
h2 = m_state[2];
h3 = m_state[3];
h4 = m_state[4];
h5 = m_state[5];
h6 = m_state[6];
h7 = m_state[7];
h8 = m_state[8];
h9 = m_state[9];
h10 = m_state[10];
h11 = m_state[11];
}
m_length = length + m_length;
// if we've got anything stuffed away, use it now
if (m_remainder)
{
uint8 prefix = sc_bufSize-m_remainder;
memcpy(&(((uint8 *)m_data)[m_remainder]), message, prefix);
u.p64 = m_data;
Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
Mix(&u.p64[sc_numVars], h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
u.p8 = ((const uint8 *)message) + prefix;
length -= prefix;
}
else
{
u.p8 = (const uint8 *)message;
}
// handle all whole blocks of sc_blockSize bytes
end = u.p64 + (length/sc_blockSize)*sc_numVars;
remainder = (uint8)(length-((const uint8 *)end-u.p8));
if (ALLOW_UNALIGNED_READS || (u.i & 0x7) == 0)
{
while (u.p64 < end)
{
Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
u.p64 += sc_numVars;
}
}
else
{
while (u.p64 < end)
{
memcpy(m_data, u.p8, sc_blockSize);
Mix(m_data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
u.p64 += sc_numVars;
}
}
// stuff away the last few bytes
m_remainder = remainder;
memcpy(m_data, end, remainder);
// stuff away the variables
m_state[0] = h0;
m_state[1] = h1;
m_state[2] = h2;
m_state[3] = h3;
m_state[4] = h4;
m_state[5] = h5;
m_state[6] = h6;
m_state[7] = h7;
m_state[8] = h8;
m_state[9] = h9;
m_state[10] = h10;
m_state[11] = h11;
}
// report the hash for the concatenation of all message fragments so far
void SpookyHash::Final(uint64 *hash1, uint64 *hash2)
{
// init the variables
if (m_length < sc_bufSize)
{
*hash1 = m_state[0];
*hash2 = m_state[1];
Short( m_data, m_length, hash1, hash2);
return;
}
const uint64 *data = (const uint64 *)m_data;
uint8 remainder = m_remainder;
uint64 h0 = m_state[0];
uint64 h1 = m_state[1];
uint64 h2 = m_state[2];
uint64 h3 = m_state[3];
uint64 h4 = m_state[4];
uint64 h5 = m_state[5];
uint64 h6 = m_state[6];
uint64 h7 = m_state[7];
uint64 h8 = m_state[8];
uint64 h9 = m_state[9];
uint64 h10 = m_state[10];
uint64 h11 = m_state[11];
if (remainder >= sc_blockSize)
{
// m_data can contain two blocks; handle any whole first block
Mix(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
data += sc_numVars;
remainder -= sc_blockSize;
}
// mix in the last partial block, and the length mod sc_blockSize
memset(&((uint8 *)data)[remainder], 0, (sc_blockSize-remainder));
((uint8 *)data)[sc_blockSize-1] = remainder;
// do some final mixing
End(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
*hash1 = h0;
*hash2 = h1;
}
#endif
} // anon
//
// Public functions exposed for OpenSubdiv:
//
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
namespace internal {
uint32_t
Hash32(const void *data, size_t len)
{
return SpookyHash::Hash32(data, len, /*seed=*/0);
}
uint32_t
Hash32(const void *data, size_t len, uint32_t seed)
{
return SpookyHash::Hash32(data, len, seed);
}
uint64_t
Hash64(const void *data, size_t len)
{
return SpookyHash::Hash64(data, len, /*seed=*/0);
}
uint64_t
Hash64(const void *data, size_t len, uint64_t seed)
{
return SpookyHash::Hash64(data, len, seed);
}
} // end namespace internal
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv

View File

@@ -0,0 +1,46 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_HASH_H
#define OPENSUBDIV3_BFR_HASH_H
#include "../version.h"
#include <cstdint>
#include <cstddef>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
namespace internal {
//
// Internal functions to hash data to unsigned ints for caching. Both
// 32- and 64-bit versions are provided here, but only 64-bit versions
// are currently intended for internal use (so consider removing 32).
//
// To compute a hash value for data that is not contiguous in memory,
// iterate over all the contiguous blocks of memory and accumulate the
// hash value by passing it on as a seed. Note that this is *not*
// equivalent to hashing the contiguous pieces as a whole. Support
// for that may be added in future.
//
uint32_t Hash32(const void *data, size_t len);
uint32_t Hash32(const void *data, size_t len, uint32_t seed);
uint64_t Hash64(const void *data, size_t len);
uint64_t Hash64(const void *data, size_t len, uint64_t seed);
} // end namespace internal
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_HASH_H */

View File

@@ -0,0 +1,987 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../bfr/irregularPatchBuilder.h"
#include "../bfr/patchTreeBuilder.h"
#include "../bfr/patchTree.h"
#include "../far/topologyDescriptor.h"
#include "../far/topologyRefiner.h"
#include <cstring>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// Trivial constructor -- initializes members related to the control hull:
//
IrregularPatchBuilder::IrregularPatchBuilder(
FaceSurface const & surfaceDescription, Options const & options) :
_surface(surfaceDescription),
_options(options) {
initializeControlHullInventory();
}
//
// Inline private methods for accessing indices associated with the
// face-vertex topology and indices stored in map or vector members:
//
inline IrregularPatchBuilder::Index const *
IrregularPatchBuilder::getSurfaceIndices() const {
return _surface.GetIndices();
}
inline IrregularPatchBuilder::Index const *
IrregularPatchBuilder::getCornerIndices(int corner) const {
return getSurfaceIndices() +
_cornerHullInfo[corner].surfaceIndicesOffset;
}
inline IrregularPatchBuilder::Index const *
IrregularPatchBuilder::getBaseFaceIndices() const {
FaceVertex const & corner0 = _surface.GetCornerTopology(0);
return getSurfaceIndices() +
corner0.GetFaceIndexOffset(corner0.GetFace());
}
inline IrregularPatchBuilder::Index const *
IrregularPatchBuilder::getCornerFaceIndices(int corner, int face) const {
return getCornerIndices(corner) +
_surface.GetCornerTopology(corner).GetFaceIndexOffset(face);
}
inline int
IrregularPatchBuilder::getLocalControlVertex(Index meshVertIndex) const {
return _controlVertMap.find(meshVertIndex)->second;
}
inline IrregularPatchBuilder::Index
IrregularPatchBuilder::getMeshControlVertex(int localVertIndex) const {
return _controlVerts[localVertIndex];
}
//
// The IrregularPatchBuilder assembles a control hull for the base face
// from the topology information given for each corner of the face. It
// first initializes the number of control vertices and faces required,
// along with the contributions of each from the corners of the face.
//
// Once initialized, iteration over the corners of the base face is
// expected to follow a similar pattern when inspecting the incident
// faces of a corner:
//
// - deal with faces after the base face (skipping the first)
// - deal with boundary vertex between faces after and before
// - deal with faces before the base face (all)
//
// Tags and other inventory assigned here help to expedite and simplify
// those iterations.
//
void
IrregularPatchBuilder::initializeControlHullInventory() {
//
// Iterate through the corners to identify the vertices, faces and
// face-vertices that contribute to the collective control hull --
// keeping track of a few situations that cause complications:
//
int numVal2IntCorners = 0;
int numVal3IntAdjTris = 0;
int numSrcFaceIndices = 0;
int faceSize = _surface.GetFaceSize();
_cornerHullInfo.SetSize(faceSize);
_numControlFaces = 1;
_numControlVerts = faceSize;
_numControlFaceVerts = faceSize;
for (int corner = 0; corner < faceSize; ++corner) {
FaceVertex const & cTop = _surface.GetCornerTopology(corner);
FaceVertexSubset const & cSub = _surface.GetCornerSubset(corner);
//
// Inspect faces after the corner face first -- dealing with a few
// special cases for interior vertices of low valence -- followed
// by those faces before the corner face:
//
CornerHull & cHull = _cornerHullInfo[corner];
cHull.Clear();
int numCornerFaceVerts = 0;
if (cSub._numFacesAfter) {
int nextFace = cTop.GetFaceNext(cTop.GetFace());
if (cSub.IsBoundary()) {
// Boundary -- no special cases:
for (int i = 1; i < cSub._numFacesAfter; ++i) {
nextFace = cTop.GetFaceNext(nextFace);
int S = cTop.GetFaceSize(nextFace);
cHull.numControlVerts += S - 2;
numCornerFaceVerts += S;
}
cHull.numControlFaces = cSub._numFacesAfter - 1;
// Include unshared vertex of trailing edge
cHull.numControlVerts ++;
} else if ((cSub._numFacesTotal == 3) &&
(cTop.GetFaceSize(cTop.GetFaceAfter(2)) == 3)) {
// Interior, valence-3, adjacent triangle -- special case:
if (++numVal3IntAdjTris == faceSize) {
cHull.singleSharedVert = true;
cHull.numControlVerts = 1;
}
cHull.numControlFaces = 1;
numCornerFaceVerts = 3;
} else if (cSub._numFacesTotal > 2) {
// Interior -- general case:
for (int i = 2; i < cSub._numFacesTotal; ++i) {
nextFace = cTop.GetFaceNext(nextFace);
int S = cTop.GetFaceSize(nextFace);
cHull.numControlVerts += S - 2;
numCornerFaceVerts += S;
}
cHull.numControlFaces = cSub._numFacesTotal - 2;
// Exclude vertex shared with/contributed by next corner
cHull.numControlVerts --;
} else {
// Interior, valence-2 -- special case:
if (++numVal2IntCorners == faceSize) {
cHull.singleSharedFace = true;
cHull.numControlFaces = 1;
numCornerFaceVerts = faceSize;
}
cHull.isVal2Interior = true;
}
}
if (cSub._numFacesBefore) {
assert(cSub.IsBoundary());
int nextFace = cTop.GetFaceFirst(cSub);
for (int i = 0; i < cSub._numFacesBefore; ++i) {
int S = cTop.GetFaceSize(nextFace);
nextFace = cTop.GetFaceNext(nextFace);
cHull.numControlVerts += S - 2;
numCornerFaceVerts += S;
}
cHull.numControlFaces += cSub._numFacesBefore;
// Exclude vertex shared with/contributed by next corner
cHull.numControlVerts --;
}
// Assign the contributions for this corner:
cHull.nextControlVert = _numControlVerts;
cHull.surfaceIndicesOffset = numSrcFaceIndices;
_numControlFaces += cHull.numControlFaces;
_numControlVerts += cHull.numControlVerts;
_numControlFaceVerts += numCornerFaceVerts;
numSrcFaceIndices += cTop.GetNumFaceVertices();
}
//
// Use/build a map for the control vertex indices when incident
// faces overlap to an extent that makes traversal ill-defined:
//
// Currently a single val-2 interior vertex is handled without
// the map in most cases. The presence of more than one val-2
// interior vertex leads to convoluted topology traversals that
// are avoided by the use of the map.
//
_controlFacesOverlap = (numVal2IntCorners > 1);
if (numVal2IntCorners == 1) {
//
// Identify and inspect the topology at the val-2 corner to
// see if it warrants handling with the vertex map. Tag the
// neighboring corners accordingly if not:
//
for (int corner = 0; corner < faceSize; ++corner) {
CornerHull & cHull = _cornerHullInfo[corner];
if (cHull.isVal2Interior) {
FaceVertex const & cTop = _surface.GetCornerTopology(corner);
int oppFaceSize = cTop.GetFaceSize(cTop.GetFaceAfter(1));
if (oppFaceSize == 3) {
// All three corners of the opposite face are corners
// of the base face
_controlFacesOverlap = true;
break;
}
if ((oppFaceSize == 4) && (numVal3IntAdjTris == (faceSize-2))) {
// Possible "pyramid" where a single exterior vertex
// must added as a special case
_controlFacesOverlap = true;
break;
}
// Tag the preceding corner of the val-2 corner:
int prevCorner = corner ? (corner - 1) : (faceSize - 1);
_cornerHullInfo[prevCorner].preVal2Interior = true;
break;
}
}
//
// If no significant overlap is present, adjust the control vertex
// counts for those preceding the val-2 corner (decrementing by 1)
// and adjust control vertex offsets for all that follow:
//
if (!_controlFacesOverlap) {
_numControlVerts = faceSize;
for (int corner = 0; corner < faceSize; ++corner) {
CornerHull & cHull = _cornerHullInfo[corner];
cHull.nextControlVert = _numControlVerts;
cHull.numControlVerts -= cHull.preVal2Interior;
_numControlVerts += cHull.numControlVerts;
}
}
}
_useControlVertMap = _controlFacesOverlap;
if (_useControlVertMap) {
initializeControlVertexMap();
}
}
void
IrregularPatchBuilder::addMeshControlVertex(Index meshVertIndex) {
if (_controlVertMap.find(meshVertIndex) == _controlVertMap.end()) {
int newLocalVertIndex = (int) _controlVerts.size();
_controlVertMap[meshVertIndex] = newLocalVertIndex;
_controlVerts.push_back(meshVertIndex);
}
}
void
IrregularPatchBuilder::addMeshControlVertices(Index const fVerts[], int fSize) {
// Ignore the first index of the face, which corresponds to a corner
for (int i = 1; i < fSize; ++i) {
addMeshControlVertex(fVerts[i]);
}
}
void
IrregularPatchBuilder::initializeControlVertexMap() {
//
// Add CV indices from the base face first -- be careful to ensure
// that a vector entry is made for each base face vertex in cases
// when repeated indices may occur:
Index const * baseVerts = getBaseFaceIndices();
int faceSize = _surface.GetFaceSize();
for (int i = 0; i < faceSize; ++i) {
addMeshControlVertex(baseVerts[i]);
if ((int)_controlVerts.size() == i) {
_controlVerts.push_back(baseVerts[i]);
}
}
//
// For each corner, add face-vertices to the map only for those
// incident faces that contribute to the control hull:
//
for (int corner = 0; corner < faceSize; ++corner) {
CornerHull & cHull = _cornerHullInfo[corner];
if (cHull.numControlFaces == 0) continue;
FaceVertex const & cTop = _surface.GetCornerTopology(corner);
FaceVertexSubset const & cSub = _surface.GetCornerSubset(corner);
// Special case of a single shared back-to-back face first:
if (cHull.singleSharedFace) {
int nextFace = cTop.GetFaceAfter(1);
addMeshControlVertices(getCornerFaceIndices(corner, nextFace),
cTop.GetFaceSize(nextFace));
continue;
}
// Follow the common pattern: faces after, boundary, faces before
// (no need to deal with isolated boundary vertex in this case)
if (cSub._numFacesAfter > 1) {
int nextFace = cTop.GetFaceAfter(1);
for (int j = 1; j < cSub._numFacesAfter; ++j) {
nextFace = cTop.GetFaceNext(nextFace);
addMeshControlVertices(getCornerFaceIndices(corner, nextFace),
cTop.GetFaceSize(nextFace));
}
}
if (cSub._numFacesBefore) {
int nextFace = cTop.GetFaceFirst(cSub);
for (int i = 0; i < cSub._numFacesBefore; ++i) {
addMeshControlVertices(getCornerFaceIndices(corner, nextFace),
cTop.GetFaceSize(nextFace));
nextFace = cTop.GetFaceNext(nextFace);
}
}
}
_numControlVerts = (int) _controlVerts.size();
}
//
// Methods for gathering control vertices, faces, sharpness, etc. -- the
// method to gather control vertex indices is for external use, while the
// rest are internal:
//
int
IrregularPatchBuilder::GatherControlVertexIndices(Index cvIndices[]) const {
//
// If a map was built, simply copy the associated vector of indices:
//
if (_useControlVertMap) {
std::memcpy(cvIndices, &_controlVerts[0], _numControlVerts*sizeof(int));
return _numControlVerts;
}
//
// Assign CV indices from the base face first:
//
int faceSize = _surface.GetFaceSize();
int numIndices = faceSize;
std::memcpy(cvIndices, getBaseFaceIndices(), faceSize * sizeof(Index));
//
// Assign vertex indices identified as contributed by each corner:
//
for (int corner = 0; corner < faceSize; ++corner) {
CornerHull const & cHull = _cornerHullInfo[corner];
if (cHull.numControlVerts == 0) continue;
FaceVertex const & cTop = _surface.GetCornerTopology(corner);
FaceVertexSubset const & cSub = _surface.GetCornerSubset(corner);
// Special case with all val-3 interior triangles:
if (cHull.singleSharedVert) {
assert(!cSub.IsBoundary() && (cSub._numFacesTotal == 3) &&
(cTop.GetFaceSize(cTop.GetFaceAfter(2)) == 3));
cvIndices[numIndices++] =
getCornerFaceIndices(corner, cTop.GetFaceAfter(2))[1];
continue;
}
//
// Follow the common pattern: faces after, boundary, faces before
//
if (cSub._numFacesAfter > 1) {
int nextFace = cTop.GetFaceAfter(1);
int N = cSub._numFacesAfter - 1;
for (int j = 0; j < N; ++j) {
nextFace = cTop.GetFaceNext(nextFace);
Index const * faceVerts = getCornerFaceIndices(corner,nextFace);
int S = cTop.GetFaceSize(nextFace);
int L = (j == (N-1)) ? (1 + cHull.preVal2Interior) : 0;
int M = (S - 2) - (cSub.IsBoundary() ? 0 : L);
for (int k = 1; k <= M; ++k) {
cvIndices[numIndices++] = faceVerts[k];
}
}
}
if (cSub._numFacesAfter && cSub.IsBoundary()) {
// Include trailing edge for boundary before crossing the gap:
int nextFace = cTop.GetFaceAfter(cSub._numFacesAfter);
cvIndices[numIndices++] = cTop.GetFaceIndexTrailing(nextFace,
getCornerIndices(corner));
}
if (cSub._numFacesBefore) {
int nextFace = cTop.GetFaceFirst(cSub);
int N = cSub._numFacesBefore;
for (int j = 0; j < N; ++j) {
Index const * faceVerts = getCornerFaceIndices(corner,nextFace);
int S = cTop.GetFaceSize(nextFace);
int L = (j == (N-1)) ? (1 + cHull.preVal2Interior) : 0;
int M = (S - 2) - L;
for (int k = 1; k <= M; ++k) {
cvIndices[numIndices++] = faceVerts[k];
}
nextFace = cTop.GetFaceNext(nextFace);
}
}
}
assert(numIndices == _numControlVerts);
return numIndices;
}
int
IrregularPatchBuilder::gatherControlFaces(int faceSizes[],
int faceVertices[]) const {
//
// Assign face-vertices for the first/base face:
//
int * faceVerts = faceVertices;
int faceSize = _surface.GetFaceSize();
for (int i = 0; i < faceSize; ++i) {
*faceVerts++ = i;
}
*faceSizes++ = faceSize;
//
// Assign face-vertex indices for faces "local to" each corner:
//
for (int corner = 0; corner < faceSize; ++corner) {
CornerHull const & cHull = _cornerHullInfo[corner];
if (cHull.numControlFaces == 0) continue;
FaceVertex const & cTop = _surface.GetCornerTopology(corner);
FaceVertexSubset const & cSub = _surface.GetCornerSubset(corner);
// Special case of a single shared opposing face first:
if (cHull.singleSharedFace) {
assert(_useControlVertMap);
getControlFaceVertices(faceVerts, faceSize, corner,
getCornerFaceIndices(corner, cTop.GetFaceAfter(1)));
*faceSizes++ = faceSize;
faceVerts += faceSize;
continue;
}
//
// Follow the common pattern: faces after, boundary, faces before
//
int nextVert = cHull.nextControlVert;
if (cSub._numFacesAfter > 1) {
int nextFace = cTop.GetFaceAfter(2);
int N = cSub._numFacesAfter - 1;
for (int j = 0; j < N; ++j) {
int S = cTop.GetFaceSize(nextFace);
if (_useControlVertMap) {
getControlFaceVertices(faceVerts, S, corner,
getCornerFaceIndices(corner, nextFace));
} else if (cSub.IsBoundary()) {
getControlFaceVertices(faceVerts, S, corner, nextVert);
} else {
getControlFaceVertices(faceVerts, S, corner, nextVert,
(j == (N-1)), cHull.preVal2Interior);
}
*faceSizes++ = S;
faceVerts += S;
nextVert += S - 2 - (cHull.preVal2Interior && (j == (N-1)));
nextFace = cTop.GetFaceNext(nextFace);
}
}
if (cSub._numFacesAfter && cSub.IsBoundary()) {
nextVert ++;
}
if (cSub._numFacesBefore) {
int nextFace = cTop.GetFaceFirst(cSub);
int N = cSub._numFacesBefore;
for (int j = 0; j < N; ++j) {
int S = cTop.GetFaceSize(nextFace);
if (_useControlVertMap) {
getControlFaceVertices(faceVerts, S, corner,
getCornerFaceIndices(corner, nextFace));
} else {
getControlFaceVertices(faceVerts, S, corner, nextVert,
(j == (N-1)), cHull.preVal2Interior);
}
*faceSizes++ = S;
faceVerts += S;
nextVert += S - 2 - (cHull.preVal2Interior && (j == (N-1)));
nextFace = cTop.GetFaceNext(nextFace);
}
}
}
assert((faceVerts - faceVertices) == _numControlFaceVerts);
return _numControlFaceVerts;
}
int
IrregularPatchBuilder::gatherControlVertexSharpness(
int vertIndices[], float vertSharpness[]) const {
int nSharpVerts = 0;
for (int i = 0; i < _surface.GetFaceSize(); ++i) {
FaceVertexSubset const & cSub = _surface.GetCornerSubset(i);
if (cSub._tag.IsInfSharp()) {
vertSharpness[nSharpVerts] = Sdc::Crease::SHARPNESS_INFINITE;
vertIndices[nSharpVerts++] = i;
} else if (cSub._tag.IsSemiSharp()) {
vertSharpness[nSharpVerts] = (cSub._localSharpness > 0.0f) ?
cSub._localSharpness :
_surface.GetCornerTopology(i).GetVertexSharpness();
vertIndices[nSharpVerts++] = i;
}
}
return nSharpVerts;
}
int
IrregularPatchBuilder::gatherControlEdgeSharpness(
int edgeVertPairs[], float edgeSharpness[]) const {
//
// First test the forward edge of each corner of the face (avoid
// including redundant inf-sharp boundary edges):
//
int nSharpEdges = 0;
int faceSize = _surface.GetFaceSize();
for (int corner = 0; corner < faceSize; ++corner) {
FaceVertexSubset const & cSub = _surface.GetCornerSubset(corner);
if (!cSub._tag.HasSharpEdges()) continue;
if (!cSub.IsBoundary() || cSub._numFacesBefore) {
FaceVertex const & cTop = _surface.GetCornerTopology(corner);
int cornerFace = cTop.GetFace();
float sharpness = cTop.GetFaceEdgeSharpness(cornerFace, 0);
if (Sdc::Crease::IsSharp(sharpness)) {
*edgeSharpness++ = sharpness;
*edgeVertPairs++ = corner;
*edgeVertPairs++ = (corner + 1) % faceSize;
nSharpEdges++;
}
}
}
//
// For each corner, test any interior edges connected to vertices
// on the perimeter:
//
for (int corner = 0; corner < faceSize; ++corner) {
FaceVertexSubset const & cSub = _surface.GetCornerSubset(corner);
if (!cSub._tag.HasSharpEdges()) continue;
CornerHull const & cHull = _cornerHullInfo[corner];
if (cHull.numControlFaces == 0) continue;
FaceVertex const & cTop = _surface.GetCornerTopology(corner);
//
// Inspect interior edges around the subset -- testing sharpness
// of the trailing edge of the faces after/before the corner face.
//
// Follow the common pattern: faces after, boundary, faces before
//
// Track perimeter index to identify verts at end of sharp edges:
int maxVert = _numControlVerts;
int nextVert = cHull.nextControlVert;
Index const * cVerts = getCornerIndices(corner);
if (cSub._numFacesAfter > 1) {
int nextFace = cTop.GetFaceAfter(1);
for (int i = 1; i < cSub._numFacesAfter; ++i) {
float sharpness = cTop.GetFaceEdgeSharpness(nextFace, 1);
if (Sdc::Crease::IsSharp(sharpness)) {
int edgeVert = (nextVert < maxVert) ? nextVert : faceSize;
if (_useControlVertMap) {
edgeVert = getLocalControlVertex(
cTop.GetFaceIndexTrailing(nextFace, cVerts));
}
*edgeSharpness++ = sharpness;
*edgeVertPairs++ = corner;
*edgeVertPairs++ = edgeVert;
nSharpEdges++;
}
nextFace = cTop.GetFaceNext(nextFace);
nextVert += cTop.GetFaceSize(nextFace) - 2;
}
}
if (cSub._numFacesAfter && cSub.IsBoundary()) {
nextVert += cSub.IsBoundary();
}
if (cSub._numFacesBefore) {
int nextFace = cTop.GetFaceFirst(cSub);
for (int i = 1; i < cSub._numFacesBefore; ++i) {
nextVert += cTop.GetFaceSize(nextFace) - 2;
float sharpness = cTop.GetFaceEdgeSharpness(nextFace, 1);
if (Sdc::Crease::IsSharp(sharpness)) {
int edgeVert = (nextVert < maxVert) ? nextVert : faceSize;
if (_useControlVertMap) {
edgeVert = getLocalControlVertex(
cTop.GetFaceIndexTrailing(nextFace, cVerts));
}
*edgeSharpness++ = sharpness;
*edgeVertPairs++ = corner;
*edgeVertPairs++ = edgeVert;
nSharpEdges++;
}
nextFace = cTop.GetFaceNext(nextFace);
}
}
}
return nSharpEdges;
}
//
// Methods to gather the local face-vertices for a particular incident
// face of a corner. The first is a special case that uses the vertex
// map to identify local control vertices from their indices. The second
// is the trivial case -- where all vertices other than the initial
// corner vertex lie on the perimeter and do not wrap around. The third
// handles more of the complications and is used in all cases where the
// trivial method cannot be applied.
//
//
void
IrregularPatchBuilder::getControlFaceVertices(int fVerts[], int numFVerts,
int corner, Index const srcVerts[]) const {
assert(_useControlVertMap);
*fVerts++ = corner;
for (int i = 1; i < numFVerts; ++i) {
*fVerts++ = getLocalControlVertex(srcVerts[i]);
}
}
void
IrregularPatchBuilder::getControlFaceVertices(int fVerts[], int numFVerts,
int corner, int nextPerimeterVert) const {
*fVerts++ = corner;
for (int i = 1; i < numFVerts; ++i) {
*fVerts++ = nextPerimeterVert + i - 1;
}
}
void
IrregularPatchBuilder::getControlFaceVertices(int fVerts[], int numFVerts,
int corner, int nextPerimeterVert, bool lastFace, int numVal2InLast) const {
//
// When identifying local face-vertices for a control face adjacent
// to a corner, care is required to deal with the possibility of
// exterior vertices being shared with control faces associated with
// the next corner vertex -- which becomes an issue when sharing
// between the last and first corner.
//
// After the initial corner face-vertex, the remaining face-vertices
// are identified in three groups:
// - a simple sequence of exterior vertices following the corner,
// which excludes the last two face-vertices where any sharing
// may occur
// - the next-to-last face-vertex -- an exterior vertex which may
// be shared and wrap around the base face
// - the last face vertex -- an exterior vertex which may also be
// shared/wrapped, or an adj corner vertex for the last face
//
int S = numFVerts;
int N = _surface.GetFaceSize();
// Start with the corner vertex:
*fVerts++ = corner;
// The simple sequence of exterior vertices follows the corner:
int numSequentialVerts = S - 2 - 1 - (lastFace ? numVal2InLast : 0);
for (int i = 0; i < numSequentialVerts; ++i) {
*fVerts++ = nextPerimeterVert++;
}
// The next-to-last face-vertex may be shared with the next face
// and so may wrap around the entire face:
int nextToLastPerimOfFace = nextPerimeterVert++;
if (nextToLastPerimOfFace == _numControlVerts) {
nextToLastPerimOfFace = N;
}
*fVerts++ = nextToLastPerimOfFace;
// The last face-vertex may be exterior or the adjacent corner:
if (!lastFace) {
// If exterior, it may also be shared with the next face and
// wrap around the entire face:
int lastPerimOfFace = nextPerimeterVert++;
if (lastPerimOfFace == _numControlVerts) {
lastPerimOfFace = N;
}
*fVerts = lastPerimOfFace;
} else {
// If the next/adjacent corner, successive corner vertices may
// need to be added if that corner is a val-2 interior vertex:
for (int i = numVal2InLast; i > 0; --i) {
*fVerts++ = (corner + 1 + i) % N;
}
*fVerts = (corner + 1) % N;
}
}
//
// Detection and removal of duplicate control faces -- which can only
// occur when incident faces overlap with the base face:
//
namespace {
//
// Internal helper functions to detect duplicate faces:
//
bool
doFacesMatch(int size, int const a[], int const b[], int bStart) {
for (int i = 0, j = bStart; i < size; ++i, ++j) {
j = (j == size) ? 0 : j;
if (a[i] != b[j]) return false;
}
return true;
}
bool
doFacesMatch(int size, int const a[], int const b[]) {
// Find a matching vertex to correlate possible rotation:
for (int i = 0; i < size; ++i) {
if (b[i] == a[0]) {
return doFacesMatch(size, a, b, i);
}
}
return false;
}
}
void
IrregularPatchBuilder::removeDuplicateControlFaces(
int faceSizes[], int faceVerts[],
int * numFaces, int * numFaceVerts) const {
//
// Work backwards from the last face -- detecting if it matches a
// face earlier in the arrays and removing it if so:
//
int numSizesAfter = 0;
int numVertsAfter = 0;
int * sizesAfter = faceSizes + *numFaces;
int * vertsAfter = faceVerts + *numFaceVerts;
for (int i = *numFaces - 1; i > 1; --i) {
int iSize = faceSizes[i];
int * iVerts = vertsAfter - iSize;
// Inspect the faces preceding this face for a duplicate:
bool isDuplicate = false;
int * jVerts = iVerts;
for (int j = i - 1; !isDuplicate && (j > 0); --j) {
jVerts = jVerts - faceSizes[j];
if (iSize == faceSizes[j]) {
isDuplicate = doFacesMatch(iSize, iVerts, jVerts);
}
}
// If this face was duplicated by one preceding it, remove it:
if (isDuplicate) {
if (numSizesAfter) {
std::memmove(sizesAfter - 1, sizesAfter,
numSizesAfter * sizeof(int));
std::memmove(vertsAfter - iSize, vertsAfter,
numVertsAfter * sizeof(int));
}
(*numFaces) --;
(*numFaceVerts) -= iSize;
} else {
numSizesAfter ++;
numVertsAfter += iSize;
}
sizesAfter --;
vertsAfter -= iSize;
}
}
void
IrregularPatchBuilder::sharpenBoundaryControlEdges(
int edgeVertPairs[], float edgeSharpness[], int * numSharpEdges) const {
//
// When extracting a manifold subset from a greater non-manifold
// region, the boundary edges for the subset sometimes occur on
// non-manifold edges. When extracting the subset topology in
// cases where faces overlap, those boundary edges can sometimes
// be misinterpreted as manifold interior edges -- as the extra
// faces connected to them that made the edge non-manifold are
// not included in the subset.
//
// So boundary edges are sharpened here -- which generally has no
// effect (as boundary edges are implicitly sharpened) but ensures
// that if the edge is misinterpreted as interior, it will remain
// sharp. And only boundary edges of the base face are sharpened
// here -- it is not necessary to deal with others.
//
// Append boundary edge sharpness to existing sharp edges:
edgeVertPairs += *numSharpEdges * 2;
edgeSharpness += *numSharpEdges;
int faceSize = _surface.GetFaceSize();
for (int corner = 0; corner < faceSize; ++corner) {
FaceVertexSubset const & cSub = _surface.GetCornerSubset(corner);
if (cSub.IsBoundary() && (cSub._numFacesBefore == 0)) {
*edgeSharpness++ = Sdc::Crease::SHARPNESS_INFINITE;
*edgeVertPairs++ = corner;
*edgeVertPairs++ = (corner + 1) % faceSize;
(*numSharpEdges) ++;
}
}
}
//
// The main build/assembly method to create a PatchTree:
//
// Note that the IrregularPatchBuilder was conceived to potentially build
// different representations of irregular patches (all sharing a virtual
// interface to hide that from it clients). It was here that topology
// would be inspected and builders for the different representations would
// be dispatched.
//
// At this point, the PatchTree is used for all topological cases, so
// those future intentions are not reflected here.
//
internal::IrregularPatchSharedPtr
IrregularPatchBuilder::Build() {
//
// Build a PatchTree -- the sole representation used for all irregular
// patch topologies.
//
// Given the PatchTree's origin in Far, it's builder class requires a
// Far::TopologyRefiner. Now that PatchTreeBuilder is part of Bfr, it
// could be adapted to accept the Bfr::FaceSurface directly and deal
// with any any intermediate TopologyRefiner internally.
//
// For now, the quickest way of constructing a TopologyRefiner is via
// a Far::TopologyDescriptor -- which simply refers to pre-allocated
// topology arrays. Those arrays will be allocated on the stack here
// to accommodate typical cases not involving excessively high valence.
//
// Use of TopologyDescriptor could be eliminated by defining a factory
// to create a TopologyRefiner directly from the FaceSurface, but the
// benefits relative to the cost of creating the PatchTree are not
// significant. The fact that the current assembly requires removing
// duplicate faces in some cases further complicates that process.
//
int numVerts = _numControlVerts;
int numFaces = _numControlFaces;
int numFaceVerts = _numControlFaceVerts;
int numCorners = _surface.GetFaceSize();
int numCreases = _numControlVerts;
// Allocate and partition stack buffers for the topology arrays:
int numInts = numFaces + numFaceVerts + numCorners + numCreases*2;
int numFloats = numCorners + numCreases;
Vtr::internal::StackBuffer<int, 256,true> intBuffer(numInts);
Vtr::internal::StackBuffer<float,64,true> floatBuffer(numFloats);
int * faceSizes = intBuffer;
int * faceVerts = faceSizes + numFaces;
int * cornerIndices = faceVerts + numFaceVerts;
int * creaseIndices = cornerIndices + numCorners;
float * cornerWeights = floatBuffer;
float * creaseWeights = cornerWeights + numCorners;
// Gather face topology (sizes and vertices) and explicit sharpness:
gatherControlFaces(faceSizes, faceVerts);
numCorners = _surface.GetTag().HasSharpVertices() ?
gatherControlVertexSharpness(cornerIndices, cornerWeights) : 0;
numCreases = _surface.GetTag().HasSharpEdges() ?
gatherControlEdgeSharpness(creaseIndices, creaseWeights) : 0;
// Make some adjustments when control faces may overlap:
if (controlFacesMayOverlap()) {
if (numFaces > 2) {
removeDuplicateControlFaces(faceSizes, faceVerts,
&numFaces, &numFaceVerts);
}
if (_surface.GetTag().HasBoundaryVertices()) {
sharpenBoundaryControlEdges(creaseIndices, creaseWeights,
&numCreases);
}
}
// Declare a TopologyDescriptor to reference the data gathered above:
Far::TopologyDescriptor topDescriptor;
topDescriptor.numVertices = numVerts;
topDescriptor.numFaces = numFaces;
topDescriptor.numVertsPerFace = faceSizes;
topDescriptor.vertIndicesPerFace = faceVerts;
if (numCorners) {
topDescriptor.numCorners = numCorners;
topDescriptor.cornerVertexIndices = cornerIndices;
topDescriptor.cornerWeights = cornerWeights;
}
if (numCreases) {
topDescriptor.numCreases = numCreases;
topDescriptor.creaseVertexIndexPairs = creaseIndices;
topDescriptor.creaseWeights = creaseWeights;
}
// Construct a TopologyRefiner in order to create a PatchTree:
typedef Far::TopologyDescriptor Descriptor;
typedef Far::TopologyRefinerFactory<Descriptor> RefinerFactory;
RefinerFactory::Options refinerOptions;
refinerOptions.schemeType = _surface.GetSdcScheme();
refinerOptions.schemeOptions = _surface.GetSdcOptionsInEffect();
// WIP - enable for debugging
//refinerOptions.validateFullTopology = true;
Far::TopologyRefiner * refiner =
RefinerFactory::Create(topDescriptor, refinerOptions);
// Create the PatchTree from the TopologyRefiner:
PatchTreeBuilder::Options patchTreeOptions;
patchTreeOptions.includeInteriorPatches = false;
patchTreeOptions.maxPatchDepthSharp = (unsigned char)_options.sharpLevel;
patchTreeOptions.maxPatchDepthSmooth = (unsigned char)_options.smoothLevel;
patchTreeOptions.useDoublePrecision = _options.doublePrecision;
PatchTreeBuilder patchTreeBuilder(*refiner, patchTreeOptions);
PatchTree const * patchTree = patchTreeBuilder.Build();
assert(patchTree->GetNumControlPoints() == _numControlVerts);
delete refiner;
return internal::IrregularPatchSharedPtr(patchTree);
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv

View File

@@ -0,0 +1,167 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_IRREGULAR_PATCH_BUILDER_H
#define OPENSUBDIV3_IRREGULAR_PATCH_BUILDER_H
#include "../version.h"
#include "../bfr/faceSurface.h"
#include "../bfr/irregularPatchType.h"
#include "../vtr/stackBuffer.h"
#include <map>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// IrregularPatchBuilder takes a FaceSurface (that has been flagged as not
// regular) and builds a representation for the limit surface it defines.
//
// It is intended to hide the construction details and final representation
// of the limit surface from its clients, i.e. the SurfaceFactory. If the
// preferred representation changes, or more than one is made available, it
// should have minimal impact on its clients (ideally none).
//
// WIP - the nature of the approximating options needs more work...
// - we need some way of specifying the options of PatchTree in a
// way that's more in line with the Factory's public interface
//
class IrregularPatchBuilder {
public:
typedef FaceSurface::Index Index;
public:
// WIP - see note above
struct Options {
Options() : sharpLevel(6), smoothLevel(2), doublePrecision(false) { }
int sharpLevel;
int smoothLevel;
bool doublePrecision;
};
public:
IrregularPatchBuilder(FaceSurface const & surfaceDescription,
Options const & options = Options());
~IrregularPatchBuilder() { }
// Debugging:
void print() const;
public:
// This will not be necessary if use of the index map is ever removed:
bool ControlHullDependsOnMeshIndices() const { return _useControlVertMap; }
// Methods to query the number and indices of control vertices:
int GetNumControlVertices() const { return _numControlVerts; }
int GatherControlVertexIndices(Index cvIndices[]) const;
public:
// Methods to build irregular patches:
internal::IrregularPatchSharedPtr Build();
private:
// Private methods to assemble the control hull:
// A simple struct keeps track of the contribution of each corner to
// the collective control hull. These are first initialized and then
// used by methods to gather the various topological data that define
// the hull.
//
// Note that vertices of the base face and the base face itself are
// not included as part of this inventory for each corner, e.g. the
// number of control vertices or control faces may be zero if the
// corner has no incident faces.
//
struct CornerHull {
void Clear() { std::memset(this, 0, sizeof(*this)); }
int numControlFaces;
int numControlVerts;
int nextControlVert;
int surfaceIndicesOffset;
unsigned int isVal2Interior : 1;
unsigned int preVal2Interior : 1;
unsigned int singleSharedVert : 1;
unsigned int singleSharedFace : 1;
};
void initializeControlHullInventory();
// Methods to access the control vertex indices:
Index const * getSurfaceIndices() const;
Index const * getBaseFaceIndices() const;
Index const * getCornerIndices(int corner) const;
Index const * getCornerFaceIndices(int corner, int face) const;
// Methods to gather topology defining the control hull:
int gatherControlFaces(int faceSizes[], int faceVertices[]) const;
int gatherControlVertexSharpness(int indices[], float sharpness[]) const;
int gatherControlEdgeSharpness( int indices[], float sharpness[]) const;
// Methods to identify face-verts for an individual control face:
void getControlFaceVertices(int faceVerts[], int numFaceVerts,
int corner, int nextPerimeterVert) const;
void getControlFaceVertices(int faceVerts[], int numFaceVerts,
int corner, int nextPerimeterVert,
bool lastFace, int numVal2InLast) const;
void getControlFaceVertices(int faceVerts[], int numFaceVerts,
int corner, Index const srcVerts[]) const;
// Methods for dealing with the control vertex map:
void initializeControlVertexMap();
void addMeshControlVertex(Index faceVertIndex);
void addMeshControlVertices(Index const faceVertIndices[], int faceSize);
int getLocalControlVertex(Index meshVertexIndex) const;
Index getMeshControlVertex( int localVertexIndex) const;
// Methods for dealing with potentially overlapping faces:
bool controlFacesMayOverlap() const { return _controlFacesOverlap; }
void removeDuplicateControlFaces(int faceSizes[], int faceVerts[],
int * numFaces, int * numFaceVerts) const;
void sharpenBoundaryControlEdges(int edgeIndices[], float edgeSharpness[],
int * numSharpEdges) const;
private:
// Private members:
FaceSurface const & _surface;
Options _options;
// Members defining the control hull of the surface -- some storing
// contributions to the control hull for each corner:
typedef Vtr::internal::StackBuffer<CornerHull,8,true> CornerHullArray;
int _numControlVerts;
int _numControlFaces;
int _numControlFaceVerts;
bool _controlFacesOverlap;
bool _useControlVertMap;
CornerHullArray _cornerHullInfo;
std::map<Index,int> _controlVertMap;
std::vector<Index> _controlVerts;
};
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_IRREGULAR_PATCH_BUILDER_H */

View File

@@ -0,0 +1,46 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_IRREGULAR_PATCH_TYPE_H
#define OPENSUBDIV3_BFR_IRREGULAR_PATCH_TYPE_H
#include "../version.h"
#include <memory>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// This header was intended to contain an abstract class providing the
// common interface to potentially more than one representations of the
// irregular patches. But given only one implementation exists -- the
// PatchTree -- that level of abstraction has been deferred.
//
// So this header serves to encapsulate the forward declaration of that
// single representation, along with related typedefs for internal use.
//
class PatchTree;
namespace internal {
typedef PatchTree IrregularPatchType;
typedef std::shared_ptr<const IrregularPatchType> IrregularPatchSharedPtr;
} // end namespace internal
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_IRREGULAR_PATCH_TYPE */

View File

@@ -0,0 +1,38 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_LIMITS_H
#define OPENSUBDIV3_BFR_LIMITS_H
#include "../version.h"
#include "../far/types.h"
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
///
/// @brief Simple struct with limits related to topology
///
struct Limits {
/// @brief Returns the maximum allowable valence for a vertex
static int MaxValence() { return Far::VALENCE_LIMIT; }
/// @brief Returns the maximum allowable size for a face (number of
/// vertices)
static int MaxFaceSize() { return Far::VALENCE_LIMIT; }
};
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_LIMITS_H */

View File

@@ -0,0 +1,220 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../bfr/parameterization.h"
#include "../bfr/limits.h"
#include "../sdc/types.h"
#include <cmath>
#include <cassert>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// Construction:
//
Parameterization::Parameterization(Sdc::SchemeType scheme, int faceSize) {
int regFaceSize = Sdc::SchemeTypeTraits::GetRegularFaceSize(scheme);
_type = (unsigned char) ((regFaceSize == 4) ? QUAD : TRI);
_faceSize = (unsigned short) faceSize;
_uDim = 0;
if (faceSize != regFaceSize) {
if ((faceSize < 3) || (faceSize > Limits::MaxFaceSize())) {
// Reset size to 0 (invalid) for degenerate or excessive size
_faceSize = 0;
} else if (regFaceSize == 3) {
// Reset size to 0 (invalid) for non-triangles of tri schemes:
_faceSize = 0;
} else {
// Quad sub-faces -- use int sqrt for udim to preserve accuracy:
_type = QUAD_SUBFACES;
_uDim = (faceSize < 10) ?
(unsigned char)(2 + (faceSize > 4)) :
(unsigned char)(1 + (int) std::sqrt((float)(faceSize - 1)));
}
}
}
//
// Simple coordinate queries:
//
template <typename REAL>
void
Parameterization::GetVertexCoord(int vertex, REAL uv[2]) const {
switch (GetType()) {
case QUAD:
uv[0] = (REAL) (vertex && (vertex < 3));
uv[1] = (REAL) (vertex > 1);
break;
case TRI:
uv[0] = (REAL) (vertex == 1);
uv[1] = (REAL) (vertex == 2);
break;
case QUAD_SUBFACES:
uv[0] = (REAL) (vertex % _uDim);
uv[1] = (REAL) (vertex / _uDim);
break;
default:
uv[0] = -1.0f;
uv[1] = -1.0f;
break;
}
}
template <typename REAL>
void
Parameterization::GetEdgeCoord(int edge, REAL t, REAL uv[2]) const {
switch (GetType()) {
case QUAD:
switch (edge) {
case 0: uv[0] = t; uv[1] = 0.0f; break;
case 1: uv[0] = 1.0f; uv[1] = t; break;
case 2: uv[0] = 1.0f - t; uv[1] = 1.0f; break;
case 3: uv[0] = 0.0f; uv[1] = 1.0f - t; break;
}
break;
case TRI:
switch (edge) {
case 0: uv[0] = t; uv[1] = 0.0f; break;
case 1: uv[0] = 1.0f - t; uv[1] = t; break;
case 2: uv[0] = 0.0f; uv[1] = 1.0f - t; break;
}
break;
case QUAD_SUBFACES:
if (t < 0.5f) {
GetVertexCoord(edge, uv);
uv[0] += t;
} else {
GetVertexCoord((edge + 1) % _faceSize, uv);
uv[1] += 1.0f - t;
}
break;
default:
uv[0] = -1.0f;
uv[1] = -1.0f;
break;
}
}
template <typename REAL>
void
Parameterization::GetCenterCoord(REAL uv[2]) const {
if (GetType() == TRI) {
uv[0] = 1.0f / 3.0f;
uv[1] = 1.0f / 3.0f;
} else {
uv[0] = 0.5f;
uv[1] = 0.5f;
}
}
//
// Private sub-face coordinate conversion methods used externally:
//
template <typename REAL>
int
Parameterization::convertCoordToSubFace(bool normalized,
REAL const uvCoord[2], REAL subCoord[2]) const {
assert(HasSubFaces());
// Include an interval around the domain when identifying the subface:
int uTile = (int) (uvCoord[0] + 0.25f);
int vTile = (int) (uvCoord[1] + 0.25f);
// Clamp tiles in the U and V direction to valid sub-face tiles on
// the boundaries of the rectangular subset. This is trivial in the
// U direction but less so in V as the top row is usually not full
// (so max in V is greater for the first colums in U):
uTile = std::max(0, std::min(uTile, _uDim - 1));
vTile = std::max(0, vTile);
if ((vTile * _uDim + uTile) >= _faceSize) {
// Only clamp upper bound in V when tile exceeds face size:
vTile = (_faceSize / _uDim) - 1 + (uTile < (_faceSize % _uDim));
}
// Be sure to support in-place conversion (i.e. uvCoord == subCoord):
subCoord[0] = uvCoord[0] - (REAL) uTile;
subCoord[1] = uvCoord[1] - (REAL) vTile;
if (normalized) {
subCoord[0] *= 2.0f;
subCoord[1] *= 2.0f;
}
return vTile * _uDim + uTile;
}
template <typename REAL>
void
Parameterization::convertSubFaceToCoord(bool normalized, int subFace,
REAL const subCoord[2], REAL uvCoord[2]) const {
assert(HasSubFaces());
int uTile = subFace % _uDim;
int vTile = subFace / _uDim;
// Be sure this assignment always supports in-place conversion:
if (normalized) {
uvCoord[0] = (REAL) uTile + subCoord[0] * 0.5f;
uvCoord[1] = (REAL) vTile + subCoord[1] * 0.5f;
} else {
uvCoord[0] = (REAL) uTile + subCoord[0];
uvCoord[1] = (REAL) vTile + subCoord[1];
}
}
//
// Explicit instantiation of template methods for <REAL>:
//
// Coordinate queries:
template void
Parameterization::GetVertexCoord<float>(int, float uv[2]) const;
template void
Parameterization::GetEdgeCoord<float>(int, float, float uv[2]) const;
template void
Parameterization::GetCenterCoord<float>(float uv[2]) const;
template void
Parameterization::GetVertexCoord<double>(int, double uv[2]) const;
template void
Parameterization::GetEdgeCoord<double>(int, double, double uv[2]) const;
template void
Parameterization::GetCenterCoord<double>(double uv[2]) const;
// Sub-face conversions:
template int
Parameterization::convertCoordToSubFace<float>(bool,
float const uvIn[2], float uvOut[2]) const;
template void
Parameterization::convertSubFaceToCoord<float>(bool, int,
float const uvIn[2], float uvOut[2]) const;
template int
Parameterization::convertCoordToSubFace<double>(bool,
double const uvIn[2], double uvOut[2]) const;
template void
Parameterization::convertSubFaceToCoord<double>(bool, int,
double const uvIn[2], double uvOut[2]) const;
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv

View File

@@ -0,0 +1,230 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_PARAMETERIZATION_H
#define OPENSUBDIV3_BFR_PARAMETERIZATION_H
#include "../version.h"
#include "../sdc/types.h"
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
///
/// @brief Simple class defining the 2D parameterization of a face
///
/// Parameterization is a simple class that provides information about the
/// parameterization of a face in a local (u,v) coordinate system. It is
/// defined by the size of a face (i.e. its number of vertices) and the
/// subdivision scheme that determines its limit surface.
///
/// As an example of how the subdivision scheme is essential in determining
/// the Parameterization, consider the case of a triangle. A triangle is
/// regular for the Loop scheme and so has a very simple parameterization
/// as a triangular patch. But for the Catmull-Clark scheme, a triangle is
/// an irregular face that must first be subdivided -- making its limit
/// surface a piecewise collection of quadrilateral patches.
///
class Parameterization {
public:
///
/// @brief Enumerated type for the different kinds of Parameterizations.
///
/// The three kinds of parameterizations defined are: quadrilateral,
/// triangle and quadrangulated sub-faces. This is not intended for
/// common use, but is publicly available for situations when it is
/// necessary to distinguish:
///
enum Type { QUAD, ///< Quadrilateral
TRI, ///< Triangle
QUAD_SUBFACES ///< Partitioned into quadrilateral sub-faces
};
public:
//@{
/// @name Construction and initialization
///
/// Valid construction of a Parameterization is only achieved with
/// the non-default constructor. A Parameterization will be invalid
/// (and so unusable) if default constructed, or constructed using
/// arguments that describe a face that cannot be parameterized.
///
/// @brief Primary constructor with subdivision scheme and face size
Parameterization(Sdc::SchemeType scheme, int faceSize);
/// @brief Returns true if correctly initialized
bool IsValid() const { return (_faceSize > 0); }
/// @brief Default construction produces an invalid instance
Parameterization() : _type(0), _uDim(0), _faceSize(0) { }
Parameterization(Parameterization const &) = default;
Parameterization & operator=(Parameterization const &) = default;
~Parameterization() = default;
//@}
//@{
/// @name Simple queries
///
/// Simple queries of a valid Parameterization.
///
/// @brief Returns the type of parameterization assigned
Type GetType() const { return (Type) _type; }
/// @brief Returns the size (number of vertices) of the corresponding face
int GetFaceSize() const { return _faceSize; }
//@}
public:
//@{
/// @name Methods to inspect parametric features
///
/// Methods are available to inspect common topological features of a
/// Parameterization, i.e. the parametric coordinates corresponding
/// to the vertices, edges or center of the face it represents.
///
/// Methods for vertices and edges require an index of the desired
/// vertex or edge. The edge parameter "t" locally parameterizes the
/// edge over [0,1] in a counter-clockwise orientation.
///
/// @brief Returns the (u,v) coordinate of a given vertex
template <typename REAL>
void GetVertexCoord(int vertexIndex, REAL uvCoord[2]) const;
/// @brief Returns the (u,v) coordinate at any point on a given edge
template <typename REAL>
void GetEdgeCoord(int edgeIndex, REAL t, REAL uvCoord[2]) const;
/// @brief Returns the (u,v) coordinate for the center of the face
template <typename REAL>
void GetCenterCoord(REAL uvCoord[2]) const;
//@}
public:
//@{
/// @name Methods to deal with discontinuous parameterizations
///
/// Parameterizations that have been partitioned into sub-faces are
/// discontinuous and warrant care in order to process them effectively --
/// often requiring explicit conversions.
///
/// These conversion methods to and from the local coordinates of a
/// sub-face are only for use with instances of Parameterization that
/// have such sub-faces. Results for input coordinates that are
/// significantly outside the domain of the input parameterization are
/// undefined.
///
/// Note that sub-face coordinates that are normalized correspond to
/// coordinates for Ptex faces.
///
/// @brief Returns if Parameterization has been partitioned into sub-faces
bool HasSubFaces() const;
/// @brief Returns the integer sub-face containing the given (u,v)
template <typename REAL>
int GetSubFace(REAL const uvCoord[2]) const;
/// @brief Convert (u,v) to a sub-face (return value) and its local (u,v)
/// coordinate
template <typename REAL>
int ConvertCoordToSubFace(
REAL const uvCoord[2], REAL subFaceCoord[2]) const;
/// @brief Convert a sub-face and its local (u,v) coordinate to (u,v)
template <typename REAL>
void ConvertSubFaceToCoord(int subFace,
REAL const subFaceCoord[2], REAL uvCoord[2]) const;
/// @brief Convert (u,v) to a sub-face (return value) and its normalized
/// (u,v) coordinate
template <typename REAL>
int ConvertCoordToNormalizedSubFace(
REAL const uvCoord[2], REAL subFaceCoord[2]) const;
/// @brief Convert a sub-face and its normalized (u,v) coordinate to (u,v)
template <typename REAL>
void ConvertNormalizedSubFaceToCoord(int subFace,
REAL const subFaceCoord[2], REAL uvCoord[2]) const;
//@}
private:
template <typename REAL>
int convertCoordToSubFace(bool normalized,
REAL const uvCoord[2], REAL subFaceCoord[2]) const;
template <typename REAL>
void convertSubFaceToCoord(bool normalized, int subFace,
REAL const subFaceCoord[2], REAL uvCoord[2]) const;
private:
unsigned char _type;
unsigned char _uDim;
unsigned short _faceSize;
};
//
// Inline sub-face coordinate conversion methods:
//
inline bool
Parameterization::HasSubFaces() const {
return (_type == QUAD_SUBFACES);
}
template <typename REAL>
inline int
Parameterization::GetSubFace(REAL const uvCoord[2]) const {
if (!HasSubFaces()) return 0;
int uTile = (int) uvCoord[0];
int vTile = (int) uvCoord[1];
return (vTile + ((uvCoord[1] - (REAL) vTile) > 0.75f)) * _uDim +
(uTile + ((uvCoord[0] - (REAL) uTile) > 0.75f));
}
// Conversions to unnormalized sub-face coordinates:
template <typename REAL>
inline int
Parameterization::ConvertCoordToSubFace(
REAL const uvCoord[2], REAL subCoord[2]) const {
return convertCoordToSubFace<REAL>(false, uvCoord, subCoord);
}
template <typename REAL>
inline void
Parameterization::ConvertSubFaceToCoord(
int subFace, REAL const subCoord[2], REAL uvCoord[2]) const {
convertSubFaceToCoord<REAL>(false, subFace, subCoord, uvCoord);
}
// Conversions to normalized sub-face coordinates:
template <typename REAL>
inline int
Parameterization::ConvertCoordToNormalizedSubFace(
REAL const uvCoord[2], REAL subCoord[2]) const {
return convertCoordToSubFace<REAL>(true, uvCoord, subCoord);
}
template <typename REAL>
inline void
Parameterization::ConvertNormalizedSubFaceToCoord(
int subFace, REAL const subCoord[2], REAL uvCoord[2]) const {
convertSubFaceToCoord<REAL>(true, subFace, subCoord, uvCoord);
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_PARAMETERIZATION */

View File

@@ -0,0 +1,486 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../bfr/patchTree.h"
#include "../far/patchBasis.h"
#include <algorithm>
#include <cstdio>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
using Far::PatchDescriptor;
using Far::PatchParam;
//
// Avoid warnings comparing floating point values to zero:
//
namespace {
#ifdef __INTEL_COMPILER
#pragma warning (push)
#pragma warning disable 1572
#endif
template <typename REAL>
inline bool isWeightZero(REAL w) { return (w == (REAL)0.0); }
#ifdef __INTEL_COMPILER
#pragma warning (pop)
#endif
}
//
// Simple inline methods for the PatchTree::TreeNode:
//
// sets all the children to point to the patch of given index
inline void
PatchTree::TreeNode::SetChildren(int index) {
for (int i=0; i<4; ++i) {
children[i].isSet = true;
children[i].isLeaf = true;
children[i].SetIndex(index);
}
}
// sets the child in "quadrant" to point to node or patch of the given index
inline void
PatchTree::TreeNode::SetChild(int quadrant, int index, bool isLeaf) {
assert(!children[quadrant].isSet);
children[quadrant].isSet = true;
children[quadrant].isLeaf = isLeaf;
children[quadrant].SetIndex(index);
}
//
// PatchTree constructor and destructor:
//
PatchTree::PatchTree() :
_useDoublePrecision(false),
_patchesIncludeNonLeaf(false),
_patchesAreTriangular(false),
_regPatchType(PatchDescriptor::NON_PATCH),
_irregPatchType(PatchDescriptor::NON_PATCH),
_regPatchSize(0),
_irregPatchSize(0),
_patchPointStride(0),
_numSubFaces(0),
_numControlPoints(0),
_numRefinedPoints(0),
_numSubPatchPoints(0),
_numIrregPatches(0),
_treeDepth(-1) {
}
PatchTree::~PatchTree() {
}
//
// Class methods supporting access to patches:
//
PatchTree::PatchPointArray
PatchTree::GetSubPatchPoints(int patchIndex) const {
return PatchPointArray(
&_patchPoints[patchIndex * _patchPointStride],
_patchParams[patchIndex].IsRegular() ? _regPatchSize
: _irregPatchSize);
}
template <typename REAL>
int
PatchTree::EvalSubPatchBasis(int patchIndex, REAL u, REAL v,
REAL wP[], REAL wDu[], REAL wDv[],
REAL wDuu[], REAL wDuv[], REAL wDvv[]) const {
PatchParam const & param = _patchParams[patchIndex];
return Far::internal::EvaluatePatchBasis(
param.IsRegular() ? _regPatchType : _irregPatchType,
param, u, v, wP, wDu, wDv, wDuu, wDuv, wDvv);
}
template <typename REAL>
int
PatchTree::EvalSubPatchStencils(int patchIndex, REAL u, REAL v,
REAL sP[], REAL sDu[], REAL sDv[],
REAL sDuu[], REAL sDuv[], REAL sDvv[]) const{
//
// If evaluating a regular interior patch at the base level, evaluate
// basis directly into the output stencils:
//
PatchParam const & param = _patchParams[patchIndex];
if ((param.GetDepth() == 0) && param.IsRegular() && !param.GetBoundary()) {
assert(_regPatchSize == _numControlPoints);
return Far::internal::EvaluatePatchBasis(
_regPatchType, param, u, v, sP, sDu, sDv, sDuu, sDuv, sDuv);
}
// Invoke according to precision of the internal stencil matrix:
if (_useDoublePrecision) {
return evalSubPatchStencils<double>(patchIndex, u, v,
sP, sDu, sDv, sDuu, sDuv, sDvv);
} else {
return evalSubPatchStencils<float>(patchIndex, u, v,
sP, sDu, sDv, sDuu, sDuv, sDvv);
}
}
namespace {
template <typename REAL_SRC, typename REAL_DST>
void addToArray(REAL_DST * dst, int n, REAL_DST w, REAL_SRC const * src) {
if (isWeightZero(w)) return;
// WIP - we can guarantee these vectors are aligned (in future),
// so anything here to make use of SSE/AVX will be worth it
// - prefer something portable to ensure auto-vectorization
// - we can also pad the matrix so "n" is a multiple of 4...
// - note will need to specialize when cast required
for (int i = 0; i < n; ++i) {
dst[i] += (REAL_DST) (w * src[i]);
}
}
}
template <typename REAL_MATRIX, typename REAL>
int
PatchTree::evalSubPatchStencils(int patchIndex, REAL u, REAL v,
REAL sP[], REAL sDu[], REAL sDv[],
REAL sDuu[], REAL sDuv[], REAL sDvv[]) const{
PatchParam const & param = _patchParams[patchIndex];
//
// Basis weights must be evaluated into local arrays and transformed
// into stencil weights (in terms of the base level control points
// rather than the patch's control points):
//
REAL wDuBuffer[20], wDvBuffer[20];
REAL wDuuBuffer[20], wDuvBuffer[20], wDvvBuffer[20];
REAL wP[20];
REAL * wDu = 0;
REAL * wDv = 0;
REAL * wDuu = 0;
REAL * wDuv = 0;
REAL * wDvv = 0;
bool d1 = sDu && sDv;
if (d1) {
wDu = wDuBuffer;
wDv = wDvBuffer;
}
bool d2 = d1 && sDuu && sDuv && sDvv;
if (d2) {
wDuu = wDuuBuffer;
wDuv = wDuvBuffer;
wDvv = wDvvBuffer;
}
Far::internal::EvaluatePatchBasis(
param.IsRegular() ? _regPatchType : _irregPatchType,
param, u, v, wP, wDu, wDv, wDuu, wDuv, wDvv);
PatchPointArray patchPoints = GetSubPatchPoints(patchIndex);
//
// Clear and accumulate the stencil weights for the contribution of
// each point of the patch:
//
std::memset(sP, 0, sizeof(REAL) * _numControlPoints);
if (d1) {
std::memset(sDu, 0, sizeof(REAL) * _numControlPoints);
std::memset(sDv, 0, sizeof(REAL) * _numControlPoints);
}
if (d2) {
std::memset(sDuu, 0, sizeof(REAL) * _numControlPoints);
std::memset(sDuv, 0, sizeof(REAL) * _numControlPoints);
std::memset(sDvv, 0, sizeof(REAL) * _numControlPoints);
}
for (int i = 0; i < patchPoints.size(); ++i) {
int pIndex = patchPoints[i];
if (pIndex < _numControlPoints) {
sP[pIndex] += wP [i];
if (d1) {
sDu[pIndex] += wDu[i];
sDv[pIndex] += wDv[i];
}
if (d2) {
sDuu[pIndex] += wDuu[i];
sDuv[pIndex] += wDuv[i];
sDvv[pIndex] += wDvv[i];
}
} else {
std::vector<REAL_MATRIX> const & pStencilMtx =
getStencilMatrix<REAL_MATRIX>();
assert(!pStencilMtx.empty());
REAL_MATRIX const * pStencilRow =
&pStencilMtx[(pIndex - _numControlPoints) * _numControlPoints];
addToArray(sP, _numControlPoints, wP[i], pStencilRow);
if (d1) {
addToArray(sDu, _numControlPoints, wDu[i], pStencilRow);
addToArray(sDv, _numControlPoints, wDv[i], pStencilRow);
}
if (d2) {
addToArray(sDuu, _numControlPoints, wDuu[i], pStencilRow);
addToArray(sDuv, _numControlPoints, wDuv[i], pStencilRow);
addToArray(sDvv, _numControlPoints, wDvv[i], pStencilRow);
}
}
}
return _numControlPoints;
}
//
// Local functions and class methods supporting tree searches and construction:
//
namespace {
template <typename T>
inline int
transformUVToQuadQuadrant(T const & median, T & u, T & v) {
int uHalf = (u >= median);
if (uHalf) u -= median;
int vHalf = (v >= median);
if (vHalf) v -= median;
return (vHalf << 1) | uHalf;
}
template <typename T>
int inline
transformUVToTriQuadrant(T const & median, T & u, T & v, bool & rotated) {
if (!rotated) {
if (u >= median) {
u -= median;
return 1;
}
if (v >= median) {
v -= median;
return 2;
}
if ((u + v) >= median) {
rotated = true;
return 3;
}
return 0;
} else {
if (u < median) {
v -= median;
return 1;
}
if (v < median) {
u -= median;
return 2;
}
u -= median;
v -= median;
if ((u + v) < median) {
rotated = true;
return 3;
}
return 0;
}
}
} // end namespace
inline PatchTree::TreeNode *
PatchTree::assignLeafOrChildNode(TreeNode * node,
bool isLeaf, int quadrant, int patchIndex) {
// This is getting far enough away from PatchMap's original
// structure and implementation that it warrants a face lift...
if (!node->children[quadrant].isSet) {
if (isLeaf) {
node->SetChild(quadrant, patchIndex, true);
return node;
} else {
int newNodeIndex = (int)_treeNodes.size();
_treeNodes.push_back(TreeNode());
node->SetChild(quadrant, newNodeIndex, false);
return &_treeNodes[newNodeIndex];
}
}
if (isLeaf || node->children[quadrant].isLeaf) {
// Need to replace the leaf index with new node and index:
int newNodeIndex = (int)_treeNodes.size();
_treeNodes.push_back(TreeNode());
TreeNode * newNode = &_treeNodes[newNodeIndex];
// Move existing patch index from child to new child node:
newNode->patchIndex = node->children[quadrant].index;
node->children[quadrant].SetIndex(newNodeIndex);
node->children[quadrant].isLeaf = false;
if (isLeaf) {
newNode->SetChild(quadrant, patchIndex, true);
}
return newNode;
} else {
// Simply return the existing interior node:
return &_treeNodes[node->children[quadrant].index];
}
}
void
PatchTree::buildQuadtree() {
int numPatches = (int) _patchParams.size();
_treeNodes.reserve(numPatches);
_treeNodes.resize(_numSubFaces ? _numSubFaces : 1);
_treeDepth = 0;
for (int patchIndex = 0; patchIndex < numPatches; ++patchIndex) {
PatchParam const & param = _patchParams[patchIndex];
int depth = param.GetDepth();
int rootDepth = param.NonQuadRoot();
int subFace = param.GetFaceId();
assert((subFace == 0) || (subFace < _numSubFaces));
TreeNode * node = &_treeNodes[subFace];
_treeDepth = std::max(depth, _treeDepth);
if (depth == rootDepth) {
node->patchIndex = patchIndex;
continue;
}
if (!_patchesAreTriangular) {
// Use the UV bits of the PatchParam directly for quad patches:
int u = param.GetU();
int v = param.GetV();
for (int j = rootDepth + 1; j <= depth; ++j) {
int uBit = (u >> (depth - j)) & 1;
int vBit = (v >> (depth - j)) & 1;
int quadrant = (vBit << 1) | uBit;
node = assignLeafOrChildNode(node, (j == depth), quadrant,
patchIndex);
}
} else {
// Use an interior UV point of triangles to identify quadrants:
double u = 0.25f;
double v = 0.25f;
param.UnnormalizeTriangle(u, v);
double median = 0.5f;
bool triRotated = false;
for (int j = rootDepth + 1; j <= depth; ++j, median *= 0.5f) {
int quadrant = transformUVToTriQuadrant(median, u, v,
triRotated);
node = assignLeafOrChildNode(node, (j == depth), quadrant,
patchIndex);
}
}
}
}
int
PatchTree::searchQuadtree(double u, double v,
int subFace, int searchDepth) const {
//
// These details warrant closer inspection and possible tweaking
// since non-leaf patches were optionally added to the tree...
//
//
// Identify the root patch and make a quick exit when seeking it. If
// there is no patch at level 0 but subpatches present (possible e.g.
// if adjacent to an irregular face) force the search to level 1:
//
TreeNode const * node = &_treeNodes[subFace];
if (_treeDepth == 0) {
assert(node->patchIndex >= 0);
return node->patchIndex;
}
int maxDepth = ((searchDepth >= 0) && _patchesIncludeNonLeaf) ? searchDepth
: _treeDepth;
if (maxDepth == (_numSubFaces > 0)) {
if (node->patchIndex >= 0) {
return node->patchIndex;
}
maxDepth = 1;
}
//
// Search the tree for the sub-patch containing the given (u,v)
//
double median = 0.5f;
bool triRotated = false;
for (int depth = 1; depth <= maxDepth; ++depth, median *= 0.5f) {
int quadrant = _patchesAreTriangular
? transformUVToTriQuadrant(median, u, v, triRotated)
: transformUVToQuadQuadrant(median, u, v);
// Identify child patch if leaf, otherwise child node:
if (node->children[quadrant].isLeaf) {
return node->children[quadrant].index;
} else if (node->children[quadrant].isSet) {
node = &_treeNodes[node->children[quadrant].index];
}
}
assert(node->patchIndex >= 0);
return node->patchIndex;
}
//
// Explicit instantiation for methods supporting float and double:
//
template int PatchTree::EvalSubPatchBasis<float>(int patchIndex,
float u, float v,
float wP[], float wDu[], float wDv[],
float wDuu[], float wDuv[], float wDvv[]) const;
template int PatchTree::EvalSubPatchStencils<float>(int patchIndex,
float u, float v,
float sP[], float sDu[], float sDv[],
float sDuu[], float sDuv[], float sDvv[]) const;
template int PatchTree::EvalSubPatchBasis<double>(int patchIndex,
double u, double v,
double wP[], double wDu[], double wDv[],
double wDuu[], double wDuv[], double wDvv[]) const;
template int PatchTree::EvalSubPatchStencils<double>(int patchIndex,
double u, double v,
double sP[], double sDu[], double sDv[],
double sDuu[], double sDuv[], double sDvv[]) const;
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv

View File

@@ -0,0 +1,234 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_PATCH_TREE_H
#define OPENSUBDIV3_BFR_PATCH_TREE_H
#include "../version.h"
#include "../far/patchDescriptor.h"
#include "../far/patchParam.h"
#include "../vtr/array.h"
#include <vector>
#include <cstring>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// A PatchTree is a hierarchical collection of parametric patches that
// form a piecewise representation of the limit surface for a single face
// of a mesh. Using the patch representations from Far, it combines
// stripped down versions of the PatchTable and PatchMap from Far and a
// raw representation of stencils into a more compact representation
// suited to evaluating a single face. These are constructed based on
// adaptive refinement of a single face of a Far::TopologyRefiner.
//
// As the internal representation for the limit surface of a face with
// irregular topology, the PatchTree is not publicly exposed. As is the
// case with other internal Bfr classes whose headers are not exported,
// it is not further protected by the "internal" namespace.
//
// PatchTree was initially developed as an internal class for Far, so
// some comments may still reflect that origin.
//
// PatchTree also includes functionality beyond what is needed for Bfr
// for potential future use. Most notable is the ability for the tree
// to store a patch at interior nodes -- in addition to the leaf nodes.
// This allows the depth of evaluation to be varied, which takes place
// when searching a patch from the tree (specifying a maximum depth for
// the search). Construction options allow this functionality to be
// selectively enabled/disabled, and Bfr currently disables it.
//
class PatchTree {
public:
// Constructors are protected
~PatchTree();
// Simple public accessors:
int GetNumControlPoints() const { return _numControlPoints; }
int GetNumSubPatchPoints() const { return _numSubPatchPoints; }
int GetNumPointsTotal() const { return _numControlPoints +
_numSubPatchPoints; }
// These queries may not be necessary...
int GetDepth() const { return _treeDepth; }
int GetNumPatches() const { return (int)_patchParams.size(); }
// Methods to access stencils to compute patch points:
template <typename REAL>
REAL const * GetStencilMatrix() const;
bool UsesDoublePrecision() const { return _useDoublePrecision; }
// Methods supporting evaluation:
int HasSubFaces() const { return _numSubFaces > 0; }
int GetNumSubFaces() const { return _numSubFaces; }
int FindSubPatch(double u, double v, int subFace=0, int maxDep=-1) const;
typedef Vtr::ConstArray<int> PatchPointArray;
PatchPointArray GetSubPatchPoints(int subPatch) const;
Far::PatchParam GetSubPatchParam( int subPatch) const;
// Main evaluation methods - basis weights or limit stencils:
template <typename REAL>
int EvalSubPatchBasis(int subPatch, REAL u, REAL v, REAL w[],
REAL wDu[], REAL wDv[],
REAL wDuu[], REAL wDuv[], REAL wDvv[]) const;
template <typename REAL>
int EvalSubPatchStencils(int subPatch, REAL u, REAL v, REAL s[],
REAL sDu[], REAL sDv[],
REAL sDuu[], REAL sDuv[], REAL sDvv[]) const;
protected:
PatchTree();
friend class PatchTreeBuilder;
//
// Internal utilities to support the stencil matrix of variable precision
//
template <typename REAL> std::vector<REAL> const & getStencilMatrix() const;
template <typename REAL> std::vector<REAL> & getStencilMatrix();
template <typename REAL_MATRIX, typename REAL>
int evalSubPatchStencils(int subPatch, REAL u, REAL v, REAL s[],
REAL sDu[], REAL sDv[],
REAL sDuu[], REAL sDuv[], REAL sDvv[]) const;
protected:
// Internal quad-tree node type and assembly and search methods:
struct TreeNode {
struct Child {
unsigned int isSet : 1;
unsigned int isLeaf : 1;
unsigned int index : 28;
void SetIndex(int indexArg) { index = indexArg & 0xfffffff; }
};
TreeNode() : patchIndex(-1) {
std::memset(children, 0, sizeof(children));
}
void SetChildren(int index);
void SetChild(int quadrant, int index, bool isLeaf);
int patchIndex;
Child children[4];
};
int searchQuadtree(double u, double v, int subFace=0, int depth=-1) const;
void buildQuadtree();
TreeNode * assignLeafOrChildNode(TreeNode * node,
bool isLeaf, int quadrant, int index);
private:
// Private members:
typedef Far::PatchDescriptor::Type PatchType;
// Simple configuration members:
unsigned int _useDoublePrecision : 1;
unsigned int _patchesIncludeNonLeaf : 1;
unsigned int _patchesAreTriangular : 1;
PatchType _regPatchType;
PatchType _irregPatchType;
int _regPatchSize;
int _irregPatchSize;
int _patchPointStride;
// Simple topology inventory members:
int _numSubFaces;
int _numControlPoints;
int _numRefinedPoints;
int _numSubPatchPoints;
int _numIrregPatches;
// Vectors for points and PatchParams of all patches:
//
// Note we store both regular and irregular patch point indices in the
// same vector (using a common stride for each patch) and the patch type
// determined by the PatchParam -- in the same way that face-varying
// patches are stored in the PatchTable. Could also be stored in
// separate "patch arrays" or separated in other ways and managed with
// a bit more book-keeping.
//
std::vector<int> _patchPoints;
std::vector<Far::PatchParam> _patchParams;
// The quadtree organizing the patches:
std::vector<TreeNode> _treeNodes;
int _treeDepth;
// Array of stencils for computing patch points from control points
// (single or double to be used as specified on construction):
std::vector<float> _stencilMatrixFloat;
std::vector<double> _stencilMatrixDouble;
};
//
// Internal specializations to support the stencil matrix of variable
// precision -- const access presumes it is non-empty (and so assert)
// while non-const access may be used to populate it.
//
template <>
inline std::vector<float> const &
PatchTree::getStencilMatrix<float>() const {
assert(!_stencilMatrixFloat.empty());
return _stencilMatrixFloat;
}
template <>
inline std::vector<double> const &
PatchTree::getStencilMatrix<double>() const {
assert(!_stencilMatrixDouble.empty());
return _stencilMatrixDouble;
}
template <>
inline std::vector<float> &
PatchTree::getStencilMatrix<float>() {
return _stencilMatrixFloat;
}
template <>
inline std::vector<double> &
PatchTree::getStencilMatrix<double>() {
return _stencilMatrixDouble;
}
//
// Inline methods:
//
inline Far::PatchParam
PatchTree::GetSubPatchParam(int subPatch) const {
return _patchParams[subPatch];
}
inline int
PatchTree::FindSubPatch(double u, double v, int subFace, int maxDep) const {
return searchQuadtree(u, v, subFace, maxDep);
}
template <typename REAL>
inline REAL const *
PatchTree::GetStencilMatrix() const {
return &getStencilMatrix<REAL>()[0];
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_PATCH_TREE */

View File

@@ -0,0 +1,582 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../bfr/patchTreeBuilder.h"
#include "../far/primvarRefiner.h"
#include "../far/topologyRefiner.h"
#include "../far/topologyDescriptor.h"
#include "../far/patchBuilder.h"
#include "../far/sparseMatrix.h"
#include "../far/ptexIndices.h"
#include "../vtr/stackBuffer.h"
#include <cstdio>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
using Vtr::internal::Level;
using Vtr::internal::StackBuffer;
using Far::TopologyRefiner;
using Far::Index;
using Far::ConstIndexArray;
using Far::SparseMatrix;
using Far::PatchBuilder;
using Far::PatchDescriptor;
using Far::PatchParam;
//
// Construction initializes some of the main components of the
// build process (e.g. the Far::PatchBuilder) but defers most of
// the work to other methods:
//
PatchTreeBuilder::PatchTreeBuilder(TopologyRefiner & faceRefiner,
Options const & options) :
_patchTree(new PatchTree),
_faceRefiner(faceRefiner),
_faceAtRoot(0),
_patchBuilder(0) {
//
// Adaptive refinement in Far requires smooth level <= sharp level,
// with the sharp level taking precedence. And if attempting to
// generate patches at the base level, force at least one level of
// refinement when necessary:
//
int adaptiveLevelPrimary = options.maxPatchDepthSharp;
int adaptiveLevelSecondary = options.maxPatchDepthSmooth;
if (adaptiveLevelSecondary > adaptiveLevelPrimary) {
adaptiveLevelSecondary = adaptiveLevelPrimary;
}
// If primary is 0, so is secondary -- see if level 1 required:
if (adaptiveLevelSecondary == 0) {
if (rootFaceNeedsRefinement()) {
adaptiveLevelPrimary = std::max(1, adaptiveLevelPrimary);
adaptiveLevelSecondary = 1;
}
}
//
// Apply adaptive refinement to a local refiner for this face:
//
TopologyRefiner::AdaptiveOptions adaptiveOptions(adaptiveLevelPrimary);
adaptiveOptions.SetSecondaryLevel(adaptiveLevelSecondary);
adaptiveOptions.useInfSharpPatch = true;
adaptiveOptions.useSingleCreasePatch = false;
adaptiveOptions.considerFVarChannels = false;
ConstIndexArray baseFaceArray(&_faceAtRoot, 1);
_faceRefiner.RefineAdaptive(adaptiveOptions, baseFaceArray);
//
// Determine offsets per level (we could eventually include local
// points in the levels in which the patch occurs)
//
int numLevels = _faceRefiner.GetNumLevels();
_levelOffsets.resize(1 + numLevels);
_levelOffsets[0] = 0;
for (int i = 0; i < numLevels; ++i) {
_levelOffsets[1 + i] = _levelOffsets[i]
+ _faceRefiner.GetLevel(i).GetNumVertices();
}
//
// Create a PatchBuilder for this refiner:
//
PatchBuilder::BasisType patchBuilderIrregularBasis;
if (options.irregularBasis == Options::REGULAR) {
patchBuilderIrregularBasis = PatchBuilder::BASIS_REGULAR;
} else if (options.irregularBasis == Options::LINEAR) {
patchBuilderIrregularBasis = PatchBuilder::BASIS_LINEAR;
} else {
patchBuilderIrregularBasis = PatchBuilder::BASIS_GREGORY;
}
PatchBuilder::Options patchOptions;
patchOptions.regBasisType = PatchBuilder::BASIS_REGULAR;
patchOptions.irregBasisType = patchBuilderIrregularBasis;
patchOptions.approxInfSharpWithSmooth = false;
patchOptions.approxSmoothCornerWithSharp = false;
patchOptions.fillMissingBoundaryPoints = true;
_patchBuilder = PatchBuilder::Create(faceRefiner, patchOptions);
//
// Initialize general PatchTree members relating to patch topology:
//
Vtr::internal::Level const & baseLevel = _faceRefiner.getLevel(0);
int thisFaceSize = baseLevel.getFaceVertices(_faceAtRoot).size();
int regFaceSize = _patchBuilder->GetRegularFaceSize();
// Configuration:
_patchTree->_useDoublePrecision = options.useDoublePrecision;
_patchTree->_patchesIncludeNonLeaf = options.includeInteriorPatches;
_patchTree->_patchesAreTriangular = (regFaceSize == 3);
_patchTree->_regPatchType = _patchBuilder->GetRegularPatchType();
_patchTree->_irregPatchType = _patchBuilder->GetIrregularPatchType();
_patchTree->_regPatchSize =
PatchDescriptor(_patchTree->_regPatchType).GetNumControlVertices();
_patchTree->_irregPatchSize =
PatchDescriptor(_patchTree->_irregPatchType).GetNumControlVertices();
_patchTree->_patchPointStride =
std::max(_patchTree->_regPatchSize, _patchTree->_irregPatchSize);
// Topology:
_patchTree->_numSubFaces = (thisFaceSize == regFaceSize) ? 0 : thisFaceSize;
_patchTree->_numControlPoints = _faceRefiner.GetLevel(0).GetNumVertices();
_patchTree->_numRefinedPoints = _faceRefiner.GetNumVerticesTotal()
- _patchTree->_numControlPoints;
_patchTree->_numSubPatchPoints = _patchTree->_numRefinedPoints;
}
PatchTreeBuilder::~PatchTreeBuilder() {
delete _patchBuilder;
}
const PatchTree *
PatchTreeBuilder::Build() {
identifyPatches();
initializePatches();
if (_patchTree->_useDoublePrecision) {
initializeStencilMatrix<double>();
} else {
initializeStencilMatrix<float>();
}
initializeQuadTree();
return _patchTree;
}
bool
PatchTreeBuilder::rootFaceNeedsRefinement() const {
//
// The Far::PatchBuilder cannot construct a single patch from a face
// in the base level under the following circumstances:
//
// - the face is or is adjacent to an irregular face (non-quad)
// - the face contains an inf-sharp dart vertex
// - the face contains an interior val-2 vertex
// - the face contains an interior val-3 vertex adj to a tri
//
// All but the first are subject to additional conditions (e.g.
// whether the irregular feature is isolated or not) but until those
// conditions are clear, such features will trigger refinement.
//
int baseFace = _faceAtRoot;
Level const & baseLevel = _faceRefiner.getLevel(0);
Level::VTag const & fTags = baseLevel.getFaceCompositeVTag(baseFace);
ConstIndexArray fVerts = baseLevel.getFaceVertices(baseFace);
//
// Vertices incident non-quads in any way are easily detected:
//
if (fTags._incidIrregFace) return true;
//
// A dart and inf-sharp irregularity may indicate an inf-sharp dart,
// so inspect the face-vertices:
//
if ((fTags._rule & Sdc::Crease::RULE_DART) && fTags._infIrregular) {
for (int i = 0; i < fVerts.size(); ++i) {
Level::VTag const & vTag = baseLevel.getVertexTag(fVerts[i]);
if ((vTag._rule & Sdc::Crease::RULE_DART) && vTag._infSharpEdges) {
// WIP - inf-sharp dart is fine in some cases (TBD):
// - possibly when the edge-end isolated
// - refine for any occurrence until fully determined
return true;
}
}
}
//
// Interior extra-ordinary vertices of low valence require inspection
// of the face-vertices to test valence and other conditions:
//
if (fTags._xordinary) {
for (int i = 0, fSize = fVerts.size(); i < fSize; ++i) {
Level::VTag const & vTag = baseLevel.getVertexTag(fVerts[i]);
if (vTag._xordinary && !vTag._boundary && !vTag._infSharpEdges) {
int vValence = baseLevel.getVertexFaces(fVerts[i]).size();
if ((vValence == 2) || ((vValence == 3) && (fSize == 3))) {
// WIP - low valence verts are fine in some cases (TBD)
// - val-2 a problem only when two adjacent
// - refine for any occurrence until fully determined
return true;
}
}
}
}
return false;
}
bool
PatchTreeBuilder::testFaceAncestors() const {
// Conditions of overlapping faces that require testing base face:
return (_patchBuilder->GetRegularFaceSize() == 3) &&
(_faceRefiner.getLevel(0).getNumEdges() == 3) &&
(_faceRefiner.getLevel(0).getNumFaces() > 1);
}
bool
PatchTreeBuilder::faceAncestorIsRoot(int level, int face) const {
// Move up the hierarchy to the base level:
for (int i = level; i > 0; --i) {
face = _faceRefiner.getRefinement(i-1).getChildFaceParentFace(face);
}
return (face == _faceAtRoot);
}
void
PatchTreeBuilder::identifyPatches() {
//
// Take inventory of the patches. Only one face exists at the base
// level -- the root face. Check all other levels breadth first:
//
bool incNonLeaf = _patchTree->_patchesIncludeNonLeaf;
_patchFaces.clear();
int numIrregPatches = 0;
if (_patchBuilder->IsFaceAPatch(0, _faceAtRoot)) {
if (incNonLeaf || _patchBuilder->IsFaceALeaf(0, _faceAtRoot)) {
bool isRegular = _patchBuilder->IsPatchRegular(0, _faceAtRoot);
_patchFaces.push_back(PatchFace(0, _faceAtRoot, isRegular));
numIrregPatches += !isRegular;
}
}
// Under rare circumstances, the normally quick test for a patch is
// flawed and includes faces descended from neighboring faces:
bool testBaseFace = testFaceAncestors();
int numLevels = _faceRefiner.GetNumLevels();
for (int level = 1; level < numLevels; ++level) {
int numFaces = _faceRefiner.getLevel(level).getNumFaces();
for (int face = 0; face < numFaces; ++face) {
if (testBaseFace && !faceAncestorIsRoot(level, face)) continue;
if (_patchBuilder->IsFaceAPatch(level, face)) {
if (incNonLeaf || _patchBuilder->IsFaceALeaf(level, face)) {
bool isRegular = _patchBuilder->IsPatchRegular(level, face);
_patchFaces.push_back(PatchFace(level, face, isRegular));
numIrregPatches += !isRegular;
}
}
}
}
//
// Allocate and populate the arrays of patch data for the identified
// patches:
//
int numPatches = (int) _patchFaces.size();
assert(numPatches);
_patchTree->_patchPoints.resize(numPatches * _patchTree->_patchPointStride);
_patchTree->_patchParams.resize(numPatches);
_patchTree->_numIrregPatches = numIrregPatches;
_patchTree->_numSubPatchPoints += numIrregPatches *
_patchTree->_irregPatchSize;
}
void
PatchTreeBuilder::initializePatches() {
// Keep track of the growing index of local points in irregular patches:
int irregPointIndexBase = _patchTree->_numControlPoints +
_patchTree->_numRefinedPoints;
Far::PtexIndices ptexIndices(_faceRefiner);
for (size_t i = 0; i < _patchFaces.size(); ++i) {
PatchFace const & pf = _patchFaces[i];
PatchParam & patchParam = _patchTree->_patchParams[i];
Index * patchPoints =
&_patchTree->_patchPoints[i * _patchTree->_patchPointStride];
if (pf.isRegular) {
// Determine boundary mask before computing/assigning PatchParam:
int boundaryMask =
_patchBuilder->GetRegularPatchBoundaryMask(pf.level, pf.face);
patchParam = _patchBuilder->ComputePatchParam(pf.level, pf.face,
ptexIndices, true, boundaryMask, true);
// Gather the points of the patch -- since they are assigned
// directly into the PatchTree's buffer by the PatchBuilder
// here, they must be offset as a post-process:
_patchBuilder->GetRegularPatchPoints(pf.level, pf.face,
boundaryMask, patchPoints);
for (int j = 0; j < _patchTree->_regPatchSize; ++j) {
patchPoints[j] += _levelOffsets[pf.level];
}
} else {
// Compute/assign the PatchParam for an irregular patch:
patchParam = _patchBuilder->ComputePatchParam(pf.level, pf.face,
ptexIndices, false /*irreg*/, 0 /*mask*/, false);
// Assign indices of new/local points for this irregular patch:
for (int j = 0; j < _patchTree->_irregPatchSize; ++j) {
patchPoints[j] = irregPointIndexBase ++;
}
}
}
}
//
// Some local interpolatable types for combining stencil vectors -- the
// rows of the stencil matrix:
//
namespace {
//
// When accessing a "row" for a control point, the only non-zero
// entry is that at the index, with a value of 1, so just store
// that index so the StencilRows can combine it:
//
struct ControlRow {
ControlRow(int index) : _index(index) { }
ControlRow() { }
ControlRow operator[] (int index) const {
return ControlRow(index);
}
// Members:
int _index;
};
//
// A "row" for each stencil is just our typical vector of variable
// size that needs to support [].
//
// For the first level, there are no source rows for the control
// points so combine with the proxy ControlRow defined above. All
// other levels will accumulate StencilRows as weighted combinations
// of other StencilRows.
//
// WIP - consider combining StencilRows to exploit SSE/AVX vectorization
// - we can (in future) easily guarantee both are 4-word aligned
// - we can also pad the rows to a multiple of 4
// - prefer writing the combination in a portable way that makes
// use of auto-vectorization
//
template <typename REAL>
struct StencilRow {
StencilRow() : _data(0), _size(0) { }
StencilRow(REAL * data, int size) :
_data(data), _size(size) { }
StencilRow(REAL const * data, int size) :
_data(const_cast<REAL*>(data)), _size(size) { }
void Clear() {
for (int i = 0; i < _size; ++i) {
_data[i] = 0.0f;
}
}
void AddWithWeight(ControlRow const & src, REAL weight) {
assert(src._index >= 0);
_data[src._index] += weight;
}
void AddWithWeight(StencilRow const & src, REAL weight) {
assert(src._size == _size);
// Weights passed here by PrimvarRefiner should be non-zero
// WIP - see note on potential/future auto-vectorization above
for (int i = 0; i < _size; ++i) {
_data[i] += weight * src._data[i];
}
}
StencilRow operator[](int index) const {
return StencilRow(_data + index * _size, _size);
}
// Members:
REAL * _data;
int _size;
};
}
template <typename REAL>
void
PatchTreeBuilder::initializeStencilMatrix() {
if (_patchTree->_numSubPatchPoints == 0) return;
//
// Allocate and initialize a full matrix of true stencils (i.e.
// factored in terms of the control points):
//
int numPointStencils = _patchTree->_numRefinedPoints +
(_patchTree->_numIrregPatches *
_patchTree->_irregPatchSize);
int numControlPoints = _patchTree->_numControlPoints;
std::vector<REAL> & stencilMatrix = _patchTree->getStencilMatrix<REAL>();
stencilMatrix.resize(numPointStencils*numControlPoints);
//
// For refined points, initialize successive rows of the stencil matrix
// a level at a time using the PrimvarRefiner to accumulate contributing
// rows:
//
int numLevels = _faceRefiner.GetNumLevels();
if (numLevels > 1) {
Far::PrimvarRefinerReal<REAL> primvarRefiner(_faceRefiner);
StencilRow<REAL> dstRow(&stencilMatrix[0], numControlPoints);
primvarRefiner.Interpolate(1, ControlRow(-1), dstRow);
for (int level = 2; level < numLevels; ++level) {
StencilRow<REAL> srcRow = dstRow;
dstRow = srcRow[_faceRefiner.getLevel(level-1).getNumVertices()];
primvarRefiner.Interpolate(level, srcRow, dstRow);
}
}
//
// For irregular patch points, append rows for each irregular patch:
//
if (_patchTree->_numIrregPatches) {
SparseMatrix<REAL> irregConvMatrix;
std::vector<Index> irregSourcePoints;
int stencilIndexBase = _patchTree->_numRefinedPoints;
for (size_t i = 0; i < _patchFaces.size(); ++i) {
if (!_patchFaces[i].isRegular) {
getIrregularPatchConversion(_patchFaces[i],
irregConvMatrix, irregSourcePoints);
appendConversionStencilsToMatrix(stencilIndexBase,
irregConvMatrix,irregSourcePoints);
stencilIndexBase += _patchTree->_irregPatchSize;
}
}
}
}
template <typename REAL>
void
PatchTreeBuilder::appendConversionStencilsToMatrix(
int stencilBaseIndex,
SparseMatrix<REAL> const & conversionMatrix,
std::vector<Index> const & sourcePoints) {
//
// Each row of the sparse conversion matrix corresponds to a row
// of the stencil matrix -- which will be computed from the weights
// and indices of stencils indicated by the SparseMatrix row:
//
int numControlPoints = _patchTree->_numControlPoints;
int numPatchPoints = conversionMatrix.GetNumRows();
std::vector<REAL> & stencilMatrix = _patchTree->getStencilMatrix<REAL>();
StencilRow<REAL> srcStencils(&stencilMatrix[0], numControlPoints);
StencilRow<REAL> dstStencils = srcStencils[stencilBaseIndex];
for (int i = 0; i < numPatchPoints; ++i) {
StencilRow<REAL> dstStencil = dstStencils[i];
dstStencil.Clear();
int const * rowIndices = &conversionMatrix.GetRowColumns(i)[0];
REAL const * rowWeights = &conversionMatrix.GetRowElements(i)[0];
int rowSize = conversionMatrix.GetRowSize(i);
for (int j = 0; j < rowSize; ++j) {
REAL srcWeight = rowWeights[j];
int srcIndex = sourcePoints[rowIndices[j]];
// Simply increment single weight if this is a control point
if (srcIndex < numControlPoints) {
dstStencil._data[srcIndex] += srcWeight;
} else {
int srcStencilIndex = srcIndex - numControlPoints;
StencilRow<REAL> srcStencil = srcStencils[srcStencilIndex];
dstStencil.AddWithWeight(srcStencil, srcWeight);
}
}
}
}
void
PatchTreeBuilder::initializeQuadTree() {
_patchTree->buildQuadtree();
}
template <typename REAL>
void
PatchTreeBuilder::getIrregularPatchConversion(PatchFace const & pf,
SparseMatrix<REAL> & conversionMatrix,
std::vector<Index> & sourcePoints) {
//
// The topology of an irregular patch is determined by its four corners:
//
Level::VSpan cornerSpans[4];
_patchBuilder->GetIrregularPatchCornerSpans(pf.level, pf.face, cornerSpans);
//
// Compute the conversion matrix from refined/source points to the
// set of points local to this patch:
//
_patchBuilder->GetIrregularPatchConversionMatrix(pf.level, pf.face,
cornerSpans, conversionMatrix);
//
// Identify the refined/source points for the patch and append stencils
// for the local patch points in terms of the source points:
//
int numSourcePoints = conversionMatrix.GetNumColumns();
sourcePoints.resize(numSourcePoints);
_patchBuilder->GetIrregularPatchSourcePoints(pf.level, pf.face,
cornerSpans, &sourcePoints[0]);
int sourceIndexOffset = _levelOffsets[pf.level];
for (int i = 0; i < numSourcePoints; ++i) {
sourcePoints[i] += sourceIndexOffset;
}
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv

View File

@@ -0,0 +1,128 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_PATCH_TREE_BUILDER_H
#define OPENSUBDIV3_BFR_PATCH_TREE_BUILDER_H
#include "../version.h"
#include "../bfr/patchTree.h"
#include "../far/topologyRefiner.h"
#include "../far/patchBuilder.h"
#include "../far/sparseMatrix.h"
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// The PatchTreeBuilder class assemble a PatchTree from one or more
// topological descriptions -- keeping the PatchTree class free from
// the dependencies of whatever its topological source may be.
//
// Having been originally conceived as part of Far, the PatchTree is
// assembled from a given Far::TopologyRefiner, which is constructed
// for a local neighborhood by another class. Some of the internal
// details of PatchTree assembly also include complexity from that
// history when a PatchTree could be constructed from any face of any
// given TopologyRefiner -- which is no longer the case. The public
// now prevents such unsupported flexibility, but internal support
// remains.
//
class PatchTreeBuilder {
public:
//
// Minimize the number of shape approximating Options here (compared
// to the Far classes):
//
// Note that the "interior patches" capability of PatchTree is not
// used in Bfr and so is never enabled. It is left available as a
// reminder of that ability for future use.
//
struct Options {
enum BasisType { REGULAR, GREGORY, LINEAR };
Options(int depth = 4) : irregularBasis((unsigned char) GREGORY),
maxPatchDepthSharp((unsigned char) depth),
maxPatchDepthSmooth(15),
includeInteriorPatches(false),
useDoublePrecision(false) { }
unsigned char irregularBasis;
unsigned char maxPatchDepthSharp;
unsigned char maxPatchDepthSmooth;
unsigned char includeInteriorPatches : 1;
unsigned char useDoublePrecision : 1;
};
public:
//
// Public interface intended for use by other builders requiring
// PatchTrees -- now reduced essentially to a single method:
//
PatchTreeBuilder(Far::TopologyRefiner & refiner, Options const & options);
~PatchTreeBuilder();
const PatchTree * Build();
PatchTree * GetPatchTree() const { return _patchTree; }
private:
// Internal struct for a patch in the refinement hierarchy:
struct PatchFace {
PatchFace(int levelArg, int faceArg, bool isReg = true) :
face(faceArg), level((short)levelArg), isRegular(isReg) { }
int face;
short level;
short isRegular;
};
// Internal methods to identify and assemble patches and the tree:
bool rootFaceNeedsRefinement() const;
bool testFaceAncestors() const;
bool faceAncestorIsRoot(int level, int face) const;
void identifyPatches();
void initializePatches();
void initializeQuadTree();
// Internal methods to assemble the matrix of stencils converting
// points of irregular patches from points in the refined levels:
template <typename REAL>
void initializeStencilMatrix();
template <typename REAL>
void getIrregularPatchConversion(PatchFace const & patchFace,
Far::SparseMatrix<REAL> & convMatrix,
std::vector<Far::Index> & srcPoints);
template <typename REAL>
void appendConversionStencilsToMatrix(int stencilIndexBase,
Far::SparseMatrix<REAL> const & convMatrix,
std::vector<Far::Index> const & srcPoints);
private:
// The PatchTree instance being assembled:
PatchTree * _patchTree;
// Member variables supporting its assembly:
Far::TopologyRefiner & _faceRefiner;
Far::Index _faceAtRoot;
std::vector<int> _levelOffsets;
std::vector<PatchFace> _patchFaces;
Far::PatchBuilder * _patchBuilder;
};
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_PATCH_TREE_BUILDER_H */

View File

@@ -0,0 +1,526 @@
//
// Copyright 2022 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_POINT_OPERATIONS_H
#define OPENSUBDIV3_BFR_POINT_OPERATIONS_H
#include "../version.h"
#include <cstring>
#include <cassert>
#include <cstdio>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// Internal utilities for efficiently dealing with single and multiple
// floating point tuples, i.e. "points":
//
namespace points {
//
// Simple classes for primitive point operations -- to be specialized for
// small, fixed sizes via template <int SIZE>.
//
// These class templates with static methods are partially specialized for
// SIZE. The copy operation is made a separate class due to its additional
// template parameters (different precision for source and destination).
// Combining the two can lead to undesired ambiguities with the desired
// partial specializations.
//
template <typename REAL, int SIZE>
struct PointBuilder {
static void Set(REAL pDst[], REAL w, REAL const pSrc[], int size) {
for (int i = 0; i < size; ++i) {
pDst[i] = w * pSrc[i];
}
}
static void Add(REAL pDst[], REAL w, REAL const pSrc[], int size) {
for (int i = 0; i < size; ++i) {
pDst[i] += w * pSrc[i];
}
}
};
template <typename REAL_DST, typename REAL_SRC, int SIZE = 0>
struct PointCopier {
static void Copy(REAL_DST * pDst, REAL_SRC const * pSrc, int size) {
for (int i = 0; i < size; ++i) {
pDst[i] = (REAL_DST) pSrc[i];
}
}
};
// Specialization for SIZE = 1:
template <typename REAL>
struct PointBuilder<REAL, 1> {
static void Set(REAL * pDst, REAL w, REAL const * pSrc, int) {
pDst[0] = w * pSrc[0];
}
static void Add(REAL * pDst, REAL w, REAL const * pSrc, int) {
pDst[0] += w * pSrc[0];
}
};
template <typename REAL>
struct PointCopier<REAL, REAL, 1> {
static void Copy(REAL * pDst, REAL const * pSrc, int) {
pDst[0] = pSrc[0];
}
};
// Specialization for SIZE = 2:
template <typename REAL>
struct PointBuilder<REAL, 2> {
static void Set(REAL * pDst, REAL w, REAL const * pSrc, int) {
pDst[0] = w * pSrc[0];
pDst[1] = w * pSrc[1];
}
static void Add(REAL * pDst, REAL w, REAL const * pSrc, int) {
pDst[0] += w * pSrc[0];
pDst[1] += w * pSrc[1];
}
};
template <typename REAL>
struct PointCopier<REAL, REAL, 2> {
static void Copy(REAL * pDst, REAL const * pSrc, int) {
pDst[0] = pSrc[0];
pDst[1] = pSrc[1];
}
};
// Specialization for SIZE = 3:
template <typename REAL>
struct PointBuilder<REAL, 3> {
static void Set(REAL * pDst, REAL w, REAL const * pSrc, int) {
pDst[0] = w * pSrc[0];
pDst[1] = w * pSrc[1];
pDst[2] = w * pSrc[2];
}
static void Add(REAL * pDst, REAL w, REAL const * pSrc, int) {
pDst[0] += w * pSrc[0];
pDst[1] += w * pSrc[1];
pDst[2] += w * pSrc[2];
}
};
template <typename REAL>
struct PointCopier<REAL, REAL, 3> {
static void Copy(REAL * pDst, REAL const * pSrc, int) {
pDst[0] = pSrc[0];
pDst[1] = pSrc[1];
pDst[2] = pSrc[2];
}
};
// Specialization for SIZE = 4:
template <typename REAL>
struct PointBuilder<REAL, 4> {
static void Set(REAL * pDst, REAL w, REAL const * pSrc, int) {
pDst[0] = w * pSrc[0];
pDst[1] = w * pSrc[1];
pDst[2] = w * pSrc[2];
pDst[3] = w * pSrc[3];
}
static void Add(REAL * pDst, REAL w, REAL const * pSrc, int) {
pDst[0] += w * pSrc[0];
pDst[1] += w * pSrc[1];
pDst[2] += w * pSrc[2];
pDst[3] += w * pSrc[3];
}
};
template <typename REAL>
struct PointCopier<REAL, REAL, 4> {
static void Copy(REAL * pDst, REAL const * pSrc, int) {
pDst[0] = pSrc[0];
pDst[1] = pSrc[1];
pDst[2] = pSrc[2];
pDst[3] = pSrc[3];
}
};
// Additional specialization for copy when precision matches:
template <typename REAL, int SIZE>
struct PointCopier<REAL, REAL, SIZE> {
static void Copy(REAL * pDst, REAL const * pSrc, int size) {
std::memcpy(pDst, pSrc, size * sizeof(REAL));
}
};
//
// Each major operation is encapsulated in a separate class consisting of
// the following:
//
// - a struct containing all parameters of the operation
// - a single private method with a generic implementing for all SIZEs
// - a public method invoking specializations for small SIZEs
//
// Further specializations of the private implementation of each operation
// are possible.
//
//
// The Parameters for the operations vary slightly (based on the operation
// and potentially varying data destinations) but generally consist of the
// following:
//
// - a description of a full set of input points involved, including:
// - the data, size and stride of the array of points
//
// - a description of the subset of the input points used, including
// - the number and optional indices for each "source" point
//
// - a description of the resulting points, including:
// - the number of resulting points
// - an array of locations or single location for resulting points
// - an array of or consecutive weights for all resulting points
//
// Several operations combining control points for patch evaluation make
// use of the same parameters, so these are encapsulated in a common set.
// All classes are expected to declare Parameters for their operation --
// even if it is simply a typedef for the set of common parameters.
//
// Common set of parameters for operations combining points:
template <typename REAL>
struct CommonCombinationParameters {
REAL const * pointData;
int pointSize;
int pointStride;
int const * srcIndices;
int srcCount;
int resultCount;
REAL ** resultArray;
REAL const * const * weightArray;
};
//
// Combination of source points into a single result (for use computing
// position only, applying single stencils, and other purposes):
//
template <typename REAL>
class Combine1 {
public:
typedef CommonCombinationParameters<REAL> Parameters;
private:
template <int SIZE = 0>
static void apply(Parameters const & args) {
typedef struct PointBuilder<REAL,SIZE> Point;
int pSize = args.pointSize;
int pStride = args.pointStride;
REAL const * w = args.weightArray[0];
REAL * p = args.resultArray[0];
if (args.srcIndices == 0) {
REAL const * pSrc = args.pointData;
Point::Set(p, w[0], pSrc, pSize);
for (int i = 1; i < args.srcCount; ++i) {
pSrc += pStride;
Point::Add(p, w[i], pSrc, pSize);
}
} else {
REAL const * pSrc = args.pointData + pStride * args.srcIndices[0];
Point::Set(p, w[0], pSrc, pSize);
for (int i = 1; i < args.srcCount; ++i) {
pSrc = args.pointData + pStride * args.srcIndices[i];
Point::Add(p, w[i], pSrc, pSize);
}
}
}
public:
static void Apply(Parameters const & parameters) {
switch (parameters.pointSize) {
case 1: apply<1>(parameters); break;
case 2: apply<2>(parameters); break;
case 3: apply<3>(parameters); break;
case 4: apply<4>(parameters); break;
default: apply<>(parameters); break;
}
}
};
//
// Combination of source points into three results (for use computing
// position and 1st derivatives):
//
template <typename REAL>
class Combine3 {
public:
typedef CommonCombinationParameters<REAL> Parameters;
private:
template <int SIZE = 0>
static void apply(Parameters const & args) {
typedef struct PointBuilder<REAL,SIZE> Point;
int pSize = args.pointSize;
int pStride = args.pointStride;
REAL const * const * wArray = args.weightArray;
REAL ** pArray = args.resultArray;
//
// Apply each successive control point to all derivatives at once,
// rather than computing each derivate independently:
//
REAL const * pSrc = (args.srcIndices == 0) ? args.pointData :
(args.pointData + pStride * args.srcIndices[0]);
Point::Set(pArray[0], wArray[0][0], pSrc, pSize);
Point::Set(pArray[1], wArray[1][0], pSrc, pSize);
Point::Set(pArray[2], wArray[2][0], pSrc, pSize);
for (int i = 1; i < args.srcCount; ++i) {
pSrc = (args.srcIndices == 0) ? (pSrc + pStride) :
(args.pointData + pStride * args.srcIndices[i]);
Point::Add(pArray[0], wArray[0][i], pSrc, pSize);
Point::Add(pArray[1], wArray[1][i], pSrc, pSize);
Point::Add(pArray[2], wArray[2][i], pSrc, pSize);
}
}
public:
static void Apply(Parameters const & parameters) {
switch (parameters.pointSize) {
case 1: apply<1>(parameters); break;
case 2: apply<2>(parameters); break;
case 3: apply<3>(parameters); break;
case 4: apply<4>(parameters); break;
default: apply<>(parameters); break;
}
}
};
//
// Combination of source points into an arbitrary array of results (for
// use computing position with all derivatives, i.e. 6 results):
//
template <typename REAL>
class CombineMultiple {
public:
typedef CommonCombinationParameters<REAL> Parameters;
private:
template <int SIZE = 0>
static void
apply(Parameters const & args) {
typedef struct PointBuilder<REAL,SIZE> Point;
int pSize = args.pointSize;
int pStride = args.pointStride;
REAL const * const * wArray = args.weightArray;
REAL ** pArray = args.resultArray;
//
// Apply each successive control point to all derivatives at once,
// rather than computing each derivate independently:
//
REAL const * pSrc = (args.srcIndices == 0) ? args.pointData :
(args.pointData + pStride * args.srcIndices[0]);
for (int j = 0; j < args.resultCount; ++j) {
Point::Set(pArray[j], wArray[j][0], pSrc, pSize);
}
for (int i = 1; i < args.srcCount; ++i) {
pSrc = (args.srcIndices == 0) ? (pSrc + pStride) :
(args.pointData + pStride * args.srcIndices[i]);
for (int j = 0; j < args.resultCount; ++j) {
Point::Add(pArray[j], wArray[j][i], pSrc, pSize);
}
}
}
public:
static void
Apply(Parameters const & parameters) {
switch (parameters.pointSize) {
case 1: apply<1>(parameters); break;
case 2: apply<2>(parameters); break;
case 3: apply<3>(parameters); break;
case 4: apply<4>(parameters); break;
default: apply<>(parameters); break;
}
}
};
//
// Combination of a subset of N input points into M resulting points
// in consecutive memory locations. The weights for the resulting M
// points (N for the input points contributing to each result) are
// also stored consecutively:
//
template <typename REAL>
class CombineConsecutive {
public:
struct Parameters {
REAL const * pointData;
int pointSize;
int pointStride;
int srcCount;
int resultCount;
REAL * resultData;
REAL const * weightData;
};
private:
template <int SIZE = 0>
static void
apply(Parameters const & args) {
typedef struct PointBuilder<REAL,SIZE> Point;
REAL const * w = args.weightData;
REAL * p = args.resultData;
for (int i = 0; i < args.resultCount; ++i) {
REAL const * pSrc = args.pointData;
Point::Set(p, w[0], pSrc, args.pointSize);
for (int j = 1; j < args.srcCount; ++j) {
pSrc += args.pointStride;
Point::Add(p, w[j], pSrc, args.pointSize);
}
p += args.pointStride;
w += args.srcCount;
}
}
public:
static void
Apply(Parameters const & parameters) {
switch (parameters.pointSize) {
case 1: apply<1>(parameters); break;
case 2: apply<2>(parameters); break;
case 3: apply<3>(parameters); break;
case 4: apply<4>(parameters); break;
default: apply<>(parameters); break;
}
}
};
//
// Split the N-sided face formed by the N input control points, i.e.
// compute the midpoint of the face and the midpoint of each edge --
// to be stored consecutively in the given location for results:
//
template <typename REAL>
class SplitFace {
public:
struct Parameters {
REAL const * pointData;
int pointSize;
int pointStride;
int srcCount;
REAL * resultData;
};
private:
template <int SIZE = 0>
static void apply(Parameters const & args) {
typedef struct PointBuilder<REAL,SIZE> Point;
int N = args.srcCount;
REAL invN = 1.0f / (REAL) N;
REAL * facePoint = args.resultData;
std::memset(facePoint, 0, args.pointSize * sizeof(REAL));
for (int i = 0; i < N; ++i) {
int j = (i < (N - 1)) ? (i + 1) : 0;
REAL const * pi = args.pointData + args.pointStride * i;
REAL const * pj = args.pointData + args.pointStride * j;
Point::Add(facePoint, invN, pi, args.pointSize);
REAL * edgePoint = args.resultData + args.pointStride * (1 + i);
Point::Set(edgePoint, 0.5f, pi, args.pointSize);
Point::Add(edgePoint, 0.5f, pj, args.pointSize);
}
}
public:
static void Apply(Parameters const & parameters) {
switch (parameters.pointSize) {
case 1: apply<1>(parameters); break;
case 2: apply<2>(parameters); break;
case 3: apply<3>(parameters); break;
case 4: apply<4>(parameters); break;
default: apply<>(parameters); break;
}
}
};
//
// Copy a subset of N input points -- identified by the indices given for
// each -- to the resulting location specified:
//
template <typename REAL_DST, typename REAL_SRC>
class CopyConsecutive {
public:
struct Parameters {
REAL_SRC const * pointData;
int pointSize;
int pointStride;
int const * srcIndices;
int srcCount;
REAL_DST * resultData;
int resultStride;
};
private:
template <int SIZE = 0>
static void apply(Parameters const & args) {
typedef struct PointCopier<REAL_DST,REAL_SRC,SIZE> Point;
for (int i = 0; i < args.srcCount; ++i) {
REAL_DST * pDst = args.resultData + args.resultStride * i;
REAL_SRC const * pSrc = args.pointData +
args.pointStride * args.srcIndices[i];
Point::Copy(pDst, pSrc, args.pointSize);
}
}
public:
static void Apply(Parameters const & parameters) {
switch (parameters.pointSize) {
case 1: apply<1>(parameters); break;
case 2: apply<2>(parameters); break;
case 3: apply<3>(parameters); break;
case 4: apply<4>(parameters); break;
default: apply<>(parameters); break;
}
}
};
} // end namespace points
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_POINT_OPERATIONS_H */

View File

@@ -0,0 +1,537 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../bfr/refinerSurfaceFactory.h"
#include "../bfr/vertexDescriptor.h"
#include "../far/topologyRefiner.h"
#include <map>
#include <cstdio>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
using Far::ConstIndexArray;
using Far::ConstLocalIndexArray;
//
// Main constructor and destructor:
//
RefinerSurfaceFactoryBase::RefinerSurfaceFactoryBase(
Far::TopologyRefiner const & mesh, Options const & factoryOptions) :
SurfaceFactory(mesh.GetSchemeType(),
mesh.GetSchemeOptions(),
factoryOptions),
_mesh(mesh),
_numFaces(mesh.GetLevel(0).GetNumFaces()),
_numFVarChannels(mesh.GetNumFVarChannels()) {
// Management of internal cache deferred to subclasses
}
//
// Inline support method to provide a valid face-varying channel from
// a given face-varying ID/handle used in the factory interface:
//
inline int
RefinerSurfaceFactoryBase::getFaceVaryingChannel(FVarID fvarID) const {
return ((0 <= fvarID) && (fvarID < _numFVarChannels)) ? (int)fvarID : -1;
}
//
// Virtual methods from the SurfaceFactoryMeshAdapter interface supporting
// Surface construction and initialization:
//
// Simple/trivial face queries:
//
bool
RefinerSurfaceFactoryBase::isFaceHole(Index face) const {
return _mesh.HasHoles() && _mesh.getLevel(0).isFaceHole(face);
}
int
RefinerSurfaceFactoryBase::getFaceSize(Index baseFace) const {
return _mesh.GetLevel(0).GetFaceVertices(baseFace).size();
}
//
// Specifying vertex or face-varying indices for a face:
//
int
RefinerSurfaceFactoryBase::getFaceVertexIndices(Index baseFace,
Index indices[]) const {
ConstIndexArray fVerts = _mesh.GetLevel(0).GetFaceVertices(baseFace);
std::memcpy(indices, &fVerts[0], fVerts.size() * sizeof(Index));
return fVerts.size();
}
int
RefinerSurfaceFactoryBase::getFaceFVarValueIndices(Index baseFace,
FVarID fvarID, Index indices[]) const {
int fvarChannel = getFaceVaryingChannel(fvarID);
if (fvarChannel < 0) return 0;
ConstIndexArray fvarValues =
_mesh.GetLevel(0).GetFaceFVarValues(baseFace, fvarChannel);
std::memcpy(indices, &fvarValues[0], fvarValues.size() * sizeof(Index));
return fvarValues.size();
}
//
// Specifying the topology around a face-vertex:
//
int
RefinerSurfaceFactoryBase::populateFaceVertexDescriptor(
Index baseFace, int cornerVertex,
VertexDescriptor * vertexDescriptor) const {
VertexDescriptor & vd = *vertexDescriptor;
//
// Identify the vertex index for the specified corner of the face
// and topology information related to it:
//
Vtr::internal::Level const & baseLevel = _mesh.getLevel(0);
Far::Index vIndex = baseLevel.getFaceVertices(baseFace)[cornerVertex];
ConstIndexArray vFaces = baseLevel.getVertexFaces(vIndex);
int nFaces = vFaces.size();
Vtr::internal::Level::VTag vTag = baseLevel.getVertexTag(vIndex);
bool isManifold = !vTag._nonManifold;
//
// Initialize, assign and finalize the vertex topology:
//
// Note there is no need to check valence or face sizes with any
// max here as TopologyRefiner construction excludes extreme cases.
//
vd.Initialize(nFaces);
{
// Assign ordering and boundary status:
vd.SetManifold(isManifold);
vd.SetBoundary(vTag._boundary);
// Assign face sizes if not all regular:
if (vTag._incidIrregFace) {
for (int i = 0; i < nFaces; ++i) {
vd.SetIncidentFaceSize(i,
baseLevel.getFaceVertices(vFaces[i]).size());
}
}
// Assign vertex sharpness when present:
if (vTag._semiSharp || vTag._infSharp) {
vd.SetVertexSharpness(
baseLevel.getVertexSharpness(vIndex));
}
// Assign edge sharpness when present:
if (vTag._semiSharpEdges || vTag._infSharpEdges) {
if (isManifold) {
// Can use manifold/ordered edge indices here:
ConstIndexArray vEdges = baseLevel.getVertexEdges(vIndex);
for (int i = 0; i < vEdges.size(); ++i) {
vd.SetManifoldEdgeSharpness(i,
baseLevel.getEdgeSharpness(vEdges[i]));
}
} else {
// Must use face-edges and identify next/prev edges in face:
ConstLocalIndexArray vInFace =
baseLevel.getVertexFaceLocalIndices(vIndex);
for (int i = 0; i < nFaces; ++i) {
ConstIndexArray fEdges = baseLevel.getFaceEdges(vFaces[i]);
int eLeading = vInFace[i];
int eTrailing = (eLeading ? eLeading : fEdges.size()) - 1;
vd.SetIncidentFaceEdgeSharpness(i,
baseLevel.getEdgeSharpness(fEdges[eLeading]),
baseLevel.getEdgeSharpness(fEdges[eTrailing]));
}
}
}
}
vd.Finalize();
//
// Return the index of the base face around the vertex:
//
// Remember that for some non-manifold cases the face may occur
// multiple times around this vertex, so make sure to identify the
// instance that matches the specified corner of the face.
//
if (isManifold) {
return vFaces.FindIndex(baseFace);
} else {
ConstLocalIndexArray vInFace =
baseLevel.getVertexFaceLocalIndices(vIndex);
for (int i = 0; i < vFaces.size(); ++i) {
if ((vFaces[i] == baseFace) && (vInFace[i] == cornerVertex)) {
return i;
}
}
assert("Cannot identify face-vertex around non-manifold vertex." == 0);
return -1;
}
}
//
// Specifying vertex and face-varying indices around a face-vertex --
// both virtual methods trivially use a common internal method to get
// the indices for a particular vertex Index:
//
int
RefinerSurfaceFactoryBase::getFaceVertexPointIndices(
Index baseFace, int cornerVertex,
Index indices[], int vtxOrFVarChannel) const {
Vtr::internal::Level const & baseLevel = _mesh.getLevel(0);
Far::Index vIndex = baseLevel.getFaceVertices(baseFace)[cornerVertex];
ConstIndexArray vFaces = baseLevel.getVertexFaces(vIndex);
ConstLocalIndexArray vInFace = baseLevel.getVertexFaceLocalIndices(vIndex);
int nIndices = 0;
for (int i = 0; i < vFaces.size(); ++i) {
ConstIndexArray srcIndices = (vtxOrFVarChannel < 0) ?
baseLevel.getFaceVertices(vFaces[i]) :
baseLevel.getFaceFVarValues(vFaces[i], vtxOrFVarChannel);
int srcStart = vInFace[i];
int srcCount = srcIndices.size();
for (int j = srcStart; j < srcCount; ++j) {
indices[nIndices++] = srcIndices[j];
}
for (int j = 0; j < srcStart; ++j) {
indices[nIndices++] = srcIndices[j];
}
}
return nIndices;
}
int
RefinerSurfaceFactoryBase::getFaceVertexIncidentFaceVertexIndices(
Index baseFace, int cornerVertex,
Index indices[]) const {
return getFaceVertexPointIndices(baseFace, cornerVertex, indices, -1);
}
int
RefinerSurfaceFactoryBase::getFaceVertexIncidentFaceFVarValueIndices(
Index baseFace, int corner,
FVarID fvarID, Index indices[]) const {
int fvarChannel = getFaceVaryingChannel(fvarID);
if (fvarChannel < 0) return 0;
return getFaceVertexPointIndices(baseFace, corner, indices, fvarChannel);
}
//
// Optional SurfaceFactoryMeshAdapter methods for determining if a face has
// purely regular topology, and retrieving its control point indices if so:
//
bool
RefinerSurfaceFactoryBase::getFaceNeighborhoodVertexIndicesIfRegular(
Index baseFace, Index vtxIndices[]) const {
//
// Get the composite tag for the corners of the face and reject some
// of the obvious irregular features first:
//
Vtr::internal::Level const & baseLevel = _mesh.getLevel(0);
Vtr::internal::Level::VTag fTag = baseLevel.getFaceCompositeVTag(baseFace);
if (fTag._xordinary || fTag._nonManifold
|| fTag._incidIrregFace
|| fTag._semiSharp || fTag._semiSharpEdges
|| fTag._infIrregular) {
return false;
}
//
// At this point, we have not rejected inf-sharp features, as they
// are intertwined with boundaries -- which may be regular. Recall
// that both edges and vertices may have been explicitly sharpened
// by application of the boundary interpolation options, so we must
// exclude faces with any inf-sharp features added elsewhere.
//
// Recall also that in the case of "boundary none", a face that does
// not have a limit surface will have been tagged as a hole. So all
// faces here -- and all regular faces in general -- have a limit.
//
// To determine regular patches with inf-sharp features, we can first
// trivially reject an interior face if it has any inf-sharp features.
// Otherwise, we have to inspect the vertices of boundary patches:
//
assert(!baseLevel.isFaceHole(baseFace));
if (!fTag._boundary) {
if (fTag._infSharp || fTag._infSharpEdges) {
return false;
}
} else {
ConstIndexArray fVerts = baseLevel.getFaceVertices(baseFace);
for (int i = 0; i < fVerts.size(); ++i) {
Far::Index vIndex = fVerts[i];
Vtr::internal::Level::VTag vTag = baseLevel.getVertexTag(vIndex);
if (!vTag._boundary) {
if (vTag._rule != Sdc::Crease::RULE_SMOOTH) return false;
} else if (baseLevel.getVertexFaces(vIndex).size() == 1) {
if (vTag._rule != Sdc::Crease::RULE_CORNER) return false;
} else {
if (vTag._rule != Sdc::Crease::RULE_CREASE) return false;
}
}
}
// Only regular cases make it this far -- assign indices if requested:
if (vtxIndices) {
getFacePatchPointIndices(baseFace, vtxIndices, -1);
}
return true;
}
bool
RefinerSurfaceFactoryBase::getFaceNeighborhoodFVarValueIndicesIfRegular(
Index baseFace, FVarID fvarID, Index fvarIndices[]) const {
int fvarChannel = getFaceVaryingChannel(fvarID);
if (fvarChannel < 0) return false;
//
// This method will only be invoked when the vertex topology is
// regular, so no need to confirm that here.
//
// It is also recommended that this method only be used when the
// face-varying topology exactly matches the vertex topology, i.e.
// don't try to return a regular boundary patch that is a subset
// of a regular interior patch, as face-varying interpolation
// rules may affect that boundary patch (making it irregular).
//
Vtr::internal::Level const & baseLevel = _mesh.getLevel(0);
bool isRegular = baseLevel.doesFaceFVarTopologyMatch(baseFace, fvarChannel);
if (isRegular && fvarIndices) {
getFacePatchPointIndices(baseFace, fvarIndices, fvarChannel);
}
return isRegular;
}
//
// Supporting functions to extract regular patch points.
//
// The two main functions here load the patch points from faces into
// arrays for patches -- knowing that the mesh topology is regular.
// Various methods to do all or part if this in different forms exist
// in other places in the Far code, but they typically cater to more
// general purposes (e.g. including rotations). These are written to
// be as fast as possible for the purpose here.
//
namespace {
// Local less-verbose typedefs for Far indices and arrays:
typedef Vtr::internal::Level Level;
typedef Far::Index Index;
typedef ConstIndexArray IArray;
typedef ConstLocalIndexArray LIArray;
// Avoid repeated integer modulo N operations:
inline int _mod3(int x) { return (x < 3) ? x : (x - 3); }
inline int _mod4(int x) { return (x & 3); }
inline int _mod6(int x) { return (x < 6) ? x : (x - 6); }
//
// Retrieval of the 16-point patch for quad schemes:
//
template <typename POINT>
int
gatherPatchPoints4(Level const & level, Index face, IArray const & fVerts,
POINT P[], int fvar) {
static int const pointsPerCorner[4][4] = { { 5, 4, 0, 1 },
{ 6, 2, 3, 7 },
{ 10, 11, 15, 14 },
{ 9, 13, 12, 8 } };
for (int i = 0; i < 4; ++i) {
int const * corner = pointsPerCorner[i];
int vIndex = fVerts[i];
IArray vFaces = level.getVertexFaces(vIndex);
LIArray vInFace = level.getVertexFaceLocalIndices(vIndex);
if (vFaces.size() == 4) {
int iOpposite = _mod4(vFaces.FindIndexIn4Tuple(face) + 2);
Index fj = vFaces[iOpposite];
int j = vInFace[iOpposite];
IArray FV = (fvar < 0) ? level.getFaceVertices(fj) :
level.getFaceFVarValues(fj, fvar);
P[corner[0]] = FV[j];
P[corner[1]] = FV[_mod4(j + 1)];
P[corner[2]] = FV[_mod4(j + 2)];
P[corner[3]] = FV[_mod4(j + 3)];
} else if (vFaces.size() == 1) {
Index FVcorner = (fvar < 0) ? vIndex :
level.getFaceFVarValues(vFaces[0], fvar)[vInFace[0]];
P[corner[0]] = FVcorner;
P[corner[1]] = -1;
P[corner[2]] = -1;
P[corner[3]] = -1;
} else if (vFaces[0] == face) {
Index f1 = vFaces[1];
int j1 = vInFace[1];
IArray FV = (fvar < 0) ? level.getFaceVertices(f1) :
level.getFaceFVarValues(f1, fvar);
P[corner[0]] = FV[j1];
P[corner[1]] = FV[_mod4(j1 + 3)];
P[corner[2]] = -1;
P[corner[3]] = -1;
} else {
Index f0 = vFaces[0];
int j0 = vInFace[0];
IArray FV = (fvar < 0) ? level.getFaceVertices(f0) :
level.getFaceFVarValues(f0, fvar);
P[corner[0]] = FV[j0];
P[corner[1]] = -1;
P[corner[2]] = -1;
P[corner[3]] = FV[_mod4(j0 + 1)];
}
}
return 16;
}
//
// Retrieval of the 12-point patch for triangular schemes:
//
template <typename POINT>
int
gatherPatchPoints3(Level const & level, Index face, IArray const & fVerts,
POINT P[], int fvar) {
static int const pointsPerCorner[3][4] = { { 4, 3, 0, 1 },
{ 5, 2, 6, 9 },
{ 8, 11, 10, 7 } };
for (int i = 0; i < 3; ++i) {
int const * corner = pointsPerCorner[i];
int vIndex = fVerts[i];
IArray vFaces = level.getVertexFaces(vIndex);
LIArray vInFace = level.getVertexFaceLocalIndices(vIndex);
if (vFaces.size() == 6) {
int iOpposite = _mod6(vFaces.FindIndex(face) + 3);
Index f0 = vFaces[iOpposite];
int j0 = vInFace[iOpposite];
IArray FV0 = (fvar < 0) ? level.getFaceVertices(f0) :
level.getFaceFVarValues(f0, fvar);
Index f1 = vFaces[_mod6(iOpposite + 1)];
int j1 = vInFace[_mod6(iOpposite + 1)];
IArray FV1 = (fvar < 0) ? level.getFaceVertices(f1) :
level.getFaceFVarValues(f1, fvar);
P[corner[0]] = FV0[j0];
P[corner[1]] = FV0[_mod3(j0 + 1)];
P[corner[2]] = FV0[_mod3(j0 + 2)];
P[corner[3]] = FV1[_mod3(j1 + 2)];
} else if (vFaces.size() == 1) {
Index FVcorner = (fvar < 0) ? vIndex :
level.getFaceFVarValues(vFaces[0], fvar)[vInFace[0]];
P[corner[0]] = FVcorner;
P[corner[1]] = -1;
P[corner[2]] = -1;
P[corner[3]] = -1;
} else if (vFaces[0] == face) {
Index f2 = vFaces[2];
int j2 = vInFace[2];
IArray FV = (fvar < 0) ? level.getFaceVertices(f2) :
level.getFaceFVarValues(f2, fvar);
P[corner[0]] = FV[j2];
P[corner[1]] = FV[_mod3(j2 + 2)];
P[corner[2]] = -1;
P[corner[3]] = -1;
} else if (vFaces[1] == face) {
Index f0 = vFaces[0];
int j0 = vInFace[0];
IArray FV = (fvar < 0) ? level.getFaceVertices(f0) :
level.getFaceFVarValues(f0, fvar);
P[corner[0]] = FV[j0];
P[corner[1]] = -1;
P[corner[2]] = -1;
P[corner[3]] = FV[_mod3(j0 + 1)];
} else { // (vFaces[2] == face)
Index f0 = vFaces[0];
int j0 = vInFace[0];
IArray FV = (fvar < 0) ? level.getFaceVertices(f0) :
level.getFaceFVarValues(f0, fvar);
P[corner[0]] = FV[j0];
P[corner[1]] = -1;
P[corner[2]] = FV[_mod3(j0 + 1)];
P[corner[3]] = FV[_mod3(j0 + 2)];
}
}
return 12;
}
}
//
// Private method to dispatch the above patch point retrieval functions:
//
int
RefinerSurfaceFactoryBase::getFacePatchPointIndices(Index baseFace,
Index indices[], int vtxOrFVarChannel) const {
Vtr::internal::Level const & baseLevel = _mesh.getLevel(0);
ConstIndexArray baseFaceVerts = baseLevel.getFaceVertices(baseFace);
if (baseFaceVerts.size() == 4) {
return gatherPatchPoints4(baseLevel, baseFace, baseFaceVerts,
indices, vtxOrFVarChannel);
} else {
return gatherPatchPoints3(baseLevel, baseFace, baseFaceVerts,
indices, vtxOrFVarChannel);
}
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv

View File

@@ -0,0 +1,162 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_REFINER_SURFACE_FACTORY_H
#define OPENSUBDIV3_BFR_REFINER_SURFACE_FACTORY_H
#include "../version.h"
#include "../bfr/surfaceFactory.h"
#include "../bfr/surfaceFactoryCache.h"
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Far {
class TopologyRefiner;
}
namespace Bfr {
///
/// @brief Intermediate subclass of SurfaceFactory with Far::TopologyRefiner
/// as the mesh
///
/// RefinerSurfaceFactoryBase is an intermediate subclass of SurfaceFactory
/// using Far::TopologyRefiner as the connected mesh representation.
///
/// The SurfaceFactoryMeshAdapter interface for TopologyRefiner is provided
/// in full, along with some public extensions specific to TopologyRefiner.
///
/// Additional caching expectations of SurfaceFactory are NOT specified
/// here. These are deferred to subclasses to implement different behaviors
/// of the factory's internal caching. A template for such subclasses is
/// additionally provided -- allowing clients desiring a thread-safe cache
/// to simply declare a subclass for a preferred thread-safe type.
///
class RefinerSurfaceFactoryBase : public SurfaceFactory {
public:
//@{
/// @name Construction and initialization
///
/// Construction and initialization
///
RefinerSurfaceFactoryBase(Far::TopologyRefiner const & mesh,
Options const & options);
~RefinerSurfaceFactoryBase() override = default;
//@}
//@{
/// @name Simple queries related to Far::TopologyRefiner
///
/// Simple queries related to Far::TopologyRefiner
///
/// @brief Return the instance of the mesh
Far::TopologyRefiner const & GetMesh() const { return _mesh; }
/// @brief Return the number of faces
int GetNumFaces() const { return _numFaces; }
/// @brief Return the number of face-varying channels
int GetNumFVarChannels() const { return _numFVarChannels; }
//@}
protected:
/// @cond PROTECTED
//
// Virtual overrides to satisfy the SurfaceFactoryMeshAdapter interface:
//
bool isFaceHole( Index faceIndex) const override;
int getFaceSize(Index faceIndex) const override;
int getFaceVertexIndices(Index faceIndex,
Index vertexIndices[]) const override;
int getFaceFVarValueIndices(Index faceIndex,
FVarID fvarID, Index fvarValueIndices[]) const override;
int populateFaceVertexDescriptor(Index faceIndex, int faceVertex,
VertexDescriptor * vertexDescriptor) const override;
int getFaceVertexIncidentFaceVertexIndices(
Index faceIndex, int faceVertex,
Index vertexIndices[]) const override;
int getFaceVertexIncidentFaceFVarValueIndices(
Index faceIndex, int faceVertex,
FVarID fvarID, Index fvarValueIndices[]) const override;
// Optional SurfaceFactoryMeshAdapter overrides for regular patches:
bool getFaceNeighborhoodVertexIndicesIfRegular(
Index faceIndex,
Index vertexIndices[]) const override;
bool getFaceNeighborhoodFVarValueIndicesIfRegular(
Index faceIndex,
FVarID fvarID, Index fvarValueIndices[]) const override;
/// @endcond
private:
//
// Internal supporting methods:
//
int getFaceVaryingChannel(FVarID fvarID) const;
int getFaceVertexPointIndices(Index faceIndex, int faceVertex,
Index indices[], int vtxOrFVarChannel) const;
int getFacePatchPointIndices(Index faceIndex,
Index indices[], int vtxOrFVarChannel) const;
private:
// Additional members for the subclass:
Far::TopologyRefiner const & _mesh;
int _numFaces;
int _numFVarChannels;
};
//
/// @brief Template for concrete subclasses of RefinerSurfaceFactoryBase
///
/// This class template is used to declare concrete subclasses of
/// RefinerSurfaceFactoryBase with the additional support of an internal
/// cache used by the base class. With an instance of a thread-safe
/// subclass of SurfaceFactoryCache declared as a member, the resulting
/// factory will be thread-safe.
///
/// @tparam CACHE_TYPE A subclass of SurfaceFactoryCache
///
/// Note a default template parameter uses the base SurfaceFactoryCache
/// for convenience, but which is not thread-safe.
///
template <class CACHE_TYPE = SurfaceFactoryCache>
class RefinerSurfaceFactory : public RefinerSurfaceFactoryBase {
public:
RefinerSurfaceFactory(Far::TopologyRefiner const & mesh,
Options const & options = Options()) :
RefinerSurfaceFactoryBase(mesh, options),
_localCache() {
SurfaceFactory::setInternalCache(&_localCache);
}
~RefinerSurfaceFactory() override = default;
private:
CACHE_TYPE _localCache;
};
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_REFINER_SURFACE_FACTORY_H */

View File

@@ -0,0 +1,424 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../bfr/regularPatchBuilder.h"
#include <cstring>
#include <cstdio>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// Internal helper functions:
//
namespace {
inline int
encodeTriBoundaryMask(int eBits, int vBits) {
int upperBits = 0;
int lowerBits = eBits;
if (vBits) {
if (eBits == 0) {
upperBits = 1;
lowerBits = vBits;
} else if ((vBits == 7) &&
((eBits == 1) || (eBits == 2) || (eBits == 4))) {
upperBits = 2;
lowerBits = eBits;
}
}
return (upperBits << 3) | lowerBits;
}
}
//
// Static methods:
//
int
RegularPatchBuilder::GetBoundaryMask(int faceSize, Index const cvs[]) {
bool isQuad = (faceSize == 4);
if (isQuad) {
int eMask = ((cvs[ 1] < 0) << 0) |
((cvs[ 7] < 0) << 1) |
((cvs[14] < 0) << 2) |
((cvs[ 8] < 0) << 3);
return eMask;
} else {
int eMask = ((cvs[ 1] < 0) << 0) |
((cvs[ 9] < 0) << 1) |
((cvs[ 7] < 0) << 2) ;
int vMask = (((cvs[ 0] < 0) | (cvs[ 3] < 0)) << 0) |
(((cvs[ 2] < 0) | (cvs[ 6] < 0)) << 1) |
(((cvs[10] < 0) | (cvs[11] < 0)) << 2) ;
return encodeTriBoundaryMask(eMask, vMask);
}
}
//
// Constructor (empty destructor is inline):
//
RegularPatchBuilder::RegularPatchBuilder(FaceSurface const & surface) :
_surface(surface) {
_isQuad = (_surface.GetTopology()._faceSize == 4);
if (_isQuad) {
assert(_surface.GetTopology()._regFaceSize == 4);
_patchType = Far::PatchDescriptor::REGULAR;
_patchSize = 16;
} else {
assert(_surface.GetTopology()._faceSize == 3);
assert(_surface.GetTopology()._regFaceSize == 3);
_patchType = Far::PatchDescriptor::LOOP;
_patchSize = 12;
}
_isBoundary = _surface.GetTag().HasBoundaryVertices();
if (!_isBoundary) {
_boundaryMask = 0;
} else if (_isQuad) {
// Boundary mask for quad trivial -- bit for each boundary edge:
FaceVertexSubset const * C = _surface.GetSubsets();
int eMask = ((C[0].IsBoundary() & (C[0]._numFacesBefore == 0)) << 0) |
((C[1].IsBoundary() & (C[1]._numFacesBefore == 0)) << 1) |
((C[2].IsBoundary() & (C[2]._numFacesBefore == 0)) << 2) |
((C[3].IsBoundary() & (C[3]._numFacesBefore == 0)) << 3);
_boundaryMask = eMask;
} else {
// Boundary mask for tris not so trivial -- boundary verts can exist
// on tris without boundary edges, so bits for both are combined:
FaceVertexSubset const * C = _surface.GetSubsets();
int eMask = ((C[0].IsBoundary() & (C[0]._numFacesBefore == 0)) << 0) |
((C[1].IsBoundary() & (C[1]._numFacesBefore == 0)) << 1) |
((C[2].IsBoundary() & (C[2]._numFacesBefore == 0)) << 2);
int vMask = (C[0].IsBoundary() << 0) |
(C[1].IsBoundary() << 1) |
(C[2].IsBoundary() << 2);
_boundaryMask = encodeTriBoundaryMask(eMask, vMask);
}
}
//
// Methods for gathering control vertices:
//
void
RegularPatchBuilder::gatherInteriorPatchPoints4(Index P[]) const {
Index const * fvIndices = &_surface.GetIndices()[0];
Index const * fvOpposite = 0;
//
// For each of the 4 corners, identify the opposite face in the ring
// and assign its 4 indices to the corresponding quadrant of the patch:
//
FaceVertex const & cTop0 = _surface.GetCornerTopology(0);
fvOpposite = fvIndices + cTop0.GetFaceIndexOffset(cTop0.GetFaceAfter(2));
P[ 5] = fvOpposite[0];
P[ 4] = fvOpposite[1];
P[ 0] = fvOpposite[2];
P[ 1] = fvOpposite[3];
fvIndices += cTop0.GetNumFaceVertices();
FaceVertex const & cTop1 = _surface.GetCornerTopology(1);
fvOpposite = fvIndices + cTop1.GetFaceIndexOffset(cTop1.GetFaceAfter(2));
P[ 6] = fvOpposite[0];
P[ 2] = fvOpposite[1];
P[ 3] = fvOpposite[2];
P[ 7] = fvOpposite[3];
fvIndices += cTop1.GetNumFaceVertices();
FaceVertex const & cTop2 = _surface.GetCornerTopology(2);
fvOpposite = fvIndices + cTop2.GetFaceIndexOffset(cTop2.GetFaceAfter(2));
P[10] = fvOpposite[0];
P[11] = fvOpposite[1];
P[15] = fvOpposite[2];
P[14] = fvOpposite[3];
fvIndices += cTop2.GetNumFaceVertices();
FaceVertex const & cTop3 = _surface.GetCornerTopology(3);
fvOpposite = fvIndices + cTop3.GetFaceIndexOffset(cTop3.GetFaceAfter(2));
P[ 9] = fvOpposite[0];
P[13] = fvOpposite[1];
P[12] = fvOpposite[2];
P[ 8] = fvOpposite[3];
}
void
RegularPatchBuilder::gatherBoundaryPatchPoints4(Index P[]) const {
Index const * fvIndices = &_surface.GetIndices()[0];
//
// For each of the 4 corners -- whether boundary or interior -- one
// incident face contains all indices that will contribute to the points
// of the corresponding patch. Identify it first and then retrieve and
// assign the indices accordingly:
//
for (int i = 0; i < 4; ++i) {
FaceVertex const & cTop = _surface.GetCornerTopology(i);
FaceVertexSubset const & cSub = _surface.GetCornerSubset(i);
int faceCorner = cTop.GetFace();
int faceOther = faceCorner;
if (!cSub.IsBoundary()) {
faceOther = cTop.GetFaceAfter(2);
} else if (cSub._numFacesAfter) {
faceOther = cTop.GetFaceNext(faceCorner);
} else if (cSub._numFacesBefore) {
faceOther = cTop.GetFacePrevious(faceCorner);
}
Index const * fvOther = fvIndices + cTop.GetFaceIndexOffset(faceOther);
Index fvPhantom = fvOther[0];
switch (i) {
case 0:
P[5] = fvOther[0];
if (!cSub.IsBoundary()) {
P[4] = fvOther[1];
P[0] = fvOther[2];
P[1] = fvOther[3];
} else {
P[4] = cSub._numFacesAfter ? fvOther[3] : fvPhantom;
P[0] = fvPhantom;
P[1] = cSub._numFacesBefore ? fvOther[1] : fvPhantom;
}
break;
case 1:
P[6] = fvOther[0];
if (!cSub.IsBoundary()) {
P[2] = fvOther[1];
P[3] = fvOther[2];
P[7] = fvOther[3];
} else {
P[2] = cSub._numFacesAfter ? fvOther[3] : fvPhantom;
P[3] = fvPhantom;
P[7] = cSub._numFacesBefore ? fvOther[1] : fvPhantom;
}
break;
case 2:
P[10] = fvOther[0];
if (!cSub.IsBoundary()) {
P[11] = fvOther[1];
P[15] = fvOther[2];
P[14] = fvOther[3];
} else {
P[11] = cSub._numFacesAfter ? fvOther[3] : fvPhantom;
P[15] = fvPhantom;
P[14] = cSub._numFacesBefore ? fvOther[1] : fvPhantom;
}
break;
case 3:
P[ 9] = fvOther[0];
if (!cSub.IsBoundary()) {
P[13] = fvOther[1];
P[12] = fvOther[2];
P[ 8] = fvOther[3];
} else {
P[13] = cSub._numFacesAfter ? fvOther[3] : fvPhantom;
P[12] = fvPhantom;
P[ 8] = cSub._numFacesBefore ? fvOther[1] : fvPhantom;
}
break;
}
fvIndices += cTop.GetNumFaceVertices();
}
}
void
RegularPatchBuilder::gatherInteriorPatchPoints3(Index P[]) const {
Index const * fvIndices = &_surface.GetIndices()[0];
//
// For each of the 3 corners, the indices for the four contributing
// points come from the 2nd and 3rd faces following the corner face:
//
Index const * fvNext2 = 0;
Index const * fvNext3 = 0;
FaceVertex const & cTop0 = _surface.GetCornerTopology(0);
fvNext2 = fvIndices + cTop0.GetFaceIndexOffset(cTop0.GetFaceAfter(2));
fvNext3 = fvIndices + cTop0.GetFaceIndexOffset(cTop0.GetFaceAfter(3));
P[ 4] = fvNext2[0];
P[ 7] = fvNext2[1];
P[ 3] = fvNext2[2];
P[ 0] = fvNext3[2];
fvIndices += cTop0.GetNumFaceVertices();
FaceVertex const & cTop1 = _surface.GetCornerTopology(1);
fvNext2 = fvIndices + cTop1.GetFaceIndexOffset(cTop1.GetFaceAfter(2));
fvNext3 = fvIndices + cTop1.GetFaceIndexOffset(cTop1.GetFaceAfter(3));
P[ 5] = fvNext2[0];
P[ 1] = fvNext2[1];
P[ 2] = fvNext2[2];
P[ 6] = fvNext3[2];
fvIndices += cTop1.GetNumFaceVertices();
FaceVertex const & cTop2 = _surface.GetCornerTopology(2);
fvNext2 = fvIndices + cTop2.GetFaceIndexOffset(cTop2.GetFaceAfter(2));
fvNext3 = fvIndices + cTop2.GetFaceIndexOffset(cTop2.GetFaceAfter(3));
P[ 8] = fvNext2[0];
P[ 9] = fvNext2[1];
P[11] = fvNext2[2];
P[10] = fvNext3[2];
}
void
RegularPatchBuilder::gatherBoundaryPatchPoints3(Index P[]) const {
Index const * fvIndices = &_surface.GetIndices()[0];
//
// For each of the 3 corners, one incident face contains all indices
// that will contribute to the points of the corresponding patch, but
// interior vertices require two:
//
for (int i = 0; i < 3; ++i) {
FaceVertex const & cTop = _surface.GetCornerTopology(i);
FaceVertexSubset const & cSub = _surface.GetCornerSubset(i);
int faceCorner = cTop.GetFace();
int faceOther = -1;
if (!cSub.IsBoundary()) {
faceOther = cTop.GetFaceAfter(2);
} else if (cSub._numFacesTotal == 1) {
faceOther = faceCorner;
} else if (cSub._numFacesBefore == 0) {
faceOther = cTop.GetFaceAfter(2);
} else if (cSub._numFacesAfter == 0) {
faceOther = cTop.GetFaceBefore(2);
} else {
faceOther = cTop.GetFaceNext(faceCorner);
}
assert(faceOther >= 0);
Index const * fvOther = fvIndices + cTop.GetFaceIndexOffset(faceOther);
Index fvPhantom = fvOther[0];
switch (i) {
case 0:
P[4] = fvOther[0];
if (!cSub.IsBoundary()) {
P[7] = fvOther[1];
P[3] = fvOther[2];
fvOther = fvIndices +
cTop.GetFaceIndexOffset(cTop.GetFaceNext(faceOther));
P[0] = fvOther[2];
} else {
P[7] = (cSub._numFacesAfter) ? fvOther[3 - cSub._numFacesAfter]
: fvPhantom;
P[3] = (cSub._numFacesAfter == 2) ? fvOther[2] : fvPhantom;
P[0] = (cSub._numFacesBefore == 2) ? fvOther[1] : fvPhantom;
}
break;
case 1:
P[5] = fvOther[0];
if (!cSub.IsBoundary()) {
P[1] = fvOther[1];
P[2] = fvOther[2];
fvOther = fvIndices +
cTop.GetFaceIndexOffset(cTop.GetFaceNext(faceOther));
P[6] = fvOther[2];
} else {
P[1] = (cSub._numFacesAfter) ? fvOther[3 - cSub._numFacesAfter]
: fvPhantom;
P[2] = (cSub._numFacesAfter == 2) ? fvOther[2] : fvPhantom;
P[6] = (cSub._numFacesBefore == 2) ? fvOther[1] : fvPhantom;
}
break;
case 2:
P[8] = fvOther[0];
if (!cSub.IsBoundary()) {
P[ 9] = fvOther[1];
P[11] = fvOther[2];
fvOther = fvIndices +
cTop.GetFaceIndexOffset(cTop.GetFaceNext(faceOther));
P[10] = fvOther[2];
} else {
P[ 9] = (cSub._numFacesAfter) ? fvOther[3 - cSub._numFacesAfter]
: fvPhantom;
P[11] = (cSub._numFacesAfter == 2) ? fvOther[2] : fvPhantom;
P[10] = (cSub._numFacesBefore == 2) ? fvOther[1] : fvPhantom;
}
break;
}
fvIndices += cTop.GetNumFaceVertices();
}
}
int
RegularPatchBuilder::GatherControlVertexIndices(Index cvIndices[]) const {
if (_isQuad) {
if (_isBoundary) {
gatherBoundaryPatchPoints4(cvIndices);
} else {
gatherInteriorPatchPoints4(cvIndices);
}
} else {
if (_isBoundary) {
gatherBoundaryPatchPoints3(cvIndices);
} else {
gatherInteriorPatchPoints3(cvIndices);
}
}
return _patchSize;
}
//
// Methods for debugging...
//
void
RegularPatchBuilder::print(Index const P[]) const {
printf("RegularPatchBuilder:\n");
if (_patchType == Far::PatchDescriptor::REGULAR) {
printf(" patch type = REGULAR (B-Spline, quad)\n");
} else if (_patchType == Far::PatchDescriptor::LOOP) {
printf(" patch type = LOOP (Box-Spline, tri)\n");
} else {
assert("Unknown _patchType for RegularPatchBuilder" == 0);
}
printf(" patch size = %d\n", _patchSize);
printf(" is quad = %d\n", _isQuad);
printf(" is boundary = %d\n", _isBoundary);
if (P) {
const char * label = " patch points:";
const char * indent = " ";
if (_isQuad) {
printf("%s %4d %4d %4d %4d\n", label, P[12], P[13], P[14], P[15]);
printf("%s %4d %4d %4d %4d\n", indent, P[ 8], P[ 9], P[10], P[11]);
printf("%s %4d %4d %4d %4d\n", indent, P[ 4], P[ 5], P[ 6], P[ 7]);
printf("%s %4d %4d %4d %4d\n", indent, P[ 0], P[ 1], P[ 2], P[ 3]);
} else {
printf("%s %4d %4d\n", label, P[10], P[11]);
printf("%s %4d %4d %4d\n", indent, P[7], P[8], P[9]);
printf("%s %4d %4d %4d %4d\n", indent, P[3], P[4], P[5], P[6]);
printf("%s %4d %4d %4d\n", indent, P[0], P[1], P[2]);
}
}
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv

View File

@@ -0,0 +1,89 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_REGULAR_PATCH_BUILDER_H
#define OPENSUBDIV3_REGULAR_PATCH_BUILDER_H
#include "../version.h"
#include "../bfr/faceSurface.h"
#include "../far/patchDescriptor.h"
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// RegularPatchBuilder ...
//
class RegularPatchBuilder {
public:
typedef FaceSurface::Index Index;
public:
RegularPatchBuilder(FaceSurface const & surfaceDescription);
~RegularPatchBuilder() { }
// Debugging...
void print(Index const cvIndices[] = 0) const;
public:
// Methods to query the number and indices of control vertices:
int GetNumControlVertices() const { return _patchSize; }
int GatherControlVertexIndices(Index cvIndices[]) const;
public:
// Methods to query patch properties:
bool IsQuadPatch() const { return _isQuad; }
bool IsBoundaryPatch() const { return _isBoundary; }
Far::PatchDescriptor::Type GetPatchType() const { return _patchType; }
// Note the bit-mask here is specific for use with Far::PatchParam
int GetPatchParamBoundaryMask() const { return _boundaryMask; }
public:
// Static methods for use without a FaceSurface:
static int GetPatchSize(int regFaceSize) {
return (regFaceSize == 4) ? 16 : 12;
}
static Far::PatchDescriptor::Type GetPatchType(int regFaceSize) {
return (regFaceSize == 4) ? Far::PatchDescriptor::REGULAR :
Far::PatchDescriptor::LOOP;
}
static int GetBoundaryMask(int regFaceSize, Index const patchPoints[]);
private:
// Internal methods for assembling quad and tri patches:
void gatherInteriorPatchPoints4(Index cvIndices[]) const;
void gatherBoundaryPatchPoints4(Index cvIndices[]) const;
void gatherInteriorPatchPoints3(Index cvIndices[]) const;
void gatherBoundaryPatchPoints3(Index cvIndices[]) const;
private:
// Private members:
FaceSurface const & _surface;
unsigned int _isQuad : 1;
unsigned int _isBoundary : 1;
int _boundaryMask;
int _patchSize;
Far::PatchDescriptor::Type _patchType;
};
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_REGULAR_PATCH_BUILDER_H */

View File

@@ -0,0 +1,680 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../bfr/surface.h"
#include "../bfr/surfaceData.h"
#include "../bfr/pointOperations.h"
#include "../bfr/patchTree.h"
#include "../far/patchParam.h"
#include "../far/patchDescriptor.h"
#include "../far/patchBasis.h"
#include <algorithm>
#include <cassert>
#include <cstdio>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// Constructor for the Surface -- defers to the constructor for its full
// set of member variables, but marks the precision as double when needed
// (as a specialization here):
//
template <typename REAL>
Surface<REAL>::Surface() : _data() {
// Surface<> should not be adding members outside its SurfaceData:
assert(sizeof(*this) == sizeof(internal::SurfaceData));
}
template <>
Surface<double>::Surface() : _data() {
_data.setDouble(true);
}
//
// Simple internal utilities:
//
template <typename REAL>
inline internal::IrregularPatchType const &
Surface<REAL>::getIrregPatch() const {
return _data.getIrregPatch();
}
template <typename REAL>
int
Surface<REAL>::GetNumPatchPoints() const {
if (IsRegular()) {
return GetNumControlPoints();
} else if (IsLinear()) {
return 2 * GetNumControlPoints() + 1;
} else {
return getIrregPatch().GetNumPointsTotal();
}
}
template <typename REAL>
int
Surface<REAL>::GetControlPointIndices(Index cvs[]) const {
std::memcpy(cvs, _data.getCVIndices(), _data.getNumCVs() * sizeof(Index));
return _data.getNumCVs();
}
//
// Methods for gathering and computing control and patch points:
//
template <typename REAL>
template <typename REAL_MESH>
void
Surface<REAL>::GatherControlPoints(
REAL_MESH const meshPoints[], PointDescriptor const & meshDesc,
REAL controlPoints[], PointDescriptor const & controlDesc) const {
//
// Assemble parameters of the point copy operation and apply:
//
typedef points::CopyConsecutive<REAL,REAL_MESH> PointCopier;
typename PointCopier::Parameters copyParams;
copyParams.pointData = meshPoints;
copyParams.pointSize = meshDesc.size;
copyParams.pointStride = meshDesc.stride;
copyParams.srcCount = GetNumControlPoints();
copyParams.srcIndices = _data.getCVIndices();
copyParams.resultData = controlPoints;
copyParams.resultStride = controlDesc.stride;
PointCopier::Apply(copyParams);
}
template <typename REAL>
void
Surface<REAL>::computeLinearPatchPoints(REAL pointData[],
PointDescriptor const & pointDesc) const {
//
// The initial control points of the N-sided face will be followed
// by the midpoint of the face and the midpoint of the N edges.
//
// Assemble parameters of the face splitting operation and apply:
//
int N = GetNumControlPoints();
typedef points::SplitFace<REAL> PointSplitter;
typename PointSplitter::Parameters splitParams;
splitParams.pointData = pointData;
splitParams.pointSize = pointDesc.size;
splitParams.pointStride = pointDesc.stride;
splitParams.srcCount = N;
splitParams.resultData = pointData + pointDesc.stride * N;
PointSplitter::Apply(splitParams);
}
template <typename REAL>
void
Surface<REAL>::computeIrregularPatchPoints(REAL pointData[],
PointDescriptor const & pointDesc) const {
//
// An "irregular patch" may be represented by a regular patch in
// rare cases, so be sure there are patch points to compute:
//
internal::IrregularPatchType const & irregPatch = getIrregPatch();
int numControlPoints = GetNumControlPoints();
int numPatchPoints = irregPatch.GetNumPointsTotal();
if (numPatchPoints == numControlPoints) return;
//
// Assemble parameters of the point combination operation and apply:
//
typedef points::CombineConsecutive<REAL> PointCombiner;
typename PointCombiner::Parameters combParams;
combParams.pointData = pointData;
combParams.pointSize = pointDesc.size;
combParams.pointStride = pointDesc.stride;
combParams.srcCount = numControlPoints;
combParams.resultCount = numPatchPoints - numControlPoints;
combParams.resultData = pointData + pointDesc.stride * numControlPoints;
combParams.weightData = irregPatch.GetStencilMatrix<REAL>();
PointCombiner::Apply(combParams);
}
//
// Methods for computing the extent of the control points:
//
template <typename REAL>
void
Surface<REAL>::BoundControlPoints(
REAL const controlPoints[], PointDescriptor const & pointDesc,
REAL boundMin[], REAL boundMax[]) const {
int numPoints = GetNumControlPoints();
int pointSize = pointDesc.size;
REAL const * p = controlPoints;
std::memcpy(boundMin, p, pointSize * sizeof(REAL));
std::memcpy(boundMax, p, pointSize * sizeof(REAL));
for (int i = 1; i < numPoints; ++i) {
p += pointDesc.stride;
for (int j = 0; j < pointSize; ++j) {
boundMin[j] = std::min(boundMin[j], p[j]);
boundMax[j] = std::max(boundMax[j], p[j]);
}
}
}
template <typename REAL>
void
Surface<REAL>::BoundControlPointsFromMesh(
REAL const meshPoints[], PointDescriptor const & pointDesc,
REAL boundMin[], REAL boundMax[]) const {
int numPoints = GetNumControlPoints();
int pointSize = pointDesc.size;
int const * meshIndices = _data.getCVIndices();
REAL const * p = meshPoints + pointDesc.stride * meshIndices[0];
std::memcpy(boundMin, p, pointSize * sizeof(REAL));
std::memcpy(boundMax, p, pointSize * sizeof(REAL));
for (int i = 1; i < numPoints; ++i) {
p = meshPoints + pointDesc.stride * meshIndices[i];
for (int j = 0; j < pointSize; ++j) {
boundMin[j] = std::min(boundMin[j], p[j]);
boundMax[j] = std::max(boundMax[j], p[j]);
}
}
}
//
// Internal helper for evaluation:
//
namespace {
template <typename REAL>
inline int
assignWeightsPerDeriv(REAL * const deriv[6], int wSize, REAL wBuffer[],
REAL * wDeriv[6]) {
std::memset(wDeriv, 0, 6 * sizeof(REAL*));
wDeriv[0] = wBuffer;
if (deriv[1] && deriv[2]) {
wDeriv[1] = wDeriv[0] + wSize;
wDeriv[2] = wDeriv[1] + wSize;
if (deriv[3] && deriv[4] && deriv[5]) {
wDeriv[3] = wDeriv[2] + wSize;
wDeriv[4] = wDeriv[3] + wSize;
wDeriv[5] = wDeriv[4] + wSize;
return 6;
}
return 3;
}
return 1;
}
}
//
// Evaluation methods accessing the local data for a simple regular patch:
//
template <typename REAL>
void
Surface<REAL>::evalRegularBasis(REAL const uv[2], REAL * wDeriv[]) const {
Far::PatchParam patchParam;
patchParam.Set(0, 0, 0, 0, 0, getRegPatchMask(), 0, true);
Far::internal::EvaluatePatchBasisNormalized(
getRegPatchType(), patchParam, uv[0], uv[1],
wDeriv[0], wDeriv[1], wDeriv[2], wDeriv[3], wDeriv[4], wDeriv[5]);
}
template <typename REAL>
int
Surface<REAL>::evalRegularStencils(REAL const uv[2], REAL * sDeriv[]) const {
//
// The control points of a regular patch are always the full set
// of points required by a patch, i.e. phantom points will have an
// entry of some kind (a duplicate). For example, for an isolated
// quad, its regular patch still has 16 control points. So we can
// return the basis weights as stencil weights for all cases.
//
Far::PatchParam patchParam;
patchParam.Set(0, 0, 0, 0, 0, getRegPatchMask(), 0, true);
Far::internal::EvaluatePatchBasisNormalized(
getRegPatchType(), patchParam, uv[0], uv[1],
sDeriv[0], sDeriv[1], sDeriv[2], sDeriv[3], sDeriv[4], sDeriv[5]);
return GetNumControlPoints();
}
template <typename REAL>
void
Surface<REAL>::evalRegularDerivs(REAL const uv[2],
REAL const patchPoints[], PointDescriptor const & pointDesc,
REAL * deriv[]) const {
//
// Regular basis evaluation simply returns weights for use with
// the entire set of patch control points.
//
// Assign weights for requested derivatives and evaluate:
//
REAL wBuffer[6 * 20];
REAL * wDeriv[6];
int numDerivs = assignWeightsPerDeriv(deriv, 20, wBuffer, wDeriv);
evalRegularBasis(uv, wDeriv);
//
// Assemble parameters of the point combination operation and apply:
//
points::CommonCombinationParameters<REAL> combineParams;
combineParams.pointData = patchPoints;
combineParams.pointSize = pointDesc.size;
combineParams.pointStride = pointDesc.stride;
combineParams.srcCount = GetNumControlPoints();
combineParams.srcIndices = 0;
combineParams.resultCount = numDerivs;
combineParams.resultArray = deriv;
combineParams.weightArray = wDeriv;
if (numDerivs == 1) {
points::Combine1<REAL>::Apply(combineParams);
} else if (numDerivs == 3) {
points::Combine3<REAL>::Apply(combineParams);
} else {
points::CombineMultiple<REAL>::Apply(combineParams);
}
}
//
// Evaluation methods accessing the PatchTree for irregular patches:
//
template <typename REAL>
typename Surface<REAL>::IndexArray
Surface<REAL>::evalIrregularBasis(REAL const UV[2], REAL * wDeriv[]) const {
Parameterization param = GetParameterization();
REAL uv[2] = { UV[0], UV[1] };
int subFace = param.HasSubFaces() ?
param.ConvertCoordToNormalizedSubFace(uv, uv) : 0;
internal::IrregularPatchType const & irregPatch = getIrregPatch();
int subPatchIndex = irregPatch.FindSubPatch(uv[0], uv[1], subFace);
assert(subPatchIndex >= 0);
irregPatch.EvalSubPatchBasis(subPatchIndex, uv[0], uv[1],
wDeriv[0], wDeriv[1], wDeriv[2], wDeriv[3], wDeriv[4], wDeriv[5]);
return irregPatch.GetSubPatchPoints(subPatchIndex);
}
template <typename REAL>
int
Surface<REAL>::evalIrregularStencils(REAL const UV[2], REAL * sDeriv[]) const {
Parameterization param = GetParameterization();
REAL uv[2] = { UV[0], UV[1] };
int subFace = param.HasSubFaces() ?
param.ConvertCoordToNormalizedSubFace(uv, uv) : 0;
internal::IrregularPatchType const & irregPatch = getIrregPatch();
int subPatchIndex = irregPatch.FindSubPatch(uv[0], uv[1], subFace);
assert(subPatchIndex >= 0);
return irregPatch.EvalSubPatchStencils(
subPatchIndex, uv[0], uv[1],
sDeriv[0], sDeriv[1], sDeriv[2], sDeriv[3], sDeriv[4], sDeriv[5]);
}
template <typename REAL>
void
Surface<REAL>::evalIrregularDerivs(REAL const uv[2],
REAL const patchPoints[], PointDescriptor const & pointDesc,
REAL * deriv[]) const {
//
// Non-linear irregular basis evaluation returns both the weights
// and the corresponding points of a sub-patch defined by a subset
// of the given patch points.
//
// Assign weights for requested derivatives and evaluate:
//
REAL wBuffer[6 * 20];
REAL * wDeriv[6];
int numDerivs = assignWeightsPerDeriv(deriv, 20, wBuffer, wDeriv);
IndexArray indices = evalIrregularBasis(uv, wDeriv);
//
// Assemble parameters of the point combination operation and apply:
//
points::CommonCombinationParameters<REAL> combineParams;
combineParams.pointData = patchPoints;
combineParams.pointSize = pointDesc.size;
combineParams.pointStride = pointDesc.stride;
combineParams.srcCount = indices.size();
combineParams.srcIndices = &indices[0];
combineParams.resultCount = numDerivs;
combineParams.resultArray = deriv;
combineParams.weightArray = wDeriv;
if (numDerivs == 1) {
points::Combine1<REAL>::Apply(combineParams);
} else if (numDerivs == 3) {
points::Combine3<REAL>::Apply(combineParams);
} else {
points::CombineMultiple<REAL>::Apply(combineParams);
}
}
//
// Supporting methods for the N-sided quadrangulated linear patch:
//
namespace {
//
// For stencils, there are four unique weights derived from the
// four bilinear weights of the sub-face. Given these weights as
// input for a sub-face with origin at base point P, the resulting
// weights are associated with the N base points as follows:
//
// w[0] = the point at the origin (P)
// w[1] = the point following P
// w[2] = the N-3 points not adjacent to P (contributing to center)
// w[3] = the point preceding P
//
template <typename REAL>
inline void
transformLinearQuadWeightsToStencil(REAL w[4], int N) {
REAL wOrigin = w[0];
REAL wNext = w[1] * 0.5f;
REAL wCenter = w[2] / (REAL)N;
REAL wPrev = w[3] * 0.5f;
w[0] = wCenter + wNext + wPrev + wOrigin;
w[1] = wCenter + wNext;
w[2] = wCenter;
w[3] = wCenter + wPrev;
}
template <typename REAL>
inline void
scaleWeights4(REAL w[4], REAL derivScale) {
if (w) {
w[0] *= derivScale;
w[1] *= derivScale;
w[2] *= derivScale;
w[3] *= derivScale;
}
}
}
template <typename REAL>
int
Surface<REAL>::evalMultiLinearBasis(REAL const UV[2], REAL *wDeriv[]) const {
Parameterization param = GetParameterization();
assert(param.GetType() == Parameterization::QUAD_SUBFACES);
REAL uv[2];
int subFace = param.ConvertCoordToNormalizedSubFace(UV, uv);
// WIP - Prefer to eval Linear basis directly, i.e.:
//
// Far::internal::EvalBasisLinear(u, v, wP, wDu, wDv);
//
// but this internal Far function is sometimes optimized out, causing
// link errors. Need to fix in Far with explicit instantiation...
Far::internal::EvaluatePatchBasisNormalized(Far::PatchDescriptor::QUADS,
Far::PatchParam(), uv[0], uv[1],
wDeriv[0], wDeriv[1], wDeriv[2], wDeriv[3], wDeriv[4], wDeriv[5]);
// Scale weights for derivatives (only mixed partial of 2nd is non-zero):
scaleWeights4<REAL>(wDeriv[1], 2.0f);
scaleWeights4<REAL>(wDeriv[2], 2.0f);
scaleWeights4<REAL>(wDeriv[4], 4.0f);
return subFace;
}
template <typename REAL>
int
Surface<REAL>::evalMultiLinearStencils(REAL const uv[2], REAL *sDeriv[]) const {
//
// Linear evaluation of irregular N-sided faces evaluates one of N
// locally subdivided quad faces and identifies that sub-face -- also
// the origin vertex of the quad. The basis weights are subsequently
// transformed into the four unique values that are then assigned to
// the N vertices of the face.
//
// Assign weights for requested stencils and evaluate:
//
REAL wBuffer[6 * 4];
REAL * wDeriv[6];
int numDerivs = assignWeightsPerDeriv(sDeriv, 4, wBuffer, wDeriv);
int iOrigin = evalMultiLinearBasis(uv, wDeriv);
//
// Transform the four linear weights to four unique stencil weights:
//
int numControlPoints = GetNumControlPoints();
transformLinearQuadWeightsToStencil(wDeriv[0], numControlPoints);
if (numDerivs > 1) {
transformLinearQuadWeightsToStencil(wDeriv[1], numControlPoints);
transformLinearQuadWeightsToStencil(wDeriv[2], numControlPoints);
if (numDerivs > 3) {
transformLinearQuadWeightsToStencil(wDeriv[4], numControlPoints);
}
}
//
// Assign the N stencil weights from the four unique values:
//
int iNext = (iOrigin + 1) % numControlPoints;
int iPrev = (iOrigin + numControlPoints - 1) % numControlPoints;
for (int i = 0; i < numControlPoints; ++i) {
int wIndex = 2;
if (i == iOrigin) {
wIndex = 0;
} else if (i == iNext) {
wIndex = 1;
} else if (i == iPrev) {
wIndex = 3;
}
sDeriv[0][i] = wDeriv[0][wIndex];
if (numDerivs > 1) {
sDeriv[1][i] = wDeriv[1][wIndex];
sDeriv[2][i] = wDeriv[2][wIndex];
if (numDerivs > 3) {
sDeriv[3][i] = 0.0f;
sDeriv[4][i] = wDeriv[4][wIndex];
sDeriv[5][i] = 0.0f;
}
}
}
return numControlPoints;
}
template <typename REAL>
void
Surface<REAL>::evalMultiLinearDerivs(REAL const uv[],
REAL const patchPoints[], PointDescriptor const & pointDesc,
REAL * deriv[]) const {
//
// Linear evaluation of irregular N-sided faces evaluates one of N
// locally subdivided quad faces and identifies that sub-face.
//
// Assign weights for requested derivatives and evaluate:
//
REAL wBuffer[6 * 4];
REAL * wDeriv[6];
int numDerivs = assignWeightsPerDeriv(deriv, 4, wBuffer, wDeriv);
int subQuad = evalMultiLinearBasis(uv, wDeriv);
//
// Identify the patch points for the sub-face and interpolate:
//
int N = GetNumControlPoints();
int quadIndices[4];
quadIndices[0] = subQuad;
quadIndices[1] = N + 1 + subQuad;
quadIndices[2] = N;
quadIndices[3] = N + 1 + (subQuad + N - 1) % N;
//
// Assemble parameters of the point combination operation and apply:
//
points::CommonCombinationParameters<REAL> combineParams;
combineParams.pointData = patchPoints;
combineParams.pointSize = pointDesc.size;
combineParams.pointStride = pointDesc.stride;
combineParams.srcCount = 4;
combineParams.srcIndices = quadIndices;
combineParams.resultCount = numDerivs;
combineParams.resultArray = deriv;
combineParams.weightArray = wDeriv;
if (numDerivs == 1) {
points::Combine1<REAL>::Apply(combineParams);
} else if (numDerivs == 3) {
points::Combine3<REAL>::Apply(combineParams);
} else {
points::CombineMultiple<REAL>::Apply(combineParams);
}
}
//
// Public methods to apply stencils:
//
template <typename REAL>
void
Surface<REAL>::ApplyStencilFromMesh(REAL const stencil[],
REAL const meshPoints[], PointDescriptor const & pointDesc,
REAL result[]) const {
//
// Assemble parameters of the point combination operation and apply:
//
typedef points::Combine1<REAL> PointCombiner;
typename PointCombiner::Parameters combParams;
combParams.pointData = meshPoints;
combParams.pointSize = pointDesc.size;
combParams.pointStride = pointDesc.stride;
combParams.srcCount = GetNumControlPoints();
combParams.srcIndices = _data.getCVIndices();
combParams.resultCount = 1;
combParams.resultArray = &result;
combParams.weightArray = &stencil;
PointCombiner::Apply(combParams);
}
template <typename REAL>
void
Surface<REAL>::ApplyStencil(REAL const stencil[],
REAL const controlPoints[], PointDescriptor const & pointDesc,
REAL result[]) const {
//
// Assemble parameters of the point combination operation and apply:
//
typedef points::Combine1<REAL> PointCombiner;
typename PointCombiner::Parameters combParams;
combParams.pointData = controlPoints;
combParams.pointSize = pointDesc.size;
combParams.pointStride = pointDesc.stride;
combParams.srcCount = GetNumControlPoints();
combParams.srcIndices = 0;
combParams.resultCount = 1;
combParams.resultArray = &result;
combParams.weightArray = &stencil;
PointCombiner::Apply(combParams);
}
//
// Explicitly instantiate Surface<> implementations for float and double:
//
template class Surface<float>;
template class Surface<double>;
//
// Explicitly instantiate template methods for converting precision:
//
template void Surface<float>::GatherControlPoints(
float const [], PointDescriptor const &,
float [], PointDescriptor const &) const;
template void Surface<float>::GatherControlPoints(
double const [], PointDescriptor const &,
float [], PointDescriptor const &) const;
template void Surface<double>::GatherControlPoints(
double const [], PointDescriptor const &,
double [], PointDescriptor const &) const;
template void Surface<double>::GatherControlPoints(
float const [], PointDescriptor const &,
double [], PointDescriptor const &) const;
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv

View File

@@ -0,0 +1,429 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_SURFACE_H
#define OPENSUBDIV3_BFR_SURFACE_H
#include "../version.h"
#include "../bfr/surfaceData.h"
#include "../bfr/parameterization.h"
#include "../vtr/array.h"
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
///
/// @brief Encapsulates the limit surface for a face of a mesh
///
/// The Surface class encapsulates the limit surface for a face of a mesh
/// for any data interpolation type (vertex, varying and face-varying) and
/// provides the public interface for its evaluation. Surface is a class
/// template parameterized to support evaluation in single or double
/// precision.
///
/// @tparam REAL Floating point precision (float or double only)
///
/// Instances of Surface are created or initialized by a subclass of the
/// SurfaceFactory. Since existing instances can be re-initialized, they
/// should be tested for validity after such re-initialization.
///
/// All Surfaces are assigned a Parameterization based on the subdivision
/// scheme and the size of the face, which can then be used for evaluation
/// and tessellation of the surface.
///
template <typename REAL>
class Surface {
public:
/// @brief Simple struct defining the size and stride of points in
/// arrays.
struct PointDescriptor {
PointDescriptor() : size(0), stride(0) { }
PointDescriptor(int n) : size(n), stride(n) { }
PointDescriptor(int n, int m) : size(n), stride(m) { }
int size, stride;
};
/// @brief Integer type representing a mesh index
typedef int Index;
public:
//@{
/// @name Construction and initialization
///
/// Instances of Surface may be explicitly constructed, but are
/// initialized by SurfaceFactory and so only default construction
/// is provided. An instance will be invalid (and so unusable) if
/// default constructed, or if the factory that initialized it
/// determined that the face associated with it has no limit surface.
///
/// @brief Return true if successfully initialized
bool IsValid() const { return _data.isValid(); }
/// @brief Clear a previously initialized Surface
void Clear() { _data.reinitialize(); }
/// @brief Default construction produces an invalid instance
Surface();
Surface(Surface const & src) = default;
Surface& operator=(Surface const & src) = default;
~Surface() = default;
//@}
//@{
/// @name Simple queries
///
/// Simple queries of valid Surface.
///
/// @brief Return the Parameterization
Parameterization GetParameterization() const { return _data.getParam(); }
/// @brief Return the size of the face
int GetFaceSize() const { return GetParameterization().GetFaceSize(); }
/// @brief Return if the Surface is a single regular patch
bool IsRegular() const { return _data.isRegular(); }
/// @brief Return if the Surface is linear
bool IsLinear() const { return _data.isLinear(); }
//@}
//@{
/// @name Methods to manage control points
///
/// Control points are the subset of points in the mesh that influence
/// a Surface. They can be identified as part of the mesh data by their
/// indices, or gathered into an array for other purposes.
///
/// It is not necessary to deal directly with control points for
/// evaluation, but they are useful with limit stencils and other
/// purposes, e.g. computing a bounding box of the control hull of
/// the Surface.
///
/// Note that methods that access control points from the array of
/// mesh data require that the array be contiguous. If a large data
/// set is fragmented into blocks or pages, these methods cannot be
/// used and control points will need to be gathered explicitly.
///
/// @brief Return the number of control points affecting the Surface
int GetNumControlPoints() const { return _data.getNumCVs(); }
/// @brief Identify indices of control points in the mesh
int GetControlPointIndices(Index meshPointIndices[]) const;
/// @brief Gather control points in a local array
///
/// @tparam REAL_MESH Floating point precision of mesh points
///
/// @param meshPoints Input array of mesh point data
/// @param meshPointDesc The size and stride of mesh point data
/// @param controlPoints Output array of control point data
/// @param controlPointDesc The size and stride of control point data
///
template <typename REAL_MESH>
void GatherControlPoints(REAL_MESH const meshPoints[],
PointDescriptor const & meshPointDesc,
REAL controlPoints[],
PointDescriptor const & controlPointDesc) const;
/// @brief Compute bounds of control points from a local array
void BoundControlPoints(REAL const controlPoints[],
PointDescriptor const & controlPointDesc,
REAL minExtent[],
REAL maxExtent[]) const;
/// @brief Compute bounds of control points from the mesh data
void BoundControlPointsFromMesh(REAL const meshPoints[],
PointDescriptor const & meshPointDesc,
REAL minExtent[],
REAL maxExtent[]) const;
//@}
//@{
/// @name Methods to manage patch points
///
/// Patch points are derived from the control points and are used to
/// evaluate the Surface. The patch points always include the control
/// points as a subset.
///
/// @brief Return the number of patch points representing the Surface
int GetNumPatchPoints() const;
///
/// @brief Prepare patch points in a local array for evaluation
///
/// The patch points consist of the control points plus any additional
/// points derived from them that may be required to represent the
/// limit surface as one or more parametric patches.
///
/// @param meshPoints Input array of mesh point data
/// @param meshPointDesc The size and stride of mesh point data
/// @param patchPoints Output array of patch point data
/// @param patchPointDesc The size and stride of patch point data
///
/// Note that this method requires the mesh data be in a contiguous
/// array. If a large data set is fragmented into blocks or pages, this
/// method cannot be used. The control points will need to be gathered
/// explicitly as the subset of patch points, after which the method to
/// compute the remaining patch points can be used.
///
void PreparePatchPoints(REAL const meshPoints[],
PointDescriptor const & meshPointDesc,
REAL patchPoints[],
PointDescriptor const & patchPointDesc) const;
/// @brief Compute all patch points following the control points
///
/// For cases where the control points have already been gathered into
/// an array allocated for the patch points, the remaining patch points
/// will be computed.
///
/// @param patchPoints Array of patch point data to be modified
/// @param patchPointDesc The size and stride of patch point data
///
void ComputePatchPoints(REAL patchPoints[],
PointDescriptor const & patchPointDesc) const;
//@}
//@{
/// @name Evaluation of positions and derivatives
///
/// Evaluation methods use the patch points to compute position, 1st and
/// 2nd derivatives of the Surface at a given (u,v) coordinate within
/// the domain of the Surface's Parameterization. All parameters of the
/// different overloads are required.
///
/// @brief Evaluation of position
void Evaluate(REAL const uv[2],
REAL const patchPoints[], PointDescriptor const & pointDesc,
REAL P[]) const;
/// @brief Overload of evaluation for 1st derivatives
void Evaluate(REAL const uv[2],
REAL const patchPoints[], PointDescriptor const & pointDesc,
REAL P[], REAL Du[], REAL Dv[]) const;
/// @brief Overload of evaluation for 2nd derivatives
void Evaluate(REAL const uv[2],
REAL const patchPoints[], PointDescriptor const & pointDesc,
REAL P[], REAL Du[], REAL Dv[],
REAL Duu[], REAL Duv[], REAL Dvv[]) const;
//@}
//@{
/// @name Evaluation and application of limit stencils
///
/// Limit stencils are sets of coefficients that express an evaluation
/// as a linear combination of the control points. As with the direct
/// evaluation methods, they are overloaded to optionally provide
/// evaluation for 1st and 2nd derivatives.
///
/// In addition to methods to provide limit stencils, methods are also
/// provided to apply them to the control points. Since application of
/// stencils is identical for each (i.e. the same for position and any
/// derivative) no overloads are provided for derivatives.
///
/// @brief Evaluation of the limit stencil for position
int EvaluateStencil(REAL const uv[2], REAL sP[]) const;
/// @brief Overload of limit stencil evaluation for 1st derivatives
int EvaluateStencil(REAL const uv[2], REAL sP[],
REAL sDu[], REAL sDv[]) const;
/// @brief Overload of limit stencil evaluation for 2nd derivatives
int EvaluateStencil(REAL const uv[2], REAL sP[],
REAL sDu[], REAL sDv[],
REAL sDuu[], REAL sDuv[], REAL sDvv[]) const;
/// @brief Apply a single stencil to control points from a local array
void ApplyStencil(REAL const stencil[],
REAL const controlPoints[], PointDescriptor const &,
REAL result[]) const;
/// @brief Apply a single stencil to control points from the mesh data
void ApplyStencilFromMesh(REAL const stencil[],
REAL const meshPoints[], PointDescriptor const &,
REAL result[]) const;
//@}
private:
// Internal methods for evaluating derivatives, basis weights and
// stencils for regular, irregular and irregular linear patches:
typedef Vtr::ConstArray<int> IndexArray;
void evaluateDerivs(REAL const uv[2], REAL const patchPoints[],
PointDescriptor const &, REAL * derivs[]) const;
void evalRegularDerivs(REAL const uv[2], REAL const patchPoints[],
PointDescriptor const &, REAL * derivs[]) const;
void evalIrregularDerivs(REAL const uv[2], REAL const patchPoints[],
PointDescriptor const &, REAL * derivs[]) const;
void evalMultiLinearDerivs(REAL const uv[2], REAL const patchPoints[],
PointDescriptor const &, REAL * derivs[]) const;
void evalRegularBasis(REAL const uv[2], REAL * wDeriv[]) const;
IndexArray evalIrregularBasis(REAL const uv[2], REAL * wDeriv[]) const;
int evalMultiLinearBasis(REAL const uv[2], REAL * wDeriv[]) const;
int evaluateStencils(REAL const uv[2], REAL * sDeriv[]) const;
int evalRegularStencils(REAL const uv[2], REAL * sDeriv[]) const;
int evalIrregularStencils(REAL const uv[2], REAL * sDeriv[]) const;
int evalMultiLinearStencils(REAL const uv[2], REAL * sDeriv[]) const;
// Internal methods to compute patch points:
void computeLinearPatchPoints(REAL p[], PointDescriptor const &) const;
void computeIrregularPatchPoints(REAL p[], PointDescriptor const &) const;
// Internal methods specific to regular or irregular patches:
unsigned char getRegPatchType() const { return _data.getRegPatchType(); }
unsigned char getRegPatchMask() const { return _data.getRegPatchMask(); }
internal::IrregularPatchType const & getIrregPatch() const;
private:
// Access to the set of member variables - provided to the Factory:
friend class SurfaceFactory;
internal::SurfaceData & getSurfaceData() { return _data; }
internal::SurfaceData const & getSurfaceData() const { return _data; }
private:
// All member variables encapsulated in a single class:
internal::SurfaceData _data;
};
//
// Simple inline methods composed of other methods:
//
template <typename REAL>
inline void
Surface<REAL>::ComputePatchPoints(REAL points[],
PointDescriptor const & pointDesc) const {
if (!IsRegular()) {
if (IsLinear()) {
computeLinearPatchPoints(points, pointDesc);
} else {
computeIrregularPatchPoints(points, pointDesc);
}
}
}
template <typename REAL>
inline void
Surface<REAL>::PreparePatchPoints(
REAL const meshPoints[], PointDescriptor const & meshPointDesc,
REAL patchPoints[], PointDescriptor const & patchPointDesc) const {
GatherControlPoints(meshPoints, meshPointDesc, patchPoints, patchPointDesc);
ComputePatchPoints(patchPoints, patchPointDesc);
}
//
// Inline invocations of more general methods for derivative overloads:
//
template <typename REAL>
inline void
Surface<REAL>::evaluateDerivs(REAL const uv[2],
REAL const patchPoints[],
PointDescriptor const & pointDesc,
REAL * derivatives[]) const {
if (IsRegular()) {
evalRegularDerivs(uv, patchPoints, pointDesc, derivatives);
} else if (IsLinear()) {
evalMultiLinearDerivs(uv, patchPoints, pointDesc, derivatives);
} else {
evalIrregularDerivs(uv, patchPoints, pointDesc, derivatives);
}
}
template <typename REAL>
inline void
Surface<REAL>::Evaluate(REAL const uv[2],
REAL const patchPoints[],
PointDescriptor const & pointDesc,
REAL P[]) const {
REAL * derivatives[6] = { P, 0, 0, 0, 0, 0 };
evaluateDerivs(uv, patchPoints, pointDesc, derivatives);
}
template <typename REAL>
inline void
Surface<REAL>::Evaluate(REAL const uv[2],
REAL const patchPoints[],
PointDescriptor const & pointDesc,
REAL P[], REAL Du[], REAL Dv[]) const {
REAL * derivatives[6] = { P, Du, Dv, 0, 0, 0 };
evaluateDerivs(uv, patchPoints, pointDesc, derivatives);
}
template <typename REAL>
inline void
Surface<REAL>::Evaluate(REAL const uv[2],
REAL const patchPoints[],
PointDescriptor const & pointDesc,
REAL P[], REAL Du[], REAL Dv[],
REAL Duu[], REAL Duv[], REAL Dvv[]) const {
REAL * derivatives[6] = { P, Du, Dv, Duu, Duv, Dvv };
evaluateDerivs(uv, patchPoints, pointDesc, derivatives);
}
template <typename REAL>
inline int
Surface<REAL>::evaluateStencils(REAL const uv[2], REAL * sDeriv[]) const {
if (IsRegular()) {
return evalRegularStencils(uv, sDeriv);
} else if (IsLinear()) {
return evalMultiLinearStencils(uv, sDeriv);
} else {
return evalIrregularStencils(uv, sDeriv);
}
}
template <typename REAL>
inline int
Surface<REAL>::EvaluateStencil(REAL const uv[2], REAL sP[]) const {
REAL * derivativeStencils[6] = { sP, 0, 0, 0, 0, 0 };
return evaluateStencils(uv, derivativeStencils);
}
template <typename REAL>
inline int
Surface<REAL>::EvaluateStencil(REAL const uv[2],
REAL sP[], REAL sDu[], REAL sDv[]) const {
REAL * derivativeStencils[6] = { sP, sDu, sDv, 0, 0, 0 };
return evaluateStencils(uv, derivativeStencils);
}
template <typename REAL>
inline int
Surface<REAL>::EvaluateStencil(REAL const uv[2],
REAL sP[], REAL sDu[], REAL sDv[],
REAL sDuu[], REAL sDuv[], REAL sDvv[]) const {
REAL * derivativeStencils[6] = { sP, sDu, sDv, sDuu, sDuv, sDvv };
return evaluateStencils(uv, derivativeStencils);
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_SURFACE */

View File

@@ -0,0 +1,68 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../bfr/surfaceData.h"
#include "../bfr/patchTree.h"
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
namespace internal {
//
// Constructors and other methods to manage data members for copy and
// destruction:
//
SurfaceData::SurfaceData() : _cvIndices(), _param(),
_isValid(false),
_isDouble(false),
_isRegular(true),
_isLinear(false),
_regPatchType(0),
_regPatchMask(0),
_irregPatch() {
}
SurfaceData &
SurfaceData::operator=(SurfaceData const & src) {
// No need to explicitly manage pre-existing resources in destination
// as they will be either re-used or released when re-assigned
// No copy/operator= supported by StackBuffer so resize and copy:
_cvIndices.SetSize(src._cvIndices.GetSize());
std::memcpy(&_cvIndices[0],
&src._cvIndices[0], src._cvIndices.GetSize() * sizeof(Index));
_param = src._param;
_isValid = src._isValid;
_isDouble = src._isDouble;
_isRegular = src._isRegular;
_isLinear = src._isLinear;
_regPatchType = src._regPatchType;
_regPatchMask = src._regPatchMask;
_irregPatch = src._irregPatch;
return *this;
}
void
SurfaceData::invalidate() {
// Release any attached memory before marking as invalid:
_irregPatch = 0;
_isValid = false;
}
} // end namespace internal
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv

View File

@@ -0,0 +1,112 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_SURFACE_DATA_H
#define OPENSUBDIV3_BFR_SURFACE_DATA_H
#include "../version.h"
#include "../bfr/parameterization.h"
#include "../bfr/irregularPatchType.h"
#include "../vtr/stackBuffer.h"
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
namespace internal {
//
// SurfaceData is a simple internal class that encapsulates all member
// variables of a Surface -- allowing the SurfaceFactory to initialize
// a Surface independent of its final type.
//
// Since internal, and access to instances of SurfaceData is restricted
// by other means, all accessors and modifiers are made public (though
// only the SurfaceFactory modifies an instance).
//
class SurfaceData {
public:
SurfaceData();
SurfaceData(SurfaceData const & src) { *this = src; }
SurfaceData & operator=(SurfaceData const & src);
~SurfaceData() { invalidate(); }
public:
// Simple accessors used by both Surface and SurfaceFactory:
typedef int Index;
int getNumCVs() const { return (int)_cvIndices.GetSize(); }
Index const * getCVIndices() const { return &_cvIndices[0]; }
Parameterization getParam() const { return _param; }
bool isValid() const { return _isValid; }
bool isDouble() const { return _isDouble; }
bool isRegular() const { return _isRegular; }
bool isLinear() const { return _isLinear; }
unsigned char getRegPatchType() const { return _regPatchType; }
unsigned char getRegPatchMask() const { return _regPatchMask; }
// Local types and accessors for references to irregular patches:
typedef internal::IrregularPatchType IrregPatchType;
typedef internal::IrregularPatchSharedPtr IrregPatchPtr;
bool hasIrregPatch() const { return _irregPatch != 0; }
IrregPatchType const & getIrregPatch() const { return *_irregPatch; }
IrregPatchPtr getIrregPatchPtr() const { return _irregPatch; }
public:
// Modifiers used by SurfaceFactory to assemble a Surface:
void invalidate();
void reinitialize() { if (isValid()) invalidate(); }
Index * getCVIndices() { return &_cvIndices[0]; }
Index * resizeCVs(int size) {
_cvIndices.SetSize(size);
return &_cvIndices[0];
}
void setParam(Parameterization p) { _param = p; }
void setValid(bool on) { _isValid = on; }
void setDouble(bool on) { _isDouble = on; }
void setRegular(bool on) { _isRegular = on; }
void setLinear(bool on) { _isLinear = on; }
void setRegPatchType(int t) { _regPatchType = (unsigned char) t; }
void setRegPatchMask(int m) { _regPatchMask = (unsigned char) m; }
void setIrregPatchPtr(IrregPatchPtr const & ptr) { _irregPatch = ptr; }
private:
// Member variables -- try to avoid redundancy and/or wasted space
// here as some may choose to cache all Surfaces of a mesh:
typedef Vtr::internal::StackBuffer<Index,20,true> CVIndexArray;
CVIndexArray _cvIndices;
Parameterization _param;
unsigned char _isValid : 1;
unsigned char _isDouble : 1;
unsigned char _isRegular : 1;
unsigned char _isLinear : 1;
unsigned char _regPatchType;
unsigned char _regPatchMask;
IrregPatchPtr _irregPatch;
};
} // end namespace internal
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_SURFACE_DATA */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,546 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_SURFACE_FACTORY_H
#define OPENSUBDIV3_BFR_SURFACE_FACTORY_H
#include "../version.h"
#include "../bfr/surface.h"
#include "../bfr/surfaceFactoryMeshAdapter.h"
#include "../sdc/options.h"
#include "../sdc/types.h"
#include <cstdint>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// Forward declarations of public and internal classes used by factories:
//
class SurfaceFactoryCache;
class FaceTopology;
class FaceSurface;
///
/// @brief Base class providing initialization of a Surface for each face
/// of a mesh
///
/// SurfaceFactory is an abstract class that provides the majority of
/// the implementation and the interface for a factory that initializes
/// instances of Surface for the faces of a mesh.
///
/// A subclass of SurfaceFactory is written to support a specific type
/// of connected mesh. The public interface of SurfaceFactory is both
/// inherited by and extended by the subclasses. Expected extensions to
/// the interface include one or more constructors (i.e. given a specific
/// instance of the subclass' mesh type) as well as other methods that
/// may involve the mesh's data types (primvars) in their native form.
///
/// By inheriting the SurfaceFactoryMeshAdapter interface, SurfaceFactory
/// requires its subclasses to implement the small suite of pure
/// virtual methods to complete the factory's implementation for the
/// subclass' mesh type. These methods provide the base factory with
/// topological information about faces of that mesh -- from which it
/// creates instances of Surface defining their limit surface.
///
/// The SurfaceFactory inherits rather than contains SurfaceFactoryMeshAdapter
/// as instances of SurfaceFactoryMeshAdapter serve no purpose on their own,
/// and the interface between the two is designed with the specific needs
/// of the SurfaceFactory. When customizing a subclass of SurfaceFactory
/// for a particular mesh type, this inheritance also avoids the need to
/// coordinate the subclass of SurfaceFactory with the separate subclass
/// of SurfaceFactoryMeshAdapter.
///
/// It must be emphasized that a subclass of SurfaceFactory is written to
/// support a specific type of "connected" mesh -- not simply a container
/// of data defining a mesh. The SurfaceFactoryMeshAdapter interface describes
/// the complete topological neighborhood around a specific face, and
/// without any connectivity between mesh components (e.g. given a vertex,
/// what are its incident faces?), satisfying these methods will be
/// impossible, or, at best, extremely inefficient.
///
/// Ultimately a subclass of SurfaceFactory is expected to be a lightweight
/// interface to a connected mesh -- lightweight in terms of both time and
/// memory usage. It's construction is expected to be trivial, after which
/// it can quickly and efficiently provide a Surface for one or more faces
/// of a mesh for immediate evaluation. So construction of an instance of
/// a subclass should involve no heavy pre-processing -- the greater the
/// overhead of a subclass constructor, the more it violates the intention
/// of the base class as a lightweight interface.
///
/// Instances of SurfaceFactory are initialized with a set of Options that
/// form part of the state of the factory and remain fixed for its lifetime.
/// Such options are intended to ensure that the instances of Surface that
/// it creates are consistent, as well as to enable/disable or otherwise
/// manage caching for construction efficiency -- either internally or
/// between itself and other factories (advanced).
///
class SurfaceFactory : public SurfaceFactoryMeshAdapter {
public:
///
/// @brief Simple set of options assigned to instances of SurfaceFactory
///
/// The Options class is a simple container specifying options for the
/// construction of the SurfaceFactory to be applied during its lifetime.
///
/// These options currently include choices to identify a default
/// face-varying ID, to control caching behavior (on or off, use of
/// external vs internal cache), and to control the accuracy of the
/// resulting limit surface representations.
///
class Options {
public:
Options() : _dfltFVarID(-1), _externCache(0), _enableCache(true),
_approxLevelSmooth(2), _approxLevelSharp(6) { }
/// @brief Assign the default face-varying ID (none assigned by
/// default)
Options & SetDefaultFVarID(FVarID id);
/// @brief Return the default face-varying ID
FVarID GetDefaultFVarID() const { return _dfltFVarID; }
/// @brief Enable or disable caching (default is true):
Options & EnableCaching(bool on);
/// @brief Return if caching is enable
bool IsCachingEnabled() const { return _enableCache; }
/// @brief Assign an external cache to override the internal
Options & SetExternalCache(SurfaceFactoryCache * c);
/// @brief Return any assigned external cache
SurfaceFactoryCache * GetExternalCache() const { return _externCache; }
// Set refinement levels used to approximate the limit surface
// for smooth and sharp features (reasonable defaults assigned):
/// @brief Assign maximum refinement level for smooth features
Options & SetApproxLevelSmooth(int level);
/// @brief Return maximum refinement level for smooth features
int GetApproxLevelSmooth() const { return _approxLevelSmooth; }
/// @brief Assign maximum refinement level for sharp features
Options & SetApproxLevelSharp(int level);
/// @brief Return maximum refinement level for sharp features
int GetApproxLevelSharp() const { return _approxLevelSharp; }
private:
// Member variables:
FVarID _dfltFVarID;
SurfaceFactoryCache * _externCache;
unsigned char _enableCache : 1;
unsigned char _approxLevelSmooth;
unsigned char _approxLevelSharp;
};
public:
~SurfaceFactory() override;
//@{
/// @name Simple queries of subdivision properties
///
/// Simple queries to inspect subdivision properties.
///
/// @brief Return the subdivision scheme
Sdc::SchemeType GetSchemeType() const { return _subdivScheme; }
/// @brief Return the set of subdivision options
Sdc::Options GetSchemeOptions() const { return _subdivOptions; }
//@}
public:
//@{
/// @name Simple queries influencing Surface construction
///
/// Methods to quickly inspect faces that influence Surface construction.
///
/// A small set of methods is useful to inspect faces in order to
/// determine if their corresponding Surfaces should be initialized.
/// The Surface initialization methods will fail when a limit surface
/// does exist, so the methods here are intended for purposes when
/// that simple failure on initialization is not suitable, e.g. to
/// address some kind of pre-processing need prior to the initialization
/// of any Surfaces.
///
/// @brief Return if a specified face has a limit surface
///
/// This method determines if a face has an associated limit surface,
/// and so supports initialization of Surface for evaluation. This
/// is usually the case, except when the face is tagged as a hole, or
/// due to the use of uncommon boundary interpolation options (i.e.
/// Sdc::Options::VTX_BOUNDARY_NONE). The test of a hole is trivial,
/// but the boundary test is not when such uncommon options are used.
///
bool FaceHasLimitSurface(Index faceIndex) const;
/// @brief Return the Parameterization of a face with a limit surface
///
/// This method simply returns the Parameterization of the specified
/// face. It is presumed the face has an existing limit surface and
/// so is a quick and simple accessor.
///
Parameterization GetFaceParameterization(Index faceIndex) const;
//@}
public:
//@{
/// @name Methods to initialize Surfaces
///
/// Methods to initialize instances of Surface for a specific face.
///
/// Given the different interpolation types for data associated with
/// mesh vertices (i.e. vertex, varying and face-varying data), the
/// topology of the limit surface potentially (likely) differs between
/// them. So it is necessary to specify the type of data to be associated
/// with the Surface. Methods exist to initialize a single surface for
/// each of the three data types, and to initialize multiple surfaces
/// for different data types at once (which will avoid repeated effort
/// in a single-threaded context).
///
/// Failure of these initialization methods is expected (and so to be
/// tested) when a face has no limit surface -- either due to it being
/// a hole or through the use of less common boundary interpolation
/// options. Failure is also possible if the subclass fails to provide
/// a valid topological description of the face. (WIP - consider more
/// extreme failure for these cases, e.g. possible assertions.)
///
/// @brief Initialize a Surface for vertex data
///
/// @param faceIndex Index of face with limit surface of interest
/// @param surface Surface to initialize for vertex data
/// @return True if the face has a limit surface and it was
/// successfully constructed
///
template <typename REAL>
bool InitVertexSurface(Index faceIndex, Surface<REAL> * surface) const;
/// @brief Initialize a Surface for varying data
///
/// @param faceIndex Index of face with limit surface of interest
/// @param surface Surface to initialize for varying data
/// @return True if the face has a limit surface and it was
/// successfully constructed
///
template <typename REAL>
bool InitVaryingSurface(Index faceIndex, Surface<REAL> * surface) const;
/// @brief Initialize a Surface for the default face-varying data
///
/// For this variant, no explicit face-varying ID is specified. The
/// default is determined from the Options with which the SurfaceFactory
/// was created (assignment of that default is required).
///
/// @param faceIndex Index of face with limit surface of interest
/// @param surface Surface to initialize for face-varying data
/// @return True if the face has a limit surface, the default
/// face-varying ID was valid, and its Surface was
/// successfully constructed
///
template <typename REAL>
bool InitFaceVaryingSurface(Index faceIndex, Surface<REAL> * surface) const;
/// @brief Initialize a Surface for specified face-varying data
///
/// @param faceIndex Index of face with limit surface of interest
/// @param surface Surface to initialize for face-varying data
/// @param fvarID Identifier of a specific set of face-varying data
/// @return True if the face has a limit surface, the given
/// face-varying ID was valid, and its Surface was
/// successfully constructed
///
template <typename REAL>
bool InitFaceVaryingSurface(Index faceIndex, Surface<REAL> * surface,
FVarID fvarID) const;
///
/// @brief Initialize multiple Surfaces at once
///
/// This method initializes multiple Surfaces at once -- for any
/// combination of the three different data interpolation types.
/// Its use is recommended when two are more surfaces are known to
/// be non-linear, which will avoid the repeated effort if each
/// Surface is individually initialized.
///
/// Arguments are ordered here to satisfy common cases easily with
/// the use of optional arguments for less common cases.
///
/// @param faceIndex Index of face with limit surfaces of interest
/// @param vtxSurface Surface to initialize for vertex data
/// @param fvarSurfaces Surface array to initialize for face-varying data
/// @param fvarIDs Array of face-varying IDs corresponding to the
/// face-varying Surfaces to be initialized
/// (optional -- defaults to an integer sequence
/// [0 .. fvarCount-1] if absent)
/// @param fvarCount Size of array of face-varying Surfaces (optional)
/// @param varSurface Surface to initialize for varying data (optional)
/// @return True if the face has a limit surface, any given
/// face-varying IDs were valid, and all Surfaces
/// were successfully constructed.
///
template <typename REAL>
bool InitSurfaces(Index faceIndex, Surface<REAL> * vtxSurface,
Surface<REAL> * fvarSurfaces,
FVarID const fvarIDs[] = 0,
int fvarCount = 0,
Surface<REAL> * varSurface = 0) const;
//@}
//@{
/// @name Methods to construct Surfaces
///
/// Methods to both allocate and initialize a single Surface.
//
// WIP - considering removing these since non-essential
//
/// @brief Construct a Surface for vertex data
template <typename REAL=float>
Surface<REAL> * CreateVertexSurface(Index faceIndex) const;
/// @brief Construct a Surface for varying data
template <typename REAL=float>
Surface<REAL> * CreateVaryingSurface(Index faceIndex) const;
/// @brief Construct a Surface for the default face-varying data
template <typename REAL=float>
Surface<REAL> * CreateFaceVaryingSurface(Index faceIndex) const;
/// @brief Construct a Surface for specified face-varying data
template <typename REAL=float>
Surface<REAL> * CreateFaceVaryingSurface(Index faceIndex, FVarID id) const;
//@}
protected:
//@{
/// @name Protected methods supporting subclass construction
///
/// Protected methods supporting subclass construction.
///
///
/// @brief Constructor to be used by subclasses
///
/// Construction requires specification of the subdivision scheme and
/// options associated with the mesh (as is the case with other classes
/// in Far). These will typically reflect the settings in the mesh but
/// can also be used to override them -- as determined by the subclass.
/// Common uses of overrides are to assign a subdivision scheme to a
/// simple polygonal mesh, or to change the face-varying interpolation
/// for the faster linear interpolation of UVs.
///
SurfaceFactory(Sdc::SchemeType schemeType,
Sdc::Options const & schemeOptions,
Options const & limitOptions);
/// @brief Subclass to identify an internal cache for use by base class
void setInternalCache(SurfaceFactoryCache * cache);
SurfaceFactory(SurfaceFactory const &) = delete;
SurfaceFactory & operator=(SurfaceFactory const &) = delete;
//@}
private:
// Supporting internal methods:
void setSubdivisionOptions(Sdc::SchemeType, Sdc::Options const & options);
void setFactoryOptions(Options const & factoryOptions);
bool faceHasLimitSimple(Index faceIndex, int faceSize) const;
bool faceHasLimitNeighborhood(Index faceIndex) const;
bool faceHasLimitNeighborhood(FaceTopology const & faceTopology) const;
class SurfaceSet;
bool populateAllSurfaces( Index faceIndex, SurfaceSet * sSetPtr) const;
bool populateLinearSurfaces( Index faceIndex, SurfaceSet * sSetPtr) const;
bool populateNonLinearSurfaces(Index faceIndex, SurfaceSet * sSetPtr) const;
bool initSurfaces(Index faceIndex, internal::SurfaceData * vtxSurface,
internal::SurfaceData * varSurface,
internal::SurfaceData * fvarSurfaces,
int fvarCount,
FVarID const fvarIDs[]) const;
// Methods to assemble topology and corresponding indices for entire face:
bool isFaceNeighborhoodRegular(Index faceIndex,
FVarID const * fvarPtrOrVtx,
Index indices[]) const;
bool initFaceNeighborhoodTopology(Index faceIndex,
FaceTopology * topology) const;
bool gatherFaceNeighborhoodTopology(Index faceIndex,
FaceTopology * topology) const;
int gatherFaceNeighborhoodIndices(Index faceIndex,
FaceTopology const & topology,
FVarID const * fvarPtrOrVtx,
Index indices[]) const;
// Methods to assemble Surfaces for the different categories of patch:
typedef internal::SurfaceData SurfaceType;
void assignLinearSurface(SurfaceType * surfacePtr,
Index faceIndex,
FVarID const * fvarPtrOrVtx) const;
void assignRegularSurface(SurfaceType * surfacePtr,
Index const surfacePatchPoints[]) const;
void assignRegularSurface(SurfaceType * surfacePtr,
FaceSurface const & surfaceDescription) const;
void assignIrregularSurface(SurfaceType * surfacePtr,
FaceSurface const & surfaceDescription) const;
void copyNonLinearSurface(SurfaceType * surfacePtr,
SurfaceType const & surfaceSource,
FaceSurface const & surfaceDescription) const;
private:
// Members describing options and subdivision properties (very little
// memory and low initialization cost)
Sdc::SchemeType _subdivScheme;
Sdc::Options _subdivOptions;
Options _factoryOptions;
// Members related to subdivision topology, options and limit tests:
unsigned int _linearScheme : 1;
unsigned int _linearFVarInterp : 1;
unsigned int _testNeighborhoodForLimit : 1;
unsigned int _rejectSmoothBoundariesForLimit : 1;
unsigned int _rejectIrregularFacesForLimit : 1;
int _regFaceSize;
// Members related to caching:
SurfaceFactoryCache mutable * _topologyCache;
};
//
// Inline methods for Options:
//
inline SurfaceFactory::Options &
SurfaceFactory::Options::SetDefaultFVarID(FVarID id) {
_dfltFVarID = id;
return *this;
}
inline SurfaceFactory::Options &
SurfaceFactory::Options::EnableCaching(bool on) {
_enableCache = on;
return *this;
}
inline SurfaceFactory::Options &
SurfaceFactory::Options::SetExternalCache(SurfaceFactoryCache * c) {
_externCache = c;
return *this;
}
inline SurfaceFactory::Options &
SurfaceFactory::Options::SetApproxLevelSmooth(int level) {
_approxLevelSmooth = (unsigned char) level;
return *this;
}
inline SurfaceFactory::Options &
SurfaceFactory::Options::SetApproxLevelSharp(int level) {
_approxLevelSharp = (unsigned char) level;
return *this;
}
//
// Inline methods to initializes Surfaces:
//
template <typename REAL>
inline bool
SurfaceFactory::InitVertexSurface(Index face, Surface<REAL> * s) const {
return initSurfaces(face, &s->getSurfaceData(), 0, 0, 0, 0);
}
template <typename REAL>
inline bool
SurfaceFactory::InitVaryingSurface(Index face, Surface<REAL> * s) const {
return initSurfaces(face, 0, &s->getSurfaceData(), 0, 0, 0);
}
template <typename REAL>
inline bool
SurfaceFactory::InitFaceVaryingSurface(Index face, Surface<REAL> * s,
FVarID fvarID) const {
return initSurfaces(face, 0, 0, &s->getSurfaceData(), 1, &fvarID);
}
template <typename REAL>
inline bool
SurfaceFactory::InitFaceVaryingSurface(Index face, Surface<REAL> * s) const {
FVarID dfltID = _factoryOptions.GetDefaultFVarID();
return initSurfaces(face, 0, 0, &s->getSurfaceData(), 1, &dfltID);
}
template <typename REAL>
inline bool
SurfaceFactory::InitSurfaces(Index faceIndex, Surface<REAL> * vtxSurface,
Surface<REAL> * fvarSurfaces, FVarID const fvarIDs[], int fvarCount,
Surface<REAL> * varSurface) const {
bool useDfltFVarID = fvarSurfaces && (fvarIDs == 0) && (fvarCount == 0);
FVarID dfltFVarID = useDfltFVarID ? _factoryOptions.GetDefaultFVarID() : 0;
return initSurfaces(faceIndex,
vtxSurface ? &vtxSurface->getSurfaceData() : 0,
varSurface ? &varSurface->getSurfaceData() : 0,
fvarSurfaces ? &fvarSurfaces->getSurfaceData() : 0,
fvarCount ? fvarCount : (fvarSurfaces != 0),
useDfltFVarID ? &dfltFVarID : fvarIDs);
}
//
// Inline methods to allocate and initialize Surfaces:
//
template <typename REAL>
inline Surface<REAL> *
SurfaceFactory::CreateVertexSurface(Index faceIndex) const {
Surface<REAL> * s = new Surface<REAL>();
if (InitVertexSurface<REAL>(faceIndex, s)) return s;
delete s;
return 0;
}
template <typename REAL>
inline Surface<REAL> *
SurfaceFactory::CreateVaryingSurface(Index faceIndex) const {
Surface<REAL> * s = new Surface<REAL>();
if (InitVaryingSurface<REAL>(faceIndex, s)) return s;
delete s;
return 0;
}
template <typename REAL>
inline Surface<REAL> *
SurfaceFactory::CreateFaceVaryingSurface(Index faceIndex, FVarID fvarID) const {
Surface<REAL> * s = new Surface<REAL>();
if (InitFaceVaryingSurface<REAL>(faceIndex, s, fvarID)) return s;
delete s;
return 0;
}
template <typename REAL>
inline Surface<REAL> *
SurfaceFactory::CreateFaceVaryingSurface(Index face) const {
FVarID dfltID = _factoryOptions.GetDefaultFVarID();
return CreateFaceVaryingSurface<REAL>(face, dfltID);
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_SURFACE_FACTORY_H */

View File

@@ -0,0 +1,64 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../bfr/surfaceFactoryCache.h"
#include "../bfr/patchTree.h"
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// Trivial constructor and destructor:
//
SurfaceFactoryCache::SurfaceFactoryCache() : _map() {
}
SurfaceFactoryCache::~SurfaceFactoryCache() {
// Potentially monitor usage on destruction
}
//
// Internal methods to find and add map entries:
//
SurfaceFactoryCache::DataType
SurfaceFactoryCache::find(KeyType const & key) const {
MapType::const_iterator itFound = _map.find(key);
return (itFound != _map.end()) ? itFound->second : DataType(0);
}
SurfaceFactoryCache::DataType
SurfaceFactoryCache::add(KeyType const & key, DataType const & data) {
MapType::const_iterator itFound = _map.find(key);
return (itFound != _map.end()) ? itFound->second : (_map[key] = data);
}
//
// Virtual method defaults -- intended to be overridden for thread-safety:
//
SurfaceFactoryCache::DataType
SurfaceFactoryCache::Find(KeyType const & key) const {
return find(key);
}
SurfaceFactoryCache::DataType
SurfaceFactoryCache::Add(KeyType const & key, DataType const & data) {
return add(key, data);
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv

View File

@@ -0,0 +1,133 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_SURFACE_FACTORY_CACHE_H
#define OPENSUBDIV3_BFR_SURFACE_FACTORY_CACHE_H
#include "../version.h"
#include "../bfr/irregularPatchType.h"
#include <map>
#include <cstdint>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
///
/// @brief Container used internally by SurfaceFactory to store reusable
/// information
///
/// SurfaceFactoryCache is a container for storing/caching instances of
/// the internal representation of complex patches used by SurfaceFactory
/// so that they can be quickly identified and retrieved for reuse.
///
/// It is intended for internal use by SurfaceFactory. Public access is
/// available but limited to construction only -- allowing an instance to
/// be reused by assigning it to more than one SurfaceFactory.
///
//
// Initial/expected use requires simple searches of and additions to the
// cache by the SurfaceFactory or its Builders. Longer term, with the
// possibility of instances of caches being shared between meshes and
// factories, additional options and/or public methods may be warranted
// to limit what is cached or to prune the cache if it gets too large.
//
class SurfaceFactoryCache {
public:
SurfaceFactoryCache();
virtual ~SurfaceFactoryCache();
SurfaceFactoryCache(SurfaceFactoryCache const &) = delete;
SurfaceFactoryCache & operator=(SurfaceFactoryCache const &) = delete;
protected:
/// @cond PROTECTED
// Access restricted to the Factory, its Builders, etc.
friend class SurfaceFactory;
typedef std::uint64_t KeyType;
typedef internal::IrregularPatchSharedPtr DataType;
/// @endcond PROTECTED
protected:
/// @cond PROTECTED
size_t Size() const { return _map.size(); }
//
// Potential overrides by subclasses for thread-safety:
//
virtual DataType Find(KeyType const & key) const;
virtual DataType Add(KeyType const & key, DataType const & data);
//
// Common implementation used by all subclasses:
//
DataType find(KeyType const & key) const;
DataType add(KeyType const & key, DataType const & data);
/// @endcond PROTECTED
private:
typedef std::map<KeyType, DataType> MapType;
MapType _map;
};
///
/// @brief Template for declaring thread-safe subclasses of SurfaceFactoryCache
///
/// SurfaceFactoryCacheThreaded extends SurfaceFactoryCache by protecting
/// access to the cache to ensure thread-safe operation. A mutex type and
/// associated locks are specified to declare a subclass with appropriately
/// protected read and write access.
///
/// @tparam MUTEX_TYPE A mutex type with supported lock guards
/// @tparam READ_LOCK_GUARD_TYPE A scoped lock guard allowing potentially
/// shared access for read operations.
/// @tparam WRITE_LOCK_GUARD_TYPE A scoped lock guard allowing exclusive
/// access for write operations.
///
// Separate read and write locks are provided to support mutex types
// allowing shared (read) or exclusive (write) access.
//
template <class MUTEX_TYPE, class READ_LOCK_GUARD_TYPE,
class WRITE_LOCK_GUARD_TYPE>
class SurfaceFactoryCacheThreaded : public SurfaceFactoryCache {
public:
SurfaceFactoryCacheThreaded() : SurfaceFactoryCache() { }
~SurfaceFactoryCacheThreaded() override = default;
protected:
/// @cond PROTECTED
//
// Virtual overrides from base:
//
DataType Find(KeyType const & key) const override {
READ_LOCK_GUARD_TYPE lockGuard(_mutex);
return find(key);
}
DataType Add(KeyType const & key, DataType const & data) override {
WRITE_LOCK_GUARD_TYPE lockGuard(_mutex);
return add(key, data);
}
/// @endcond PROTECTED
private:
MUTEX_TYPE mutable _mutex;
};
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_SURFACE_FACTORY_CACHE_H */

View File

@@ -0,0 +1,219 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_SURFACE_FACTORY_ADAPTER_H
#define OPENSUBDIV3_BFR_SURFACE_FACTORY_ADAPTER_H
#include "../version.h"
#include <cstdint>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
class VertexDescriptor;
///
/// @brief Abstract interface adapting SurfaceFactory to a connected mesh
/// representation
///
// SurfaceFactoryMeshAdapter is an abstract class that defines the interface
// through which subclasses of SurfaceFactory adapt to a connected mesh
// representation. The interface defines methods that describe the mesh
// topology and control indices in the neighborhood of a mesh -- from
// which the SurfaceFactory to identifies an appropriate limit surface.
//
// SurfaceFactoryMeshAdapter methods require a subclass to provide a complete
// description of the topology around a base face, as well as indices
// associated with it (both vertex and face-varying). The intent here is
// to keep the number of methods required to a minimum, and also to minimize
// the number of invocations required by the factory.
//
// With the need to support both linear and non-linear cases (for which
// linear is trivial by comparison) and the limit surface for both vertex
// and face-varying topologies, the result is a small set of methods
// covering this matrix of functionality.
//
// Since face-varying data may differ in topology from the vertex data --
// with each set of face-varying data potentially having its own unique
// topology -- sets of face-varying data are uniquely distinguished by an
// associated integer (a face-varying ID).
//
class SurfaceFactoryMeshAdapter {
public:
/// @brief Integer type representing a mesh index
typedef int Index;
/// @brief Type used to identify and specify face-varying primvars
///
/// A face-varying ID is used to specify face-varying primvars for
/// evaluation so that they can be identified by the subclass for
/// the mesh. It can be assigned as either a positive integer ID
/// or pointer, with the subclass determining its interpretation.
///
/// Often only one face-varying primvar is of interest, so a default
/// can be assigned to the factory to avoid repeated specification.
///
typedef std::intptr_t FVarID;
protected:
/// @cond PROTECTED
SurfaceFactoryMeshAdapter() { }
virtual ~SurfaceFactoryMeshAdapter() { }
/// @endcond
protected:
//@{
/// @name Methods to query simple face properties
///
/// Methods to return simple properties associated with a face.
///
/// @brief Returns if a face is a hole
virtual bool isFaceHole(Index faceIndex) const = 0;
/// @brief Returns the size of a face (number of vertices)
virtual int getFaceSize(Index faceIndex) const = 0;
//@}
//@{
/// @name Methods to gather indices for the face's vertices
///
/// These methods gather indices associated with the vertices of a
/// face, e.g. the indices of the vertices themselves, or the indices
/// of face-varying values associated with the vertices. These are
/// used to quickly deal with linear limit surfaces without any
/// inspection of the neighboring topology.
///
/// @brief Gather the indices of the face's vertices
virtual int getFaceVertexIndices(Index faceIndex,
Index vertexIndices[]) const = 0;
/// @brief Gather the face-varying indices of the face's vertices
virtual int getFaceFVarValueIndices(Index faceIndex,
FVarID fvarID, Index fvarValueIndices[]) const = 0;
//@}
protected:
//@{
/// @name Methods to identify the neighborhood of a face-vertex
///
/// These methods identify the topology and associated indices for
/// the complete set of incident faces surrounding a corner (or
/// face-vertex) of a face.
///
/// Methods here use "FaceVertex" in the name to emphasize that they
/// require information for a particular corner vertex of the face.
///
/// The topology around the face-vertex is described by populating a
/// given instance of a simple VertexDescriptor class -- which fully
/// describes the face-vertex, it incident faces and any sharpness
/// assigned at or around the face-vertex. (See the comments with
/// the VertexDescriptor definition for more details.)
///
/// Additional methods are then required to identify indices for the
/// incident faces around a face-vertex. One method gathers the
/// indices for control vertices of the mesh assigned to the incident
/// faces (their VertexIndices), while the other gathers indices for
/// a particular set of face-varying values assigned to them (their
/// FVarValueIndices).
///
/// Both methods expect the incident faces to be ordered consistent
/// with the specification in VertexDescriptor, and all indices for
/// all incident faces are required.
///
/// The order of indices assigned to each face for these methods must
/// also be specified relative to the face-vertex, rather than the
/// way the face is defined. For example, if a quad Q is defined by
/// the four vertices {A, B, C, D}, when gathering the indices for Q
/// as part of face-vertex C, the indices should be specified starting
/// with C, i.e. as {C, D, A, B}. Ordering indices this way makes it
/// much easier for the factory to identify when face-varying topology
/// differs from the vertex topology, and both the face-varying and
/// vertex indices are ordered this way for consistency.
///
/// @brief Describe the topology of incident faces around a face-vertex
virtual int populateFaceVertexDescriptor(
Index faceIndex, int faceVertex,
VertexDescriptor * vertexDescriptor) const = 0;
/// @brief Gather vertex indices of incident faces around a face-vertex
virtual int getFaceVertexIncidentFaceVertexIndices(
Index faceIndex, int faceVertex,
Index vertexIndices[]) const = 0;
/// @brief Gather face-varying indices of incident faces around a
/// face-vertex
virtual int getFaceVertexIncidentFaceFVarValueIndices(
Index faceIndex, int faceVertex,
FVarID fvarID, Index fvarValueIndices[]) const = 0;
//@}
protected:
//@{
/// @name Optional methods for purely regular topology
///
/// Optional methods for advanced use to accelerate the case of
/// purely regular topology around a face.
///
/// For cases when a mesh can quickly determine if the neighborhood
/// around a faces is purely regular, these methods can be used to
/// quickly identify the control point indices for the corresponding
/// regular patch defining its limit surface. In doing so, the more
/// tedious topological assembly requiring information about each
/// face-vertex can be avoided.
///
/// The indices returned must be ordered according to the regular
/// patch type corresponding to the subdivision scheme of the mesh.
/// Boundary vertices are allowed and indicated by an Index of -1.
///
/// The face-varying version will only be invoked if the vertex
/// version is purely regular, in which case, the face-varying
/// topology is expected to be similar.
///
/// Note that these methods allow the caller (the SurfaceFactory) to
/// pass nullptr (0) for the index arrays -- in which case only the
/// return value should be provided.
///
virtual bool getFaceNeighborhoodVertexIndicesIfRegular(
Index faceIndex, Index vertexIndices[]) const;
virtual bool getFaceNeighborhoodFVarValueIndicesIfRegular(
Index faceIndex, FVarID fvarID, Index fvarValueIndices[]) const;
//@}
private:
// No private members
};
//
// Inline defaults for optional methods:
//
inline bool
SurfaceFactoryMeshAdapter::getFaceNeighborhoodVertexIndicesIfRegular(
Index, Index[]) const {
return false;
}
inline bool
SurfaceFactoryMeshAdapter::getFaceNeighborhoodFVarValueIndicesIfRegular(
Index, FVarID, Index[]) const {
return false;
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_SURFACE_FACTORY_ADAPTER_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,379 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_TESSELLATION_H
#define OPENSUBDIV3_BFR_TESSELLATION_H
#include "../version.h"
#include "../bfr/parameterization.h"
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
///
/// @brief Encapsulates a specific tessellation pattern of a Parameterization
///
/// Tessellation is a simple class that encapsulates a specified tessellation
/// pattern for a given Parameterization. Tessellation parameters are given
/// on construction and are fixed for its lifetime.
///
/// Methods allow inspection of the pattern in terms of the 2D coordinates of
/// the points comprising the pattern and the faces that connect them. The
/// 2D coordinates are referred to both in the documentation and the interface
/// as "coords" while the faces connecting them are referred to as "facets"
/// (to distinguish from the faces of the mesh, to which a Tessellation is
/// applied).
///
class Tessellation {
public:
///
/// @brief Options configure a Tessellation to specify the nature of
/// both its results and the structure of the coordinate and
/// facet index arrays that its methods will populate.
///
/// The sizes and strides of the target arrays should be specified
/// explicitly as they are not inferred by the presence of other
/// options.
///
/// In the case of facets, the size is constant for all facets of the
/// output array but triangles may sometimes be generated in 4-sided
/// facets with non-uniform tessellation rates (quad-preservation
/// reduces but does not eliminate all triangles). Such triangles are
/// indicated by the use of an invalid/negative index in the fourth
/// position.
///
/// Modifiers of Options return a reference to itself to facilitate
/// inline usage.
///
class Options {
public:
Options() : _preserveQuads(false), _facetSize4(false),
_coordStride(0), _facetStride(0) { }
/// @brief Select preservation of quads for quad-based subdivision
/// (requires 4-sided facets, default is off)
Options & PreserveQuads(bool on);
/// @brief Return if preservation of quads is set
bool PreserveQuads() const { return _preserveQuads; }
/// @brief Assign the number of indices per facet (must be 3 or 4,
/// default is 3)
Options & SetFacetSize(int numIndices);
/// @brief Return the number of indices per facet
int GetFacetSize() const { return 3 + (int)_facetSize4; }
/// @brief Assign the stride between facets (default is facet size)
Options & SetFacetStride(int stride);
/// @brief Return the stride between facets
int GetFacetStride() const { return _facetStride; }
/// @brief Assign the stride between (u,v) pairs (default is 2)
Options & SetCoordStride(int stride);
/// @brief Return the stride between (u,v) pairs
int GetCoordStride() const { return _coordStride; }
private:
unsigned int _preserveQuads : 1;
unsigned int _facetSize4 : 1;
short _coordStride;
short _facetStride;
};
public:
//@{
/// @name Construction and initialization
///
/// Constructors require a Parameterization of a face, a set of one or
/// more tessellation rates, and a standard set of options.
///
/// As with other classes, constructors can produce invalid instances if
/// given obviously invalid arguments, e.g. an invalid Parameterization,
/// non-positive tessellation rate, etc.
///
/// @brief Simple constructor providing a single uniform tessellation rate.
///
/// @param p Parameterization of a face to be tessellated
/// @param uniformRate Integer tessellation rate (non-zero)
/// @param options Options describing tessellation results
///
Tessellation(Parameterization const & p, int uniformRate,
Options const & options = Options());
///
/// @brief General constructor providing multiple tessellation rates for
/// a non-uniform tessellation.
///
/// @param p Parameterization of a face to be tessellated
/// @param numRates The number of tessellation rates provided, which
/// usually includes one per edge of the face (more
/// details below)
/// @param rates The array of non-zero integer tessellation rates
/// @param options Options describing tessellation results
///
/// For a Parameterization of a face with N edges, the acceptable number
/// of tessellation rates can vary. Aside from N "outer" tessellation
/// rates (one for each edge), all faces can have at least one "inner"
/// rate additionally specified while quads can have two inner rates.
///
/// If inner rates are not specified in addition to the N outer rates,
/// they will be inferred (so it is not necessary to initialize quads
/// distinctly from other faces). Similarly -- though less useful -- the
/// smaller set of inner rates can be specified, leaving all outer rates
/// to be inferred.
///
/// For a face with N edges, the full set of acceptable rates and their
/// interpretations is as follows:
///
/// 1 - single explicit inner rate (uniform)
/// 2 - (quads only) two explicit inner rates, outer rates inferred
/// N - explicit edge rates, inner rates inferred
/// N+1 - explicit edge rates, explicit inner rate
/// N+2 - (quads only) explicit edge rates, two explicit inner rates
///
/// When associating rates with edges, note that rates[0] corresponds
/// to the edge between vertices 0 and 1. This is consistent with use
/// elsewhere in OpenSubdiv -- where edge i lies between vertices i and
/// i+1 -- but differs from the conventions used with many hardware
/// tessellation interfaces.
///
Tessellation(Parameterization const & p, int numRates, int const rates[],
Options const & options = Options());
/// @brief Return true if correctly initialized
bool IsValid() const { return _isValid; }
/// @brief Default construction is unavailable
Tessellation() = delete;
Tessellation(Tessellation const &) = delete;
Tessellation & operator=(Tessellation const &) = delete;
~Tessellation();
//@}
//@{
/// @name Simple queries
///
/// Simple queries of a valid Tessellation.
///
/// @brief Return the Parameterization
Parameterization GetParameterization() const { return _param; }
/// @brief Return the size of the face
int GetFaceSize() const { return _param.GetFaceSize(); }
/// @brief Retrieve the rates assigned
int GetRates(int rates[]) const;
/// @brief Return if the pattern is uniform
bool IsUniform() const { return _isUniform; }
//@}
//@{
/// @name Methods to inspect and gather coordinates
///
/// Methods to determine the number of sample points involved in the
/// tessellation pattern and their content are available for the entire
/// pattern, or for parts of the boundary or interior of the pattern.
///
/// The methods that assign the coordinate arrays also return the number
/// of coordinates assigned, so the methods that just return those sizes
/// are not necessary if arrays for the resulting coords have already
/// been sufficiently allocated.
///
/// @brief Return the number of coordinates in the entire pattern
int GetNumCoords() const { return _numInteriorPoints + _numBoundaryPoints; }
/// @brief Return the number of elements between each coordinate
int GetCoordStride() const { return _coordStride; }
/// @brief Return the number of boundary coordinates
int GetNumBoundaryCoords() const { return _numBoundaryPoints; }
/// @brief Return the number of interior coordinates
int GetNumInteriorCoords() const { return _numInteriorPoints; }
/// @brief Return the number of coordinates within a given edge
/// (excluding those at its end vertices)
int GetNumEdgeCoords(int edge) const { return _outerRates[edge] - 1; }
/// @brief Retrieve the coordinates for the entire pattern
template <typename REAL>
int GetCoords(REAL coordTuples[]) const;
/// @brief Retrieve the coordinates for the boundary
template <typename REAL>
int GetBoundaryCoords(REAL coordTuples[]) const;
/// @brief Retrieve the coordinates for the boundary
template <typename REAL>
int GetInteriorCoords(REAL coordTuples[]) const;
/// @brief Retrieve the coordinate for a given vertex of the face
template <typename REAL>
int GetVertexCoord(int vertex, REAL coordTuples[]) const;
/// @brief Retrieve the coordinates for a given edge of the face
/// (excluding those at its end vertices)
template <typename REAL>
int GetEdgeCoords(int edge, REAL coordTuples[]) const;
//@}
//@{
/// @name Methods to inspect and gather facets
///
/// Methods to inspect the number and values of facets. Facets are
/// simply integer tuples of size 3 or 4 which contain the indices
/// of the coordinates generated by the Tessellation.
///
/// When using facets of size 4, triangles may sometimes be generated
/// and are indicated by the use of an invalid/negative index in the
/// fourth position.
///
/// Unlike the coordinates -- which can be separated into those on
/// the boundary or interior of the pattern -- the facets are not
/// distinguished in any way.
///
/// @brief Return the number of facets in the entire pattern
int GetNumFacets() const { return _numFacets; }
/// @brief Return the number of indices assigned to each facet
int GetFacetSize() const { return _facetSize; }
/// @brief Return the number of elements between each facet
int GetFacetStride() const { return _facetStride; }
/// @brief Retrieve the facet indices for the entire pattern
int GetFacets(int facetTuples[]) const;
//@}
//@{
/// @name Methods to modify the coordinate indices of facets
///
/// Methods to modify the coordinate indices of facets rely on the
/// coordinate indices being identifiable as those of boundary or
/// interior coordinates. The first N coordinates generated by
/// Tessellation will be on the boundary, while the remaining M
/// will be for the interior.
///
/// The boundary and interior coordinate indices can be transformed
/// collectively or separately by offsets or by explicit reassignment.
/// Both the boundary and interior indices must be modified at the
/// same time while the Tessellation can distinguish them, i.e. a
/// boundary coord is identified by index < N and an interior coord
/// by index >= N.
///
/// @brief Apply a common offset to all facet coordinate indices
void TransformFacetCoordIndices(int facetTuples[], int commonOffset);
/// @brief Reassign indices of boundary coordinates while offseting
/// those of interior coordinates
void TransformFacetCoordIndices(int facetTuples[],
int const boundaryIndices[],
int interiorOffset);
/// @brief Reassign all facet coordinate indices
void TransformFacetCoordIndices(int facetTuples[],
int const boundaryIndices[],
int const interiorIndices[]);
//@}
private:
// Private initialization methods:
bool validateArguments(Parameterization const & p,
int nRates, int const rates[], Options const & options);
void initialize(Parameterization const & p,
int nRates, int const rates[], Options const & options);
void initializeDefaults();
int initializeRates(int nRates, int const rates[]);
void initializeInventoryForParamTri(int sumOfOuterRates);
void initializeInventoryForParamQuad(int sumOfOuterRates);
void initializeInventoryForParamQPoly(int sumOfOuterRates);
private:
// Private members:
Parameterization _param;
unsigned short _isValid : 1;
unsigned short _isUniform : 1;
unsigned short _triangulate : 1;
unsigned short _singleFace : 1;
unsigned short _segmentedFace : 1;
unsigned short _triangleFan : 1;
unsigned short _splitQuad : 1;
short _facetSize;
int _facetStride;
int _coordStride;
int _numGivenRates;
int _numBoundaryPoints;
int _numInteriorPoints;
int _numFacets;
int _innerRates[2];
int* _outerRates;
int _outerRatesLocal[4];
};
//
// Inline implementations:
//
inline Tessellation::Options &
Tessellation::Options::PreserveQuads(bool on) {
_preserveQuads = on;
return *this;
}
inline Tessellation::Options &
Tessellation::Options::SetFacetSize(int numIndices) {
_facetSize4 = (numIndices == 4);
return *this;
}
inline Tessellation::Options &
Tessellation::Options::SetFacetStride(int stride) {
_facetStride = (short) stride;
return *this;
}
inline Tessellation::Options &
Tessellation::Options::SetCoordStride(int stride) {
_coordStride = (short) stride;
return *this;
}
template <typename REAL>
inline int
Tessellation::GetVertexCoord(int vertex, REAL coord[]) const {
_param.GetVertexCoord(vertex, coord);
return 1;
}
template <typename REAL>
inline int
Tessellation::GetCoords(REAL coordTuples[]) const {
int nCoords = GetBoundaryCoords(coordTuples);
nCoords += GetInteriorCoords(coordTuples + nCoords * _coordStride);
return nCoords;
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_TESSELLATION */

View File

@@ -0,0 +1,105 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../bfr/vertexDescriptor.h"
#include "../bfr/limits.h"
#include "../sdc/crease.h"
#include <cstring>
#include <cstdio>
#include <algorithm>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// Main initialize/finalize methods used by clients to delimit the
// assignment (most work is now handled by the containing class):
//
bool
VertexDescriptor::Initialize(int numFaces) {
// Mark invalid if too many or too few incident faces specified:
_isValid = (numFaces > 0) && (numFaces <= Limits::MaxValence());
_numFaces = _isValid ? (short) numFaces : 0;
// Initialize all other members regardless of the above:
_vertSharpness = 0.0f;
_isManifold = false;
_isBoundary = false;
_hasFaceSizes = false;
_hasEdgeSharpness = false;
_isInitialized = _isValid;
_isFinalized = false;
return _isInitialized;
}
bool
VertexDescriptor::Finalize() {
// Fail if already invalid:
if (!_isValid) return false;
// Test for valid face size assignments while converting the sizes
// to offsets. Also detect if the faces are all the same size -- in
// which case, ignore the explicit assignments:
if (_hasFaceSizes) {
int size0 = _faceSizeOffsets[0];
bool sameSizes = true;
int sum = 0;
for (int i = 0; i < _numFaces; ++i) {
int faceSize = _faceSizeOffsets[i];
if ((faceSize < 3) || (faceSize > Limits::MaxFaceSize())) {
_isValid = false;
return false;
}
sameSizes &= (faceSize == size0);
_faceSizeOffsets[i] = sum;
sum += faceSize;
}
_faceSizeOffsets[_numFaces] = sum;
// No need to make use of explicit face sizes and offsets:
if (sameSizes) {
_hasFaceSizes = false;
}
}
_isFinalized = true;
return true;
}
//
// Internal methods for resizing local buffers:
//
void
VertexDescriptor::initFaceSizes() {
_faceSizeOffsets.SetSize(_numFaces + 1);
std::fill(&_faceSizeOffsets[0], &_faceSizeOffsets[_numFaces + 1], 0);
_hasFaceSizes = true;
}
void
VertexDescriptor::initEdgeSharpness() {
_faceEdgeSharpness.SetSize(_numFaces * 2);
std::fill(&_faceEdgeSharpness[0], &_faceEdgeSharpness[_numFaces * 2], 0.0f);
_hasEdgeSharpness = true;
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv

View File

@@ -0,0 +1,432 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_VERTEX_DESCRIPTOR_H
#define OPENSUBDIV3_BFR_VERTEX_DESCRIPTOR_H
#include "../version.h"
#include "../vtr/stackBuffer.h"
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
///
/// @brief Simple class used by subclasses of SurfaceFactory to describe a
/// vertex
///
/// VertexDescriptor is a simple class used by SurfaceFactory and its
/// subclasses to provide a complete topological description around the
/// vertex of a face, i.e. its valence, the sizes of its incident faces,
/// sharpness values, etc.
///
/// Instances are created and partially initialized by SurfaceFactory
/// before being passed to its subclasses to be fully populated. So
/// public construction is not available (or useful).
///
//
// WIP - need to migrate some of these comments into Doxygen
// - others will be moved to the external documentation
//
// It is used by subclasses of SurfaceFactory to provide a complete
// topological description for each vertex of a face, i.e. invoked via
// the virtual method:
//
// int populateFaceVertexDescriptor(Index baseFace,
// int cornerVertex,
// VertexDescriptor & v) const;
//
// Assignment of the full topology can be involved in the presence of
// irregular faces, non-manifold topology or creasing around a vertex, but
// many cases will be simple. For example, to specify a regular boundary
// vertex of a Catmark mesh without any optional sharpness:
//
// int numIncidentFaces = 2;
// bool vertexOnBoundary = true;
//
// vd.Initialize(numIncidentFaces);
// vd.SetManifold(true);
// vd.SetBoundary(vertexOnBoundary);
// vd.ClearIncidentFaceSizes();
// vd.Finalize();
//
// For a more general example, to assign a vertex of some valence whose
// incident faces are of different sizes (e.g. required when triangles
// appear around a vertex in an otherwise quad-dominant Catmark mesh):
//
// int numIncidentFaces = meshVertex.GetNumIncidentFaces();
// bool vertexOnBoundary = meshVertex.IsBoundar();
//
// vd.Initialize(numIncidentFaces);
// vd.SetManifold(true);
// vd.SetBoundary(vertexOnBoundary);
//
// for (int i = 0; i < numIncidentFaces; ++i) {
// vd.SetIncidentFaceSize(i, meshVertex.GetIncidentFaceSize(i));
// }
// vd.Finalize();
//
// These examples specify the incident faces as forming a manifold ring
// (or half-ring) around the vertex, i.e. they can be specified as a
// continuous, connected sequence in counter-clockwise order (and also
// without degeneracies). In the case of a boundary vertex, the first
// face must be on the leading edge of the boundary while the last is on
// the trailing edge. For an interior vertex, which face is specified
// first does not matter (since the set is periodic).
//
// In both cases, the location of the base face in this sequence -- the
// face whose corner vertex is being described here -- must be specified
// in the return value to populateFaceVertexDescriptor() (e.g. when a
// boundary vertex has 3 incident faces, a return value of 0, 1 or 2
// will indicate which is the base face).
//
// The corresponding methods to specify mesh control vertex indices (or
// face-varying indices) complete the specification of the neighborhood:
//
// int getFaceCornerVertexIndices(Index baseFace, int cornerVertex,
// Index vertexIndices[]) const;
//
// int getFaceCornerFVarValueIndices(Index baseFace, int cornerVertex,
// Index fvarValueIndices[],
// int fvarChannel) const;
//
// and are invoked by the Factory when needed.
//
// For each incident face, the indices for all vertices of that face are
// to be specified (not the one-ring or some other subset). These indices
// must also be specified in an orientation relative to the vertex, i.e.
// for a vertex A and an incident face with face-vertices that may be
// stored internally as {D, C, A, B}, they must be specified with A first
// as {A, B, C, D}. This may seem a bit cumbersome, but it has clear
// advantages when dealing with face-varying indices and unordered faces.
//
// More compact ways of specifying vertex indices for ordered, manifold
// cases may be worth exploring in future, but face-varying indices and
// non-manifold (unordered) vertices will always require such a full set,
// so both methods will need to co-exist.
//
class VertexDescriptor {
public:
// The full declaration must be enclosed by calls to these methods:
//
// Note that vertex valences or face sizes in excess of those defined
// in Bfr::Limits (typically 16-bits) are not valid. When specifying
// values in excess of these limits, initialization will fail and/or
// the descriptor will be marked invalid and finalization will fail.
//
//@{
/// @name Methods to begin and end specification
///
/// Partially constructed instances are populated using a set of
/// methods between calls to Initialize() and Finalize(). Both return
/// false to indicate failure due to invalid input, or the instance
/// can be inspected after each to determine if valid.
///
/// @brief Initialize specification with the number of incident faces
bool Initialize(int numIncidentFaces);
/// @brief Terminate the sequence of specifications
bool Finalize();
/// @brief Return if instance is valid
bool IsValid() const;
//@}
//
// WIP - need to migrate these comments into Doxygen
//
// Three groups of methods describe the topology around a vertex:
// - simple properties (vertex is a boundary, manifold, etc.)
// - sizes of incident faces (constant or size for each face)
// - sharpness of the vertex and its incident edges (optional)
//
// Manifold and boundary conditions:
//
// The manifold property is a strict condition but preferred for
// efficiency and is usually available from common connected mesh
// representations. When declaring the topology as "manifold",
// the Factory assumes the following:
//
// - all incident faces are "ordered" (counter-clockwise)
// - all incident faces are consistently oriented
// - all incident edges are non-degenerate
//
// If not certain that all of these conditions are met, it is best
// to not declare manifold -- leaving the Factory to make sense of
// the set of incident faces from the face-vertex indices that are
// provided elsewhere.
//
//@{
/// @name Methods to specify topology
///
/// Methods to specify the overall topology, the sizes of incident
/// faces and any assigned sharpness values.
/// @brief Declare the vertex neighborhood as manifold (ordered)
void SetManifold(bool isManifold);
/// @brief Declare the vertex neighborhood as being on a boundary
void SetBoundary(bool isOnBoundary);
/// @brief Assign the size of an incident face
void SetIncidentFaceSize(int faceIndex, int faceSize);
/// @brief Remove any assigned sizes of incident faces
void ClearIncidentFaceSizes();
/// @brief Assign sharpness to the vertex
void SetVertexSharpness(float sharpness);
/// @brief Remove any sharpness assigned to the vertex
void ClearVertexSharpness();
/// @brief Assign sharpness to the edge of a manifold neighborhood
///
/// For use with a vertex declared manifold only, assigns a given
/// sharpness to the indicated edge in the ordered sequence of edges
/// around the vertex. In the case of a boundary vertex, the number
/// of incident edges in this ordered sequence will exceed the number
/// of incident faces by one.
///
/// @param edgeIndex Index of the edge in the ordered sequence
/// @param edgeSharpness Sharpness to be assigned to the edge
///
void SetManifoldEdgeSharpness(int edgeIndex, float edgeSharpness);
/// @brief Assign sharpness to the edges of an incident face
///
/// In all cases, sharpness can be assigned to edges by associating
/// those edges with their incident faces. This method assigns sharpness
/// to the two edges incident edges of an incident face. An alternative
/// is available for the case of a manifold vertex.
///
/// @param faceIndex Index of the incident face
/// @param leadingEdgeSharp Sharpness to assign to the leading edge
/// of the incident face, i.e. the edge of the
/// face following the vertex.
/// @param trailingEdgeSharp Sharpness to assign to the trailing edge
/// of the incident face, i.e. the edge of the
/// face preceding the vertex.
///
void SetIncidentFaceEdgeSharpness(int faceIndex, float leadingEdgeSharp,
float trailingEdgeSharp);
/// @brief Remove any sharpness assigned to the incident edges
void ClearEdgeSharpness();
//@}
//@{
/// @name Methods to inspect topology to confirm assignment
///
/// While the public interface is primarily intended for assignment,
/// methods are available to inspect intermediate results.
///
/// @brief Return if vertex neighborhood is manifold
bool IsManifold() const;
/// @brief Return if vertex neighborhood is on a boundary
bool IsBoundary() const;
/// @brief Return if the sizes of incident faces are assigned
bool HasIncidentFaceSizes() const;
/// @brief Return the size of an incident face
int GetIncidentFaceSize(int faceIndex) const;
/// @brief Return if sharpness was assigned to the vertex
bool HasVertexSharpness() const;
/// @brief Return the sharpness of the vertex
float GetVertexSharpness() const;
/// @brief Return if sharpness was assigned to the incident edges
bool HasEdgeSharpness() const;
/// @brief Return the sharpness assigned to a manifold edge
float GetManifoldEdgeSharpness(int edgeIndex) const;
/// @brief Return the sharpness assigned to edges of an incident face
void GetIncidentFaceEdgeSharpness(int faceIndex,
float * leadingEdgeSharp, float * trailingEdgeSharp) const;
//@}
protected:
/// @cond PROTECTED
friend class FaceVertex;
VertexDescriptor() { }
~VertexDescriptor() { }
typedef Vtr::internal::StackBuffer<int,8,true> IntBuffer;
typedef Vtr::internal::StackBuffer<float,16,true> FloatBuffer;
void initFaceSizes();
void initEdgeSharpness();
/// @endcond
protected:
/// @cond PROTECTED
// Member variables assigned through the above interface:
unsigned short _isValid : 1;
unsigned short _isInitialized : 1;
unsigned short _isFinalized : 1;
unsigned short _isManifold : 1;
unsigned short _isBoundary : 1;
unsigned short _hasFaceSizes : 1;
unsigned short _hasEdgeSharpness : 1;
short _numFaces;
float _vertSharpness;
FloatBuffer _faceEdgeSharpness;
IntBuffer _faceSizeOffsets;
/// @endcond
};
//
// Public inline methods for simple assignment:
//
inline bool
VertexDescriptor::IsValid() const {
return _isValid;
}
inline void
VertexDescriptor::SetManifold(bool isManifold) {
_isManifold = isManifold;
}
inline bool
VertexDescriptor::IsManifold() const {
return _isManifold;
}
inline void
VertexDescriptor::SetBoundary(bool isBoundary) {
_isBoundary = isBoundary;
}
inline bool
VertexDescriptor::IsBoundary() const {
return _isBoundary;
}
//
// Public inline methods involving sizes of incident faces:
//
inline bool
VertexDescriptor::HasIncidentFaceSizes() const {
return _hasFaceSizes;
}
inline void
VertexDescriptor::ClearIncidentFaceSizes() {
_hasFaceSizes = false;
}
inline void
VertexDescriptor::SetIncidentFaceSize(int incFaceIndex, int faceSize) {
if (!_hasFaceSizes) initFaceSizes();
_faceSizeOffsets[incFaceIndex] = faceSize;
}
inline int
VertexDescriptor::GetIncidentFaceSize(int incFaceIndex) const {
return _isFinalized ?
(_faceSizeOffsets[incFaceIndex+1] - _faceSizeOffsets[incFaceIndex]) :
_faceSizeOffsets[incFaceIndex];
}
//
// Public inline methods involving vertex sharpness:
//
inline bool
VertexDescriptor::HasVertexSharpness() const {
return _vertSharpness > 0.0f;
}
inline void
VertexDescriptor::ClearVertexSharpness() {
_vertSharpness = 0.0f;
}
inline void
VertexDescriptor::SetVertexSharpness(float vertSharpness) {
_vertSharpness = vertSharpness;
}
inline float
VertexDescriptor::GetVertexSharpness() const {
return _vertSharpness;
}
//
// Public inline methods involving vertex sharpness:
//
inline bool
VertexDescriptor::HasEdgeSharpness() const {
return _hasEdgeSharpness;
}
inline void
VertexDescriptor::ClearEdgeSharpness() {
_hasEdgeSharpness = false;
}
inline void
VertexDescriptor::SetManifoldEdgeSharpness(int edgeIndex, float sharpness) {
if (!_hasEdgeSharpness) initEdgeSharpness();
// Assign the leading edge of the face after the edge (even index):
if (edgeIndex < _numFaces) {
_faceEdgeSharpness[2*edgeIndex] = sharpness;
}
// Assign the trailing edge of the face before the edge (odd index):
if (edgeIndex > 0) {
_faceEdgeSharpness[2*edgeIndex-1] = sharpness;
} else if (!IsBoundary()) {
_faceEdgeSharpness[2*_numFaces-1] = sharpness;
}
}
inline float
VertexDescriptor::GetManifoldEdgeSharpness(int edgeIndex) const {
// All edges are first of the pair (even index) except last of boundary
return _faceEdgeSharpness[2*edgeIndex - (edgeIndex == _numFaces)];
}
inline void
VertexDescriptor::SetIncidentFaceEdgeSharpness(int faceIndex,
float leadingEdgeSharpness, float trailingEdgeSharpness) {
if (!_hasEdgeSharpness) initEdgeSharpness();
_faceEdgeSharpness[2*faceIndex ] = leadingEdgeSharpness;
_faceEdgeSharpness[2*faceIndex+1] = trailingEdgeSharpness;
}
inline void
VertexDescriptor::GetIncidentFaceEdgeSharpness(int faceIndex,
float * leadingEdgeSharpness, float * trailingEdgeSharpness) const {
*leadingEdgeSharpness = _faceEdgeSharpness[2*faceIndex];
*trailingEdgeSharpness = _faceEdgeSharpness[2*faceIndex+1];
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_VERTEX_DESCRIPTOR_H */

View File

@@ -0,0 +1,146 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#ifndef OPENSUBDIV3_BFR_VERTEX_TAG_H
#define OPENSUBDIV3_BFR_VERTEX_TAG_H
#include "../version.h"
#include <cstring>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// VertexTag is a simple set of bits that identify exceptional properties
// at the corner vertices of a face that warrant closer inspection (and
// potential additional processing). As with some bitfields in Far, this
// supports bitwise-OR so that tags for the corners of a face can quickly
// be combined to determine properties of the associated limit surface.
//
// In order to accommodate the two separate uses more clearly -- that of
// a set of bits applying to a single corner/vertex versus a set of bits
// resulting from the combination (bitwise-OR) of several -- the bitfield
// is defined as a base class and two separate classes are derived from
// it to suit those purposes.
//
class FeatureBits {
public:
FeatureBits() { }
~FeatureBits() { }
public:
// Integer/bit conversions and operations:
typedef unsigned short IntType;
IntType GetBits() const {
IntType bits;
std::memcpy(&bits, this, sizeof(*this));
return bits;
}
void SetBits(IntType bits) {
std::memcpy(this, &bits, sizeof(*this));
}
void Clear() {
SetBits(0);
}
protected:
friend class FaceVertex;
friend struct FaceVertexSubset;
IntType _boundaryVerts : 1;
IntType _infSharpVerts : 1;
IntType _infSharpEdges : 1;
IntType _infSharpDarts : 1;
IntType _semiSharpVerts : 1;
IntType _semiSharpEdges : 1;
IntType _unCommonFaceSizes : 1;
IntType _irregularFaceSizes : 1;
IntType _unOrderedFaces : 1;
IntType _nonManifoldVerts : 1;
IntType _boundaryNonSharp : 1;
};
//
// VertexTag wraps the FeatureBits for use with a single corner/vertex:
//
// Note that a bit is not defined to detect extra-ordinary or regular
// valence. Since subsets of the topology are ultimately used in the
// limit surface definition, and face-varying surfaces are potentially
// subsets of subsets, we intentionally avoid having to re-compute
// that bit for each subset. Such a bit has little value for a single
// corner in Bfr, so the collective presence is determined when the
// surface definition is finalized in the regular/irregular test.
//
class VertexTag : public FeatureBits {
public:
VertexTag() { }
~VertexTag() { }
// Queries for single corner/vertex (some reversing sense of the bit):
bool IsBoundary() const { return _boundaryVerts; }
bool IsInterior() const { return !_boundaryVerts; }
bool IsInfSharp() const { return _infSharpVerts; }
bool HasInfSharpEdges() const { return _infSharpEdges; }
bool IsInfSharpDart() const { return _infSharpDarts; }
bool IsSemiSharp() const { return _semiSharpVerts; }
bool HasSemiSharpEdges() const { return _semiSharpEdges; }
bool HasUnCommonFaceSizes() const { return _unCommonFaceSizes; }
bool HasIrregularFaceSizes() const { return _irregularFaceSizes; }
bool IsOrdered() const { return !_unOrderedFaces; }
bool IsUnOrdered() const { return _unOrderedFaces; }
bool IsManifold() const { return !_nonManifoldVerts; }
bool IsNonManifold() const { return _nonManifoldVerts; }
bool HasNonSharpBoundary() const { return _boundaryNonSharp; }
bool HasSharpEdges() const { return HasInfSharpEdges() ||
HasSemiSharpEdges(); }
};
//
// MultiVertexTag wraps the FeatureBits for use with bits combined from
// several corners/vertices. It includes the Combine() method to apply
// the bitwise-OR with a given VertexTag, in addition to using different
// names for the access methods to reflect their collective nature (e.g.
// the use of "has" versus "is").
//
class MultiVertexTag : public FeatureBits {
public:
MultiVertexTag() { }
~MultiVertexTag() { }
// Queries for multiple VertexTags combined into one:
bool HasBoundaryVertices() const { return _boundaryVerts; }
bool HasInfSharpVertices() const { return _infSharpVerts; }
bool HasInfSharpEdges() const { return _infSharpEdges; }
bool HasInfSharpDarts() const { return _infSharpDarts; }
bool HasSemiSharpVertices() const { return _semiSharpVerts; }
bool HasSemiSharpEdges() const { return _semiSharpEdges; }
bool HasUnCommonFaceSizes() const { return _unCommonFaceSizes; }
bool HasIrregularFaceSizes() const { return _irregularFaceSizes; }
bool HasUnOrderedVertices() const { return _unOrderedFaces; }
bool HasNonManifoldVertices() const { return _nonManifoldVerts; }
bool HasNonSharpBoundary() const { return _boundaryNonSharp; }
bool HasSharpVertices() const { return HasInfSharpVertices() ||
HasSemiSharpVertices(); }
bool HasSharpEdges() const { return HasInfSharpEdges() ||
HasSemiSharpEdges(); }
void Combine(VertexTag const & tag) {
SetBits(GetBits() | tag.GetBits());
}
};
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
using namespace OPENSUBDIV_VERSION;
} // end namespace OpenSubdiv
#endif /* OPENSUBDIV3_BFR_VERTEX_TAG_H */