// // Copyright (C) Pixar. All rights reserved. // // This license governs use of the accompanying software. If you // use the software, you accept this license. If you do not accept // the license, do not use the software. // // 1. Definitions // The terms "reproduce," "reproduction," "derivative works," and // "distribution" have the same meaning here as under U.S. // copyright law. A "contribution" is the original software, or // any additions or changes to the software. // A "contributor" is any person or entity that distributes its // contribution under this license. // "Licensed patents" are a contributor's patent claims that read // directly on its contribution. // // 2. Grant of Rights // (A) Copyright Grant- Subject to the terms of this license, // including the license conditions and limitations in section 3, // each contributor grants you a non-exclusive, worldwide, // royalty-free copyright license to reproduce its contribution, // prepare derivative works of its contribution, and distribute // its contribution or any derivative works that you create. // (B) Patent Grant- Subject to the terms of this license, // including the license conditions and limitations in section 3, // each contributor grants you a non-exclusive, worldwide, // royalty-free license under its licensed patents to make, have // made, use, sell, offer for sale, import, and/or otherwise // dispose of its contribution in the software or derivative works // of the contribution in the software. // // 3. Conditions and Limitations // (A) No Trademark License- This license does not grant you // rights to use any contributor's name, logo, or trademarks. // (B) If you bring a patent claim against any contributor over // patents that you claim are infringed by the software, your // patent license from such contributor to the software ends // automatically. // (C) If you distribute any portion of the software, you must // retain all copyright, patent, trademark, and attribution // notices that are present in the software. // (D) If you distribute any portion of the software in source // code form, you may do so only under this license by including a // complete copy of this license with your distribution. If you // distribute any portion of the software in compiled or object // code form, you may only do so under a license that complies // with this license. // (E) The software is licensed "as-is." You bear the risk of // using it. The contributors give no express warranties, // guarantees or conditions. You may have additional consumer // rights under your local laws which this license cannot change. // To the extent permitted under your local laws, the contributors // exclude the implied warranties of merchantability, fitness for // a particular purpose and non-infringement. // #ifndef FAR_MESH_FACTORY_H #define FAR_MESH_FACTORY_H #include "../version.h" // Activate Hbr feature adaptive tagging : in order to process the HbrMesh // adaptively, some tag data is added to HbrFace, HbrVertex and HbrHalfedge. // While small, these tags incur some performance costs and are by default // disabled. #define HBR_ADAPTIVE #include "../hbr/mesh.h" #include "../hbr/bilinear.h" #include "../hbr/catmark.h" #include "../hbr/loop.h" #include "../far/mesh.h" #include "../far/dispatcher.h" #include "../far/bilinearSubdivisionTablesFactory.h" #include "../far/catmarkSubdivisionTablesFactory.h" #include "../far/loopSubdivisionTablesFactory.h" #include "../far/patchTablesFactory.h" #include "../far/vertexEditTablesFactory.h" #include #include namespace OpenSubdiv { namespace OPENSUBDIV_VERSION { /// \brief Instantiates a FarMesh from an HbrMesh. /// /// FarMeshFactory requires a 2 steps process : /// 1. Instantiate a FarMeshFactory object from an HbrMesh /// 2. Call "Create" to obtain the FarMesh instance /// /// This tiered factory approach offers client-code the opportunity to access /// useful transient information tied to the lifespan of the factory instance. /// Specifically, regression code needs to access the remapping tables that /// tie HbrMesh vertices to their FarMesh counterparts for comparison. template class FarMeshFactory { public: /// \brief Constructor for the factory. /// Analyzes the HbrMesh and stores transient data used to create the /// adaptive patch representation. Once the new rep has been instantiated /// with 'Create', this factory object can be deleted safely. FarMeshFactory(HbrMesh * mesh, int maxlevel, bool adaptive=false); /// Create a table-based mesh representation FarMesh * Create( bool requirePtexCoordinate=false, // XXX yuck. bool requireFVarData=false ); /// The Hbr mesh that this factory is converting HbrMesh const * GetHbrMesh() const { return _hbrMesh; } /// Maximum level of subidivision supported by this factory int GetMaxLevel() const { return _maxlevel; } /// The number of coarse vertices found in the HbrMesh before refinement int GetNumCoarseVertices() const { return _numCoarseVertices; } /// Total number of faces across up to a level int GetNumFacesTotal(int level) const { return sumList *>(_facesList, level); } /// Return the corresponding index of the HbrVertex in the new mesh int GetVertexID( HbrVertex * v ); /// Returns a the mapping between HbrVertex->GetID() and Far vertices indices std::vector const & GetRemappingTable( ) const { return _remapTable; } private: friend class FarBilinearSubdivisionTablesFactory; friend class FarCatmarkSubdivisionTablesFactory; friend class FarLoopSubdivisionTablesFactory; friend class FarSubdivisionTablesFactory; friend class FarVertexEditTablesFactory; // Non-copyable, so these are not implemented: FarMeshFactory( FarMeshFactory const & ); FarMeshFactory & operator=(FarMeshFactory const &); // True if the HbrMesh applies the bilinear subdivision scheme static bool isBilinear(HbrMesh const * mesh); // True if the HbrMesh applies the Catmull-Clark subdivision scheme static bool isCatmark(HbrMesh const * mesh); // True if the HbrMesh applies the Loop subdivision scheme static bool isLoop(HbrMesh const * mesh); // True if the factory is refining adaptively bool isAdaptive() { return _adaptive; } // False if v prevents a face from being represented with a BSpline static bool vertexIsBSpline( HbrVertex * v, bool next ); // True if a vertex is a regular boundary static bool vertexIsRegularBoundary( HbrVertex * v ); // Non-const accessor to the remapping table std::vector & getRemappingTable( ) { return _remapTable; } template static int sumList( std::vector > const & list, int level ); // Returns the highest level of subdivision required to adaptively refine the mesh static int computeAdaptiveMaxLevel( HbrMesh * mesh, int nfaces, int maxIsolate ); // Calls Hbr to refines the neighbors of v static void refineVertexNeighbors(HbrVertex * v); // Densely refine the Hbr mesh static void refine( HbrMesh * mesh, int maxlevel ); // Adaptively refine the Hbr mesh int refineAdaptive( HbrMesh * mesh, int maxIsolate ); // Generates local sub-face coordinates for Ptex textures void generatePtexCoordinates( std::vector & vec, int level ); // Generates local sub-face face-varying UV coordinates void generateFVarData( std::vector & vec, int level ); // Generates non-adaptive quad topology // XXXX manuelk we should introduce an equivalent to FarPatchTables for // non-adaptive stuff void generateQuadsTopology( std::vector & vec, int level ); private: HbrMesh * _hbrMesh; bool _adaptive; int _maxlevel, _numVertices, _numCoarseVertices, _numFaces, _maxValence; // remapping table to translate vertex ID's between Hbr indices and the // order of the same vertices in the tables std::vector _remapTable; // list of faces sorted by level std::vector *> > _facesList; }; template template int FarMeshFactory::sumList( std::vector > const & list, int level) { level = std::min(level, (int)list.size()-1); int total = 0; for (int i=0; i<=level; ++i) total += (int)list[i].size(); return total; } // Refines non-adaptively an Hbr mesh template void FarMeshFactory::refine( HbrMesh * mesh, int maxlevel ) { for (int level=0, firstface=0; levelGetNumFaces(); for (int i=firstface; i * f = mesh->GetFace(i); if (f->GetDepth()==level) { if (not f->IsHole()) { f->Refine(); } else { // Hole faces need to maintain the 1-ring of vertices so we // have to create an extra row of children faces around the // hole. HbrHalfedge * e = f->GetFirstEdge(); for (int i=0; iGetNumVertices(); ++i) { assert(e); if ((not e->IsBoundary()) and (not e->GetOpposite()->GetFace()->IsHole())) { // RefineFaceAtVertex only creates a single child face // centered on the passed vertex HbrSubdivision * s = mesh->GetSubdivision(); s->RefineFaceAtVertex(mesh,f,e->GetOrgVertex()); s->RefineFaceAtVertex(mesh,f,e->GetDestVertex()); } e = e->GetNext(); } } } } // Hbr allocates faces sequentially, so there is no need to iterate over // faces that have already been refined. firstface = nfaces; } } // Scan the faces of a mesh and compute the max level of subdivision required template int FarMeshFactory::computeAdaptiveMaxLevel( HbrMesh * mesh, int nfaces, int maxIsolate ) { assert(mesh); int editmax=0; float sharpmax=0.0f; for( unsigned int i=0 ; i<(unsigned int)nfaces ; ++i ) { HbrFace * f = mesh->GetFace(i); assert( f->IsCoarse() ); // Check for edits if (f->HasVertexEdits()) { HbrVertexEdit ** edits = (HbrVertexEdit**)f->GetHierarchicalEdits(); while (HbrVertexEdit * edit = *edits++) editmax = std::max( editmax , edit->GetNSubfaces() ); } // Check for sharpness for (int j=0; jGetNumVertices(); ++j) { HbrHalfedge * e = f->GetEdge(j); if (not e->IsBoundary()) sharpmax = std::max( sharpmax, f->GetEdge(j)->GetSharpness() ); HbrVertex * v = f->GetVertex(j); if (not v->OnBoundary()) sharpmax = std::max( sharpmax, f->GetVertex(j)->GetSharpness() ); } } int maxlevel = std::max( maxIsolate+1, (int)ceil(sharpmax)+1 ); maxlevel = std::max( maxlevel, editmax+1 ); maxlevel = std::min( maxlevel, (int)HbrHalfedge::k_InfinitelySharp ); return maxlevel; } // True if a vertex is a regular boundary template bool FarMeshFactory::vertexIsRegularBoundary( HbrVertex * v ) { int valence = v->GetValence(); return (v->OnBoundary() and (valence==2 or valence==3)); } // True if the vertex can be incorporated into a B-spline patch template bool FarMeshFactory::vertexIsBSpline( HbrVertex * v, bool next ) { int valence = v->GetValence(); bool isRegBoundary = v->OnBoundary() and (valence==3); // Extraordinary vertices that are not on a regular boundary if (v->IsExtraordinary() and not isRegBoundary ) return false; // Irregular boundary vertices (high valence) if (v->OnBoundary() and (valence>3)) return false; // Creased vertices that aren't corner / boundaries if (v->IsSharp(next) and not v->OnBoundary()) return false; return true; } // Calls Hbr to refines the neighbors of v template void FarMeshFactory::refineVertexNeighbors(HbrVertex * v) { assert(v); HbrHalfedge * start = v->GetIncidentEdge(), * next=start; do { if (next->GetRightFace()) next->GetRightFace()->_adaptiveFlags.isTagged=true; if (next->GetLeftFace()) next->GetLeftFace()->_adaptiveFlags.isTagged=true; HbrHalfedge * istart = next, * inext = istart; do { inext->GetOrgVertex()->Refine(); inext = inext->GetNext(); } while (istart != inext); next = v->GetNextEdge( next ); } while (next and next!=start); } // XXXX manuelk : std::sets are slow. // with the "isTagged" flag on verts we can prob. ditch verts / nextverts !!! template struct VertCompare { bool operator()(HbrVertex const * v1, HbrVertex const * v2 ) const { //return v1->GetID() < v2->GetID(); return (void*)(v1) < (void*)(v2); } }; // Refines an Hbr mesh adaptively around extraordinary features template int FarMeshFactory::refineAdaptive( HbrMesh * mesh, int maxIsolate ) { int ncoarsefaces = mesh->GetNumCoarseFaces(), ncoarseverts = mesh->GetNumVertices(); // XXX manuelk : disabling guesstimate of the max. isolate level for now //int maxlevel = computeAdaptiveMaxLevel(mesh, ncoarsefaces, maxIsolate); int maxlevel = maxIsolate+1; // First pass : tag coarse vertices & faces that need refinement typedef std::set *,VertCompare > VertSet; VertSet verts, nextverts; for (int i=0; i * v = mesh->GetVertex(i); // Tag non-BSpline vertices for refinement if (not vertexIsBSpline(v, false)) { v->_adaptiveFlags.isTagged=true; nextverts.insert(v); } } for (int i=0; i * f = mesh->GetFace(i); for (int j=0; jGetNumVertices(); ++j) { HbrHalfedge * e = f->GetEdge(j); assert(e); // Tag sharp edges for refinement if (e->IsSharp(true) and (not e->IsBoundary())) { nextverts.insert(e->GetOrgVertex()); nextverts.insert(e->GetDestVertex()); e->GetOrgVertex()->_adaptiveFlags.isTagged=true; e->GetDestVertex()->_adaptiveFlags.isTagged=true; } // Tag extraordinary (non-quad) faces for refinement if (mesh->GetSubdivision()->FaceIsExtraordinary(mesh,f) or f->HasVertexEdits()) { HbrVertex * v = f->GetVertex(j); v->_adaptiveFlags.isTagged=true; nextverts.insert(v); } } } // Second pass : refine adaptively around singularities for (int level=0; level * v = *i; assert(v); if (level>0) v->_adaptiveFlags.isTagged=true; else v->_adaptiveFlags.wasTagged=true; refineVertexNeighbors(v); // Tag non-BSpline vertices for refinement if (not vertexIsBSpline(v, true)) nextverts.insert(v->Subdivide()); // Refine edges with creases or edits int valence = v->GetValence(); _maxValence = std::max(_maxValence, valence); HbrHalfedge * e = v->GetIncidentEdge(); for (int j=0; jIsSharp(false) and (not e->IsBoundary())) { nextverts.insert( e->Subdivide() ); nextverts.insert( e->GetOrgVertex()->Subdivide() ); nextverts.insert( e->GetDestVertex()->Subdivide() ); } HbrHalfedge * next = v->GetNextEdge(e); e = next ? next : e->GetPrev(); } // Flag verts with hierarchical edits for neighbor refinement at the next level HbrVertex * childvert = v->Subdivide(); HbrHalfedge * childedge = childvert->GetIncidentEdge(); assert( childvert->GetValence()==valence); for (int j=0; j * f = childedge->GetFace(); if (f->HasVertexEdits()) { int nv = f->GetNumVertices(); for (int k=0; kGetVertex(k) ); } if (not (childedge = childvert->GetNextEdge(childedge))) break; } } // Add coarse verts from extraordinary faces if (level==0) { for (int i=0; i * f = mesh->GetFace(i); assert (f->IsCoarse()); if (mesh->GetSubdivision()->FaceIsExtraordinary(mesh,f)) nextverts.insert( f->Subdivide() ); } } } return maxlevel-1; } // Assumption : the order of the vertices in the HbrMesh could be set in any // random order, so the builder runs 2 passes over the entire vertex list to // gather the counters needed to generate the indexing tables. template FarMeshFactory::FarMeshFactory( HbrMesh * mesh, int maxlevel, bool adaptive ) : _hbrMesh(mesh), _adaptive(adaptive), _maxlevel(maxlevel), _numVertices(-1), _numCoarseVertices(-1), _numFaces(-1), _maxValence(4), _facesList(maxlevel+1) { _numCoarseVertices = mesh->GetNumVertices(); // Subdivide the Hbr mesh up to maxlevel. // // Note : using a placeholder vertex class 'T' can greatly speed up the // topological analysis if the interpolation results are not used. if (adaptive) _maxlevel=refineAdaptive( mesh, maxlevel ); else refine( mesh, maxlevel); _numFaces = mesh->GetNumFaces(); _numVertices = mesh->GetNumVertices(); if (not adaptive) { // Populate the face lists int fsize=0; for (int i=0; i<_numFaces; ++i) { HbrFace * f = mesh->GetFace(i); assert(f); if (f->GetDepth()==0 and (not f->IsHole())) fsize += mesh->GetSubdivision()->GetFaceChildrenCount( f->GetNumVertices() ); } _facesList[0].reserve(mesh->GetNumCoarseFaces()); _facesList[1].reserve(fsize); for (int l=2; l<=maxlevel; ++l) _facesList[l].reserve( _facesList[l-1].capacity()*4 ); for (int i=0; i<_numFaces; ++i) { HbrFace * f = mesh->GetFace(i); if (f->GetDepth()<=maxlevel and (not f->IsHole())) _facesList[ f->GetDepth() ].push_back(f); } } } template bool FarMeshFactory::isBilinear(HbrMesh const * mesh) { return typeid(*(mesh->GetSubdivision()))==typeid(HbrBilinearSubdivision); } template bool FarMeshFactory::isCatmark(HbrMesh const * mesh) { return typeid(*(mesh->GetSubdivision()))==typeid(HbrCatmarkSubdivision); } template bool FarMeshFactory::isLoop(HbrMesh const * mesh) { return typeid(*(mesh->GetSubdivision()))==typeid(HbrLoopSubdivision); } template void FarMeshFactory::generateQuadsTopology( std::vector & vec, int level ) { assert( GetHbrMesh() ); int nv=-1; if ( isCatmark(GetHbrMesh()) or isBilinear(GetHbrMesh()) ) nv=4; else if ( isLoop(GetHbrMesh()) ) nv=3; assert(nv>0); vec.resize( nv * _facesList[level].size(), -1 ); for (int i=0; i<(int)_facesList[level].size(); ++i) { HbrFace * f = _facesList[level][i]; assert( f and f->GetNumVertices()==nv); for (int j=0; jGetNumVertices(); ++j) vec[nv*i+j]=_remapTable[f->GetVertex(j)->GetID()]; } } template void copyVertex( T & dest, U const & src ) { } template void copyVertex( T & dest, T const & src ) { dest = src; } // Computes per-face or per-patch local ptex texture coordinates. // // int 1 : // Adaptive : [face index (13 bits)] [rotation (2bits)] [non-quad (1bit)] // // Non-adaptive : [non-quad (sign bit)] [face index)] // int 2 : // [ u (16 bits) ] [ v (16 bits) ] // template int * computePtexCoordinate(HbrFace const *f, int *coord, bool isAdaptive) { short u,v; unsigned short ofs = 1, depth; bool nonquad = false; if (coord == NULL) return NULL; int rots = f->_adaptiveFlags.rots; // track upwards towards coarse parent face, accumulating u,v indices HbrFace const * p = f->GetParent(); for ( u=v=depth=0; p!=NULL; depth++ ) { int nverts = p->GetNumVertices(); if ( nverts != 4 ) { // non-quad coarse face : stop accumulating offsets nonquad = true; // set non-quad bit break; } for (unsigned char i=0; iGetChild( i )==f ) { switch ( i ) { case 0 : break; case 1 : { u+=ofs; } break; case 2 : { u+=ofs; v+=ofs; } break; case 3 : { v+=ofs; } break; } break; } } ofs = ofs << 1; f = p; p = f->GetParent(); } // bit0 : non-quad bit // bit1,2 : rotation bit if (isAdaptive) coord[0] = (f->GetPtexIndex() << 3) | (rots << 1) | (nonquad ? 1 : 0); else coord[0] = nonquad ? -f->GetPtexIndex() : f->GetPtexIndex(); coord[1] = (int)u << 16; coord[1] += v; return coord+2; } // This currently only supports the Catmark / Bilinear schemes. Loop template void FarMeshFactory::generatePtexCoordinates( std::vector & vec, int level ) { assert( _hbrMesh ); if (_facesList[0].empty() or _facesList[level][0]->GetPtexIndex() == -1) return; vec.resize( _facesList[level].size()*2, -1 ); int *p = &vec[0]; for (int i=0; i<(int)_facesList[level].size(); ++i) { HbrFace const * f = _facesList[level][i]; assert(f); p = computePtexCoordinate(f, p, /*isAdaptive=*/false); } } template float * computeFVarData(HbrFace const *f, const int width, float *coord, bool isAdaptive) { if (coord == NULL) return NULL; if (isAdaptive) { int rots = f->_adaptiveFlags.rots; int nverts = f->GetNumVertices(); assert(nverts==4); for ( int j=0; j < nverts; ++j ) { HbrVertex *v = f->GetVertex((j+rots)%4); float *fvdata = v->GetFVarData(f).GetData(0); for ( int k=0; kGetNumVertices(); for ( int j=0; j < nverts; ++j ) { HbrVertex *v = f->GetVertex(j); float *fvdata = v->GetFVarData(f).GetData(0); for ( int k=0; k void FarMeshFactory::generateFVarData( std::vector & vec, int level ) { assert( _hbrMesh ); if (_facesList[0].empty()) return; // initialize coordinate vector: numFaces*4verts*numFVarDatumPerVert int totalFVarWidth = _hbrMesh->GetTotalFVarWidth(); vec.resize( _facesList[level].size()*4 * totalFVarWidth, -1.0 ); // pointer will be advanced through vector as we go through faces float *p = &vec[0]; for (int i=0; i<(int)_facesList[level].size(); ++i) { HbrFace const * f = _facesList[level][i]; assert(f); p = computeFVarData(f, totalFVarWidth, p, /*isAdaptive=*/false); } } template FarMesh * FarMeshFactory::Create( bool requirePtexCoordinate, // XXX yuck. bool requireFVarData ) { assert( GetHbrMesh() ); // Note : we cannot create a Far rep of level 0 (coarse mesh) if (GetMaxLevel()<1) return 0; FarMesh * result = new FarMesh(); if ( isBilinear( GetHbrMesh() ) ) { result->_subdivisionTables = FarBilinearSubdivisionTablesFactory::Create(this, result); } else if ( isCatmark( GetHbrMesh() ) ) { result->_subdivisionTables = FarCatmarkSubdivisionTablesFactory::Create(this, result); } else if ( isLoop(GetHbrMesh()) ) { result->_subdivisionTables = FarLoopSubdivisionTablesFactory::Create(this, result); } else assert(0); assert(result->_subdivisionTables); const_cast *>(result->GetSubdivisionTables())->_numCoarseVertices=GetNumCoarseVertices(); // If the vertex classes aren't place-holders, copy the data of the coarse // vertices into the vertex buffer. result->_vertices.resize( _numVertices ); if (sizeof(U)>1) { for (int i=0; i_vertices[i], GetHbrMesh()->GetVertex(i)->GetData()); } // Create the element indices tables (patches for adaptive, quads for non-adaptive) if (isAdaptive()) { FarPatchTablesFactory factory(GetHbrMesh(), _numFaces, _remapTable); // XXXX: currently PatchGregory shader supports up to 29 valence result->_patchTables = factory.Create(GetMaxLevel()+1, _maxValence, requirePtexCoordinate, requireFVarData); assert( result->_patchTables ); if (requireFVarData) { result->_totalFVarWidth = _hbrMesh->GetTotalFVarWidth(); } } else { // XXXX : we should let the client control what to copy, most of this may be irrelevant result->_faceverts.resize(GetMaxLevel()+1); for (int l=1; l<=GetMaxLevel(); ++l) generateQuadsTopology(result->_faceverts[l], l); if (requirePtexCoordinate) { // Generate Ptex coordinates result->_ptexcoordinates.resize(GetMaxLevel()+1); for (int l=1; l<=GetMaxLevel(); ++l) generatePtexCoordinates(result->_ptexcoordinates[l], l); } if (requireFVarData) { // Generate fvar data result->_totalFVarWidth = _hbrMesh->GetTotalFVarWidth(); result->_fvarData.resize(GetMaxLevel()+1); for (int l=1; l<=GetMaxLevel(); ++l) generateFVarData(result->_fvarData[l], l); } } // Create VertexEditTables if necessary if (GetHbrMesh()->HasVertexEdits()) { result->_vertexEditTables = FarVertexEditTablesFactory::Create( this, result, GetMaxLevel() ); assert(result->_vertexEditTables); } return result; } template int FarMeshFactory::GetVertexID( HbrVertex * v ) { assert( v and (v->GetID() < _remapTable.size()) ); return _remapTable[ v->GetID() ]; } } // end namespace OPENSUBDIV_VERSION using namespace OPENSUBDIV_VERSION; } // end namespace OpenSubdiv #endif /* FAR_MESH_FACTORY_H */