OpenSubdiv/opensubdiv/far/endCapGregoryBasisPatchFactory.cpp
Takahito Tejima 43aa2500c4 Refactor far factories.
This change moves all gregory patch generation from Far::PatchTablesFactory
so that we can construct patch tables without stencil tables as well as client
can chose any end patch strategies (we have 3 options for now: legacy 2.x style
gregory patch, gregory basis patch and experimental regular patch approximation).

Also Far::EndCapGregoryBasisPatchFactory provides index mapping from patch index
to vtr face index, which can be used for single gregory patch evaluation on top
of refined points, without involving heavier stencil tables generation.
2015-04-20 18:59:07 -07:00

466 lines
15 KiB
C++

//
// Copyright 2013 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 "../far/gregoryBasis.h"
#include "../far/endCapGregoryBasisPatchFactory.h"
#include "../far/error.h"
#include "../far/stencilTablesFactory.h"
#include "../far/topologyRefiner.h"
#include <cassert>
#include <cmath>
#include <cstring>
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Far {
static inline bool
checkMaxValence(Vtr::Level const & level) {
if (level.getMaxValence()>EndCapGregoryBasisPatchFactory::GetMaxValence()) {
// The proto-basis closed-form table limits valence to 'MAX_VALENCE'
Error(FAR_RUNTIME_ERROR,
"Vertex valence %d exceeds maximum %d supported",
level.getMaxValence(), EndCapGregoryBasisPatchFactory::GetMaxValence());
return false;
}
return true;
}
// ---------------------------------------------------------------------------
//
// Factory context
//
struct EndCapGregoryBasisPatchFactory::Context {
Context(TopologyRefiner const *refiner, bool shareBoundaryVertices) :
refiner(refiner), shareBoundaryVertices(shareBoundaryVertices),
numGregoryBasisVertices(0), numGregoryBasisPatches(0) { }
std::vector<GregoryBasis::Point> vertexStencils;
std::vector<GregoryBasis::Point> varyingStencils;
TopologyRefiner const *refiner;
bool shareBoundaryVertices;
int numGregoryBasisVertices;
int numGregoryBasisPatches;
std::vector<Index> basisIndices;
std::vector<Index> topology;
};
// ---------------------------------------------------------------------------
//
// EndCapGregoryBasisPatchFactory for Vertex StencilTables
//
EndCapGregoryBasisPatchFactory::EndCapGregoryBasisPatchFactory(
TopologyRefiner const & refiner, bool shareBoundaryVertices) :
_context(NULL) {
// Sanity check: the mesh must be adaptively refined
assert(not refiner.IsUniform());
// create context
_context = new EndCapGregoryBasisPatchFactory::Context(
&refiner, shareBoundaryVertices);
}
EndCapGregoryBasisPatchFactory::~EndCapGregoryBasisPatchFactory() {
delete _context;
}
int
EndCapGregoryBasisPatchFactory::GetMaxValence() {
return GregoryBasis::MAX_VALENCE;
}
//
// Stateless EndCapGregoryBasisPatchFactory
//
GregoryBasis const *
EndCapGregoryBasisPatchFactory::Create(TopologyRefiner const & refiner,
Index faceIndex, int fvarChannel) {
// Gregory patches are end-caps: they only exist on max-level
Vtr::Level const & level = refiner.getLevel(refiner.GetMaxLevel());
if (not checkMaxValence(level)) {
return 0;
}
GregoryBasis::ProtoBasis basis(level, faceIndex, fvarChannel);
int nelems= basis.GetNumElements();
GregoryBasis * result = new GregoryBasis;
result->_indices.resize(nelems);
result->_weights.resize(nelems);
basis.Copy(result->_sizes, &result->_indices[0], &result->_weights[0]);
// note: this function doesn't create varying stencils.
for (int i=0, offset=0; i<20; ++i) {
result->_offsets[i] = offset;
offset += result->_sizes[i];
}
return result;
}
static void factorizeBasisVertex(StencilTables const * stencils,
GregoryBasis::Point const & p,
ProtoStencil dst) {
// Use the Allocator to factorize the Gregory patch influence CVs with the
// supporting CVs from the stencil tables.
if (!stencils) return;
dst.Clear();
for (int j=0; j<p.GetSize(); ++j) {
dst.AddWithWeight(*stencils,
p.GetIndices()[j], p.GetWeights()[j]);
}
}
bool
EndCapGregoryBasisPatchFactory::addPatchBasis(Index faceIndex,
bool verticesMask[4][5]) {
// Gregory patches only exist on the hight
Vtr::Level const & level = _context->refiner->getLevel(
_context->refiner->GetMaxLevel());
if (not checkMaxValence(level)) {
return false;
}
// Gather the CVs that influence the Gregory patch and their relative
// weights in a basis
GregoryBasis::ProtoBasis basis(level, faceIndex);
for (int i = 0; i < 4; ++i) {
if (verticesMask[i][0]) {
_context->vertexStencils.push_back(basis.P[i]);
_context->varyingStencils.push_back(basis.V[i]);
}
if (verticesMask[i][1]) {
_context->vertexStencils.push_back(basis.Ep[i]);
_context->varyingStencils.push_back(basis.V[i]);
}
if (verticesMask[i][2]) {
_context->vertexStencils.push_back(basis.Em[i]);
_context->varyingStencils.push_back(basis.V[i]);
}
if (verticesMask[i][3]) {
_context->vertexStencils.push_back(basis.Fp[i]);
_context->varyingStencils.push_back(basis.V[i]);
}
if (verticesMask[i][4]) {
_context->vertexStencils.push_back(basis.Fm[i]);
_context->varyingStencils.push_back(basis.V[i]);
}
}
return true;
}
static void
createStencil(StencilAllocator &alloc,
StencilTables const *baseStencils,
TopologyRefiner const *refiner,
std::vector<GregoryBasis::Point> const &gregoryStencils) {
// Gregory limit stencils have indices that are relative to the level
// (maxlevel) of subdivision. These indices need to be offset to match
// the indices from the multi-level adaptive stencil tables.
// In addition: stencil tables can be built with singular stencils
// (single weight of 1.0f) as place-holders for coarse mesh vertices,
// which also needs to be accounted for.
int stencilsIndexOffset = 0;
{
int maxlevel = refiner->GetMaxLevel();
int nverts = refiner->GetNumVerticesTotal();
int nBaseStencils = baseStencils->GetNumStencils();
if (nBaseStencils == nverts) {
// the table contain stencils for the control vertices
stencilsIndexOffset = nverts - refiner->GetNumVertices(maxlevel);
} else if (nBaseStencils == (nverts -refiner->GetNumVertices(0))) {
// the table does not contain stencils for the control vertices
stencilsIndexOffset = nverts - refiner->GetNumVertices(maxlevel)
- refiner->GetNumVertices(0);
} else {
// these are not the stencils you are looking for...
assert(0);
return;
}
}
int nStencils = (int)gregoryStencils.size();
alloc.Resize(nStencils);
for (int i = 0; i < nStencils; ++i) {
GregoryBasis::Point p = gregoryStencils[i];
p.OffsetIndices(stencilsIndexOffset);
factorizeBasisVertex(baseStencils, p, alloc[i]);
}
}
StencilTables const *
EndCapGregoryBasisPatchFactory::createStencilTables(StencilAllocator &alloc,
StencilTables const *baseStencils,
bool append,
int const permute[20]) {
int nStencils = alloc.GetNumStencils();
int nelems = alloc.GetNumVerticesTotal();
// return NULL if empty
if (nStencils==0 or nelems==0) {
return NULL;
}
// Finalize the stencil tables from the temporary pool allocator
StencilTables * result = new StencilTables;
result->_numControlVertices = _context->refiner->GetNumVertices(0);
result->resize(nStencils, nelems);
Stencil dst(&result->_sizes.at(0),
&result->_indices.at(0), &result->_weights.at(0));
for (int i = 0; i < nStencils; ++i) {
Index index = i;
if (permute) {
int localIndex = i % 20,
baseIndex = i - localIndex;
index = baseIndex + permute[localIndex];
}
*dst._size = alloc.CopyStencil(index, dst._indices, dst._weights);
dst.Next();
}
result->generateOffsets();
if (append) {
StencilTables const *inStencilTables[] = {
baseStencils, result
};
StencilTables const *concatStencilTables =
StencilTablesFactory::Create(2, inStencilTables);
delete result;
return concatStencilTables;
} else {
return result;
}
}
PatchDescriptor::Type
EndCapGregoryBasisPatchFactory::GetPatchType(PatchTablesFactoryBase::PatchFaceTag const &) const {
return PatchDescriptor::GREGORY_BASIS;
}
//
// Populates the topology table used by Gregory-basis patches
//
// Note : 'faceIndex' values are expected to be sorted in ascending order !!!
// Note 2: this code attempts to identify basis vertices shared along
// gregory patch edges
ConstIndexArray
EndCapGregoryBasisPatchFactory::GetTopology(
Vtr::Level const& level, Index faceIndex,
PatchTablesFactoryBase::PatchFaceTag const * levelPatchTags,
int /*not used: levelVertsOffset*/)
{
// allocate indices (awkward)
// assert(Vtr::INDEX_INVALID==0xFFFFFFFF);
for (int i = 0; i < 20; ++i) {
_context->topology.push_back(Vtr::INDEX_INVALID);
}
Index * dest = &_context->topology[_context->numGregoryBasisPatches * 20];
int gregoryVertexOffset = _context->refiner->GetNumVerticesTotal();
if (_context->shareBoundaryVertices) {
ConstIndexArray fedges = level.getFaceEdges(faceIndex);
assert(fedges.size()==4);
for (int i=0; i<4; ++i) {
Index edge = fedges[i], adjface = 0;
{ // Gather adjacent faces
ConstIndexArray adjfaces = level.getEdgeFaces(edge);
for (int i=0; i<adjfaces.size(); ++i) {
if (adjfaces[i]==faceIndex) {
// XXXX manuelk if 'edge' is non-manifold, arbitrarily pick the
// next face in the list of adjacent faces
adjface = (adjfaces[(i+1)%adjfaces.size()]);
break;
}
}
}
// We are looking for adjacent faces that:
// - exist (no boundary)
// - have already been processed (known CV indices)
// - are also Gregory basis patches
if (adjface!=Vtr::INDEX_INVALID and (adjface < faceIndex) and
(not levelPatchTags[adjface]._isRegular)) {
ConstIndexArray aedges = level.getFaceEdges(adjface);
int aedge = aedges.FindIndexIn4Tuple(edge);
assert(aedge!=Vtr::INDEX_INVALID);
// Find index of basis in the list of basis already generated
struct compare {
static int op(void const * a, void const * b) {
return *(Index *)a - *(Index *)b;
}
};
Index * ptr = (Index *)std::bsearch(&adjface,
&_context->basisIndices[0],
_context->basisIndices.size(),
sizeof(Index), compare::op);
int srcBasisIdx = (int)(ptr - &_context->basisIndices[0]);
if (!ptr) {
// if the adjface is hole, it won't be found
break;
}
assert(ptr
and srcBasisIdx>=0
and srcBasisIdx<(int)_context->basisIndices.size());
// Copy the indices of CVs from the face on the other side of the
// shared edge
static int const gregoryEdgeVerts[4][4] = { { 0, 1, 7, 5},
{ 5, 6, 12, 10},
{10, 11, 17, 15},
{15, 16, 2, 0} };
Index * src = &_context->topology[srcBasisIdx*20];
for (int j=0; j<4; ++j) {
// invert direction
// note that src indices have already been offsetted.
dest[gregoryEdgeVerts[i][3-j]] = src[gregoryEdgeVerts[aedge][j]];
}
}
}
}
bool newVerticesMask[4][5];
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 5; ++j) {
if (dest[i*5+j]==Vtr::INDEX_INVALID) {
// assign new vertex
dest[i*5+j] =
_context->numGregoryBasisVertices + gregoryVertexOffset;
++_context->numGregoryBasisVertices;
newVerticesMask[i][j] = true;
} else {
// share vertex
newVerticesMask[i][j] = false;
}
}
}
_context->basisIndices.push_back(faceIndex);
// add basis
addPatchBasis(faceIndex, newVerticesMask);
++_context->numGregoryBasisPatches;
// return cvs;
return ConstIndexArray(dest, 20);
}
Index
EndCapGregoryBasisPatchFactory::GetFaceIndex(Index patchIndex) const {
return _context->basisIndices[patchIndex];
}
int
EndCapGregoryBasisPatchFactory::GetNumGregoryBasisPatches() const {
return _context->numGregoryBasisPatches;
}
int
EndCapGregoryBasisPatchFactory::GetNumGregoryBasisVertices() const {
return _context->numGregoryBasisVertices;
}
StencilTables const *
EndCapGregoryBasisPatchFactory::CreateVertexStencilTables(
StencilTables const *baseStencils,
bool append,
int const permute[20]) {
// Factorize the basis CVs with the stencil tables: the basis is now
// expressed as a linear combination of vertices from the coarse control
// mesh with no data dependencies
int maxvalence = _context->refiner->GetMaxValence();
StencilAllocator alloc(GregoryBasis::getNumMaxElements(maxvalence));
createStencil(alloc, baseStencils,
_context->refiner, _context->vertexStencils);
return createStencilTables(alloc, baseStencils, append, permute);
}
StencilTables const *
EndCapGregoryBasisPatchFactory::CreateVaryingStencilTables(
StencilTables const *baseStencils,
bool append,
int const permute[20]) {
int maxvalence = _context->refiner->GetMaxValence();
StencilAllocator alloc(GregoryBasis::getNumMaxElements(maxvalence));
createStencil(alloc, baseStencils,
_context->refiner, _context->varyingStencils);
return createStencilTables(alloc, baseStencils, append, permute);
}
} // end namespace Far
} // end namespace OPENSUBDIV_VERSION
} // end namespace OpenSubdiv