bfr_tutorial_3_2.cpp
//------------------------------------------------------------------------------ // Tutorial description: // // This tutorial is a variation of tutorials showing simple uniform // tessellation. Rather than constructing and evaluating a Surface at // a time, this tutorial shows how Surfaces can be created and saved // for repeated use. // // A simple SurfaceCache class is created that creates and stores the // Surface for each face, along with the patch points associated with // it. The main tessellation function remains essentially the same, // but here it access the Surfaces from the SurfaceCache rather than // computing them locally. // // Note that while this example illustrated the retention of all // Surfaces for a mesh, this behavior is not recommended. It does not // scale well for large meshes and undermines the memory savings that // transient use of Surfaces is designed to achieve. Rather than // storing Surfaces for all faces, maintaining a priority queue for a // fixed number may be a reasonable compromise. // #include <opensubdiv/far/topologyRefiner.h> #include <opensubdiv/bfr/refinerSurfaceFactory.h> #include <opensubdiv/bfr/surface.h> #include <opensubdiv/bfr/tessellation.h> #include <vector> #include <memory> #include <string> #include <cstring> #include <cstdio> // Local headers with support for this tutorial in "namespace tutorial" #include "./meshLoader.h" #include "./objWriter.h" using namespace OpenSubdiv; // // Simple command line arguments to provide input and run-time options: // class Args { public: std::string inputObjFile; std::string outputObjFile; Sdc::SchemeType schemeType; int tessUniformRate; bool tessQuadsFlag; public: Args(int argc, char * argv[]) : inputObjFile(), outputObjFile(), schemeType(Sdc::SCHEME_CATMARK), tessUniformRate(5), tessQuadsFlag(false) { for (int i = 1; i < argc; ++i) { if (strstr(argv[i], ".obj")) { if (inputObjFile.empty()) { inputObjFile = std::string(argv[i]); } else { fprintf(stderr, "Warning: Extra Obj file '%s' ignored\n", argv[i]); } } else if (!strcmp(argv[i], "-o")) { if (++i < argc) outputObjFile = std::string(argv[i]); } else if (!strcmp(argv[i], "-bilinear")) { schemeType = Sdc::SCHEME_BILINEAR; } else if (!strcmp(argv[i], "-catmark")) { schemeType = Sdc::SCHEME_CATMARK; } else if (!strcmp(argv[i], "-loop")) { schemeType = Sdc::SCHEME_LOOP; } else if (!strcmp(argv[i], "-res")) { if (++i < argc) tessUniformRate = atoi(argv[i]); } else if (!strcmp(argv[i], "-quads")) { tessQuadsFlag = true; } else { fprintf(stderr, "Warning: Unrecognized argument '%s' ignored\n", argv[i]); } } } private: Args() { } }; // // This simple class creates and dispenses Surfaces for all faces of // a mesh. It consists primarily of an array of simple structs (entries) // for each face and a single array of patch points for all Surfaces // created. // // There are many ways to create such a cache depending on requirements. // This is a simple example, but the interface presents some options that // are worth considering. A SurfaceCache is constructed here given the // following: // // - a reference to the SurfaceFactory: // - the cache could just as easily take a reference to the mesh // and construct the SurfaceFactory internally // // - the position data for the mesh: // - this is needed to compute patch points for the Surfaces // - if caching UVs or any other primvar, other data needs to be // provided -- along with the interpolation type for that data // (vertex, face-varying, etc.) // // - option to "cache patch points": // - the cache could store the Surfaces only or also include // their patch points // - storing patch points takes more memory but will eliminate // any preparation time for evaluation of the Surface // // - option to "cache all surfaces": // - the benefits to caching simple linear or regular surfaces // are minimal -- and may even be detrimental // - so only caching non-linear irregular surfaces is an option // worth considering // // The SurfaceCache implementation here provides the options noted above. // But for simplicity, the actual usage of the SurfaceCache does not deal // with the permutations of additional work that is necessary when the // Surfaces or their patch points are not cached. // class SurfaceCache { public: typedef Bfr::Surface<float> Surface; typedef Bfr::RefinerSurfaceFactory<> SurfaceFactory; public: SurfaceCache(SurfaceFactory const & surfaceFactory, std::vector<float> const & meshPoints, bool cachePatchPoints = true, bool cacheAllSurfaces = true); SurfaceCache() = delete; ~SurfaceCache() = default; // // Public methods to retrieved cached Surfaces and their pre-computed // patch points: // bool FaceHasLimitSurface(int face) { return _entries[face].hasLimit; } Surface const * GetSurface(int face) { return _entries[face].surface.get();} float const * GetPatchPoints(int face) { return getPatchPoints(face); } private: // Simple struct to keep track of Surface and more for each face: struct FaceEntry { FaceEntry() : surface(), hasLimit(false), pointOffset(-1) { } std::unique_ptr<Surface const> surface; bool hasLimit; int pointOffset; }; // Non-const version to be used internally to aide assignment: float * getPatchPoints(int face) { return (_entries[face].surface && !_points.empty()) ? (_points.data() + _entries[face].pointOffset * 3) : 0; } private: std::vector<FaceEntry> _entries; std::vector<float> _points; }; SurfaceCache::SurfaceCache(SurfaceFactory const & surfaceFactory, std::vector<float> const & meshPoints, bool cachePatchPoints, bool cacheAllSurfaces) { int numFaces = surfaceFactory.GetNumFaces(); _entries.resize(numFaces); int numPointsInCache = 0; for (int face = 0; face < numFaces; ++face) { Surface * s = surfaceFactory.CreateVertexSurface<float>(face); if (s) { FaceEntry & entry = _entries[face]; entry.hasLimit = true; if (cacheAllSurfaces || (!s->IsRegular() && !s->IsLinear())) { entry.surface.reset(s); entry.pointOffset = numPointsInCache; numPointsInCache += s->GetNumPatchPoints(); } else { delete s; } } } if (cachePatchPoints) { _points.resize(numPointsInCache * 3); for (int face = 0; face < numFaces; ++face) { float * patchPoints = getPatchPoints(face); if (patchPoints) { GetSurface(face)->PreparePatchPoints(meshPoints.data(), 3, patchPoints, 3); } } } } // // The main tessellation function: given a mesh and vertex positions, // tessellate each face -- writing results in Obj format. // void tessellateToObj(Far::TopologyRefiner const & meshTopology, std::vector<float> const & meshVertexPositions, Args const & options) { // // Use simpler local type names for the Surface and its factory: // typedef Bfr::RefinerSurfaceFactory<> SurfaceFactory; typedef Bfr::Surface<float> Surface; // // Initialize the SurfaceFactory for the given base mesh (very low // cost in terms of both time and space) and tessellate each face // independently (i.e. no shared vertices): // // Note that the SurfaceFactory is not thread-safe by default due to // use of an internal cache. Creating a separate instance of the // SurfaceFactory for each thread is one way to safely parallelize // this loop. Another (preferred) is to assign a thread-safe cache // to the single instance. // // First declare any evaluation options when initializing (though // none are used in this simple case): // SurfaceFactory::Options surfaceOptions; SurfaceFactory meshSurfaceFactory(meshTopology, surfaceOptions); // // Initialize a SurfaceCache to construct Surfaces for all faces. // From this point forward the SurfaceFactory is no longer used to // access Surfaces. Note also that usage below is specific to the // options used to initialize the SurfaceCache: // bool cachePatchPoints = true; bool cacheAllSurfaces = true; SurfaceCache surfaceCache(meshSurfaceFactory, meshVertexPositions, cachePatchPoints, cacheAllSurfaces); // // As with previous tutorials, output data associated with the face // can be declared in the scope local to each face. But since dynamic // memory is involved with these variables, it is preferred to declare // them outside that loop to preserve and reuse that dynamic memory. // std::vector<float> outCoords; std::vector<float> outPos, outDu, outDv; std::vector<int> outFacets; // // Assign Tessellation Options applied for all faces. Tessellations // allow the creating of either 3- or 4-sided faces -- both of which // are supported here via a command line option: // int const tessFacetSize = 3 + options.tessQuadsFlag; Bfr::Tessellation::Options tessOptions; tessOptions.SetFacetSize(tessFacetSize); tessOptions.PreserveQuads(options.tessQuadsFlag); // // Process each face, writing the output of each in Obj format: // tutorial::ObjWriter objWriter(options.outputObjFile); int numFaces = meshSurfaceFactory.GetNumFaces(); for (int faceIndex = 0; faceIndex < numFaces; ++faceIndex) { // // Retrieve the Surface for this face when present: // if (!surfaceCache.FaceHasLimitSurface(faceIndex)) continue; Surface const & faceSurface = * surfaceCache.GetSurface(faceIndex); // // Declare a simple uniform Tessellation for the Parameterization // of this face and identify coordinates of the points to evaluate: // Bfr::Tessellation tessPattern(faceSurface.GetParameterization(), options.tessUniformRate, tessOptions); int numOutCoords = tessPattern.GetNumCoords(); outCoords.resize(numOutCoords * 2); tessPattern.GetCoords(outCoords.data()); // // Retrieve the patch points for the Surface, then use them to // evaluate output points for all identified coordinates: // float const * facePatchPoints = surfaceCache.GetPatchPoints(faceIndex); int pointSize = 3; outPos.resize(numOutCoords * pointSize); outDu.resize(numOutCoords * pointSize); outDv.resize(numOutCoords * pointSize); for (int i = 0, j = 0; i < numOutCoords; ++i, j += pointSize) { faceSurface.Evaluate(&outCoords[i*2], facePatchPoints, pointSize, &outPos[j], &outDu[j], &outDv[j]); } // // Identify the faces of the Tessellation: // // Note the need to offset vertex indices for the output faces -- // using the number of vertices generated prior to this face. One // of several Tessellation methods to transform the facet indices // simply translates all indices by the desired offset. // int objVertexIndexOffset = objWriter.GetNumVertices(); int numFacets = tessPattern.GetNumFacets(); outFacets.resize(numFacets * tessFacetSize); tessPattern.GetFacets(outFacets.data()); tessPattern.TransformFacetCoordIndices(outFacets.data(), objVertexIndexOffset); // // Write the evaluated points and faces connecting them as Obj: // objWriter.WriteGroupName("baseFace_", faceIndex); objWriter.WriteVertexPositions(outPos); objWriter.WriteVertexNormals(outDu, outDv); objWriter.WriteFaces(outFacets, tessFacetSize, true, false); } } // // Load command line arguments, specified or default geometry and process: // int main(int argc, char * argv[]) { Args args(argc, argv); Far::TopologyRefiner * meshTopology = 0; std::vector<float> meshVtxPositions; std::vector<float> meshFVarUVs; meshTopology = tutorial::createTopologyRefiner( args.inputObjFile, args.schemeType, meshVtxPositions, meshFVarUVs); if (meshTopology == 0) { return EXIT_FAILURE; } tessellateToObj(*meshTopology, meshVtxPositions, args); delete meshTopology; return EXIT_SUCCESS; } //------------------------------------------------------------------------------