OpenSubdiv/opensubdiv/bfr/surfaceFactory.cpp
Barry Fowler a1c7be7c8e Addition of Bfr interface (1 of 4): opensubdiv/bfr
This set of commits includes the addition of a new evaluation interface
that treats a subdivision mesh more like a piecewise parametric surface
primitive.  The new interface was placed in namespace "Bfr" for "Base
Face Representation" as all concepts and classes relate to a single face
of the base mesh.
2022-08-02 20:38:17 -07:00

1294 lines
46 KiB
C++

//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "../bfr/hash.h"
#include "../bfr/limits.h"
#include "../bfr/surface.h"
#include "../bfr/surfaceFactory.h"
#include "../bfr/surfaceFactoryCache.h"
#include "../bfr/faceTopology.h"
#include "../bfr/faceSurface.h"
#include "../bfr/regularPatchBuilder.h"
#include "../bfr/irregularPatchBuilder.h"
#include "../bfr/patchTree.h"
#include <map>
#include <cstdio>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Bfr {
//
// DEBUG - static variables to keep track of constructed Surfaces
// - note that these global variables have extremely limited use:
// - they are initialized once per process
// - reported and reset on destruction of a SurfaceFactory
//
//#define _BFR_DEBUG_TOP_TYPE_STATS
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
static int __numLinearPatches = 0;
static int __numExpRegularPatches = 0;
static int __numRegularPatches = 0;
static int __numIrregularPatches = 0;
static int __numIrregularUncached = 0;
static int __numIrregularInCache = 0;
#endif
//
// Definition of the private/nested SurfaceSet class:
//
// This class (essentially a struct) encapsulates a clients specification
// of a set of multiple surfaces and their intended interpolation types
// (vertex, varying, and face-varying). The multiple public creation
// methods to request common subsets of surfaces all populate an instance
// of SurfaceSet for internal use.
//
class SurfaceFactory::SurfaceSet {
public:
SurfaceSet() : numSurfs(0), numFVarSurfs(0),
vtxSurf(0), varSurf(0),
fvarSurfs(0), fvarSurfPtrs(0), fvarIDs(0) { }
public:
// Assignment to member variable is intended to be explicit:
int numSurfs;
int numFVarSurfs;
typedef internal::SurfaceData SurfaceType;
SurfaceType * vtxSurf;
SurfaceType * varSurf;
SurfaceType * fvarSurfs;
SurfaceType ** fvarSurfPtrs;
FVarID const * fvarIDs;
void InitializeSurfaces() const {
if (vtxSurf) vtxSurf->reinitialize();
if (varSurf) varSurf->reinitialize();
for (int i = 0; i < numFVarSurfs; ++i) {
GetFVarSurface(i)->reinitialize();
}
}
public:
// Access to member variables is preferred through these methods,
// which may require a little more logic than expected:
int GetNumSurfaces() const { return numSurfs; }
bool HasVertexSurface() const { return (vtxSurf != 0); }
SurfaceType * GetVertexSurface() const { return vtxSurf; }
bool HasVaryingSurface() const { return (varSurf != 0); }
SurfaceType * GetVaryingSurface() const { return varSurf; }
// More than one FVar surface may be present, and each may have
// a unique ID:
bool HasFVarSurfaces() const { return numFVarSurfs > 0; }
int GetNumFVarSurfaces() const { return numFVarSurfs; }
FVarID GetFVarSurfaceID(int i) const {
return fvarIDs ? fvarIDs[i] : FVarID(i);
}
SurfaceType * GetFVarSurface(int i) const {
// Note that FVar Surfaces may be specified either as an
// array of Surfaces or an array of Surface pointers:
return fvarSurfs ? (fvarSurfs + i) : fvarSurfPtrs[i];
}
};
//
// Main constructor and supporting initialization methods:
//
SurfaceFactory::SurfaceFactory(Sdc::SchemeType subdivScheme,
Sdc::Options const & subdivOptions,
Options const & factoryOptions) :
_topologyCache(0) {
// Order of operations not important here:
setSubdivisionOptions(subdivScheme, subdivOptions);
setFactoryOptions(factoryOptions);
}
void
SurfaceFactory::setSubdivisionOptions(Sdc::SchemeType subdivScheme,
Sdc::Options const & subdivOptions) {
// Assign the main member variables before others derived from them:
_subdivScheme = subdivScheme;
_subdivOptions = subdivOptions;
// Initialize members dependent on subdivision topology:
_regFaceSize = Sdc::SchemeTypeTraits::GetRegularFaceSize(_subdivScheme);
_linearScheme =
(Sdc::SchemeTypeTraits::GetLocalNeighborhoodSize(_subdivScheme) == 0);
_linearFVarInterp = _linearScheme ||
(_subdivOptions.GetFVarLinearInterpolation() ==
Sdc::Options::FVAR_LINEAR_ALL);
// Initialize members related to the "face has limit" test:
_rejectSmoothBoundariesForLimit = !_linearScheme &&
(_subdivOptions.GetVtxBoundaryInterpolation() ==
Sdc::Options::VTX_BOUNDARY_NONE);
_rejectIrregularFacesForLimit = !_linearScheme && (_regFaceSize == 3);
_testNeighborhoodForLimit = _rejectSmoothBoundariesForLimit ||
_rejectIrregularFacesForLimit;
}
void
SurfaceFactory::setFactoryOptions(Options const & factoryOptions) {
// Assign the main member variable before others derived from them:
_factoryOptions = factoryOptions;
if (_factoryOptions.IsCachingEnabled()) {
if (_factoryOptions.GetExternalCache()) {
_topologyCache = _factoryOptions.GetExternalCache();
}
}
}
void
SurfaceFactory::setInternalCache(SurfaceFactoryCache * cache) {
// Remember caching must be on and an external cache takes precedence
if (_factoryOptions.IsCachingEnabled()) {
if (_factoryOptions.GetExternalCache() == 0) {
_topologyCache = cache;
}
}
}
SurfaceFactory::~SurfaceFactory() {
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
// DEBUG - report and reset inventory:
printf("SurfaceFactory destructor:\n");
printf(" __numLinearPatches = %6d\n", __numLinearPatches);
printf(" __numExpRegularPatches = %6d\n", __numExpRegularPatches);
printf(" __numRegularPatches = %6d\n", __numRegularPatches);
printf(" __numIrregularPatches = %6d\n", __numIrregularPatches);
if (!_factoryOptions.DisableTopologyCache()) {
printf("\n");
printf(" __numIrregularUncached = %6d\n", __numIrregularUncached);
printf(" __numIrregularInCache = %6d\n", __numIrregularInCache);
}
__numLinearPatches = 0;
__numExpRegularPatches = 0;
__numRegularPatches = 0;
__numIrregularPatches = 0;
__numIrregularUncached = 0;
__numIrregularInCache = 0;
#endif
}
//
// Notes on presence/absence of a limit surface...
//
// Unfortunately it is not trivial to detect when a face does not have
// an associated limit surface. There are a few cases when a face will
// not have a limit surface -- divided into simple and complex cases:
//
// - simple:
// - the face is a hole
// - the face is degenerate (< 3 edges)
// - complex:
// - boundary interpolation option "none" is assigned:
// - in which case some, not all, boundary faces have no limit
// - Loop subdivision is applied to non-triangles
//
// The simple cases are, as the name suggests, simple. But the complex
// cases require a greater inspection of the topological neighborhood of
// the face.
//
// With boundary faces when "boundary none" is set (not very often) it is
// not enough to test if a face is a boundary -- if a boundary face has all
// of its incident boundary edges (i.e. all boundary edges incident to all
// of its face-vertices) then the boundary face has a limit surface. This
// requires a complete topological description of each corner of the face.
//
// Similarly, the case of Loop subdivision in the presence of non-triangles
// required determining if any corner of the face has an incident face
// that is not a triangle.
//
// The method here inspects a corner at a time and tries to reject a face
// without a limit surface as soon as possible. But most cases are going to
// require inspection of all corners -- and that same inspection is likely
// to be applied later when constructing the limit.
//
inline bool
SurfaceFactory::faceHasLimitSimple(Index faceIndex, int faceSize) const {
return (faceSize >= 3) && (faceSize <= Limits::MaxFaceSize()) &&
!isFaceHole(faceIndex);
}
bool
SurfaceFactory::faceHasLimitNeighborhood(FaceTopology const & topology) const {
assert(_testNeighborhoodForLimit);
MultiVertexTag tag = topology.GetTag();
if ((_rejectSmoothBoundariesForLimit && tag.HasNonSharpBoundary()) ||
(_rejectIrregularFacesForLimit && tag.HasIrregularFaceSizes())) {
return false;
}
return true;
}
bool
SurfaceFactory::faceHasLimitNeighborhood(Index faceIndex) const {
assert(_testNeighborhoodForLimit);
//
// The FaceTopology was not available, and rather than construct it
// in its entirety, determine a corner at a time and return if any
// corner warrants it:
//
typedef Vtr::internal::StackBuffer<Index,32,true> CornerIndexBuffer;
CornerIndexBuffer cFaceVertIndices;
FaceVertex faceVtx;
VertexDescriptor & vtxDesc = faceVtx.GetVertexDescriptor();
int faceSize = getFaceSize(faceIndex);
for (int i = 0; i < faceSize; ++i) {
// Have the subclass load VertexDescriptor and finalize:
faceVtx.Initialize(faceSize, _regFaceSize);
int faceInRing = populateFaceVertexDescriptor(faceIndex, i, &vtxDesc);
if (faceInRing < 0) return false;
faceVtx.Finalize(faceInRing);
// Inspect the tag to reject cases with no limit surface:
VertexTag faceVtxTag = faceVtx.GetTag();
if (_rejectSmoothBoundariesForLimit) {
if (faceVtxTag.IsUnOrdered()) {
// Need to load face-vertices, connect faces and inspect...
cFaceVertIndices.SetSize(faceVtx.GetNumFaceVertices());
if (getFaceVertexIncidentFaceVertexIndices(
faceIndex, i, cFaceVertIndices) < 0) return false;
faceVtx.ConnectUnOrderedFaces(cFaceVertIndices);
}
if (faceVtxTag.HasNonSharpBoundary()) return false;
}
if (_rejectIrregularFacesForLimit) {
if (faceVtxTag.HasIrregularFaceSizes()) return false;
}
}
return true;
}
bool
SurfaceFactory::FaceHasLimitSurface(Index faceIndex) const {
if (!faceHasLimitSimple(faceIndex, getFaceSize(faceIndex))) {
return false;
}
if (_testNeighborhoodForLimit) {
if (!isFaceNeighborhoodRegular(faceIndex, 0, 0)) {
return faceHasLimitNeighborhood(faceIndex);
}
}
return true;
}
Parameterization
SurfaceFactory::GetFaceParameterization(Index faceIndex) const {
return Parameterization(_subdivScheme, getFaceSize(faceIndex));
}
//
// Namespace with internal utilities to compute keys for unique surface
// topologies to help cache their limit surface representations:
//
namespace {
// Need to redefine since SurfaceFactoryCache::Key is protected:
typedef std::uint64_t KeyIntType;
//
// Note that the data used in determining a topology key is not
// purely topological. While most data determines a unique limit
// surface, a few parameters determine the approximation to it
// (e.g. the various adaptive refinement levels) or dictate other
// properties of its representation (e.g. double precision).
//
// It may be worth separating these -- writing a method to deal
// with the pure topology first, then combining it with details
// of the representation.
//
#ifdef _OPENSUBDIV_BFR_INCLUDE_PACKED_TOPOLOGY_KEY_
//
// This alternate function for computing the cache key was applied
// to common topologies with low-valence and simply packs integer
// bitfields with the topology of the face and all of its corners.
//
// It is typically 3x faster than the hashing method, but is suited
// for general use. Given the relatively low cost of computing the
// cache key (compared to building an irregular patch) use of this
// faster function is not generally significant, but could become
// so in future when/if other costs are reduced.
//
// When the cost of building new patches is very low due to a high
// percentage of cache hits, the cost of computing the cache keys
// (relative to constructing the entire Surface) can rise to over
// 10% in extreme cases.
//
KeyIntType
packTopologyKey(FaceSurface const & surface,
IrregularPatchBuilder::Options options) {
//
// Keep the bitfield struct local in scope unless needed elsewhere:
//
struct KeyBits {
// Be sure to clear to avoid uninitialized bits:
KeyBits() { std::memset(this, 0, sizeof(*this)); }
// Bits for general options:
KeyIntType subdScheme : 2;
KeyIntType sharpLevel : 4;
KeyIntType smoothLevel : 4;
KeyIntType usesDouble : 1;
KeyIntType unused : 1; // future use
// Bits for the corners:
KeyIntType v0Valence : 6;
KeyIntType v1Valence : 6;
KeyIntType v2Valence : 6;
KeyIntType v3Valence : 6;
KeyIntType v0IsSharp : 1;
KeyIntType v1IsSharp : 1;
KeyIntType v2IsSharp : 1;
KeyIntType v3IsSharp : 1;
KeyIntType v0IsBoundary : 1;
KeyIntType v1IsBoundary : 1;
KeyIntType v2IsBoundary : 1;
KeyIntType v3IsBoundary : 1;
KeyIntType v0FaceInBoundary : 5;
KeyIntType v1FaceInBoundary : 5;
KeyIntType v2FaceInBoundary : 5;
KeyIntType v3FaceInBoundary : 5;
//
// Static methods to determine if bitfields can be used:
//
static bool InteriorValenceFits(int n) { return n < (1 << 6); }
static bool BoundaryValenceFits(int n) { return n < (1 << 5); }
// A place holder if future Sdc::Options inhibit use of bitfields:
static bool OptionsInhibitUsage(Sdc::Options) { return false; }
};
assert(sizeof(KeyBits) == sizeof(KeyIntType));
//
// Quickly test if the topology can be packed into bitfields, or
// if hashing must be used. Bitfields cannot be used when the
// following features are present:
//
// - any sharp edges of any kind (semi-sharp or inf-sharp)
// - any semi-sharp vertices (inf-sharp is 1-bit per corner)
// - any incident irregular faces
//
// These can quickly be determined by inspecting the topology tags.
// Two other situations are:
//
// - any vertex with valence too high (more than ~6 bits)
// - any Sdc::Option that cannot be encoded (in theory only)
//
// The former must inspect the valence of each face-vertex, while
// the latter requires inspecting the Sdc::Options -- and this is
// called out more as a future possibility...
//
// In theory, if certain Sdc::Options impact the limit surface,
// they might need to be encoded, or might not be able to be fully
// encoded in future. This is not currently the case in practice:
// boundary interpolation options are essentially unused as the
// boundary conditions are explicitly applied; the creasing method
// can be ignored here because creases cannot be packed; and the
// Catmark triangle subdivision option can be ignored because the
// presence of any irregular faces cannot be packed.
//
// Immediate rejection of bitfields:
MultiVertexTag combinedTag = surface.GetTag();
if (combinedTag.HasSharpEdges() ||
combinedTag.HasSemiSharpVertices() ||
combinedTag.HasIrregularFaceSizes()) {
return false;
}
// Conditional rejection of bitfields for high valence:
FaceVertexSubset const * subsets = surface.GetSubsets();
for (int i = 0; i < surface.GetFaceSize(); ++i) {
int valence = subsets[i]._numFacesTotal;
if (subsets[i].IsBoundary()) {
if (!KeyBits::BoundaryValenceFits(valence)) return false;
} else {
if (!KeyBits::InteriorValenceFits(valence)) return false;
}
}
// Conditional rejection of bitfields for specific Sdc::Options:
if (KeyBits::OptionsInhibitUsage(surface.GetSdcOptionsInEffect())) {
return false;
}
//
// Pack the topology of each FaceVertexSubset into bitfields:
//
KeyBits keyBits;
keyBits.subdScheme = surface.GetSdcScheme();
keyBits.sharpLevel = options.sharpLevel;
keyBits.smoothLevel = options.smoothLevel;
keyBits.usesDouble = options.doublePrecision;
keyBits.v0Valence = subsets[0]._numFacesTotal;
keyBits.v0IsSharp = subsets[0].IsSharp();
keyBits.v0IsBoundary = subsets[0].IsBoundary();
keyBits.v0FaceInBoundary = subsets[0]._numFacesBefore;
keyBits.v1Valence = subsets[1]._numFacesTotal;
keyBits.v1IsSharp = subsets[1].IsSharp();
keyBits.v1IsBoundary = subsets[1].IsBoundary();
keyBits.v1FaceInBoundary = subsets[1]._numFacesBefore;
keyBits.v2Valence = subsets[2]._numFacesTotal;
keyBits.v2IsSharp = subsets[2].IsSharp();
keyBits.v2IsBoundary = subsets[2].IsBoundary();
keyBits.v2FaceInBoundary = subsets[2]._numFacesBefore;
if (surface.GetFaceSize() == 4) {
keyBits.v3Valence = subsets[3]._numFacesTotal;
keyBits.v3IsSharp = subsets[3].IsSharp();
keyBits.v3IsBoundary = subsets[3].IsBoundary();
keyBits.v3FaceInBoundary = subsets[3]._numFacesBefore;
}
KeyIntType keyValue = 0;
std::memcpy(&keyValue, &keyBits, sizeof(KeyIntType));
return keyValue;
}
#endif
//
// Function to assign the topology of any FaceSurface to the desired
// integer using a hashing function that considers all topological
// features (incident face sizes, crease and corner sharpness, etc.):
//
KeyIntType
hashTopologyKey(FaceSurface const & surface,
IrregularPatchBuilder::Options options) {
//
// Structs for "headers" for the entire surface and each corner,
// to be assigned and copied into a larger buffer to be hashed:
//
typedef unsigned char uchar;
struct SurfaceHeader {
// Be sure to clear to avoid uninitialized bits:
SurfaceHeader() { std::memset(this, 0, sizeof(*this)); }
short faceSize;
uchar subdScheme;
uchar subdCreasing;
uchar subdTriSmooth;
uchar sharpLevel;
uchar smoothLevel;
uchar usesDouble;
};
struct CornerHeader {
// Be sure to clear to avoid uninitialized bits:
CornerHeader() { std::memset(this, 0, sizeof(*this)); }
short numFaces;
short faceInBoundary;
uchar isBoundary : 1;
uchar isInfSharp : 1;
uchar isSemiSharp : 1;
uchar hasFaceSizes : 1;
uchar hasSharpEdges : 1;
};
//
// Consider using a "delimiter" between corners in the buffer of
// data to be hashed, i.e. a value with a distinct bit pattern
// such as -1, to help prevent aliasing (may not be needed):
//
int delimiter = -1;
bool useCornerDelimiter = false;
//
// Local buffers for accumulating and hashing:
//
Vtr::internal::StackBuffer<float,16,true> floatBuffer;
Vtr::internal::StackBuffer<short,16,true> shortBuffer;
Vtr::internal::StackBuffer<char,256,true> hashBuffer;
//
// Determine size of the main buffer to hash ahead of time:
//
// Note there is some redundancy in the use of the uncommon
// faces sizes and sharp edges around each corner due to the
// way the corners' incident faces overlap. For typical cases
// the extra data used is not large. Only in extreme cases is
// it likely to be an issue -- but then the added processing
// and construction costs associated with such cases (e.g.
// high valence vertices, heavy use of creasing) will make
// make the overhead here insignificant.
//
size_t hashBufferSize = sizeof(SurfaceHeader);
int faceSize = surface.GetFaceSize();
for (int i = 0; i < faceSize; ++i) {
FaceVertexSubset const & cSub = surface.GetCornerSubset(i);
int N = cSub.GetNumFaces();
hashBufferSize += sizeof(CornerHeader);
hashBufferSize += cSub._tag.IsSemiSharp() ?
sizeof(float) : 0;
hashBufferSize += cSub._tag.HasUnCommonFaceSizes() ?
(sizeof(short) * N) : 0;
hashBufferSize += cSub._tag.HasSharpEdges() ?
(sizeof(float) * (N - cSub.IsBoundary())) : 0;
hashBufferSize += useCornerDelimiter ? sizeof(delimiter) : 0;
}
hashBuffer.SetSize((int)hashBufferSize);
//
// Start populating the buffer with the surface header:
//
Sdc::Options subdOptions = surface.GetSdcOptionsInEffect();
SurfaceHeader sHeader;
sHeader.faceSize = (short) faceSize;
sHeader.subdScheme = (uchar) surface.GetSdcScheme();
sHeader.subdCreasing = (uchar) subdOptions.GetCreasingMethod();
sHeader.subdTriSmooth = (uchar) subdOptions.GetTriangleSubdivision();
sHeader.sharpLevel = (uchar) options.sharpLevel;
sHeader.smoothLevel = (uchar) options.smoothLevel;
sHeader.usesDouble = (uchar) options.doublePrecision;
std::memcpy(hashBuffer, &sHeader, sizeof(sHeader));
//
// Populate the buffer for each corner of the surface:
//
char * bufferPtr = hashBuffer + sizeof(sHeader);
for (int corner = 0; corner < faceSize; ++corner) {
FaceVertex const & cTop = surface.GetCornerTopology(corner);
FaceVertexSubset const & cSub = surface.GetCornerSubset(corner);
// Assign the corner header:
CornerHeader cHeader;
cHeader.numFaces = (short) cSub.GetNumFaces();
cHeader.faceInBoundary = cSub._numFacesBefore;
cHeader.isBoundary = cSub.IsBoundary();
cHeader.isInfSharp = cSub.IsSharp();
cHeader.isSemiSharp = cSub._tag.IsSemiSharp();
cHeader.hasFaceSizes = cSub._tag.HasUnCommonFaceSizes();
cHeader.hasSharpEdges = cSub._tag.HasSharpEdges();
std::memcpy(bufferPtr, &cHeader, sizeof(cHeader));
bufferPtr += sizeof(cHeader);
if (cHeader.isSemiSharp) {
float sharpness = (cSub._localSharpness > 0.0f)
? cSub._localSharpness
: cTop.GetVertexSharpness();
std::memcpy(bufferPtr, &sharpness, sizeof(sharpness));
bufferPtr += sizeof(sharpness);
}
if (cHeader.hasFaceSizes) {
int n = cSub.GetNumFaces();
shortBuffer.SetSize(n);
for (int i = 0, f = cTop.GetFaceFirst(cSub); i < n;
f = cTop.GetFaceNext(f), ++i) {
shortBuffer[i] = (short) cTop.GetFaceSize(f);
}
std::memcpy(bufferPtr, shortBuffer, n * sizeof(short));
bufferPtr += n * sizeof(short);
}
if (cHeader.hasSharpEdges) {
int n = cSub.GetNumFaces() - cSub.IsBoundary();
floatBuffer.SetSize(n);
for (int i = 0, f = cTop.GetFaceFirst(cSub); i < n;
f = cTop.GetFaceNext(f), ++i) {
floatBuffer[i] = cTop.GetFaceEdgeSharpness(f, 1);
}
std::memcpy(bufferPtr, floatBuffer, n * sizeof(float));
bufferPtr += n * sizeof(float);
}
if (useCornerDelimiter) {
std::memcpy(bufferPtr, &delimiter, sizeof(delimiter));
bufferPtr += sizeof(delimiter);
}
}
assert((bufferPtr - hashBuffer) == (int)hashBufferSize);
return internal::Hash64(hashBuffer, hashBufferSize);
}
}
//
// Methods supporting construction of linear, regular and irregular patches:
//
void
SurfaceFactory::assignLinearSurface(SurfaceType * surfacePtr,
Index faceIndex, FVarID const * fvarPtrOrVtx) const {
SurfaceType & surface = *surfacePtr;
// Initialize instance members from the associated irregular patch:
int faceSize = getFaceSize(faceIndex);
surface.setParam(Parameterization(_subdivScheme, faceSize));
surface.setRegular(faceSize == _regFaceSize);
surface.setLinear(true);
surface.setRegPatchMask(0);
if (_regFaceSize == 4) {
surface.setRegPatchType(Far::PatchDescriptor::QUADS);
} else {
surface.setRegPatchType(Far::PatchDescriptor::TRIANGLES);
}
//
// Finally, gather patch control points from the appropriate indices:
//
Index * surfaceCVs = surface.resizeCVs(faceSize);
int count = 0;
if (fvarPtrOrVtx == 0) {
count = getFaceVertexIndices(faceIndex, surfaceCVs);
} else {
count = getFaceFVarValueIndices(faceIndex, *fvarPtrOrVtx, surfaceCVs);
}
// If subclass fails to get indices, Surface will remain invalid
if (count < faceSize) return;
surface.setValid(true);
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
__numLinearPatches ++;
#endif
}
void
SurfaceFactory::assignRegularSurface(SurfaceType * surfacePtr,
Index const patchPoints[]) const {
SurfaceType & surface = *surfacePtr;
//
// Assign the parameterization and discriminants first:
//
surface.setParam(Parameterization(_subdivScheme, _regFaceSize));
surface.setRegular(true);
surface.setLinear(false);
//
// Assemble the regular patch:
//
surface.setRegPatchType(RegularPatchBuilder::GetPatchType(_regFaceSize));
surface.setRegPatchMask(RegularPatchBuilder::GetBoundaryMask(_regFaceSize,
patchPoints));
//
// Copy the patch control points from the given indices:
//
int patchSize = RegularPatchBuilder::GetPatchSize(_regFaceSize);
Index const * pSrc = patchPoints;
Index * pDst = surface.resizeCVs(patchSize);
// Remember to replace negative indices in boundary patches:
if (surface.getRegPatchMask() == 0) {
std::memcpy(pDst, pSrc, patchSize * sizeof(Index));
} else {
// Consider delegating this task to the RegularPatchBuilder:
Index pPhantom = pSrc[5];
assert(pPhantom >= 0);
for (int i = 0; i < patchSize; ++i) {
pDst[i] = (pSrc[i] < 0) ? pPhantom : pSrc[i];
}
}
surface.setValid(true);
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
__numExpRegularPatches ++;
#endif
}
void
SurfaceFactory::assignRegularSurface(SurfaceType * surfacePtr,
FaceSurface const & descriptor) const {
SurfaceType & surface = *surfacePtr;
//
// Assign the parameterization and discriminants first:
//
surface.setParam(Parameterization(_subdivScheme, _regFaceSize));
surface.setRegular(true);
surface.setLinear(false);
//
// Assemble the regular patch:
//
RegularPatchBuilder builder(descriptor);
surface.setRegPatchType(builder.GetPatchType());
surface.setRegPatchMask(builder.GetPatchParamBoundaryMask());
//
// Gather the patch control points from the given indices:
//
builder.GatherControlVertexIndices(
surface.resizeCVs(builder.GetNumControlVertices()));
surface.setValid(true);
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
__numRegularPatches ++;
#endif
}
void
SurfaceFactory::assignIrregularSurface(SurfaceType * surfacePtr,
FaceSurface const & descriptor) const {
//
// A builder for the irregular patch is required regardless of
// whether a new instance is constructed:
//
IrregularPatchBuilder::Options buildOptions;
buildOptions.sharpLevel = _factoryOptions.GetApproxLevelSharp();
buildOptions.smoothLevel = _factoryOptions.GetApproxLevelSmooth();
buildOptions.doublePrecision = surfacePtr->isDouble();
IrregularPatchBuilder builder(descriptor, buildOptions);
//
// Construct a new irregular patch or identify one from the cache:
//
internal::IrregularPatchSharedPtr patch(0);
if (_topologyCache == 0) {
patch = builder.Build();
} else {
//
// Compute the cache key for the topology of this face, search the
// cache for an existing patch and build/add one if not found:
//
// Be sure to use the return result of Add() when adding as it may
// be the case that another thread added a patch with the same key
// while this one was being built. Using the instance assigned to
// the cache intentionally releases the one built here.
//
SurfaceFactoryCache::KeyType key =
hashTopologyKey(descriptor, buildOptions);
patch = _topologyCache->Find(key);
if (patch == 0) {
patch = _topologyCache->Add(key, builder.Build());
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
__numIrregularInCache ++;
#endif
}
}
//
// Assign the Surface parameterization, discriminants and patch:
//
SurfaceType & surface = *surfacePtr;
surface.setParam(Parameterization(_subdivScheme, descriptor.GetFaceSize()));
surface.setRegular(false);
surface.setLinear(false);
surface.setIrregPatchPtr(patch);
// Gather the patch control points from the given indices:
builder.GatherControlVertexIndices(
surface.resizeCVs(patch->GetNumControlPoints()));
surface.setValid(true);
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
__numIrregularPatches ++;
__numIrregularUncached += surface.ownsIrregPatch();
#endif
}
void
SurfaceFactory::copyNonLinearSurface(
SurfaceType * surfaceDstPtr,
SurfaceType const & surfaceSrc,
FaceSurface const & descriptor) const {
SurfaceType & surfaceDst = *surfaceDstPtr;
// Should be creating a linear patch directly rather than copying:
assert(!surfaceSrc.isLinear());
//
// Assign the topological fields of the patch first:
//
surfaceDst.setParam(surfaceSrc.getParam());
surfaceDst.setLinear(surfaceSrc.isLinear());
surfaceDst.setRegular(surfaceSrc.isRegular());
surfaceDst.resizeCVs(surfaceSrc.getNumCVs());
//
// Assign regular/irregular fields and gather control points:
//
if (surfaceDst.isRegular()) {
surfaceDst.setRegPatchType(surfaceSrc.getRegPatchType());
surfaceDst.setRegPatchMask(surfaceSrc.getRegPatchMask());
RegularPatchBuilder builder(descriptor);
assert(builder.GetNumControlVertices() == surfaceDst.getNumCVs());
builder.GatherControlVertexIndices(surfaceDst.getCVIndices());
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
__numRegularPatches ++;
#endif
} else {
surfaceDst.setIrregPatchPtr(surfaceSrc.getIrregPatchPtr());
IrregularPatchBuilder builder(descriptor);
assert(builder.GetNumControlVertices() == surfaceDst.getNumCVs());
builder.GatherControlVertexIndices(surfaceDst.getCVIndices());
#ifdef _BFR_DEBUG_TOP_TYPE_STATS
__numIrregularPatches ++;
#endif
}
surfaceDst.setValid(true);
}
//
// Methods to deal with topology assembly and inspection:
//
// Note the difference between the "init" and "gather" methods: "init"
// fully resolves and initializes the topology by gathering face indices
// locally and dealing with unordered faces if present, while the "gather"
// method simply gathers the corner information -- allowing indices to be
// provided for further use if needed.
//
bool
SurfaceFactory::initFaceNeighborhoodTopology(Index faceIndex,
FaceTopology * faceTopologyPtr) const {
FaceTopology & topology = *faceTopologyPtr;
if (!gatherFaceNeighborhoodTopology(faceIndex, &topology)) {
return false;
}
if (!topology.HasUnOrderedCorners()) {
return true;
}
// Gather the indices to determine topology between unordered faces:
typedef Vtr::internal::StackBuffer<Index,72,true> IndexBuffer;
IndexBuffer indices(topology._numFaceVertsTotal);
if (gatherFaceNeighborhoodIndices(faceIndex, topology, 0, indices) < 0) {
return false;
}
topology.ResolveUnOrderedCorners(indices);
return true;
}
bool
SurfaceFactory::gatherFaceNeighborhoodTopology(Index faceIndex,
FaceTopology * faceTopologyPtr) const {
FaceTopology & faceTopology = *faceTopologyPtr;
int N = getFaceSize(faceIndex);
faceTopology.Initialize(N);
for (int i = 0; i < N; ++i) {
FaceVertex & faceVtx = faceTopology.GetTopology(i);
VertexDescriptor & vtxDesc = faceVtx.GetVertexDescriptor();
faceVtx.Initialize(N, _regFaceSize);
// Subclass returning negative here indicates unsupported features
// or some other kind of failure:
int faceInRing = populateFaceVertexDescriptor(faceIndex, i, &vtxDesc);
if (faceInRing < 0) return false;
faceVtx.Finalize(faceInRing);
}
faceTopology.Finalize();
return true;
}
int
SurfaceFactory::gatherFaceNeighborhoodIndices(Index faceIndex,
FaceTopology const & faceTopology,
FVarID const * fvarPtrOrVtx,
Index controlIndices[]) const {
int faceSize = faceTopology.GetFaceSize();
Index * indices = controlIndices;
int nIndices = 0;
for (int i = 0; i < faceSize; ++i) {
int numFaceVerts = (fvarPtrOrVtx == 0) ?
getFaceVertexIncidentFaceVertexIndices(faceIndex, i,
indices) :
getFaceVertexIncidentFaceFVarValueIndices(faceIndex, i,
*fvarPtrOrVtx, indices);
if (numFaceVerts != faceTopology.GetNumFaceVertices(i)) {
return -1;
}
indices += numFaceVerts;
nIndices += numFaceVerts;
}
return nIndices;
}
bool
SurfaceFactory::isFaceNeighborhoodRegular(Index faceIndex,
FVarID const * fvarPtrOrVtx,
Index indices[]) const {
return (fvarPtrOrVtx == 0) ?
getFaceNeighborhoodVertexIndicesIfRegular(faceIndex, indices) :
getFaceNeighborhoodFVarValueIndicesIfRegular(faceIndex, *fvarPtrOrVtx,
indices);
}
//
// Main internal methods to populate set of limit Surfaces:
//
bool
SurfaceFactory::populateAllSurfaces(Index faceIndex,
SurfaceSet * surfaceSetPtr) const {
SurfaceSet & surfaces = *surfaceSetPtr;
// Abort if no Surfaces are specified to populate:
if (surfaces.GetNumSurfaces() == 0) {
return false;
}
//
// Be sure to re-initialize all Surfaces up-front, rather than
// deferring it to the assignment of each. A failure of any one
// surface may leave others unvisited -- leaving it unchanged
// from previous use.
//
surfaces.InitializeSurfaces();
// Quickly reject faces with no limit (typically holes) -- some cases
// require full topological inspection and will be rejected later:
if (!faceHasLimitSimple(faceIndex, getFaceSize(faceIndex))) {
return false;
}
// Determine if we have any non-linear cases to deal with -- which
// require gathering and inspection of the full neighborhood around
// the given face:
int numFVarSurfaces = surfaces.GetNumFVarSurfaces();
bool hasNonLinearSurfaces =
(surfaces.HasVertexSurface() && !_linearScheme) ||
(numFVarSurfaces && !_linearFVarInterp);
bool hasLinearSurfaces =
surfaces.HasVaryingSurface() ||
(surfaces.HasVertexSurface() && _linearScheme) ||
(numFVarSurfaces && _linearFVarInterp);
if (hasNonLinearSurfaces || _testNeighborhoodForLimit) {
if (!populateNonLinearSurfaces(faceIndex, &surfaces)) {
return false;
}
}
if (hasLinearSurfaces) {
if (!populateLinearSurfaces(faceIndex, &surfaces)) {
return false;
}
}
return true;
}
bool
SurfaceFactory::populateLinearSurfaces(Index faceIndex,
SurfaceSet * surfaceSetPtr) const {
SurfaceSet & surfaces = *surfaceSetPtr;
if (surfaces.HasVaryingSurface()) {
assignLinearSurface(surfaces.GetVaryingSurface(), faceIndex, 0);
}
if (_linearScheme && surfaces.HasVertexSurface()) {
assignLinearSurface(surfaces.GetVertexSurface(), faceIndex, 0);
}
if (_linearFVarInterp) {
int numFVarSurfaces = surfaces.GetNumFVarSurfaces();
for (int i = 0; i < numFVarSurfaces; ++i) {
FVarID fvarID = surfaces.GetFVarSurfaceID(i);
assignLinearSurface(surfaces.GetFVarSurface(i), faceIndex, &fvarID);
}
}
return true;
}
bool
SurfaceFactory::populateNonLinearSurfaces(Index faceIndex,
SurfaceSet * surfaceSetPtr) const {
SurfaceSet & surfaces = *surfaceSetPtr;
typedef Vtr::internal::StackBuffer<Index,72,true> IndexBuffer;
bool vtxIsNonLinear = surfaces.HasVertexSurface() && !_linearScheme;
bool fvarIsNonLinear = surfaces.HasFVarSurfaces() && !_linearFVarInterp;
bool anyNonLinear = vtxIsNonLinear || fvarIsNonLinear;
//
// First need to determine the vertex topology of the face and take
// appropriate action based on inputs. It may be the case that the
// topology is only used to determine if the non-linear face has a
// limit surface and no non-linear surfaces are generated here (and
// linear varying or face-varying surfaces are determined elsewhere).
//
// So determine the topology and deal with any required tests for
// the presence of limit surface.
//
// If the face is "explicitly regular", i.e. the subclass can provide
// an immediate regular patch representation, the more tedious work
// to assemble the more general topological representation is avoided.
//
// Note that while the vertex surface may be explicitly regular, if
// the face-varying topology does not match, i.e. there is a UV seam
// present around the face, the more general topological representation
// will be necessary to deal with a potentially irregular face-varying
// surface.
//
FaceTopology faceTopology(_subdivScheme, _subdivOptions);
IndexBuffer vtxIndices(16);
FaceSurface vtxSurfDesc;
bool vtxIsExplicitlyRegular =
isFaceNeighborhoodRegular(faceIndex, 0, vtxIndices);
if (vtxIsExplicitlyRegular) {
if (_testNeighborhoodForLimit && !anyNonLinear) {
return true;
}
} else {
//
// Three steps are required to get full topological description:
// - gathering the full description of the neighborhood
// - gathering vertex indices for the neighborhood
// - using the indices to resolve any unordered topology
// Gathering indices for the vertex surface and/or to resolve
// unordered topology is conditional.
//
if (!gatherFaceNeighborhoodTopology(faceIndex, &faceTopology)) {
return false;
}
if (vtxIsNonLinear || faceTopology.HasUnOrderedCorners()) {
vtxIndices.SetSize(faceTopology._numFaceVertsTotal);
if (gatherFaceNeighborhoodIndices(faceIndex, faceTopology, 0,
vtxIndices) < 0) {
return false;
}
if (faceTopology.HasUnOrderedCorners()) {
faceTopology.ResolveUnOrderedCorners(vtxIndices);
}
}
if (_testNeighborhoodForLimit) {
if (!faceHasLimitNeighborhood(faceTopology)) {
return false;
} else if (!anyNonLinear) {
return true;
}
}
// Initialize the vertex surface descriptor for use creating both
// the vertex Surface and any non-linear FVar Surfaces:
vtxSurfDesc.Initialize(faceTopology, vtxIndices);
}
//
// Construct the Surface for vertex topology first, as face-varying
// surfaces that match topology may make use of it:
//
bool vtxSurfIsValid = false;
if (vtxIsNonLinear) {
SurfaceType & vtxSurf = *surfaces.GetVertexSurface();
if (vtxIsExplicitlyRegular) {
assignRegularSurface(&vtxSurf, vtxIndices);
} else if (vtxSurfDesc.IsRegular()) {
assignRegularSurface(&vtxSurf, vtxSurfDesc);
} else {
assignIrregularSurface(&vtxSurf, vtxSurfDesc);
}
vtxSurfIsValid = vtxSurf.isValid();
}
//
// Construct the Surface for the given face-varying topologies --
// all of which are potentially distinct.
//
// If the vertex topology is explicitly regular, the face-varying
// surface can only make use of it if it shares the same topology
// and the subclass provides corresponding control points.
//
// In all other cases the full topological description and the full
// description of the vertex surface must be provided. The set of
// face-varying indices must then be gathered and used to create a
// face-varying surface descriptor, which uses the indices to find
// the relevant face-varying subsets for each corner.
//
if (fvarIsNonLinear) {
// We can re-use the vertex index buffer for face-varying indices:
IndexBuffer & fvIndices = vtxIndices;
int numFVarSurfaces = surfaces.GetNumFVarSurfaces();
for (int i = 0; i < numFVarSurfaces; ++i) {
SurfaceType & fvarSurf = *surfaces.GetFVarSurface(i);
FVarID fvarID = surfaces.GetFVarSurfaceID(i);
// First check if trivially regular, quickly assign and continue:
bool fvarIsExplicitlyRegular = vtxIsExplicitlyRegular &&
isFaceNeighborhoodRegular(faceIndex, &fvarID, fvIndices);
if (fvarIsExplicitlyRegular) {
assignRegularSurface(&fvarSurf, fvIndices);
continue;
}
// Make sure topology, indices and vertex surface are initialized
// (will not be if vertex surface was explicitly regular):
if (!vtxSurfDesc.IsInitialized()) {
if (!initFaceNeighborhoodTopology(faceIndex, &faceTopology)) {
return false;
}
vtxSurfDesc.Initialize(faceTopology, 0);
}
fvIndices.SetSize(faceTopology._numFaceVertsTotal);
// Gather FVar indices and initialize FVar surface descriptor:
if (gatherFaceNeighborhoodIndices(faceIndex, faceTopology,
&fvarID, fvIndices) < 0) {
return false;
}
FaceSurface fvarSurfDesc(vtxSurfDesc, fvIndices);
// Detect matching or other topology and dispatch accordingly:
if (fvarSurfDesc.FVarTopologyMatchesVertex() && vtxSurfIsValid) {
copyNonLinearSurface(&fvarSurf, *surfaces.GetVertexSurface(),
fvarSurfDesc);
} else if (fvarSurfDesc.IsRegular()) {
assignRegularSurface(&fvarSurf, fvarSurfDesc);
} else {
assignIrregularSurface(&fvarSurf, fvarSurfDesc);
}
}
}
return true;
}
//
// Main internal method to initialize instances of Surface:
//
bool
SurfaceFactory::initSurfaces(Index faceIndex,
internal::SurfaceData * vtxSurface,
internal::SurfaceData * varSurface,
internal::SurfaceData * fvarSurfaces,
int fvarCount,
FVarID const fvarIDs[]) const {
// Note the SurfaceData for the first FVar Surface is assumed below
// to be the head of an array of SurfaceData, which will not be true
// if additional members are added to Surface<REAL> in future:
assert(sizeof(internal::SurfaceData) == sizeof(Surface<float>));
SurfaceSet surfaces;
surfaces.vtxSurf = vtxSurface;
surfaces.varSurf = varSurface;
surfaces.fvarSurfs = fvarSurfaces;
surfaces.fvarIDs = fvarIDs;
surfaces.numFVarSurfs = fvarCount;
surfaces.numSurfs = fvarCount + (vtxSurface != 0) + (varSurface != 0);
return populateAllSurfaces(faceIndex, &surfaces);
}
} // end namespace Bfr
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv