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,27 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
include_directories(
"${OPENSUBDIV_INCLUDE_DIR}/"
)
add_subdirectory(hbr)
add_subdirectory(far)
add_subdirectory(bfr)
add_subdirectory(osd)
add_custom_target(tutorials
DEPENDS
hbr_tutorials
far_tutorials
bfr_tutorials
osd_tutorials
)

View File

@@ -0,0 +1,50 @@
#
# Copyright 2021 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
macro(osd_add_bfr_tutorial NAME)
osd_add_executable(${NAME} "tutorials/bfr"
${ARGN}
$<TARGET_OBJECTS:sdc_obj>
$<TARGET_OBJECTS:vtr_obj>
$<TARGET_OBJECTS:far_obj>
$<TARGET_OBJECTS:bfr_obj>
$<TARGET_OBJECTS:regression_common_obj>
)
install(TARGETS ${NAME} DESTINATION "${CMAKE_BINDIR_BASE}/tutorials")
endmacro()
set(TUTORIALS
tutorial_1_1
tutorial_1_2
tutorial_1_3
tutorial_1_4
tutorial_1_5
tutorial_2_1
tutorial_2_2
tutorial_3_1
tutorial_3_2
)
foreach(tutorial ${TUTORIALS})
add_subdirectory("${tutorial}")
list(APPEND TUTORIAL_TARGETS "bfr_${tutorial}")
add_test(bfr_${tutorial} ${EXECUTABLE_OUTPUT_PATH}/bfr_${tutorial})
endforeach()
add_custom_target(bfr_tutorials DEPENDS ${TUTORIAL_TARGETS})
set_target_properties(bfr_tutorials
PROPERTIES
FOLDER "tutorials/bfr"
)

View File

@@ -0,0 +1,11 @@
#
# Copyright 2021 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_bfr_tutorial(
bfr_tutorial_1_1
bfr_tutorial_1_1.cpp
)

View File

@@ -0,0 +1,242 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial illustrates the use of the SurfaceFactory, Surface
// and Parameterization classes for creating and evaluating the limit
// surface associated with each base face of a mesh.
//
// Following the creation of a connected mesh for a shape (using a
// Far::TopologyRefiner, as illustrated in Far tutorials), an instance
// of a SurfaceFactory is declared to process its faces. Each face of
// the mesh is evaluated and tessellated independently (with a simple
// triangle fan), with results written out in Obj format for inspection.
//
// These classes make it simple to evaluate and tessellate all faces
// (quads, tris or others) while supporting the full set of subdivision
// options. While a triangle fan may be a trivial tessellation (and so
// not very useful) later examples using the Tessellation class provide
// more useful results with the same simplicity.
//
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/bfr/refinerSurfaceFactory.h>
#include <opensubdiv/bfr/surface.h>
#include <vector>
#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;
public:
Args(int argc, char * argv[]) :
inputObjFile(),
outputObjFile(),
schemeType(Sdc::SCHEME_CATMARK) {
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 {
fprintf(stderr,
"Warning: Unrecognized argument '%s' ignored\n", argv[i]);
}
}
}
private:
Args() { }
};
//
// 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);
//
// The Surface to be constructed and evaluated for each face -- as
// well as the intermediate and output data associated with it -- 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.
//
Surface faceSurface;
std::vector<float> facePatchPoints;
std::vector<float> outCoords;
std::vector<float> outPos, outDu, outDv;
std::vector<int> outTriangles;
//
// 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) {
//
// Initialize the Surface for this face -- if valid (skipping
// holes and boundary faces in some rare cases):
//
if (!meshSurfaceFactory.InitVertexSurface(faceIndex, &faceSurface)) {
continue;
}
//
// Get the Parameterization of the Surface and use it to identify
// coordinates for evaluation -- in this case, at the vertices
// and center of the face to create a fan of triangles:
//
Bfr::Parameterization faceParam = faceSurface.GetParameterization();
int faceSize = faceParam.GetFaceSize();
int numOutCoords = faceSize + 1;
outCoords.resize(numOutCoords * 2);
for (int i = 0; i < faceSize; ++i) {
faceParam.GetVertexCoord(i, &outCoords[i*2]);
}
faceParam.GetCenterCoord(&outCoords[faceSize*2]);
//
// Prepare the patch points for the Surface, then use them to
// evaluate output points for all identified coordinates:
//
// Resize patch point and output arrays:
int pointSize = 3;
facePatchPoints.resize(faceSurface.GetNumPatchPoints() * pointSize);
outPos.resize(numOutCoords * pointSize);
outDu.resize(numOutCoords * pointSize);
outDv.resize(numOutCoords * pointSize);
// Populate patch point and output arrays:
faceSurface.PreparePatchPoints(meshVertexPositions.data(), pointSize,
facePatchPoints.data(), pointSize);
for (int i = 0, j = 0; i < numOutCoords; ++i, j += pointSize) {
faceSurface.Evaluate(&outCoords[i*2],
facePatchPoints.data(), pointSize,
&outPos[j], &outDu[j], &outDv[j]);
}
//
// Identify the faces of the tessellation, i.e. the triangle fan
// connecting points at the vertices to the center (last) point:
//
// Note the need to offset vertex indices for the output faces --
// using the number of vertices generated prior to this face.
//
int objVertexIndexOffset = objWriter.GetNumVertices();
outTriangles.resize(faceSize * 3);
int * outTriangle = outTriangles.data();
for (int i = 0; i < faceSize; ++i, outTriangle += 3) {
outTriangle[0] = objVertexIndexOffset + i;
outTriangle[1] = objVertexIndexOffset + (i + 1) % faceSize;
outTriangle[2] = objVertexIndexOffset + faceSize;
}
//
// Write the evaluated points and faces connecting them as Obj:
//
objWriter.WriteGroupName("baseFace_", faceIndex);
objWriter.WriteVertexPositions(outPos);
objWriter.WriteVertexNormals(outDu, outDv);
objWriter.WriteFaces(outTriangles, 3, 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;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,192 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../../../regression/common/far_utils.h"
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/far/topologyDescriptor.h>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <sstream>
// Utilities local to this tutorial:
namespace tutorial {
using namespace OpenSubdiv;
//
// Create a TopologyRefiner from default geometry:
//
Far::TopologyRefiner *
dfltTopologyRefiner(std::vector<float> & posVector,
std::vector<float> & uvVector) {
//
// Default topology and positions for a cube:
//
int dfltNumFaces = 6;
int dfltNumVerts = 8;
int dfltNumUVs = 16;
int dfltFaceSizes[6] = { 4, 4, 4, 4, 4, 4 };
int dfltFaceVerts[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
float dfltPositions[8][3] = {{ -0.5f, -0.5f, 0.5f },
{ 0.5f, -0.5f, 0.5f },
{ -0.5f, 0.5f, 0.5f },
{ 0.5f, 0.5f, 0.5f },
{ -0.5f, 0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },
{ -0.5f, -0.5f, -0.5f },
{ 0.5f, -0.5f, -0.5f }};
int dfltFaceFVars[24] = { 9, 10, 14, 13,
4, 0, 1, 5,
5, 1, 2, 6,
6, 2, 3, 7,
10, 11, 15, 14,
8, 9, 13, 12 };
float dfltUVs[16][2] = {{ 0.05f, 0.05f },
{ 0.35f, 0.15f },
{ 0.65f, 0.15f },
{ 0.95f, 0.05f },
{ 0.05f, 0.35f },
{ 0.35f, 0.45f },
{ 0.65f, 0.45f },
{ 0.95f, 0.35f },
{ 0.05f, 0.65f },
{ 0.35f, 0.55f },
{ 0.65f, 0.55f },
{ 0.95f, 0.65f },
{ 0.05f, 0.95f },
{ 0.35f, 0.85f },
{ 0.65f, 0.85f },
{ 0.95f, 0.95f }};
posVector.resize(8 * 3);
std::memcpy(&posVector[0], dfltPositions, 8 * 3 * sizeof(float));
uvVector.resize(16 * 2);
std::memcpy(&uvVector[0], dfltUVs, 16 * 2 * sizeof(float));
//
// Initialize a Far::TopologyDescriptor, from which to create
// the Far::TopologyRefiner:
//
typedef Far::TopologyDescriptor Descriptor;
Descriptor::FVarChannel uvChannel;
uvChannel.numValues = dfltNumUVs;
uvChannel.valueIndices = dfltFaceFVars;
Descriptor topDescriptor;
topDescriptor.numVertices = dfltNumVerts;
topDescriptor.numFaces = dfltNumFaces;
topDescriptor.numVertsPerFace = dfltFaceSizes;
topDescriptor.vertIndicesPerFace = dfltFaceVerts;
topDescriptor.numFVarChannels = 1;
topDescriptor.fvarChannels = &uvChannel;
Sdc::SchemeType schemeType = Sdc::SCHEME_CATMARK;
Sdc::Options schemeOptions;
schemeOptions.SetVtxBoundaryInterpolation(
Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
schemeOptions.SetFVarLinearInterpolation(
Sdc::Options::FVAR_LINEAR_CORNERS_ONLY);
typedef Far::TopologyRefinerFactory<Descriptor> RefinerFactory;
Far::TopologyRefiner * topRefiner =
RefinerFactory::Create(topDescriptor,
RefinerFactory::Options(schemeType, schemeOptions));
assert(topRefiner);
return topRefiner;
}
//
// Create a TopologyRefiner from a specified Obj file:
//
Far::TopologyRefiner *
readTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
const char * filename = objFileName.c_str();
const Shape * shape = 0;
std::ifstream ifs(filename);
if (ifs) {
std::stringstream ss;
ss << ifs.rdbuf();
ifs.close();
std::string shapeString = ss.str();
shape = Shape::parseObj(
shapeString.c_str(), ConvertSdcTypeToShapeScheme(schemeType), false);
if (shape == 0) {
fprintf(stderr,
"Error: Cannot create Shape from Obj file '%s'\n", filename);
return 0;
}
} else {
fprintf(stderr, "Error: Cannot open Obj file '%s'\n", filename);
return 0;
}
Sdc::SchemeType sdcType = GetSdcType(*shape);
Sdc::Options sdcOptions = GetSdcOptions(*shape);
Far::TopologyRefiner * refiner = Far::TopologyRefinerFactory<Shape>::Create(
*shape, Far::TopologyRefinerFactory<Shape>::Options(sdcType, sdcOptions));
if (refiner == 0) {
fprintf(stderr,
"Error: Unable to construct TopologyRefiner from Obj file '%s'\n",
filename);
return 0;
}
int numVertices = refiner->GetNumVerticesTotal();
posVector.resize(numVertices * 3);
std::memcpy(&posVector[0], &shape->verts[0], 3*numVertices*sizeof(float));
uvVector.resize(0);
if (refiner->GetNumFVarChannels()) {
int numUVs = refiner->GetNumFVarValuesTotal(0);
uvVector.resize(numUVs * 2);
std::memcpy(&uvVector[0], &shape->uvs[0], 2 * numUVs*sizeof(float));
}
delete shape;
return refiner;
}
Far::TopologyRefiner *
createTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
if (objFileName.empty()) {
return dfltTopologyRefiner(posVector, uvVector);
} else {
return readTopologyRefiner(objFileName, schemeType,
posVector, uvVector);
}
}
} // end namespace

View File

@@ -0,0 +1,176 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include <string>
#include <vector>
#include <cstdio>
#include <cmath>
#include <cassert>
// Utilities local to this tutorial:
namespace tutorial {
//
// Simple class to write vertex positions, normals and faces to a
// specified Obj file:
//
class ObjWriter {
public:
ObjWriter(std::string const &filename = 0);
~ObjWriter();
int GetNumVertices() const { return _numVertices; }
int GetNumFaces() const { return _numFaces; }
void WriteVertexPositions(std::vector<float> const & p, int size = 3);
void WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv);
void WriteVertexUVs(std::vector<float> const & uv);
void WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool writeNormalIndices = false,
bool writeUVIndices = false);
void WriteGroupName(char const * prefix, int index);
private:
void getNormal(float N[3], float const du[3], float const dv[3]) const;
private:
std::string _filename;
FILE * _fptr;
int _numVertices;
int _numNormals;
int _numUVs;
int _numFaces;
};
//
// Definitions ObjWriter methods:
//
ObjWriter::ObjWriter(std::string const &filename) :
_fptr(0), _numVertices(0), _numNormals(0), _numUVs(0), _numFaces(0) {
if (filename != std::string()) {
_fptr = fopen(filename.c_str(), "w");
if (_fptr == 0) {
fprintf(stderr, "Error: ObjWriter cannot open Obj file '%s'\n",
filename.c_str());
}
}
if (_fptr == 0) _fptr = stdout;
}
ObjWriter::~ObjWriter() {
if (_fptr != stdout) fclose(_fptr);
}
void
ObjWriter::WriteVertexPositions(std::vector<float> const & pos, int dim) {
assert(dim >= 2);
int numNewVerts = (int)pos.size() / dim;
float const * P = pos.data();
for (int i = 0; i < numNewVerts; ++i, P += dim) {
if (dim == 2) {
fprintf(_fptr, "v %f %f 0.0\n", P[0], P[1]);
} else {
fprintf(_fptr, "v %f %f %f\n", P[0], P[1], P[2]);
}
}
_numVertices += numNewVerts;
}
void
ObjWriter::getNormal(float N[3], float const du[3], float const dv[3]) const {
N[0] = du[1] * dv[2] - du[2] * dv[1];
N[1] = du[2] * dv[0] - du[0] * dv[2];
N[2] = du[0] * dv[1] - du[1] * dv[0];
float lenSqrd = N[0] * N[0] + N[1] * N[1] + N[2] * N[2];
if (lenSqrd <= 0.0f) {
N[0] = 0.0f;
N[1] = 0.0f;
N[2] = 0.0f;
} else {
float lenInv = 1.0f / std::sqrt(lenSqrd);
N[0] *= lenInv;
N[1] *= lenInv;
N[2] *= lenInv;
}
}
void
ObjWriter::WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv) {
assert(du.size() == dv.size());
int numNewNormals = (int)du.size() / 3;
float const * dPdu = &du[0];
float const * dPdv = &dv[0];
for (int i = 0; i < numNewNormals; ++i, dPdu += 3, dPdv += 3) {
float N[3];
getNormal(N, dPdu, dPdv);
fprintf(_fptr, "vn %f %f %f\n", N[0], N[1], N[2]);
}
_numNormals += numNewNormals;
}
void
ObjWriter::WriteVertexUVs(std::vector<float> const & uv) {
int numNewUVs = (int)uv.size() / 2;
for (int i = 0; i < numNewUVs; ++i) {
fprintf(_fptr, "vt %f %f\n", uv[i*2], uv[i*2+1]);
}
_numUVs += numNewUVs;
}
void
ObjWriter::WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool includeNormalIndices, bool includeUVIndices) {
int numNewFaces = (int)faceVertices.size() / faceSize;
int const * v = &faceVertices[0];
for (int i = 0; i < numNewFaces; ++i, v += faceSize) {
fprintf(_fptr, "f ");
for (int j = 0; j < faceSize; ++j) {
if (v[j] >= 0) {
// Remember Obj indices start with 1:
int vIndex = 1 + v[j];
if (includeNormalIndices && includeUVIndices) {
fprintf(_fptr, " %d/%d/%d", vIndex, vIndex, vIndex);
} else if (includeNormalIndices) {
fprintf(_fptr, " %d//%d", vIndex, vIndex);
} else if (includeUVIndices) {
fprintf(_fptr, " %d/%d", vIndex, vIndex);
} else {
fprintf(_fptr, " %d", vIndex);
}
}
}
fprintf(_fptr, "\n");
}
_numFaces += numNewFaces;
}
void
ObjWriter::WriteGroupName(char const * prefix, int index) {
fprintf(_fptr, "g %s%d\n", prefix ? prefix : "", index);
}
} // end namespace

View File

@@ -0,0 +1,11 @@
#
# Copyright 2021 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_bfr_tutorial(
bfr_tutorial_1_2
bfr_tutorial_1_2.cpp
)

View File

@@ -0,0 +1,252 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial builds on the previous tutorial that makes use of the
// SurfaceFactory and Surface for evaluating the limit surface of faces
// by using the Tessellation class to determine the points to evaluate
// and the faces that connect them.
//
// The Tessellation class replaces the explicit determination of points
// and faces for the triangle fan of the previous example. Given a
// uniform tessellation rate (via a command line option), Tessellation
// returns the set of coordinates to evaluate, and separately returns
// the faces that connect them.
//
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/bfr/refinerSurfaceFactory.h>
#include <opensubdiv/bfr/surface.h>
#include <opensubdiv/bfr/tessellation.h>
#include <vector>
#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() { }
};
//
// 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);
//
// The Surface to be constructed and evaluated for each face -- as
// well as the intermediate and output data associated with it -- 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.
//
Surface faceSurface;
std::vector<float> facePatchPoints;
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) {
//
// Initialize the Surface for this face -- if valid (skipping
// holes and boundary faces in some rare cases):
//
if (!meshSurfaceFactory.InitVertexSurface(faceIndex, &faceSurface)) {
continue;
}
//
// 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());
//
// Prepare the patch points for the Surface, then use them to
// evaluate output points for all identified coordinates:
//
// Resize patch point and output arrays:
int pointSize = 3;
facePatchPoints.resize(faceSurface.GetNumPatchPoints() * pointSize);
outPos.resize(numOutCoords * pointSize);
outDu.resize(numOutCoords * pointSize);
outDv.resize(numOutCoords * pointSize);
// Populate patch point and output arrays:
faceSurface.PreparePatchPoints(meshVertexPositions.data(), pointSize,
facePatchPoints.data(), pointSize);
for (int i = 0, j = 0; i < numOutCoords; ++i, j += pointSize) {
faceSurface.Evaluate(&outCoords[i*2],
facePatchPoints.data(), 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;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,192 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../../../regression/common/far_utils.h"
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/far/topologyDescriptor.h>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <sstream>
// Utilities local to this tutorial:
namespace tutorial {
using namespace OpenSubdiv;
//
// Create a TopologyRefiner from default geometry:
//
Far::TopologyRefiner *
dfltTopologyRefiner(std::vector<float> & posVector,
std::vector<float> & uvVector) {
//
// Default topology and positions for a cube:
//
int dfltNumFaces = 6;
int dfltNumVerts = 8;
int dfltNumUVs = 16;
int dfltFaceSizes[6] = { 4, 4, 4, 4, 4, 4 };
int dfltFaceVerts[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
float dfltPositions[8][3] = {{ -0.5f, -0.5f, 0.5f },
{ 0.5f, -0.5f, 0.5f },
{ -0.5f, 0.5f, 0.5f },
{ 0.5f, 0.5f, 0.5f },
{ -0.5f, 0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },
{ -0.5f, -0.5f, -0.5f },
{ 0.5f, -0.5f, -0.5f }};
int dfltFaceFVars[24] = { 9, 10, 14, 13,
4, 0, 1, 5,
5, 1, 2, 6,
6, 2, 3, 7,
10, 11, 15, 14,
8, 9, 13, 12 };
float dfltUVs[16][2] = {{ 0.05f, 0.05f },
{ 0.35f, 0.15f },
{ 0.65f, 0.15f },
{ 0.95f, 0.05f },
{ 0.05f, 0.35f },
{ 0.35f, 0.45f },
{ 0.65f, 0.45f },
{ 0.95f, 0.35f },
{ 0.05f, 0.65f },
{ 0.35f, 0.55f },
{ 0.65f, 0.55f },
{ 0.95f, 0.65f },
{ 0.05f, 0.95f },
{ 0.35f, 0.85f },
{ 0.65f, 0.85f },
{ 0.95f, 0.95f }};
posVector.resize(8 * 3);
std::memcpy(&posVector[0], dfltPositions, 8 * 3 * sizeof(float));
uvVector.resize(16 * 2);
std::memcpy(&uvVector[0], dfltUVs, 16 * 2 * sizeof(float));
//
// Initialize a Far::TopologyDescriptor, from which to create
// the Far::TopologyRefiner:
//
typedef Far::TopologyDescriptor Descriptor;
Descriptor::FVarChannel uvChannel;
uvChannel.numValues = dfltNumUVs;
uvChannel.valueIndices = dfltFaceFVars;
Descriptor topDescriptor;
topDescriptor.numVertices = dfltNumVerts;
topDescriptor.numFaces = dfltNumFaces;
topDescriptor.numVertsPerFace = dfltFaceSizes;
topDescriptor.vertIndicesPerFace = dfltFaceVerts;
topDescriptor.numFVarChannels = 1;
topDescriptor.fvarChannels = &uvChannel;
Sdc::SchemeType schemeType = Sdc::SCHEME_CATMARK;
Sdc::Options schemeOptions;
schemeOptions.SetVtxBoundaryInterpolation(
Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
schemeOptions.SetFVarLinearInterpolation(
Sdc::Options::FVAR_LINEAR_CORNERS_ONLY);
typedef Far::TopologyRefinerFactory<Descriptor> RefinerFactory;
Far::TopologyRefiner * topRefiner =
RefinerFactory::Create(topDescriptor,
RefinerFactory::Options(schemeType, schemeOptions));
assert(topRefiner);
return topRefiner;
}
//
// Create a TopologyRefiner from a specified Obj file:
//
Far::TopologyRefiner *
readTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
const char * filename = objFileName.c_str();
const Shape * shape = 0;
std::ifstream ifs(filename);
if (ifs) {
std::stringstream ss;
ss << ifs.rdbuf();
ifs.close();
std::string shapeString = ss.str();
shape = Shape::parseObj(
shapeString.c_str(), ConvertSdcTypeToShapeScheme(schemeType), false);
if (shape == 0) {
fprintf(stderr,
"Error: Cannot create Shape from Obj file '%s'\n", filename);
return 0;
}
} else {
fprintf(stderr, "Error: Cannot open Obj file '%s'\n", filename);
return 0;
}
Sdc::SchemeType sdcType = GetSdcType(*shape);
Sdc::Options sdcOptions = GetSdcOptions(*shape);
Far::TopologyRefiner * refiner = Far::TopologyRefinerFactory<Shape>::Create(
*shape, Far::TopologyRefinerFactory<Shape>::Options(sdcType, sdcOptions));
if (refiner == 0) {
fprintf(stderr,
"Error: Unable to construct TopologyRefiner from Obj file '%s'\n",
filename);
return 0;
}
int numVertices = refiner->GetNumVerticesTotal();
posVector.resize(numVertices * 3);
std::memcpy(&posVector[0], &shape->verts[0], 3*numVertices*sizeof(float));
uvVector.resize(0);
if (refiner->GetNumFVarChannels()) {
int numUVs = refiner->GetNumFVarValuesTotal(0);
uvVector.resize(numUVs * 2);
std::memcpy(&uvVector[0], &shape->uvs[0], 2 * numUVs*sizeof(float));
}
delete shape;
return refiner;
}
Far::TopologyRefiner *
createTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
if (objFileName.empty()) {
return dfltTopologyRefiner(posVector, uvVector);
} else {
return readTopologyRefiner(objFileName, schemeType,
posVector, uvVector);
}
}
} // end namespace

View File

@@ -0,0 +1,176 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include <string>
#include <vector>
#include <cstdio>
#include <cmath>
#include <cassert>
// Utilities local to this tutorial:
namespace tutorial {
//
// Simple class to write vertex positions, normals and faces to a
// specified Obj file:
//
class ObjWriter {
public:
ObjWriter(std::string const &filename = 0);
~ObjWriter();
int GetNumVertices() const { return _numVertices; }
int GetNumFaces() const { return _numFaces; }
void WriteVertexPositions(std::vector<float> const & p, int size = 3);
void WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv);
void WriteVertexUVs(std::vector<float> const & uv);
void WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool writeNormalIndices = false,
bool writeUVIndices = false);
void WriteGroupName(char const * prefix, int index);
private:
void getNormal(float N[3], float const du[3], float const dv[3]) const;
private:
std::string _filename;
FILE * _fptr;
int _numVertices;
int _numNormals;
int _numUVs;
int _numFaces;
};
//
// Definitions ObjWriter methods:
//
ObjWriter::ObjWriter(std::string const &filename) :
_fptr(0), _numVertices(0), _numNormals(0), _numUVs(0), _numFaces(0) {
if (filename != std::string()) {
_fptr = fopen(filename.c_str(), "w");
if (_fptr == 0) {
fprintf(stderr, "Error: ObjWriter cannot open Obj file '%s'\n",
filename.c_str());
}
}
if (_fptr == 0) _fptr = stdout;
}
ObjWriter::~ObjWriter() {
if (_fptr != stdout) fclose(_fptr);
}
void
ObjWriter::WriteVertexPositions(std::vector<float> const & pos, int dim) {
assert(dim >= 2);
int numNewVerts = (int)pos.size() / dim;
float const * P = pos.data();
for (int i = 0; i < numNewVerts; ++i, P += dim) {
if (dim == 2) {
fprintf(_fptr, "v %f %f 0.0\n", P[0], P[1]);
} else {
fprintf(_fptr, "v %f %f %f\n", P[0], P[1], P[2]);
}
}
_numVertices += numNewVerts;
}
void
ObjWriter::getNormal(float N[3], float const du[3], float const dv[3]) const {
N[0] = du[1] * dv[2] - du[2] * dv[1];
N[1] = du[2] * dv[0] - du[0] * dv[2];
N[2] = du[0] * dv[1] - du[1] * dv[0];
float lenSqrd = N[0] * N[0] + N[1] * N[1] + N[2] * N[2];
if (lenSqrd <= 0.0f) {
N[0] = 0.0f;
N[1] = 0.0f;
N[2] = 0.0f;
} else {
float lenInv = 1.0f / std::sqrt(lenSqrd);
N[0] *= lenInv;
N[1] *= lenInv;
N[2] *= lenInv;
}
}
void
ObjWriter::WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv) {
assert(du.size() == dv.size());
int numNewNormals = (int)du.size() / 3;
float const * dPdu = &du[0];
float const * dPdv = &dv[0];
for (int i = 0; i < numNewNormals; ++i, dPdu += 3, dPdv += 3) {
float N[3];
getNormal(N, dPdu, dPdv);
fprintf(_fptr, "vn %f %f %f\n", N[0], N[1], N[2]);
}
_numNormals += numNewNormals;
}
void
ObjWriter::WriteVertexUVs(std::vector<float> const & uv) {
int numNewUVs = (int)uv.size() / 2;
for (int i = 0; i < numNewUVs; ++i) {
fprintf(_fptr, "vt %f %f\n", uv[i*2], uv[i*2+1]);
}
_numUVs += numNewUVs;
}
void
ObjWriter::WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool includeNormalIndices, bool includeUVIndices) {
int numNewFaces = (int)faceVertices.size() / faceSize;
int const * v = &faceVertices[0];
for (int i = 0; i < numNewFaces; ++i, v += faceSize) {
fprintf(_fptr, "f ");
for (int j = 0; j < faceSize; ++j) {
if (v[j] >= 0) {
// Remember Obj indices start with 1:
int vIndex = 1 + v[j];
if (includeNormalIndices && includeUVIndices) {
fprintf(_fptr, " %d/%d/%d", vIndex, vIndex, vIndex);
} else if (includeNormalIndices) {
fprintf(_fptr, " %d//%d", vIndex, vIndex);
} else if (includeUVIndices) {
fprintf(_fptr, " %d/%d", vIndex, vIndex);
} else {
fprintf(_fptr, " %d", vIndex);
}
}
}
fprintf(_fptr, "\n");
}
_numFaces += numNewFaces;
}
void
ObjWriter::WriteGroupName(char const * prefix, int index) {
fprintf(_fptr, "g %s%d\n", prefix ? prefix : "", index);
}
} // end namespace

View File

@@ -0,0 +1,11 @@
#
# Copyright 2021 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_bfr_tutorial(
bfr_tutorial_1_3
bfr_tutorial_1_3.cpp
)

View File

@@ -0,0 +1,323 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial builds on the previous tutorial that makes use of the
// SurfaceFactory, Surface and Tessellation classes for evaluating and
// tessellating the limit surface of faces of a mesh by adding support
// for the evaluation of face-varying UVs.
//
// If UVs exist in the given mesh, they will be evaluated and included
// with the vertex positions and normals (previously illustrated) as
// part of the tessellation written to the Obj file.
//
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/bfr/refinerSurfaceFactory.h>
#include <opensubdiv/bfr/surface.h>
#include <opensubdiv/bfr/tessellation.h>
#include <vector>
#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;
bool uv2xyzFlag;
public:
Args(int argc, char * argv[]) :
inputObjFile(),
outputObjFile(),
schemeType(Sdc::SCHEME_CATMARK),
tessUniformRate(5),
tessQuadsFlag(false),
uv2xyzFlag(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 if (!strcmp(argv[i], "-uv2xyz")) {
uv2xyzFlag = true;
} else {
fprintf(stderr,
"Warning: Unrecognized argument '%s' ignored\n", argv[i]);
}
}
}
private:
Args() { }
};
//
// 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,
std::vector<float> const & meshFaceVaryingUVs,
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:
//
// When dealing with face-varying data, an identifier is necessary
// when constructing Surfaces in order to distinguish the different
// face-varying data channels. To avoid repeatedly specifying that
// identifier when only one is present (or of interest), it can be
// specified via the Options.
//
bool meshHasUVs = (meshTopology.GetNumFVarChannels() > 0);
SurfaceFactory::Options surfaceOptions;
if (meshHasUVs) {
surfaceOptions.SetDefaultFVarID(0);
}
SurfaceFactory surfaceFactory(meshTopology, surfaceOptions);
//
// The Surface to be constructed and evaluated for each face -- as
// well as the intermediate and output data associated with it -- 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.
//
Surface posSurface;
Surface uvSurface;
std::vector<float> facePatchPoints;
std::vector<float> outCoords;
std::vector<float> outPos, outDu, outDv;
std::vector<float> outUV;
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 = surfaceFactory.GetNumFaces();
for (int faceIndex = 0; faceIndex < numFaces; ++faceIndex) {
//
// Initialize the Surfaces for position and UVs of this face.
// There are two ways to do this -- both illustrated here:
//
// Creating Surfaces for the different data interpolation types
// independently is clear and convenient, but considerable work
// may be duplicated in the construction process in the case of
// non-linear face-varying Surfaces. So unless it is known that
// face-varying interpolation is linear, use of InitSurfaces()
// is generally preferred.
//
// Remember also that the face-varying identifier is omitted from
// the initialization methods here as it was previously assigned
// to the SurfaceFactory::Options. In the absence of an assignment
// of the default FVarID to the Options, a failure to specify the
// FVarID here will result in failure.
//
// The cases below are expanded for illustration purposes, and
// validity of the resulting Surface is tested here, rather than
// the return value of initialization methods.
//
bool createSurfacesTogether = true;
if (!meshHasUVs) {
surfaceFactory.InitVertexSurface(faceIndex, &posSurface);
} else if (createSurfacesTogether) {
surfaceFactory.InitSurfaces(faceIndex, &posSurface, &uvSurface);
} else {
if (surfaceFactory.InitVertexSurface(faceIndex, &posSurface)) {
surfaceFactory.InitFaceVaryingSurface(faceIndex, &uvSurface);
}
}
if (!posSurface.IsValid()) continue;
//
// Declare a simple uniform Tessellation for the Parameterization
// of this face and identify coordinates of the points to evaluate:
//
Bfr::Tessellation tessPattern(posSurface.GetParameterization(),
options.tessUniformRate, tessOptions);
int numOutCoords = tessPattern.GetNumCoords();
outCoords.resize(numOutCoords * 2);
tessPattern.GetCoords(outCoords.data());
//
// Prepare the patch points for the Surface, then use them to
// evaluate output points for all identified coordinates:
//
// Evaluate vertex positions:
{
// Resize patch point and output arrays:
int pointSize = 3;
facePatchPoints.resize(posSurface.GetNumPatchPoints() * pointSize);
outPos.resize(numOutCoords * pointSize);
outDu.resize(numOutCoords * pointSize);
outDv.resize(numOutCoords * pointSize);
// Populate patch point and output arrays:
posSurface.PreparePatchPoints(meshVertexPositions.data(), pointSize,
facePatchPoints.data(), pointSize);
for (int i = 0, j = 0; i < numOutCoords; ++i, j += pointSize) {
posSurface.Evaluate(&outCoords[i*2],
facePatchPoints.data(), pointSize,
&outPos[j], &outDu[j], &outDv[j]);
}
}
// Evaluate face-varying UVs (when present):
if (meshHasUVs) {
// Resize patch point and output arrays:
// - note reuse of the same patch point array as position
int pointSize = 2;
facePatchPoints.resize(uvSurface.GetNumPatchPoints() * pointSize);
outUV.resize(numOutCoords * pointSize);
// Populate patch point and output arrays:
uvSurface.PreparePatchPoints(meshFaceVaryingUVs.data(), pointSize,
facePatchPoints.data(), pointSize);
for (int i = 0, j = 0; i < numOutCoords; ++i, j += pointSize) {
uvSurface.Evaluate(&outCoords[i*2],
facePatchPoints.data(), pointSize,
&outUV[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);
if (meshHasUVs && options.uv2xyzFlag) {
objWriter.WriteVertexPositions(outUV, 2);
objWriter.WriteFaces(outFacets, tessFacetSize, false, false);
} else {
objWriter.WriteVertexPositions(outPos);
objWriter.WriteVertexNormals(outDu, outDv);
if (meshHasUVs) {
objWriter.WriteVertexUVs(outUV);
}
objWriter.WriteFaces(outFacets, tessFacetSize, true, meshHasUVs);
}
}
}
//
// 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, meshFVarUVs, args);
delete meshTopology;
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,192 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../../../regression/common/far_utils.h"
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/far/topologyDescriptor.h>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <sstream>
// Utilities local to this tutorial:
namespace tutorial {
using namespace OpenSubdiv;
//
// Create a TopologyRefiner from default geometry:
//
Far::TopologyRefiner *
dfltTopologyRefiner(std::vector<float> & posVector,
std::vector<float> & uvVector) {
//
// Default topology and positions for a cube:
//
int dfltNumFaces = 6;
int dfltNumVerts = 8;
int dfltNumUVs = 16;
int dfltFaceSizes[6] = { 4, 4, 4, 4, 4, 4 };
int dfltFaceVerts[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
float dfltPositions[8][3] = {{ -0.5f, -0.5f, 0.5f },
{ 0.5f, -0.5f, 0.5f },
{ -0.5f, 0.5f, 0.5f },
{ 0.5f, 0.5f, 0.5f },
{ -0.5f, 0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },
{ -0.5f, -0.5f, -0.5f },
{ 0.5f, -0.5f, -0.5f }};
int dfltFaceFVars[24] = { 9, 10, 14, 13,
4, 0, 1, 5,
5, 1, 2, 6,
6, 2, 3, 7,
10, 11, 15, 14,
8, 9, 13, 12 };
float dfltUVs[16][2] = {{ 0.05f, 0.05f },
{ 0.35f, 0.15f },
{ 0.65f, 0.15f },
{ 0.95f, 0.05f },
{ 0.05f, 0.35f },
{ 0.35f, 0.45f },
{ 0.65f, 0.45f },
{ 0.95f, 0.35f },
{ 0.05f, 0.65f },
{ 0.35f, 0.55f },
{ 0.65f, 0.55f },
{ 0.95f, 0.65f },
{ 0.05f, 0.95f },
{ 0.35f, 0.85f },
{ 0.65f, 0.85f },
{ 0.95f, 0.95f }};
posVector.resize(8 * 3);
std::memcpy(&posVector[0], dfltPositions, 8 * 3 * sizeof(float));
uvVector.resize(16 * 2);
std::memcpy(&uvVector[0], dfltUVs, 16 * 2 * sizeof(float));
//
// Initialize a Far::TopologyDescriptor, from which to create
// the Far::TopologyRefiner:
//
typedef Far::TopologyDescriptor Descriptor;
Descriptor::FVarChannel uvChannel;
uvChannel.numValues = dfltNumUVs;
uvChannel.valueIndices = dfltFaceFVars;
Descriptor topDescriptor;
topDescriptor.numVertices = dfltNumVerts;
topDescriptor.numFaces = dfltNumFaces;
topDescriptor.numVertsPerFace = dfltFaceSizes;
topDescriptor.vertIndicesPerFace = dfltFaceVerts;
topDescriptor.numFVarChannels = 1;
topDescriptor.fvarChannels = &uvChannel;
Sdc::SchemeType schemeType = Sdc::SCHEME_CATMARK;
Sdc::Options schemeOptions;
schemeOptions.SetVtxBoundaryInterpolation(
Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
schemeOptions.SetFVarLinearInterpolation(
Sdc::Options::FVAR_LINEAR_CORNERS_ONLY);
typedef Far::TopologyRefinerFactory<Descriptor> RefinerFactory;
Far::TopologyRefiner * topRefiner =
RefinerFactory::Create(topDescriptor,
RefinerFactory::Options(schemeType, schemeOptions));
assert(topRefiner);
return topRefiner;
}
//
// Create a TopologyRefiner from a specified Obj file:
//
Far::TopologyRefiner *
readTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
const char * filename = objFileName.c_str();
const Shape * shape = 0;
std::ifstream ifs(filename);
if (ifs) {
std::stringstream ss;
ss << ifs.rdbuf();
ifs.close();
std::string shapeString = ss.str();
shape = Shape::parseObj(
shapeString.c_str(), ConvertSdcTypeToShapeScheme(schemeType), false);
if (shape == 0) {
fprintf(stderr,
"Error: Cannot create Shape from Obj file '%s'\n", filename);
return 0;
}
} else {
fprintf(stderr, "Error: Cannot open Obj file '%s'\n", filename);
return 0;
}
Sdc::SchemeType sdcType = GetSdcType(*shape);
Sdc::Options sdcOptions = GetSdcOptions(*shape);
Far::TopologyRefiner * refiner = Far::TopologyRefinerFactory<Shape>::Create(
*shape, Far::TopologyRefinerFactory<Shape>::Options(sdcType, sdcOptions));
if (refiner == 0) {
fprintf(stderr,
"Error: Unable to construct TopologyRefiner from Obj file '%s'\n",
filename);
return 0;
}
int numVertices = refiner->GetNumVerticesTotal();
posVector.resize(numVertices * 3);
std::memcpy(&posVector[0], &shape->verts[0], 3*numVertices*sizeof(float));
uvVector.resize(0);
if (refiner->GetNumFVarChannels()) {
int numUVs = refiner->GetNumFVarValuesTotal(0);
uvVector.resize(numUVs * 2);
std::memcpy(&uvVector[0], &shape->uvs[0], 2 * numUVs*sizeof(float));
}
delete shape;
return refiner;
}
Far::TopologyRefiner *
createTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
if (objFileName.empty()) {
return dfltTopologyRefiner(posVector, uvVector);
} else {
return readTopologyRefiner(objFileName, schemeType,
posVector, uvVector);
}
}
} // end namespace

View File

@@ -0,0 +1,176 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include <string>
#include <vector>
#include <cstdio>
#include <cmath>
#include <cassert>
// Utilities local to this tutorial:
namespace tutorial {
//
// Simple class to write vertex positions, normals and faces to a
// specified Obj file:
//
class ObjWriter {
public:
ObjWriter(std::string const &filename = 0);
~ObjWriter();
int GetNumVertices() const { return _numVertices; }
int GetNumFaces() const { return _numFaces; }
void WriteVertexPositions(std::vector<float> const & p, int size = 3);
void WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv);
void WriteVertexUVs(std::vector<float> const & uv);
void WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool writeNormalIndices = false,
bool writeUVIndices = false);
void WriteGroupName(char const * prefix, int index);
private:
void getNormal(float N[3], float const du[3], float const dv[3]) const;
private:
std::string _filename;
FILE * _fptr;
int _numVertices;
int _numNormals;
int _numUVs;
int _numFaces;
};
//
// Definitions ObjWriter methods:
//
ObjWriter::ObjWriter(std::string const &filename) :
_fptr(0), _numVertices(0), _numNormals(0), _numUVs(0), _numFaces(0) {
if (filename != std::string()) {
_fptr = fopen(filename.c_str(), "w");
if (_fptr == 0) {
fprintf(stderr, "Error: ObjWriter cannot open Obj file '%s'\n",
filename.c_str());
}
}
if (_fptr == 0) _fptr = stdout;
}
ObjWriter::~ObjWriter() {
if (_fptr != stdout) fclose(_fptr);
}
void
ObjWriter::WriteVertexPositions(std::vector<float> const & pos, int dim) {
assert(dim >= 2);
int numNewVerts = (int)pos.size() / dim;
float const * P = pos.data();
for (int i = 0; i < numNewVerts; ++i, P += dim) {
if (dim == 2) {
fprintf(_fptr, "v %f %f 0.0\n", P[0], P[1]);
} else {
fprintf(_fptr, "v %f %f %f\n", P[0], P[1], P[2]);
}
}
_numVertices += numNewVerts;
}
void
ObjWriter::getNormal(float N[3], float const du[3], float const dv[3]) const {
N[0] = du[1] * dv[2] - du[2] * dv[1];
N[1] = du[2] * dv[0] - du[0] * dv[2];
N[2] = du[0] * dv[1] - du[1] * dv[0];
float lenSqrd = N[0] * N[0] + N[1] * N[1] + N[2] * N[2];
if (lenSqrd <= 0.0f) {
N[0] = 0.0f;
N[1] = 0.0f;
N[2] = 0.0f;
} else {
float lenInv = 1.0f / std::sqrt(lenSqrd);
N[0] *= lenInv;
N[1] *= lenInv;
N[2] *= lenInv;
}
}
void
ObjWriter::WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv) {
assert(du.size() == dv.size());
int numNewNormals = (int)du.size() / 3;
float const * dPdu = &du[0];
float const * dPdv = &dv[0];
for (int i = 0; i < numNewNormals; ++i, dPdu += 3, dPdv += 3) {
float N[3];
getNormal(N, dPdu, dPdv);
fprintf(_fptr, "vn %f %f %f\n", N[0], N[1], N[2]);
}
_numNormals += numNewNormals;
}
void
ObjWriter::WriteVertexUVs(std::vector<float> const & uv) {
int numNewUVs = (int)uv.size() / 2;
for (int i = 0; i < numNewUVs; ++i) {
fprintf(_fptr, "vt %f %f\n", uv[i*2], uv[i*2+1]);
}
_numUVs += numNewUVs;
}
void
ObjWriter::WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool includeNormalIndices, bool includeUVIndices) {
int numNewFaces = (int)faceVertices.size() / faceSize;
int const * v = &faceVertices[0];
for (int i = 0; i < numNewFaces; ++i, v += faceSize) {
fprintf(_fptr, "f ");
for (int j = 0; j < faceSize; ++j) {
if (v[j] >= 0) {
// Remember Obj indices start with 1:
int vIndex = 1 + v[j];
if (includeNormalIndices && includeUVIndices) {
fprintf(_fptr, " %d/%d/%d", vIndex, vIndex, vIndex);
} else if (includeNormalIndices) {
fprintf(_fptr, " %d//%d", vIndex, vIndex);
} else if (includeUVIndices) {
fprintf(_fptr, " %d/%d", vIndex, vIndex);
} else {
fprintf(_fptr, " %d", vIndex);
}
}
}
fprintf(_fptr, "\n");
}
_numFaces += numNewFaces;
}
void
ObjWriter::WriteGroupName(char const * prefix, int index) {
fprintf(_fptr, "g %s%d\n", prefix ? prefix : "", index);
}
} // end namespace

View File

@@ -0,0 +1,11 @@
#
# Copyright 2022 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_bfr_tutorial(
bfr_tutorial_1_4
bfr_tutorial_1_4.cpp
)

View File

@@ -0,0 +1,375 @@
//
// Copyright 2022 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial builds on the previous tutorial that makes use of the
// SurfaceFactory, Surface and Tessellation classes for evaluating and
// tessellating the limit surface of faces of a mesh by illustrating
// how the presence of additional data in the mesh arrays is handled.
//
// As in the previous tutorial, vertex positions and face-varying UVs
// are provided with the mesh to be evaluated. But here an additional
// color is interleaved with the position in the vertex data of the
// mesh and a third component is added to face-varying UV data (making
// it (u,v,w)).
//
// To evaluate the position and 2D UVs while avoiding the color and
// unused third UV coordinate, the Surface::PointDescriptor class is
// used to describe the size and stride of the desired data to be
// evaluated in the arrays of mesh data.
//
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/bfr/refinerSurfaceFactory.h>
#include <opensubdiv/bfr/surface.h>
#include <opensubdiv/bfr/tessellation.h>
#include <vector>
#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;
bool uv2xyzFlag;
public:
Args(int argc, char * argv[]) :
inputObjFile(),
outputObjFile(),
schemeType(Sdc::SCHEME_CATMARK),
tessUniformRate(5),
tessQuadsFlag(false),
uv2xyzFlag(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 if (!strcmp(argv[i], "-uv2xyz")) {
uv2xyzFlag = true;
} else {
fprintf(stderr,
"Warning: Unrecognized argument '%s' ignored\n", argv[i]);
}
}
}
private:
Args() { }
};
//
// 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 & meshVtxData, int vtxDataSize,
std::vector<float> const & meshFVarData, int fvarDataSize,
Args const & options) {
//
// Use simpler local type names for the Surface and its factory:
//
typedef Bfr::RefinerSurfaceFactory<> SurfaceFactory;
typedef Bfr::Surface<float> Surface;
typedef Surface::PointDescriptor SurfacePoint;
//
// Identify the source positions and UVs within more general data
// arrays for the mesh. If position and/or UV are not at the start
// of the vtx and/or fvar data, simply offset the head of the array
// here accordingly:
//
bool meshHasUVs = (meshTopology.GetNumFVarChannels() > 0);
float const * meshPosData = meshVtxData.data();
SurfacePoint meshPosPoint(3, vtxDataSize);
float const * meshUVData = meshHasUVs ? meshFVarData.data() : 0;
SurfacePoint meshUVPoint(2, fvarDataSize);
//
// 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:
//
// When dealing with face-varying data, an identifier is necessary
// when constructing Surfaces in order to distinguish the different
// face-varying data channels. To avoid repeatedly specifying that
// identifier when only one is present (or of interest), it can be
// specified via the Options.
//
SurfaceFactory::Options surfaceOptions;
if (meshHasUVs) {
surfaceOptions.SetDefaultFVarID(0);
}
SurfaceFactory surfaceFactory(meshTopology, surfaceOptions);
//
// The Surface to be constructed and evaluated for each face -- as
// well as the intermediate and output data associated with it -- 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.
//
Surface posSurface;
Surface uvSurface;
std::vector<float> facePatchPoints;
std::vector<float> outCoords;
std::vector<float> outPos, outDu, outDv;
std::vector<float> outUV;
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 = surfaceFactory.GetNumFaces();
for (int faceIndex = 0; faceIndex < numFaces; ++faceIndex) {
//
// Initialize the Surfaces for position and UVs of this face.
// There are two ways to do this -- both illustrated here:
//
// Creating Surfaces for the different data interpolation types
// independently is clear and convenient, but considerable work
// may be duplicated in the construction process in the case of
// non-linear face-varying Surfaces. So unless it is known that
// face-varying interpolation is linear, use of InitSurfaces()
// is generally preferred.
//
// Remember also that the face-varying identifier is omitted from
// the initialization methods here as it was previously assigned
// to the SurfaceFactory::Options. In the absence of an assignment
// of the default FVarID to the Options, a failure to specify the
// FVarID here will result in failure.
//
// The cases below are expanded for illustration purposes, and
// validity of the resulting Surface is tested here, rather than
// the return value of initialization methods.
//
bool createSurfacesTogether = true;
if (!meshHasUVs) {
surfaceFactory.InitVertexSurface(faceIndex, &posSurface);
} else if (createSurfacesTogether) {
surfaceFactory.InitSurfaces(faceIndex, &posSurface, &uvSurface);
} else {
if (surfaceFactory.InitVertexSurface(faceIndex, &posSurface)) {
surfaceFactory.InitFaceVaryingSurface(faceIndex, &uvSurface);
}
}
if (!posSurface.IsValid()) continue;
//
// Declare a simple uniform Tessellation for the Parameterization
// of this face and identify coordinates of the points to evaluate:
//
Bfr::Tessellation tessPattern(posSurface.GetParameterization(),
options.tessUniformRate, tessOptions);
int numOutCoords = tessPattern.GetNumCoords();
outCoords.resize(numOutCoords * 2);
tessPattern.GetCoords(outCoords.data());
//
// Prepare the patch points for the Surface, then use them to
// evaluate output points for all identified coordinates:
//
// Evaluate vertex positions:
{
// Resize patch point and output arrays:
int pointSize = meshPosPoint.size;
facePatchPoints.resize(posSurface.GetNumPatchPoints() * pointSize);
outPos.resize(numOutCoords * pointSize);
outDu.resize(numOutCoords * pointSize);
outDv.resize(numOutCoords * pointSize);
// Populate patch point and output arrays:
float * patchPosData = facePatchPoints.data();
SurfacePoint patchPosPoint(pointSize);
posSurface.PreparePatchPoints(meshPosData, meshPosPoint,
patchPosData, patchPosPoint);
for (int i = 0, j = 0; i < numOutCoords; ++i, j += pointSize) {
posSurface.Evaluate(&outCoords[i*2],
patchPosData, patchPosPoint,
&outPos[j], &outDu[j], &outDv[j]);
}
}
// Evaluate face-varying UVs (when present):
if (meshHasUVs) {
// Resize patch point and output arrays:
// - note reuse of the same patch point array as position
int pointSize = meshUVPoint.size;
facePatchPoints.resize(uvSurface.GetNumPatchPoints() * pointSize);
outUV.resize(numOutCoords * pointSize);
// Populate patch point and output arrays:
float * patchUVData = facePatchPoints.data();
SurfacePoint patchUVPoint(pointSize);
uvSurface.PreparePatchPoints(meshUVData, meshUVPoint,
patchUVData, patchUVPoint);
for (int i = 0, j = 0; i < numOutCoords; ++i, j += pointSize) {
uvSurface.Evaluate(&outCoords[i*2],
patchUVData, patchUVPoint,
&outUV[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);
if (meshHasUVs && options.uv2xyzFlag) {
objWriter.WriteVertexPositions(outUV, 2);
objWriter.WriteFaces(outFacets, tessFacetSize, false, false);
} else {
objWriter.WriteVertexPositions(outPos);
objWriter.WriteVertexNormals(outDu, outDv);
if (meshHasUVs) {
objWriter.WriteVertexUVs(outUV);
}
objWriter.WriteFaces(outFacets, tessFacetSize, true, meshHasUVs);
}
}
}
//
// 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;
}
//
// Expand the loaded position and UV arrays to include additional
// data (initialized with -1 for distinction), e.g. add a 4-tuple
// for RGBA color to the vertex data and add a third field ("w")
// to the face-varying data:
//
int numPos = (int) meshVtxPositions.size() / 3;
int vtxSize = 7;
std::vector<float> vtxData(numPos * vtxSize, -1.0f);
for (int i = 0; i < numPos; ++i) {
vtxData[i*vtxSize] = meshVtxPositions[i*3];
vtxData[i*vtxSize + 1] = meshVtxPositions[i*3 + 1];
vtxData[i*vtxSize + 2] = meshVtxPositions[i*3 + 2];
}
int numUVs = (int) meshFVarUVs.size() / 2;
int fvarSize = 3;
std::vector<float> fvarData(numUVs * fvarSize, -1.0f);
for (int i = 0; i < numUVs; ++i) {
fvarData[i*fvarSize] = meshFVarUVs[i*2];
fvarData[i*fvarSize + 1] = meshFVarUVs[i*2 + 1];
}
//
// Pass the expanded data arrays along with their respective strides:
//
tessellateToObj(*meshTopology, vtxData, vtxSize, fvarData, fvarSize, args);
delete meshTopology;
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,192 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../../../regression/common/far_utils.h"
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/far/topologyDescriptor.h>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <sstream>
// Utilities local to this tutorial:
namespace tutorial {
using namespace OpenSubdiv;
//
// Create a TopologyRefiner from default geometry:
//
Far::TopologyRefiner *
dfltTopologyRefiner(std::vector<float> & posVector,
std::vector<float> & uvVector) {
//
// Default topology and positions for a cube:
//
int dfltNumFaces = 6;
int dfltNumVerts = 8;
int dfltNumUVs = 16;
int dfltFaceSizes[6] = { 4, 4, 4, 4, 4, 4 };
int dfltFaceVerts[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
float dfltPositions[8][3] = {{ -0.5f, -0.5f, 0.5f },
{ 0.5f, -0.5f, 0.5f },
{ -0.5f, 0.5f, 0.5f },
{ 0.5f, 0.5f, 0.5f },
{ -0.5f, 0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },
{ -0.5f, -0.5f, -0.5f },
{ 0.5f, -0.5f, -0.5f }};
int dfltFaceFVars[24] = { 9, 10, 14, 13,
4, 0, 1, 5,
5, 1, 2, 6,
6, 2, 3, 7,
10, 11, 15, 14,
8, 9, 13, 12 };
float dfltUVs[16][2] = {{ 0.05f, 0.05f },
{ 0.35f, 0.15f },
{ 0.65f, 0.15f },
{ 0.95f, 0.05f },
{ 0.05f, 0.35f },
{ 0.35f, 0.45f },
{ 0.65f, 0.45f },
{ 0.95f, 0.35f },
{ 0.05f, 0.65f },
{ 0.35f, 0.55f },
{ 0.65f, 0.55f },
{ 0.95f, 0.65f },
{ 0.05f, 0.95f },
{ 0.35f, 0.85f },
{ 0.65f, 0.85f },
{ 0.95f, 0.95f }};
posVector.resize(8 * 3);
std::memcpy(&posVector[0], dfltPositions, 8 * 3 * sizeof(float));
uvVector.resize(16 * 2);
std::memcpy(&uvVector[0], dfltUVs, 16 * 2 * sizeof(float));
//
// Initialize a Far::TopologyDescriptor, from which to create
// the Far::TopologyRefiner:
//
typedef Far::TopologyDescriptor Descriptor;
Descriptor::FVarChannel uvChannel;
uvChannel.numValues = dfltNumUVs;
uvChannel.valueIndices = dfltFaceFVars;
Descriptor topDescriptor;
topDescriptor.numVertices = dfltNumVerts;
topDescriptor.numFaces = dfltNumFaces;
topDescriptor.numVertsPerFace = dfltFaceSizes;
topDescriptor.vertIndicesPerFace = dfltFaceVerts;
topDescriptor.numFVarChannels = 1;
topDescriptor.fvarChannels = &uvChannel;
Sdc::SchemeType schemeType = Sdc::SCHEME_CATMARK;
Sdc::Options schemeOptions;
schemeOptions.SetVtxBoundaryInterpolation(
Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
schemeOptions.SetFVarLinearInterpolation(
Sdc::Options::FVAR_LINEAR_CORNERS_ONLY);
typedef Far::TopologyRefinerFactory<Descriptor> RefinerFactory;
Far::TopologyRefiner * topRefiner =
RefinerFactory::Create(topDescriptor,
RefinerFactory::Options(schemeType, schemeOptions));
assert(topRefiner);
return topRefiner;
}
//
// Create a TopologyRefiner from a specified Obj file:
//
Far::TopologyRefiner *
readTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
const char * filename = objFileName.c_str();
const Shape * shape = 0;
std::ifstream ifs(filename);
if (ifs) {
std::stringstream ss;
ss << ifs.rdbuf();
ifs.close();
std::string shapeString = ss.str();
shape = Shape::parseObj(
shapeString.c_str(), ConvertSdcTypeToShapeScheme(schemeType), false);
if (shape == 0) {
fprintf(stderr,
"Error: Cannot create Shape from Obj file '%s'\n", filename);
return 0;
}
} else {
fprintf(stderr, "Error: Cannot open Obj file '%s'\n", filename);
return 0;
}
Sdc::SchemeType sdcType = GetSdcType(*shape);
Sdc::Options sdcOptions = GetSdcOptions(*shape);
Far::TopologyRefiner * refiner = Far::TopologyRefinerFactory<Shape>::Create(
*shape, Far::TopologyRefinerFactory<Shape>::Options(sdcType, sdcOptions));
if (refiner == 0) {
fprintf(stderr,
"Error: Unable to construct TopologyRefiner from Obj file '%s'\n",
filename);
return 0;
}
int numVertices = refiner->GetNumVerticesTotal();
posVector.resize(numVertices * 3);
std::memcpy(&posVector[0], &shape->verts[0], 3*numVertices*sizeof(float));
uvVector.resize(0);
if (refiner->GetNumFVarChannels()) {
int numUVs = refiner->GetNumFVarValuesTotal(0);
uvVector.resize(numUVs * 2);
std::memcpy(&uvVector[0], &shape->uvs[0], 2 * numUVs*sizeof(float));
}
delete shape;
return refiner;
}
Far::TopologyRefiner *
createTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
if (objFileName.empty()) {
return dfltTopologyRefiner(posVector, uvVector);
} else {
return readTopologyRefiner(objFileName, schemeType,
posVector, uvVector);
}
}
} // end namespace

View File

@@ -0,0 +1,176 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include <string>
#include <vector>
#include <cstdio>
#include <cmath>
#include <cassert>
// Utilities local to this tutorial:
namespace tutorial {
//
// Simple class to write vertex positions, normals and faces to a
// specified Obj file:
//
class ObjWriter {
public:
ObjWriter(std::string const &filename = 0);
~ObjWriter();
int GetNumVertices() const { return _numVertices; }
int GetNumFaces() const { return _numFaces; }
void WriteVertexPositions(std::vector<float> const & p, int size = 3);
void WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv);
void WriteVertexUVs(std::vector<float> const & uv);
void WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool writeNormalIndices = false,
bool writeUVIndices = false);
void WriteGroupName(char const * prefix, int index);
private:
void getNormal(float N[3], float const du[3], float const dv[3]) const;
private:
std::string _filename;
FILE * _fptr;
int _numVertices;
int _numNormals;
int _numUVs;
int _numFaces;
};
//
// Definitions ObjWriter methods:
//
ObjWriter::ObjWriter(std::string const &filename) :
_fptr(0), _numVertices(0), _numNormals(0), _numUVs(0), _numFaces(0) {
if (filename != std::string()) {
_fptr = fopen(filename.c_str(), "w");
if (_fptr == 0) {
fprintf(stderr, "Error: ObjWriter cannot open Obj file '%s'\n",
filename.c_str());
}
}
if (_fptr == 0) _fptr = stdout;
}
ObjWriter::~ObjWriter() {
if (_fptr != stdout) fclose(_fptr);
}
void
ObjWriter::WriteVertexPositions(std::vector<float> const & pos, int dim) {
assert(dim >= 2);
int numNewVerts = (int)pos.size() / dim;
float const * P = pos.data();
for (int i = 0; i < numNewVerts; ++i, P += dim) {
if (dim == 2) {
fprintf(_fptr, "v %f %f 0.0\n", P[0], P[1]);
} else {
fprintf(_fptr, "v %f %f %f\n", P[0], P[1], P[2]);
}
}
_numVertices += numNewVerts;
}
void
ObjWriter::getNormal(float N[3], float const du[3], float const dv[3]) const {
N[0] = du[1] * dv[2] - du[2] * dv[1];
N[1] = du[2] * dv[0] - du[0] * dv[2];
N[2] = du[0] * dv[1] - du[1] * dv[0];
float lenSqrd = N[0] * N[0] + N[1] * N[1] + N[2] * N[2];
if (lenSqrd <= 0.0f) {
N[0] = 0.0f;
N[1] = 0.0f;
N[2] = 0.0f;
} else {
float lenInv = 1.0f / std::sqrt(lenSqrd);
N[0] *= lenInv;
N[1] *= lenInv;
N[2] *= lenInv;
}
}
void
ObjWriter::WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv) {
assert(du.size() == dv.size());
int numNewNormals = (int)du.size() / 3;
float const * dPdu = &du[0];
float const * dPdv = &dv[0];
for (int i = 0; i < numNewNormals; ++i, dPdu += 3, dPdv += 3) {
float N[3];
getNormal(N, dPdu, dPdv);
fprintf(_fptr, "vn %f %f %f\n", N[0], N[1], N[2]);
}
_numNormals += numNewNormals;
}
void
ObjWriter::WriteVertexUVs(std::vector<float> const & uv) {
int numNewUVs = (int)uv.size() / 2;
for (int i = 0; i < numNewUVs; ++i) {
fprintf(_fptr, "vt %f %f\n", uv[i*2], uv[i*2+1]);
}
_numUVs += numNewUVs;
}
void
ObjWriter::WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool includeNormalIndices, bool includeUVIndices) {
int numNewFaces = (int)faceVertices.size() / faceSize;
int const * v = &faceVertices[0];
for (int i = 0; i < numNewFaces; ++i, v += faceSize) {
fprintf(_fptr, "f ");
for (int j = 0; j < faceSize; ++j) {
if (v[j] >= 0) {
// Remember Obj indices start with 1:
int vIndex = 1 + v[j];
if (includeNormalIndices && includeUVIndices) {
fprintf(_fptr, " %d/%d/%d", vIndex, vIndex, vIndex);
} else if (includeNormalIndices) {
fprintf(_fptr, " %d//%d", vIndex, vIndex);
} else if (includeUVIndices) {
fprintf(_fptr, " %d/%d", vIndex, vIndex);
} else {
fprintf(_fptr, " %d", vIndex);
}
}
}
fprintf(_fptr, "\n");
}
_numFaces += numNewFaces;
}
void
ObjWriter::WriteGroupName(char const * prefix, int index) {
fprintf(_fptr, "g %s%d\n", prefix ? prefix : "", index);
}
} // end namespace

View File

@@ -0,0 +1,11 @@
#
# Copyright 2022 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_bfr_tutorial(
bfr_tutorial_1_5
bfr_tutorial_1_5.cpp
)

View File

@@ -0,0 +1,284 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial is an alternative to an earlier tutorial that showed
// uniform tessellation. This version differs by evaluating the points
// of the tessellation using limit stencils instead of the standard
// Surface evaluation methods.
//
// Limit stencils factor the evaluation into a set of coefficients for
// each control point affecting the Surface.
//
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/bfr/refinerSurfaceFactory.h>
#include <opensubdiv/bfr/surface.h>
#include <opensubdiv/bfr/tessellation.h>
#include <vector>
#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() { }
};
//
// 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);
//
// The Surface to be constructed and evaluated for each face -- as
// well as the intermediate and output data associated with it -- 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.
//
Surface faceSurface;
std::vector<float> faceControlPoints;
std::vector<float> limitStencils;
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) {
//
// Initialize the Surface for this face -- if valid (skipping
// holes and boundary faces in some rare cases):
//
if (!meshSurfaceFactory.InitVertexSurface(faceIndex, &faceSurface)) {
continue;
}
//
// Resize stencils and control point arrays based on the number
// of control points for the Surface:
//
int numControlPoints = faceSurface.GetNumControlPoints();
limitStencils.resize(3 * numControlPoints);
float * pStencil = limitStencils.data();
float * duStencil = limitStencils.data() + numControlPoints;
float * dvStencil = limitStencils.data() + numControlPoints * 2;
//
// Limit stencils can be applied using the control points in a
// local array or directy from the mesh. Both are shown here, so
// if using the local array, resize and populate it:
//
bool gatherControlPoints = true;
if (gatherControlPoints) {
faceControlPoints.resize(numControlPoints * 3);
faceSurface.GatherControlPoints(meshVertexPositions.data(), 3,
faceControlPoints.data(), 3);
}
//
// 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());
//
// Evaluate and apply stencils to compute points of the tessellation:
//
outPos.resize(numOutCoords * 3);
outDu.resize(numOutCoords * 3);
outDv.resize(numOutCoords * 3);
for (int i = 0; i < numOutCoords; ++i) {
float const * uv = outCoords.data() + i * 2;
faceSurface.EvaluateStencil(uv, pStencil, duStencil, dvStencil);
float * p = outPos.data() + i * 3;
float * du = outDu.data() + i * 3;
float * dv = outDv.data() + i * 3;
if (gatherControlPoints) {
float const * controlPoints = faceControlPoints.data();
faceSurface.ApplyStencil(pStencil, controlPoints, 3, p);
faceSurface.ApplyStencil(duStencil, controlPoints, 3, du);
faceSurface.ApplyStencil(dvStencil, controlPoints, 3, dv);
} else {
float const * meshPoints = meshVertexPositions.data();
faceSurface.ApplyStencilFromMesh(pStencil, meshPoints, 3, p);
faceSurface.ApplyStencilFromMesh(duStencil, meshPoints, 3, du);
faceSurface.ApplyStencilFromMesh(dvStencil, meshPoints, 3, dv);
}
}
//
// 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;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,192 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../../../regression/common/far_utils.h"
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/far/topologyDescriptor.h>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <sstream>
// Utilities local to this tutorial:
namespace tutorial {
using namespace OpenSubdiv;
//
// Create a TopologyRefiner from default geometry:
//
Far::TopologyRefiner *
dfltTopologyRefiner(std::vector<float> & posVector,
std::vector<float> & uvVector) {
//
// Default topology and positions for a cube:
//
int dfltNumFaces = 6;
int dfltNumVerts = 8;
int dfltNumUVs = 16;
int dfltFaceSizes[6] = { 4, 4, 4, 4, 4, 4 };
int dfltFaceVerts[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
float dfltPositions[8][3] = {{ -0.5f, -0.5f, 0.5f },
{ 0.5f, -0.5f, 0.5f },
{ -0.5f, 0.5f, 0.5f },
{ 0.5f, 0.5f, 0.5f },
{ -0.5f, 0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },
{ -0.5f, -0.5f, -0.5f },
{ 0.5f, -0.5f, -0.5f }};
int dfltFaceFVars[24] = { 9, 10, 14, 13,
4, 0, 1, 5,
5, 1, 2, 6,
6, 2, 3, 7,
10, 11, 15, 14,
8, 9, 13, 12 };
float dfltUVs[16][2] = {{ 0.05f, 0.05f },
{ 0.35f, 0.15f },
{ 0.65f, 0.15f },
{ 0.95f, 0.05f },
{ 0.05f, 0.35f },
{ 0.35f, 0.45f },
{ 0.65f, 0.45f },
{ 0.95f, 0.35f },
{ 0.05f, 0.65f },
{ 0.35f, 0.55f },
{ 0.65f, 0.55f },
{ 0.95f, 0.65f },
{ 0.05f, 0.95f },
{ 0.35f, 0.85f },
{ 0.65f, 0.85f },
{ 0.95f, 0.95f }};
posVector.resize(8 * 3);
std::memcpy(&posVector[0], dfltPositions, 8 * 3 * sizeof(float));
uvVector.resize(16 * 2);
std::memcpy(&uvVector[0], dfltUVs, 16 * 2 * sizeof(float));
//
// Initialize a Far::TopologyDescriptor, from which to create
// the Far::TopologyRefiner:
//
typedef Far::TopologyDescriptor Descriptor;
Descriptor::FVarChannel uvChannel;
uvChannel.numValues = dfltNumUVs;
uvChannel.valueIndices = dfltFaceFVars;
Descriptor topDescriptor;
topDescriptor.numVertices = dfltNumVerts;
topDescriptor.numFaces = dfltNumFaces;
topDescriptor.numVertsPerFace = dfltFaceSizes;
topDescriptor.vertIndicesPerFace = dfltFaceVerts;
topDescriptor.numFVarChannels = 1;
topDescriptor.fvarChannels = &uvChannel;
Sdc::SchemeType schemeType = Sdc::SCHEME_CATMARK;
Sdc::Options schemeOptions;
schemeOptions.SetVtxBoundaryInterpolation(
Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
schemeOptions.SetFVarLinearInterpolation(
Sdc::Options::FVAR_LINEAR_CORNERS_ONLY);
typedef Far::TopologyRefinerFactory<Descriptor> RefinerFactory;
Far::TopologyRefiner * topRefiner =
RefinerFactory::Create(topDescriptor,
RefinerFactory::Options(schemeType, schemeOptions));
assert(topRefiner);
return topRefiner;
}
//
// Create a TopologyRefiner from a specified Obj file:
//
Far::TopologyRefiner *
readTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
const char * filename = objFileName.c_str();
const Shape * shape = 0;
std::ifstream ifs(filename);
if (ifs) {
std::stringstream ss;
ss << ifs.rdbuf();
ifs.close();
std::string shapeString = ss.str();
shape = Shape::parseObj(
shapeString.c_str(), ConvertSdcTypeToShapeScheme(schemeType), false);
if (shape == 0) {
fprintf(stderr,
"Error: Cannot create Shape from Obj file '%s'\n", filename);
return 0;
}
} else {
fprintf(stderr, "Error: Cannot open Obj file '%s'\n", filename);
return 0;
}
Sdc::SchemeType sdcType = GetSdcType(*shape);
Sdc::Options sdcOptions = GetSdcOptions(*shape);
Far::TopologyRefiner * refiner = Far::TopologyRefinerFactory<Shape>::Create(
*shape, Far::TopologyRefinerFactory<Shape>::Options(sdcType, sdcOptions));
if (refiner == 0) {
fprintf(stderr,
"Error: Unable to construct TopologyRefiner from Obj file '%s'\n",
filename);
return 0;
}
int numVertices = refiner->GetNumVerticesTotal();
posVector.resize(numVertices * 3);
std::memcpy(&posVector[0], &shape->verts[0], 3*numVertices*sizeof(float));
uvVector.resize(0);
if (refiner->GetNumFVarChannels()) {
int numUVs = refiner->GetNumFVarValuesTotal(0);
uvVector.resize(numUVs * 2);
std::memcpy(&uvVector[0], &shape->uvs[0], 2 * numUVs*sizeof(float));
}
delete shape;
return refiner;
}
Far::TopologyRefiner *
createTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
if (objFileName.empty()) {
return dfltTopologyRefiner(posVector, uvVector);
} else {
return readTopologyRefiner(objFileName, schemeType,
posVector, uvVector);
}
}
} // end namespace

View File

@@ -0,0 +1,176 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include <string>
#include <vector>
#include <cstdio>
#include <cmath>
#include <cassert>
// Utilities local to this tutorial:
namespace tutorial {
//
// Simple class to write vertex positions, normals and faces to a
// specified Obj file:
//
class ObjWriter {
public:
ObjWriter(std::string const &filename = 0);
~ObjWriter();
int GetNumVertices() const { return _numVertices; }
int GetNumFaces() const { return _numFaces; }
void WriteVertexPositions(std::vector<float> const & p, int size = 3);
void WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv);
void WriteVertexUVs(std::vector<float> const & uv);
void WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool writeNormalIndices = false,
bool writeUVIndices = false);
void WriteGroupName(char const * prefix, int index);
private:
void getNormal(float N[3], float const du[3], float const dv[3]) const;
private:
std::string _filename;
FILE * _fptr;
int _numVertices;
int _numNormals;
int _numUVs;
int _numFaces;
};
//
// Definitions ObjWriter methods:
//
ObjWriter::ObjWriter(std::string const &filename) :
_fptr(0), _numVertices(0), _numNormals(0), _numUVs(0), _numFaces(0) {
if (filename != std::string()) {
_fptr = fopen(filename.c_str(), "w");
if (_fptr == 0) {
fprintf(stderr, "Error: ObjWriter cannot open Obj file '%s'\n",
filename.c_str());
}
}
if (_fptr == 0) _fptr = stdout;
}
ObjWriter::~ObjWriter() {
if (_fptr != stdout) fclose(_fptr);
}
void
ObjWriter::WriteVertexPositions(std::vector<float> const & pos, int dim) {
assert(dim >= 2);
int numNewVerts = (int)pos.size() / dim;
float const * P = pos.data();
for (int i = 0; i < numNewVerts; ++i, P += dim) {
if (dim == 2) {
fprintf(_fptr, "v %f %f 0.0\n", P[0], P[1]);
} else {
fprintf(_fptr, "v %f %f %f\n", P[0], P[1], P[2]);
}
}
_numVertices += numNewVerts;
}
void
ObjWriter::getNormal(float N[3], float const du[3], float const dv[3]) const {
N[0] = du[1] * dv[2] - du[2] * dv[1];
N[1] = du[2] * dv[0] - du[0] * dv[2];
N[2] = du[0] * dv[1] - du[1] * dv[0];
float lenSqrd = N[0] * N[0] + N[1] * N[1] + N[2] * N[2];
if (lenSqrd <= 0.0f) {
N[0] = 0.0f;
N[1] = 0.0f;
N[2] = 0.0f;
} else {
float lenInv = 1.0f / std::sqrt(lenSqrd);
N[0] *= lenInv;
N[1] *= lenInv;
N[2] *= lenInv;
}
}
void
ObjWriter::WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv) {
assert(du.size() == dv.size());
int numNewNormals = (int)du.size() / 3;
float const * dPdu = &du[0];
float const * dPdv = &dv[0];
for (int i = 0; i < numNewNormals; ++i, dPdu += 3, dPdv += 3) {
float N[3];
getNormal(N, dPdu, dPdv);
fprintf(_fptr, "vn %f %f %f\n", N[0], N[1], N[2]);
}
_numNormals += numNewNormals;
}
void
ObjWriter::WriteVertexUVs(std::vector<float> const & uv) {
int numNewUVs = (int)uv.size() / 2;
for (int i = 0; i < numNewUVs; ++i) {
fprintf(_fptr, "vt %f %f\n", uv[i*2], uv[i*2+1]);
}
_numUVs += numNewUVs;
}
void
ObjWriter::WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool includeNormalIndices, bool includeUVIndices) {
int numNewFaces = (int)faceVertices.size() / faceSize;
int const * v = &faceVertices[0];
for (int i = 0; i < numNewFaces; ++i, v += faceSize) {
fprintf(_fptr, "f ");
for (int j = 0; j < faceSize; ++j) {
if (v[j] >= 0) {
// Remember Obj indices start with 1:
int vIndex = 1 + v[j];
if (includeNormalIndices && includeUVIndices) {
fprintf(_fptr, " %d/%d/%d", vIndex, vIndex, vIndex);
} else if (includeNormalIndices) {
fprintf(_fptr, " %d//%d", vIndex, vIndex);
} else if (includeUVIndices) {
fprintf(_fptr, " %d/%d", vIndex, vIndex);
} else {
fprintf(_fptr, " %d", vIndex);
}
}
}
fprintf(_fptr, "\n");
}
_numFaces += numNewFaces;
}
void
ObjWriter::WriteGroupName(char const * prefix, int index) {
fprintf(_fptr, "g %s%d\n", prefix ? prefix : "", index);
}
} // end namespace

View File

@@ -0,0 +1,11 @@
#
# Copyright 2022 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_bfr_tutorial(
bfr_tutorial_2_1
bfr_tutorial_2_1.cpp
)

View File

@@ -0,0 +1,383 @@
//
// Copyright 2022 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial builds on the previous tutorial that makes use of the
// SurfaceFactory, Surface and Tessellation classes by illustrating the
// use of non-uniform tessellation parameters with Tessellation.
//
// Tessellation rates for the edges of a face are determined by a
// length associated with each edge. That length may be computed using
// either the control hull or the limit surface. The length of a
// tessellation interval is required and will be inferred if not
// explicitly specified (as a command line option).
//
// The tessellation rate for an edge is computed as its length divided
// by the length of the tessellation interval. A maximum tessellation
// rate is imposed to prevent accidental unbounded tessellation, but
// can easily be raised as needed.
//
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/bfr/refinerSurfaceFactory.h>
#include <opensubdiv/bfr/surface.h>
#include <opensubdiv/bfr/tessellation.h>
#include <vector>
#include <string>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
// 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;
float tessInterval;
int tessRateMax;
bool useHullFlag;
bool tessQuadsFlag;
public:
Args(int argc, char * argv[]) :
inputObjFile(),
outputObjFile(),
schemeType(Sdc::SCHEME_CATMARK),
tessInterval(0.0f),
tessRateMax(10),
useHullFlag(false),
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], "-length")) {
if (++i < argc) tessInterval = (float) atof(argv[i]);
} else if (!strcmp(argv[i], "-max")) {
if (++i < argc) tessRateMax = atoi(argv[i]);
} else if (!strcmp(argv[i], "-hull")) {
useHullFlag = true;
} else if (!strcmp(argv[i], "-quads")) {
tessQuadsFlag = true;
} else {
fprintf(stderr,
"Warning: Unrecognized argument '%s' ignored\n", argv[i]);
}
}
}
private:
Args() { }
};
//
// Local trivial functions for simple edge length calculations and the
// determination of associated tessellation rates:
//
inline float
EdgeLength(float const * v0, float const * v1) {
float dv[3];
dv[0] = std::abs(v0[0] - v1[0]);
dv[1] = std::abs(v0[1] - v1[1]);
dv[2] = std::abs(v0[2] - v1[2]);
return std::sqrt(dv[0]*dv[0] + dv[1]*dv[1] + dv[2]*dv[2]);
}
float
FindLongestEdge(Far::TopologyRefiner const & mesh,
std::vector<float> const & vertPos, int pointSize) {
float maxLength = 0.0f;
int numEdges = mesh.GetLevel(0).GetNumEdges();
for (int i = 0; i < numEdges; ++i) {
Far::ConstIndexArray edgeVerts = mesh.GetLevel(0).GetEdgeVertices(i);
float edgeLength = EdgeLength(&vertPos[edgeVerts[0] * pointSize],
&vertPos[edgeVerts[1] * pointSize]);
maxLength = std::max(maxLength, edgeLength);
}
return maxLength;
}
void
GetEdgeTessRates(std::vector<float> const & vertPos, int pointSize,
Args const & options,
int * edgeRates) {
int numEdges = (int) vertPos.size() / pointSize;
for (int i = 0; i < numEdges; ++i) {
int j = (i + 1) % numEdges;
float edgeLength = EdgeLength(&vertPos[i * pointSize],
&vertPos[j * pointSize]);
edgeRates[i] = 1 + (int)(edgeLength / options.tessInterval);
edgeRates[i] = std::min(edgeRates[i], options.tessRateMax);
}
}
//
// 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);
//
// The Surface to be constructed and evaluated for each face -- as
// well as the intermediate and output data associated with it -- 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.
//
Surface faceSurface;
std::vector<float> facePatchPoints;
std::vector<int> faceTessRates;
std::vector<float> outCoords;
std::vector<float> outPos, outDu, outDv;
std::vector<int> outFacets;
//
// Assign Tessellation Options applied for all faces. Tessellations
// allow the creation of either 3- or 4-sided faces -- both of which
// are supported here via a command line option:
//
// Remember that the use of non-uniform tessellation rates can lead
// to triangles being generated in 4-sided facets along boundaries
// (quad-preservation does not generate all quads). Such triangles
// are indicated by the use of an invalid/negative index in the fourth
// position.
//
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) {
//
// Initialize the Surface for this face -- if valid (skipping
// holes and boundary faces in some rare cases):
//
if (!meshSurfaceFactory.InitVertexSurface(faceIndex, &faceSurface)) {
continue;
}
//
// Prepare the Surface patch points first as it may be evaluated
// to determine suitable edge-rates for Tessellation:
//
int pointSize = 3;
facePatchPoints.resize(faceSurface.GetNumPatchPoints() * pointSize);
faceSurface.PreparePatchPoints(meshVertexPositions.data(), pointSize,
facePatchPoints.data(), pointSize);
//
// For each of the N edges of the face, a tessellation rate is
// determined to initialize a non-uniform Tessellation pattern.
//
// Many metrics are possible -- some based on the geometry itself
// (size, curvature), others dependent on viewpoint (screen space
// size, center of view, etc.) and many more. Simple techniques
// are chosen here for illustration and can easily be replaced.
//
// Here two methods are shown using lengths between the corners of
// the face -- the first using the vertex positions of the face and
// the second using points evaluated at the corners of its limit
// surface. Use of the control hull is more efficient (avoiding the
// evaluation) but may prove less effective in some cases (though
// both estimates have their limitations).
//
int N = faceSurface.GetFaceSize();
// Use the output array temporarily to hold the N positions:
outPos.resize(N * pointSize);
if (options.useHullFlag) {
Far::ConstIndexArray verts =
meshTopology.GetLevel(0).GetFaceVertices(faceIndex);
for (int i = 0, j = 0; i < N; ++i, j += pointSize) {
float const * vPos = &meshVertexPositions[verts[i] * pointSize];
outPos[j ] = vPos[0];
outPos[j+1] = vPos[1];
outPos[j+2] = vPos[2];
}
} else {
Bfr::Parameterization faceParam = faceSurface.GetParameterization();
for (int i = 0, j = 0; i < N; ++i, j += pointSize) {
float uv[2];
faceParam.GetVertexCoord(i, uv);
faceSurface.Evaluate(uv, facePatchPoints.data(), pointSize,
&outPos[j]);
}
}
faceTessRates.resize(N);
GetEdgeTessRates(outPos, pointSize, options, faceTessRates.data());
//
// Declare a non-uniform Tessellation using the rates for each
// edge and identify coordinates of the points to evaluate:
//
// Additional interior rates can be optionally provided (2 for
// quads, 1 for others) but will be inferred in their absence.
//
Bfr::Tessellation tessPattern(faceSurface.GetParameterization(),
N, faceTessRates.data(), tessOptions);
int numOutCoords = tessPattern.GetNumCoords();
outCoords.resize(numOutCoords * 2);
tessPattern.GetCoords(outCoords.data());
//
// Resize the output arrays and evaluate:
//
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.data(), 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.
//
// Remember also that triangles may be generated in 4-sided facets
// along boundaries and should be detected accordingly.
//
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;
}
//
// If no interval length was specified, set one by finding the longest
// edge of the mesh and dividing it by the maximum tessellation rate:
//
if (args.tessInterval <= 0.0f) {
args.tessInterval = FindLongestEdge(*meshTopology, meshVtxPositions, 3)
/ (float) args.tessRateMax;
}
tessellateToObj(*meshTopology, meshVtxPositions, args);
delete meshTopology;
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,192 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../../../regression/common/far_utils.h"
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/far/topologyDescriptor.h>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <sstream>
// Utilities local to this tutorial:
namespace tutorial {
using namespace OpenSubdiv;
//
// Create a TopologyRefiner from default geometry:
//
Far::TopologyRefiner *
dfltTopologyRefiner(std::vector<float> & posVector,
std::vector<float> & uvVector) {
//
// Default topology and positions for a cube:
//
int dfltNumFaces = 6;
int dfltNumVerts = 8;
int dfltNumUVs = 16;
int dfltFaceSizes[6] = { 4, 4, 4, 4, 4, 4 };
int dfltFaceVerts[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
float dfltPositions[8][3] = {{ -0.5f, -0.5f, 0.5f },
{ 0.5f, -0.5f, 0.5f },
{ -0.5f, 0.5f, 0.5f },
{ 0.5f, 0.5f, 0.5f },
{ -0.5f, 0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },
{ -0.5f, -0.5f, -0.5f },
{ 0.5f, -0.5f, -0.5f }};
int dfltFaceFVars[24] = { 9, 10, 14, 13,
4, 0, 1, 5,
5, 1, 2, 6,
6, 2, 3, 7,
10, 11, 15, 14,
8, 9, 13, 12 };
float dfltUVs[16][2] = {{ 0.05f, 0.05f },
{ 0.35f, 0.15f },
{ 0.65f, 0.15f },
{ 0.95f, 0.05f },
{ 0.05f, 0.35f },
{ 0.35f, 0.45f },
{ 0.65f, 0.45f },
{ 0.95f, 0.35f },
{ 0.05f, 0.65f },
{ 0.35f, 0.55f },
{ 0.65f, 0.55f },
{ 0.95f, 0.65f },
{ 0.05f, 0.95f },
{ 0.35f, 0.85f },
{ 0.65f, 0.85f },
{ 0.95f, 0.95f }};
posVector.resize(8 * 3);
std::memcpy(&posVector[0], dfltPositions, 8 * 3 * sizeof(float));
uvVector.resize(16 * 2);
std::memcpy(&uvVector[0], dfltUVs, 16 * 2 * sizeof(float));
//
// Initialize a Far::TopologyDescriptor, from which to create
// the Far::TopologyRefiner:
//
typedef Far::TopologyDescriptor Descriptor;
Descriptor::FVarChannel uvChannel;
uvChannel.numValues = dfltNumUVs;
uvChannel.valueIndices = dfltFaceFVars;
Descriptor topDescriptor;
topDescriptor.numVertices = dfltNumVerts;
topDescriptor.numFaces = dfltNumFaces;
topDescriptor.numVertsPerFace = dfltFaceSizes;
topDescriptor.vertIndicesPerFace = dfltFaceVerts;
topDescriptor.numFVarChannels = 1;
topDescriptor.fvarChannels = &uvChannel;
Sdc::SchemeType schemeType = Sdc::SCHEME_CATMARK;
Sdc::Options schemeOptions;
schemeOptions.SetVtxBoundaryInterpolation(
Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
schemeOptions.SetFVarLinearInterpolation(
Sdc::Options::FVAR_LINEAR_CORNERS_ONLY);
typedef Far::TopologyRefinerFactory<Descriptor> RefinerFactory;
Far::TopologyRefiner * topRefiner =
RefinerFactory::Create(topDescriptor,
RefinerFactory::Options(schemeType, schemeOptions));
assert(topRefiner);
return topRefiner;
}
//
// Create a TopologyRefiner from a specified Obj file:
//
Far::TopologyRefiner *
readTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
const char * filename = objFileName.c_str();
const Shape * shape = 0;
std::ifstream ifs(filename);
if (ifs) {
std::stringstream ss;
ss << ifs.rdbuf();
ifs.close();
std::string shapeString = ss.str();
shape = Shape::parseObj(
shapeString.c_str(), ConvertSdcTypeToShapeScheme(schemeType), false);
if (shape == 0) {
fprintf(stderr,
"Error: Cannot create Shape from Obj file '%s'\n", filename);
return 0;
}
} else {
fprintf(stderr, "Error: Cannot open Obj file '%s'\n", filename);
return 0;
}
Sdc::SchemeType sdcType = GetSdcType(*shape);
Sdc::Options sdcOptions = GetSdcOptions(*shape);
Far::TopologyRefiner * refiner = Far::TopologyRefinerFactory<Shape>::Create(
*shape, Far::TopologyRefinerFactory<Shape>::Options(sdcType, sdcOptions));
if (refiner == 0) {
fprintf(stderr,
"Error: Unable to construct TopologyRefiner from Obj file '%s'\n",
filename);
return 0;
}
int numVertices = refiner->GetNumVerticesTotal();
posVector.resize(numVertices * 3);
std::memcpy(&posVector[0], &shape->verts[0], 3*numVertices*sizeof(float));
uvVector.resize(0);
if (refiner->GetNumFVarChannels()) {
int numUVs = refiner->GetNumFVarValuesTotal(0);
uvVector.resize(numUVs * 2);
std::memcpy(&uvVector[0], &shape->uvs[0], 2 * numUVs*sizeof(float));
}
delete shape;
return refiner;
}
Far::TopologyRefiner *
createTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
if (objFileName.empty()) {
return dfltTopologyRefiner(posVector, uvVector);
} else {
return readTopologyRefiner(objFileName, schemeType,
posVector, uvVector);
}
}
} // end namespace

View File

@@ -0,0 +1,176 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include <string>
#include <vector>
#include <cstdio>
#include <cmath>
#include <cassert>
// Utilities local to this tutorial:
namespace tutorial {
//
// Simple class to write vertex positions, normals and faces to a
// specified Obj file:
//
class ObjWriter {
public:
ObjWriter(std::string const &filename = 0);
~ObjWriter();
int GetNumVertices() const { return _numVertices; }
int GetNumFaces() const { return _numFaces; }
void WriteVertexPositions(std::vector<float> const & p, int size = 3);
void WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv);
void WriteVertexUVs(std::vector<float> const & uv);
void WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool writeNormalIndices = false,
bool writeUVIndices = false);
void WriteGroupName(char const * prefix, int index);
private:
void getNormal(float N[3], float const du[3], float const dv[3]) const;
private:
std::string _filename;
FILE * _fptr;
int _numVertices;
int _numNormals;
int _numUVs;
int _numFaces;
};
//
// Definitions ObjWriter methods:
//
ObjWriter::ObjWriter(std::string const &filename) :
_fptr(0), _numVertices(0), _numNormals(0), _numUVs(0), _numFaces(0) {
if (filename != std::string()) {
_fptr = fopen(filename.c_str(), "w");
if (_fptr == 0) {
fprintf(stderr, "Error: ObjWriter cannot open Obj file '%s'\n",
filename.c_str());
}
}
if (_fptr == 0) _fptr = stdout;
}
ObjWriter::~ObjWriter() {
if (_fptr != stdout) fclose(_fptr);
}
void
ObjWriter::WriteVertexPositions(std::vector<float> const & pos, int dim) {
assert(dim >= 2);
int numNewVerts = (int)pos.size() / dim;
float const * P = pos.data();
for (int i = 0; i < numNewVerts; ++i, P += dim) {
if (dim == 2) {
fprintf(_fptr, "v %f %f 0.0\n", P[0], P[1]);
} else {
fprintf(_fptr, "v %f %f %f\n", P[0], P[1], P[2]);
}
}
_numVertices += numNewVerts;
}
void
ObjWriter::getNormal(float N[3], float const du[3], float const dv[3]) const {
N[0] = du[1] * dv[2] - du[2] * dv[1];
N[1] = du[2] * dv[0] - du[0] * dv[2];
N[2] = du[0] * dv[1] - du[1] * dv[0];
float lenSqrd = N[0] * N[0] + N[1] * N[1] + N[2] * N[2];
if (lenSqrd <= 0.0f) {
N[0] = 0.0f;
N[1] = 0.0f;
N[2] = 0.0f;
} else {
float lenInv = 1.0f / std::sqrt(lenSqrd);
N[0] *= lenInv;
N[1] *= lenInv;
N[2] *= lenInv;
}
}
void
ObjWriter::WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv) {
assert(du.size() == dv.size());
int numNewNormals = (int)du.size() / 3;
float const * dPdu = &du[0];
float const * dPdv = &dv[0];
for (int i = 0; i < numNewNormals; ++i, dPdu += 3, dPdv += 3) {
float N[3];
getNormal(N, dPdu, dPdv);
fprintf(_fptr, "vn %f %f %f\n", N[0], N[1], N[2]);
}
_numNormals += numNewNormals;
}
void
ObjWriter::WriteVertexUVs(std::vector<float> const & uv) {
int numNewUVs = (int)uv.size() / 2;
for (int i = 0; i < numNewUVs; ++i) {
fprintf(_fptr, "vt %f %f\n", uv[i*2], uv[i*2+1]);
}
_numUVs += numNewUVs;
}
void
ObjWriter::WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool includeNormalIndices, bool includeUVIndices) {
int numNewFaces = (int)faceVertices.size() / faceSize;
int const * v = &faceVertices[0];
for (int i = 0; i < numNewFaces; ++i, v += faceSize) {
fprintf(_fptr, "f ");
for (int j = 0; j < faceSize; ++j) {
if (v[j] >= 0) {
// Remember Obj indices start with 1:
int vIndex = 1 + v[j];
if (includeNormalIndices && includeUVIndices) {
fprintf(_fptr, " %d/%d/%d", vIndex, vIndex, vIndex);
} else if (includeNormalIndices) {
fprintf(_fptr, " %d//%d", vIndex, vIndex);
} else if (includeUVIndices) {
fprintf(_fptr, " %d/%d", vIndex, vIndex);
} else {
fprintf(_fptr, " %d", vIndex);
}
}
}
fprintf(_fptr, "\n");
}
_numFaces += numNewFaces;
}
void
ObjWriter::WriteGroupName(char const * prefix, int index) {
fprintf(_fptr, "g %s%d\n", prefix ? prefix : "", index);
}
} // end namespace

View File

@@ -0,0 +1,11 @@
#
# Copyright 2021 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_bfr_tutorial(
bfr_tutorial_2_2
bfr_tutorial_2_2.cpp
)

View File

@@ -0,0 +1,457 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial builds on others using the SurfaceFactory, Surface
// and Tessellation classes by using more of the functionality of the
// Tessellation class to construct a tessellation of the mesh that is
// topologically watertight, i.e. resulting points evaluated along
// shared edges or vertices are shared and not duplicated.
//
// Since Tessellation provides points around its boundary first, the
// evaluated points for shared vertices and edges are identified when
// constructed and reused when shared later. The boundary of the
// tessellation of a face is therefore a collection of shared points
// and methods of Tessellation help to remap the faces generated to
// the shared set of points.
//
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/bfr/refinerSurfaceFactory.h>
#include <opensubdiv/bfr/surface.h>
#include <opensubdiv/bfr/tessellation.h>
#include <vector>
#include <string>
#include <cstring>
#include <cstdio>
#include <cassert>
// Local headers with support for this tutorial in "namespace tutorial"
#include "./meshLoader.h"
#include "./objWriter.h"
using namespace OpenSubdiv;
using Far::Index;
using Far::IndexArray;
using Far::ConstIndexArray;
//
// 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() { }
};
//
// Simple local structs supporting shared points for vertices and edges:
//
namespace {
struct SharedVertex {
SharedVertex() : pointIndex(-1) { }
bool IsSet() const { return pointIndex >= 0; }
void Set(int index) { pointIndex = index; }
int pointIndex;
};
struct SharedEdge {
SharedEdge() : pointIndex(-1), numPoints(0) { }
bool IsSet() const { return pointIndex >= 0; }
void Set(int index, int n) { pointIndex = index, numPoints = n; }
int pointIndex;
int numPoints;
};
} // end namespace
//
// The main tessellation function: given a mesh and vertex positions,
// tessellate each face -- writing results in Obj format.
//
// This tessellation function differs from earlier tutorials in that it
// computes and reuses shared points at vertices and edges of the mesh.
// There are several ways to compute these shared points, and which is
// best depends on context.
//
// Dealing with shared data poses complications for threading in general,
// so computing all points for the vertices and edges up front may be
// preferred -- despite the fact that faces will be visited more than once
// (first when generating potentially shared vertex or edge points, and
// later when generating any interior points). The loops for vertices and
// edges can be threaded and the indexing of the shared points is simpler.
//
// For the single-threaded case here, the faces are each processed in
// order and any shared points will be computed and used as needed. So
// each face is visited once (and so each Surface initialized once) but
// the bookkeeping to deal with indices of shared points becomes more
// complicated.
//
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);
//
// The Surface to be constructed and evaluated for each face -- as
// well as the intermediate and output data associated with it -- 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.
//
Surface faceSurface;
std::vector<float> facePatchPoints;
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);
//
// Declare vectors to identify shared tessellation points at vertices
// and edges and their indices around the boundary of a face:
//
Far::TopologyLevel const & baseLevel = meshTopology.GetLevel(0);
std::vector<SharedVertex> sharedVerts(baseLevel.GetNumVertices());
std::vector<SharedEdge> sharedEdges(baseLevel.GetNumEdges());
std::vector<int> tessBoundaryIndices;
//
// Process each face, writing the output of each in Obj format:
//
tutorial::ObjWriter objWriter(options.outputObjFile);
int numMeshPointsEvaluated = 0;
int numFaces = meshSurfaceFactory.GetNumFaces();
for (int faceIndex = 0; faceIndex < numFaces; ++faceIndex) {
//
// Initialize the Surface for this face -- if valid (skipping
// holes and boundary faces in some rare cases):
//
if (!meshSurfaceFactory.InitVertexSurface(faceIndex, &faceSurface)) {
continue;
}
//
// 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());
//
// Prepare the patch points for the Surface, then use them to
// evaluate output points for all identified coordinates:
//
// Resize patch point and output arrays:
int pointSize = 3;
facePatchPoints.resize(faceSurface.GetNumPatchPoints() * pointSize);
outPos.resize(numOutCoords * pointSize);
outDu.resize(numOutCoords * pointSize);
outDv.resize(numOutCoords * pointSize);
// Populate the patch point array:
faceSurface.PreparePatchPoints(meshVertexPositions.data(), pointSize,
facePatchPoints.data(), pointSize);
//
// Evaluate the sample points of the Tessellation:
//
// First traverse the boundary of the face to determine whether
// to evaluate or share points on vertices and edges of the face.
// Both pre-existing and new boundary points are identified by
// index in an array for later use. The interior points are all
// trivially computed after the boundary is dealt with.
//
// Identify the boundary and interior coords and initialize the
// index array for the potentially shared boundary points:
//
int numBoundaryCoords = tessPattern.GetNumBoundaryCoords();
int numInteriorCoords = numOutCoords - numBoundaryCoords;
float const * tessBoundaryCoords = &outCoords[0];
float const * tessInteriorCoords = &outCoords[numBoundaryCoords*2];
ConstIndexArray fVerts = baseLevel.GetFaceVertices(faceIndex);
ConstIndexArray fEdges = baseLevel.GetFaceEdges(faceIndex);
tessBoundaryIndices.resize(numBoundaryCoords);
//
// Walk around the face, inspecting each vertex and outgoing edge,
// and populating the index array of boundary points:
//
float * patchPointData = facePatchPoints.data();
int boundaryIndex = 0;
int numFacePointsEvaluated = 0;
for (int i = 0; i < fVerts.size(); ++i) {
Index vertIndex = fVerts[i];
Index edgeIndex = fEdges[i];
int edgeRate = options.tessUniformRate;
//
// Evaluate/assign or retrieve the shared point for the vertex:
//
SharedVertex & sharedVertex = sharedVerts[vertIndex];
if (!sharedVertex.IsSet()) {
// Identify indices of the new shared point in both the
// mesh and face and increment their inventory:
int indexInMesh = numMeshPointsEvaluated++;
int indexInFace = numFacePointsEvaluated++;
sharedVertex.Set(indexInMesh);
// Evaluate new shared point and assign index to boundary:
float const * uv = &tessBoundaryCoords[boundaryIndex*2];
int pIndex = indexInFace * pointSize;
faceSurface.Evaluate(uv, patchPointData, pointSize,
&outPos[pIndex], &outDu[pIndex], &outDv[pIndex]);
tessBoundaryIndices[boundaryIndex++] = indexInMesh;
} else {
// Assign shared vertex point index to boundary:
tessBoundaryIndices[boundaryIndex++] = sharedVertex.pointIndex;
}
//
// Evaluate/assign or retrieve all shared points for the edge:
//
// To keep this simple, assume the edge is manifold. So the
// second face sharing the edge has that edge in the opposite
// direction in its boundary relative to the first face --
// making it necessary to reverse the order of shared points
// for the boundary of the second face.
//
// To support a non-manifold edge, all subsequent faces that
// share the assigned shared edge must determine if their
// orientation of that edge is reversed relative to the first
// face for which the shared edge points were evaluated. So a
// little more book-keeping and/or inspection is required.
//
if (edgeRate > 1) {
int pointsPerEdge = edgeRate - 1;
SharedEdge & sharedEdge = sharedEdges[edgeIndex];
if (!sharedEdge.IsSet()) {
// Identify indices of the new shared points in both the
// mesh and face and increment their inventory:
int nextInMesh = numMeshPointsEvaluated;
int nextInFace = numFacePointsEvaluated;
numFacePointsEvaluated += pointsPerEdge;
numMeshPointsEvaluated += pointsPerEdge;
sharedEdge.Set(nextInMesh, pointsPerEdge);
// Evaluate shared points and assign indices to boundary:
float const * uv = &tessBoundaryCoords[boundaryIndex*2];
for (int j = 0; j < pointsPerEdge; ++j, uv += 2) {
int pIndex = (nextInFace++) * pointSize;
faceSurface.Evaluate(uv, patchPointData, pointSize,
&outPos[pIndex], &outDu[pIndex], &outDv[pIndex]);
tessBoundaryIndices[boundaryIndex++] = nextInMesh++;
}
} else {
// See note above on simplification for manifold edges
assert(!baseLevel.IsEdgeNonManifold(edgeIndex));
// Assign shared points to boundary in reverse order:
int nextInMesh = sharedEdge.pointIndex + pointsPerEdge - 1;
for (int j = 0; j < pointsPerEdge; ++j) {
tessBoundaryIndices[boundaryIndex++] = nextInMesh--;
}
}
}
}
//
// Evaluate any interior points unique to this face -- appending
// them to those shared points computed above for the boundary:
//
if (numInteriorCoords) {
float const * uv = tessInteriorCoords;
int iLast = numFacePointsEvaluated + numInteriorCoords;
for (int i = numFacePointsEvaluated; i < iLast; ++i, uv += 2) {
int pIndex = i * pointSize;
faceSurface.Evaluate(uv, patchPointData, pointSize,
&outPos[pIndex], &outDu[pIndex], &outDv[pIndex]);
}
numFacePointsEvaluated += numInteriorCoords;
numMeshPointsEvaluated += numInteriorCoords;
}
//
// Remember to trim/resize the arrays storing evaluation results
// for new points to reflect the size actually populated.
//
outPos.resize(numFacePointsEvaluated * pointSize);
outDu.resize(numFacePointsEvaluated * pointSize);
outDv.resize(numFacePointsEvaluated * pointSize);
//
// Identify the faces of the Tessellation:
//
// Note that the coordinate indices used by the facets are local
// to the face (i.e. they range from [0..N-1], where N is the
// number of coordinates in the pattern) and so need to be offset
// when writing to Obj format.
//
// For more advanced use, the coordinates associated with the
// boundary and interior of the pattern are distinguishable so
// that those on the boundary can be easily remapped to refer to
// shared edge or corner points, while those in the interior can
// be separately offset or similarly remapped.
//
// So transform the indices of the facets here as needed using
// the indices of shared boundary points assembled above and a
// suitable offset for the new interior points added:
//
int tessInteriorOffset = numMeshPointsEvaluated - numOutCoords;
int numFacets = tessPattern.GetNumFacets();
outFacets.resize(numFacets * tessFacetSize);
tessPattern.GetFacets(outFacets.data());
tessPattern.TransformFacetCoordIndices(outFacets.data(),
tessBoundaryIndices.data(), tessInteriorOffset);
//
// 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;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,192 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../../../regression/common/far_utils.h"
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/far/topologyDescriptor.h>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <sstream>
// Utilities local to this tutorial:
namespace tutorial {
using namespace OpenSubdiv;
//
// Create a TopologyRefiner from default geometry:
//
Far::TopologyRefiner *
dfltTopologyRefiner(std::vector<float> & posVector,
std::vector<float> & uvVector) {
//
// Default topology and positions for a cube:
//
int dfltNumFaces = 6;
int dfltNumVerts = 8;
int dfltNumUVs = 16;
int dfltFaceSizes[6] = { 4, 4, 4, 4, 4, 4 };
int dfltFaceVerts[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
float dfltPositions[8][3] = {{ -0.5f, -0.5f, 0.5f },
{ 0.5f, -0.5f, 0.5f },
{ -0.5f, 0.5f, 0.5f },
{ 0.5f, 0.5f, 0.5f },
{ -0.5f, 0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },
{ -0.5f, -0.5f, -0.5f },
{ 0.5f, -0.5f, -0.5f }};
int dfltFaceFVars[24] = { 9, 10, 14, 13,
4, 0, 1, 5,
5, 1, 2, 6,
6, 2, 3, 7,
10, 11, 15, 14,
8, 9, 13, 12 };
float dfltUVs[16][2] = {{ 0.05f, 0.05f },
{ 0.35f, 0.15f },
{ 0.65f, 0.15f },
{ 0.95f, 0.05f },
{ 0.05f, 0.35f },
{ 0.35f, 0.45f },
{ 0.65f, 0.45f },
{ 0.95f, 0.35f },
{ 0.05f, 0.65f },
{ 0.35f, 0.55f },
{ 0.65f, 0.55f },
{ 0.95f, 0.65f },
{ 0.05f, 0.95f },
{ 0.35f, 0.85f },
{ 0.65f, 0.85f },
{ 0.95f, 0.95f }};
posVector.resize(8 * 3);
std::memcpy(&posVector[0], dfltPositions, 8 * 3 * sizeof(float));
uvVector.resize(16 * 2);
std::memcpy(&uvVector[0], dfltUVs, 16 * 2 * sizeof(float));
//
// Initialize a Far::TopologyDescriptor, from which to create
// the Far::TopologyRefiner:
//
typedef Far::TopologyDescriptor Descriptor;
Descriptor::FVarChannel uvChannel;
uvChannel.numValues = dfltNumUVs;
uvChannel.valueIndices = dfltFaceFVars;
Descriptor topDescriptor;
topDescriptor.numVertices = dfltNumVerts;
topDescriptor.numFaces = dfltNumFaces;
topDescriptor.numVertsPerFace = dfltFaceSizes;
topDescriptor.vertIndicesPerFace = dfltFaceVerts;
topDescriptor.numFVarChannels = 1;
topDescriptor.fvarChannels = &uvChannel;
Sdc::SchemeType schemeType = Sdc::SCHEME_CATMARK;
Sdc::Options schemeOptions;
schemeOptions.SetVtxBoundaryInterpolation(
Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
schemeOptions.SetFVarLinearInterpolation(
Sdc::Options::FVAR_LINEAR_CORNERS_ONLY);
typedef Far::TopologyRefinerFactory<Descriptor> RefinerFactory;
Far::TopologyRefiner * topRefiner =
RefinerFactory::Create(topDescriptor,
RefinerFactory::Options(schemeType, schemeOptions));
assert(topRefiner);
return topRefiner;
}
//
// Create a TopologyRefiner from a specified Obj file:
//
Far::TopologyRefiner *
readTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
const char * filename = objFileName.c_str();
const Shape * shape = 0;
std::ifstream ifs(filename);
if (ifs) {
std::stringstream ss;
ss << ifs.rdbuf();
ifs.close();
std::string shapeString = ss.str();
shape = Shape::parseObj(
shapeString.c_str(), ConvertSdcTypeToShapeScheme(schemeType), false);
if (shape == 0) {
fprintf(stderr,
"Error: Cannot create Shape from Obj file '%s'\n", filename);
return 0;
}
} else {
fprintf(stderr, "Error: Cannot open Obj file '%s'\n", filename);
return 0;
}
Sdc::SchemeType sdcType = GetSdcType(*shape);
Sdc::Options sdcOptions = GetSdcOptions(*shape);
Far::TopologyRefiner * refiner = Far::TopologyRefinerFactory<Shape>::Create(
*shape, Far::TopologyRefinerFactory<Shape>::Options(sdcType, sdcOptions));
if (refiner == 0) {
fprintf(stderr,
"Error: Unable to construct TopologyRefiner from Obj file '%s'\n",
filename);
return 0;
}
int numVertices = refiner->GetNumVerticesTotal();
posVector.resize(numVertices * 3);
std::memcpy(&posVector[0], &shape->verts[0], 3*numVertices*sizeof(float));
uvVector.resize(0);
if (refiner->GetNumFVarChannels()) {
int numUVs = refiner->GetNumFVarValuesTotal(0);
uvVector.resize(numUVs * 2);
std::memcpy(&uvVector[0], &shape->uvs[0], 2 * numUVs*sizeof(float));
}
delete shape;
return refiner;
}
Far::TopologyRefiner *
createTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
if (objFileName.empty()) {
return dfltTopologyRefiner(posVector, uvVector);
} else {
return readTopologyRefiner(objFileName, schemeType,
posVector, uvVector);
}
}
} // end namespace

View File

@@ -0,0 +1,176 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include <string>
#include <vector>
#include <cstdio>
#include <cmath>
#include <cassert>
// Utilities local to this tutorial:
namespace tutorial {
//
// Simple class to write vertex positions, normals and faces to a
// specified Obj file:
//
class ObjWriter {
public:
ObjWriter(std::string const &filename = 0);
~ObjWriter();
int GetNumVertices() const { return _numVertices; }
int GetNumFaces() const { return _numFaces; }
void WriteVertexPositions(std::vector<float> const & p, int size = 3);
void WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv);
void WriteVertexUVs(std::vector<float> const & uv);
void WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool writeNormalIndices = false,
bool writeUVIndices = false);
void WriteGroupName(char const * prefix, int index);
private:
void getNormal(float N[3], float const du[3], float const dv[3]) const;
private:
std::string _filename;
FILE * _fptr;
int _numVertices;
int _numNormals;
int _numUVs;
int _numFaces;
};
//
// Definitions ObjWriter methods:
//
ObjWriter::ObjWriter(std::string const &filename) :
_fptr(0), _numVertices(0), _numNormals(0), _numUVs(0), _numFaces(0) {
if (filename != std::string()) {
_fptr = fopen(filename.c_str(), "w");
if (_fptr == 0) {
fprintf(stderr, "Error: ObjWriter cannot open Obj file '%s'\n",
filename.c_str());
}
}
if (_fptr == 0) _fptr = stdout;
}
ObjWriter::~ObjWriter() {
if (_fptr != stdout) fclose(_fptr);
}
void
ObjWriter::WriteVertexPositions(std::vector<float> const & pos, int dim) {
assert(dim >= 2);
int numNewVerts = (int)pos.size() / dim;
float const * P = pos.data();
for (int i = 0; i < numNewVerts; ++i, P += dim) {
if (dim == 2) {
fprintf(_fptr, "v %f %f 0.0\n", P[0], P[1]);
} else {
fprintf(_fptr, "v %f %f %f\n", P[0], P[1], P[2]);
}
}
_numVertices += numNewVerts;
}
void
ObjWriter::getNormal(float N[3], float const du[3], float const dv[3]) const {
N[0] = du[1] * dv[2] - du[2] * dv[1];
N[1] = du[2] * dv[0] - du[0] * dv[2];
N[2] = du[0] * dv[1] - du[1] * dv[0];
float lenSqrd = N[0] * N[0] + N[1] * N[1] + N[2] * N[2];
if (lenSqrd <= 0.0f) {
N[0] = 0.0f;
N[1] = 0.0f;
N[2] = 0.0f;
} else {
float lenInv = 1.0f / std::sqrt(lenSqrd);
N[0] *= lenInv;
N[1] *= lenInv;
N[2] *= lenInv;
}
}
void
ObjWriter::WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv) {
assert(du.size() == dv.size());
int numNewNormals = (int)du.size() / 3;
float const * dPdu = &du[0];
float const * dPdv = &dv[0];
for (int i = 0; i < numNewNormals; ++i, dPdu += 3, dPdv += 3) {
float N[3];
getNormal(N, dPdu, dPdv);
fprintf(_fptr, "vn %f %f %f\n", N[0], N[1], N[2]);
}
_numNormals += numNewNormals;
}
void
ObjWriter::WriteVertexUVs(std::vector<float> const & uv) {
int numNewUVs = (int)uv.size() / 2;
for (int i = 0; i < numNewUVs; ++i) {
fprintf(_fptr, "vt %f %f\n", uv[i*2], uv[i*2+1]);
}
_numUVs += numNewUVs;
}
void
ObjWriter::WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool includeNormalIndices, bool includeUVIndices) {
int numNewFaces = (int)faceVertices.size() / faceSize;
int const * v = &faceVertices[0];
for (int i = 0; i < numNewFaces; ++i, v += faceSize) {
fprintf(_fptr, "f ");
for (int j = 0; j < faceSize; ++j) {
if (v[j] >= 0) {
// Remember Obj indices start with 1:
int vIndex = 1 + v[j];
if (includeNormalIndices && includeUVIndices) {
fprintf(_fptr, " %d/%d/%d", vIndex, vIndex, vIndex);
} else if (includeNormalIndices) {
fprintf(_fptr, " %d//%d", vIndex, vIndex);
} else if (includeUVIndices) {
fprintf(_fptr, " %d/%d", vIndex, vIndex);
} else {
fprintf(_fptr, " %d", vIndex);
}
}
}
fprintf(_fptr, "\n");
}
_numFaces += numNewFaces;
}
void
ObjWriter::WriteGroupName(char const * prefix, int index) {
fprintf(_fptr, "g %s%d\n", prefix ? prefix : "", index);
}
} // end namespace

View File

@@ -0,0 +1,12 @@
#
# Copyright 2021 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_bfr_tutorial(
bfr_tutorial_3_1
bfr_tutorial_3_1.cpp
customSurfaceFactory.cpp
)

View File

@@ -0,0 +1,325 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial illustrates the definition of a custom subclass of
// Bfr::SurfaceFactory -- providing a class with the SurfaceFactory
// interface adapted to a connected mesh representation.
//
// The bulk of this code is therefore identical to a previous tutorial
// (1.3) which illustrates simple use of a Bfr::Surface factory. The
// only difference here lies in the explicit local definition of the
// subclass of Bfr::SurfaceFactory for Far::TopologyRefiner -- named
// CustomSurfaceFactory in this case.
//
#include "./customSurfaceFactory.h"
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/bfr/surface.h>
#include <opensubdiv/bfr/tessellation.h>
#include <vector>
#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;
bool uv2xyzFlag;
public:
Args(int argc, char * argv[]) :
inputObjFile(),
outputObjFile(),
schemeType(Sdc::SCHEME_CATMARK),
tessUniformRate(5),
tessQuadsFlag(false),
uv2xyzFlag(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 if (!strcmp(argv[i], "-uv2xyz")) {
uv2xyzFlag = true;
} else {
fprintf(stderr,
"Warning: Unrecognized argument '%s' ignored\n", argv[i]);
}
}
}
private:
Args() { }
};
//
// 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,
std::vector<float> const & meshFaceVaryingUVs,
Args const & options) {
//
// Use simpler local type names for the Surface and its factory:
//
typedef CustomSurfaceFactory 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:
//
// When dealing with face-varying data, an identifier is necessary
// when constructing Surfaces in order to distinguish the different
// face-varying data channels. To avoid repeatedly specifying that
// identifier when only one is present (or of interest), it can be
// specified via the Options.
//
bool meshHasUVs = (meshTopology.GetNumFVarChannels() > 0);
SurfaceFactory::Options surfaceOptions;
if (meshHasUVs) {
surfaceOptions.SetDefaultFVarID(0);
}
SurfaceFactory surfaceFactory(meshTopology, surfaceOptions);
//
// The Surface to be constructed and evaluated for each face -- as
// well as the intermediate and output data associated with it -- 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.
//
Surface posSurface;
Surface uvSurface;
std::vector<float> facePatchPoints;
std::vector<float> outCoords;
std::vector<float> outPos, outDu, outDv;
std::vector<float> outUV;
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 = surfaceFactory.GetNumFaces();
for (int faceIndex = 0; faceIndex < numFaces; ++faceIndex) {
//
// Initialize the Surfaces for position and UVs of this face.
// There are two ways to do this -- both illustrated here:
//
// Creating Surfaces for the different data interpolation types
// independently is clear and convenient, but considerable work
// may be duplicated in the construction process in the case of
// non-linear face-varying Surfaces. So unless it is known that
// face-varying interpolation is linear, use of InitSurfaces()
// is generally preferred.
//
// Remember also that the face-varying identifier is omitted from
// the initialization methods here as it was previously assigned
// to the SurfaceFactory::Options. In the absence of an assignment
// of the default FVarID to the Options, a failure to specify the
// FVarID here will result in failure.
//
// The cases below are expanded for illustration purposes, and
// validity of the resulting Surface is tested here, rather than
// the return value of initialization methods.
//
bool createSurfacesTogether = true;
if (!meshHasUVs) {
surfaceFactory.InitVertexSurface(faceIndex, &posSurface);
} else if (createSurfacesTogether) {
surfaceFactory.InitSurfaces(faceIndex, &posSurface, &uvSurface);
} else {
if (surfaceFactory.InitVertexSurface(faceIndex, &posSurface)) {
surfaceFactory.InitFaceVaryingSurface(faceIndex, &uvSurface);
}
}
if (!posSurface.IsValid()) continue;
//
// Declare a simple uniform Tessellation for the Parameterization
// of this face and identify coordinates of the points to evaluate:
//
Bfr::Tessellation tessPattern(posSurface.GetParameterization(),
options.tessUniformRate, tessOptions);
int numOutCoords = tessPattern.GetNumCoords();
outCoords.resize(numOutCoords * 2);
tessPattern.GetCoords(outCoords.data());
//
// Prepare the patch points for the Surface, then use them to
// evaluate output points for all identified coordinates:
//
// Evaluate vertex positions:
{
// Resize patch point and output arrays:
int pointSize = 3;
facePatchPoints.resize(posSurface.GetNumPatchPoints() * pointSize);
outPos.resize(numOutCoords * pointSize);
outDu.resize(numOutCoords * pointSize);
outDv.resize(numOutCoords * pointSize);
// Populate patch point and output arrays:
posSurface.PreparePatchPoints(meshVertexPositions.data(), pointSize,
facePatchPoints.data(), pointSize);
for (int i = 0, j = 0; i < numOutCoords; ++i, j += pointSize) {
posSurface.Evaluate(&outCoords[i*2],
facePatchPoints.data(), pointSize,
&outPos[j], &outDu[j], &outDv[j]);
}
}
// Evaluate face-varying UVs (when present):
if (meshHasUVs) {
// Resize patch point and output arrays:
// - note reuse of the same patch point array as position
int pointSize = 2;
facePatchPoints.resize(uvSurface.GetNumPatchPoints() * pointSize);
outUV.resize(numOutCoords * pointSize);
// Populate patch point and output arrays:
uvSurface.PreparePatchPoints(meshFaceVaryingUVs.data(), pointSize,
facePatchPoints.data(), pointSize);
for (int i = 0, j = 0; i < numOutCoords; ++i, j += pointSize) {
uvSurface.Evaluate(&outCoords[i*2],
facePatchPoints.data(), pointSize,
&outUV[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);
if (meshHasUVs && options.uv2xyzFlag) {
objWriter.WriteVertexPositions(outUV, 2);
objWriter.WriteFaces(outFacets, tessFacetSize, false, false);
} else {
objWriter.WriteVertexPositions(outPos);
objWriter.WriteVertexNormals(outDu, outDv);
if (meshHasUVs) {
objWriter.WriteVertexUVs(outUV);
}
objWriter.WriteFaces(outFacets, tessFacetSize, true, meshHasUVs);
}
}
}
//
// 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, meshFVarUVs, args);
delete meshTopology;
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,259 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "./customSurfaceFactory.h"
#include <opensubdiv/bfr/limits.h>
#include <opensubdiv/bfr/vertexDescriptor.h>
#include <opensubdiv/far/topologyLevel.h>
#include <limits>
using OpenSubdiv::Far::TopologyRefiner;
using OpenSubdiv::Far::TopologyLevel;
using OpenSubdiv::Far::Index;
using OpenSubdiv::Far::ConstIndexArray;
using OpenSubdiv::Far::ConstLocalIndexArray;
//
// Main constructor and destructor:
//
CustomSurfaceFactory::CustomSurfaceFactory(
TopologyRefiner const & mesh, Options const & factoryOptions) :
SurfaceFactory(mesh.GetSchemeType(),
mesh.GetSchemeOptions(),
factoryOptions),
_mesh(mesh),
_localCache() {
SurfaceFactory::setInternalCache(&_localCache);
}
//
// Inline support method to provide a valid face-varying channel from
// a given face-varying ID used in the factory interface:
//
inline int
CustomSurfaceFactory::getFaceVaryingChannel(FVarID fvarID) const {
// Verify bounds as the FVarIDs are specified by end users:
if ((fvarID >= 0) && (fvarID < GetNumFVarChannels())) {
return (int) fvarID;
}
return -1;
}
//
// Virtual methods supporting Surface creation and population:
//
// Simple/trivial face queries:
//
bool
CustomSurfaceFactory::isFaceHole(Index face) const {
return _mesh.HasHoles() && _mesh.GetLevel(0).IsFaceHole(face);
}
int
CustomSurfaceFactory::getFaceSize(Index baseFace) const {
return _mesh.GetLevel(0).GetFaceVertices(baseFace).size();
}
//
// Specifying vertex or face-varying indices for a face:
//
int
CustomSurfaceFactory::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
CustomSurfaceFactory::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
CustomSurfaceFactory::populateFaceVertexDescriptor(
Index baseFace, int cornerVertex,
OpenSubdiv::Bfr::VertexDescriptor * vertexDescriptor) const {
OpenSubdiv::Bfr::VertexDescriptor & vd = *vertexDescriptor;
TopologyLevel const & baseLevel = _mesh.GetLevel(0);
//
// Identify the vertex index for the specified corner of the face
// and topology information related to it:
//
Index vIndex = baseLevel.GetFaceVertices(baseFace)[cornerVertex];
ConstIndexArray vFaces = baseLevel.GetVertexFaces(vIndex);
int numFaces = vFaces.size();
bool isManifold = !baseLevel.IsVertexNonManifold(vIndex);
//
// Initialize, assign and finalize the vertex topology:
//
// Note that a SurfaceFactory cannot process vertices or faces whose
// valence or size exceeds pre-defined limits. These limits are the
// same as those in Far for TopologyRefiner (Far::VALENCE_LIMIT), so
// testing here is not strictly necessary, but assert()s are included
// here as a reminder for those mesh representations that may need to
// check and take action in such cases.
//
assert(numFaces <= OpenSubdiv::Bfr::Limits::MaxValence());
vd.Initialize(numFaces);
{
// Assign manifold (incident faces ordered) and boundary status:
vd.SetManifold(isManifold);
vd.SetBoundary(baseLevel.IsVertexBoundary(vIndex));
// Assign sizes of all incident faces:
for (int i = 0; i < numFaces; ++i) {
int incFaceSize = baseLevel.GetFaceVertices(vFaces[i]).size();
assert(incFaceSize <= OpenSubdiv::Bfr::Limits::MaxFaceSize());
vd.SetIncidentFaceSize(i, incFaceSize);
}
// Assign vertex sharpness:
vd.SetVertexSharpness(baseLevel.GetVertexSharpness(vIndex));
// Assign edge sharpness:
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 < numFaces; ++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 in the set of incident faces
// around the vertex:
//
if (isManifold) {
return vFaces.FindIndex(baseFace);
} else {
//
// Remember that for some non-manifold cases the face may occur
// multiple times around this vertex, so make sure to identify
// the instance of the base face whose corner face-vertex matches
// the one that was specified:
//
ConstLocalIndexArray vInFace =
baseLevel.GetVertexFaceLocalIndices(vIndex);
for (int i = 0; i < numFaces; ++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
CustomSurfaceFactory::getFaceVertexIncidentFaceVertexIndices(
Index baseFace, int cornerVertex,
Index indices[]) const {
return getFaceVertexPointIndices(baseFace, cornerVertex, indices, -1);
}
int
CustomSurfaceFactory::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);
}
int
CustomSurfaceFactory::getFaceVertexPointIndices(
Index baseFace, int cornerVertex,
Index indices[], int vtxOrFVarChannel) const {
TopologyLevel const & baseLevel = _mesh.GetLevel(0);
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);
// The location of this vertex in each incident face is known,
// rotate the order as we copy face-vertices to make it first:
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;
}

View File

@@ -0,0 +1,127 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include <shared_mutex>
#include <opensubdiv/bfr/surfaceFactory.h>
#include <opensubdiv/bfr/surfaceFactoryCache.h>
#include <opensubdiv/far/topologyRefiner.h>
//
// Definition of a subclass of SurfaceFactory for Far::TopologyRefiner:
//
// A subclass is free to define its own construction interface (given its
// unique mesh type) and to extend its public interface in any way that
// suits the mesh.
//
// Given each representation typically has its own way of representing
// primvars, using explicit primvar types in construction or other
// queries is likely -- especially face-varying primvars, whose topology
// is unique. For example, it may be useful to have the constructor
// specify a single face-varying primvar to be used for UVs when more
// than one are available.
//
// Unfortunately, the Far::TopologyRefiner can use integers for its face-
// varying channels, which the SurfaceFactory can use directly, so a more
// explicit association of primvars with integers is not necessary here.
//
class CustomSurfaceFactory : public OpenSubdiv::Bfr::SurfaceFactory {
public:
typedef OpenSubdiv::Far::TopologyRefiner TopologyRefiner;
public:
//
// Subclass-specific constructor:
//
CustomSurfaceFactory(TopologyRefiner const & mesh,
Options const & options = Options());
~CustomSurfaceFactory() override = default;
//
// Additional subclass-specific public methods:
//
TopologyRefiner const & GetMesh() const { return _mesh; }
//
// Convenience queries to verify bounds of integer arguments used by
// the SurfaceFactory, i.e. face indices and face-varying IDs:
//
int GetNumFaces() const;
int GetNumFVarChannels() const;
protected:
//
// Required virtual overrides to satisfy topological requirements:
//
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,
OpenSubdiv::Bfr::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;
private:
//
// Internal supporting method to gather indices -- either vertex or
// face-varying -- since both are accessed similarly:
//
int getFaceVaryingChannel(FVarID fvarID) const;
int getFaceVertexPointIndices(Index faceIndex, int faceVertex,
Index indices[], int vtxOrFVarChannel) const;
private:
//
// Typically a subclass adds member variables for an instance of a
// mesh and an instance of a local cache:
//
TopologyRefiner const & _mesh;
// The ownership of the local cache is deferred to the subclass in
// part so the subclass can choose one of its preferred type --
// depending on the level of thread-safety required.
//
// Bfr::SurfaceFactoryCache is a base class that allows for simple
// declaration of thread-safe subclasses via templates. If not
// requiring the cache to be thread-safe, using the base class is
// sufficient (as is done here). Use of threading extensions in
// more recent compilers allows for separate read and write locks,
// e.g.:
//
// typedef Bfr::ThreadSafeSurfaceFactoryCache
// < std::shared_mutex, std::shared_lock<std::shared_mutex>,
// std::unique_lock<std::shared_mutex> >
// LocalFactoryCacheType;
//
typedef OpenSubdiv::Bfr::SurfaceFactoryCache LocalFactoryCacheType;
LocalFactoryCacheType _localCache;
};
//
// Simple inline extensions to the public interface:
//
inline int
CustomSurfaceFactory::GetNumFaces() const {
return _mesh.GetLevel(0).GetNumFaces();
}
inline int
CustomSurfaceFactory::GetNumFVarChannels() const {
return _mesh.GetNumFVarChannels();
}

View File

@@ -0,0 +1,192 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../../../regression/common/far_utils.h"
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/far/topologyDescriptor.h>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <sstream>
// Utilities local to this tutorial:
namespace tutorial {
using namespace OpenSubdiv;
//
// Create a TopologyRefiner from default geometry:
//
Far::TopologyRefiner *
dfltTopologyRefiner(std::vector<float> & posVector,
std::vector<float> & uvVector) {
//
// Default topology and positions for a cube:
//
int dfltNumFaces = 6;
int dfltNumVerts = 8;
int dfltNumUVs = 16;
int dfltFaceSizes[6] = { 4, 4, 4, 4, 4, 4 };
int dfltFaceVerts[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
float dfltPositions[8][3] = {{ -0.5f, -0.5f, 0.5f },
{ 0.5f, -0.5f, 0.5f },
{ -0.5f, 0.5f, 0.5f },
{ 0.5f, 0.5f, 0.5f },
{ -0.5f, 0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },
{ -0.5f, -0.5f, -0.5f },
{ 0.5f, -0.5f, -0.5f }};
int dfltFaceFVars[24] = { 9, 10, 14, 13,
4, 0, 1, 5,
5, 1, 2, 6,
6, 2, 3, 7,
10, 11, 15, 14,
8, 9, 13, 12 };
float dfltUVs[16][2] = {{ 0.05f, 0.05f },
{ 0.35f, 0.15f },
{ 0.65f, 0.15f },
{ 0.95f, 0.05f },
{ 0.05f, 0.35f },
{ 0.35f, 0.45f },
{ 0.65f, 0.45f },
{ 0.95f, 0.35f },
{ 0.05f, 0.65f },
{ 0.35f, 0.55f },
{ 0.65f, 0.55f },
{ 0.95f, 0.65f },
{ 0.05f, 0.95f },
{ 0.35f, 0.85f },
{ 0.65f, 0.85f },
{ 0.95f, 0.95f }};
posVector.resize(8 * 3);
std::memcpy(&posVector[0], dfltPositions, 8 * 3 * sizeof(float));
uvVector.resize(16 * 2);
std::memcpy(&uvVector[0], dfltUVs, 16 * 2 * sizeof(float));
//
// Initialize a Far::TopologyDescriptor, from which to create
// the Far::TopologyRefiner:
//
typedef Far::TopologyDescriptor Descriptor;
Descriptor::FVarChannel uvChannel;
uvChannel.numValues = dfltNumUVs;
uvChannel.valueIndices = dfltFaceFVars;
Descriptor topDescriptor;
topDescriptor.numVertices = dfltNumVerts;
topDescriptor.numFaces = dfltNumFaces;
topDescriptor.numVertsPerFace = dfltFaceSizes;
topDescriptor.vertIndicesPerFace = dfltFaceVerts;
topDescriptor.numFVarChannels = 1;
topDescriptor.fvarChannels = &uvChannel;
Sdc::SchemeType schemeType = Sdc::SCHEME_CATMARK;
Sdc::Options schemeOptions;
schemeOptions.SetVtxBoundaryInterpolation(
Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
schemeOptions.SetFVarLinearInterpolation(
Sdc::Options::FVAR_LINEAR_CORNERS_ONLY);
typedef Far::TopologyRefinerFactory<Descriptor> RefinerFactory;
Far::TopologyRefiner * topRefiner =
RefinerFactory::Create(topDescriptor,
RefinerFactory::Options(schemeType, schemeOptions));
assert(topRefiner);
return topRefiner;
}
//
// Create a TopologyRefiner from a specified Obj file:
//
Far::TopologyRefiner *
readTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
const char * filename = objFileName.c_str();
const Shape * shape = 0;
std::ifstream ifs(filename);
if (ifs) {
std::stringstream ss;
ss << ifs.rdbuf();
ifs.close();
std::string shapeString = ss.str();
shape = Shape::parseObj(
shapeString.c_str(), ConvertSdcTypeToShapeScheme(schemeType), false);
if (shape == 0) {
fprintf(stderr,
"Error: Cannot create Shape from Obj file '%s'\n", filename);
return 0;
}
} else {
fprintf(stderr, "Error: Cannot open Obj file '%s'\n", filename);
return 0;
}
Sdc::SchemeType sdcType = GetSdcType(*shape);
Sdc::Options sdcOptions = GetSdcOptions(*shape);
Far::TopologyRefiner * refiner = Far::TopologyRefinerFactory<Shape>::Create(
*shape, Far::TopologyRefinerFactory<Shape>::Options(sdcType, sdcOptions));
if (refiner == 0) {
fprintf(stderr,
"Error: Unable to construct TopologyRefiner from Obj file '%s'\n",
filename);
return 0;
}
int numVertices = refiner->GetNumVerticesTotal();
posVector.resize(numVertices * 3);
std::memcpy(&posVector[0], &shape->verts[0], 3*numVertices*sizeof(float));
uvVector.resize(0);
if (refiner->GetNumFVarChannels()) {
int numUVs = refiner->GetNumFVarValuesTotal(0);
uvVector.resize(numUVs * 2);
std::memcpy(&uvVector[0], &shape->uvs[0], 2 * numUVs*sizeof(float));
}
delete shape;
return refiner;
}
Far::TopologyRefiner *
createTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
if (objFileName.empty()) {
return dfltTopologyRefiner(posVector, uvVector);
} else {
return readTopologyRefiner(objFileName, schemeType,
posVector, uvVector);
}
}
} // end namespace

View File

@@ -0,0 +1,176 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include <string>
#include <vector>
#include <cstdio>
#include <cmath>
#include <cassert>
// Utilities local to this tutorial:
namespace tutorial {
//
// Simple class to write vertex positions, normals and faces to a
// specified Obj file:
//
class ObjWriter {
public:
ObjWriter(std::string const &filename = 0);
~ObjWriter();
int GetNumVertices() const { return _numVertices; }
int GetNumFaces() const { return _numFaces; }
void WriteVertexPositions(std::vector<float> const & p, int size = 3);
void WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv);
void WriteVertexUVs(std::vector<float> const & uv);
void WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool writeNormalIndices = false,
bool writeUVIndices = false);
void WriteGroupName(char const * prefix, int index);
private:
void getNormal(float N[3], float const du[3], float const dv[3]) const;
private:
std::string _filename;
FILE * _fptr;
int _numVertices;
int _numNormals;
int _numUVs;
int _numFaces;
};
//
// Definitions ObjWriter methods:
//
ObjWriter::ObjWriter(std::string const &filename) :
_fptr(0), _numVertices(0), _numNormals(0), _numUVs(0), _numFaces(0) {
if (filename != std::string()) {
_fptr = fopen(filename.c_str(), "w");
if (_fptr == 0) {
fprintf(stderr, "Error: ObjWriter cannot open Obj file '%s'\n",
filename.c_str());
}
}
if (_fptr == 0) _fptr = stdout;
}
ObjWriter::~ObjWriter() {
if (_fptr != stdout) fclose(_fptr);
}
void
ObjWriter::WriteVertexPositions(std::vector<float> const & pos, int dim) {
assert(dim >= 2);
int numNewVerts = (int)pos.size() / dim;
float const * P = pos.data();
for (int i = 0; i < numNewVerts; ++i, P += dim) {
if (dim == 2) {
fprintf(_fptr, "v %f %f 0.0\n", P[0], P[1]);
} else {
fprintf(_fptr, "v %f %f %f\n", P[0], P[1], P[2]);
}
}
_numVertices += numNewVerts;
}
void
ObjWriter::getNormal(float N[3], float const du[3], float const dv[3]) const {
N[0] = du[1] * dv[2] - du[2] * dv[1];
N[1] = du[2] * dv[0] - du[0] * dv[2];
N[2] = du[0] * dv[1] - du[1] * dv[0];
float lenSqrd = N[0] * N[0] + N[1] * N[1] + N[2] * N[2];
if (lenSqrd <= 0.0f) {
N[0] = 0.0f;
N[1] = 0.0f;
N[2] = 0.0f;
} else {
float lenInv = 1.0f / std::sqrt(lenSqrd);
N[0] *= lenInv;
N[1] *= lenInv;
N[2] *= lenInv;
}
}
void
ObjWriter::WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv) {
assert(du.size() == dv.size());
int numNewNormals = (int)du.size() / 3;
float const * dPdu = &du[0];
float const * dPdv = &dv[0];
for (int i = 0; i < numNewNormals; ++i, dPdu += 3, dPdv += 3) {
float N[3];
getNormal(N, dPdu, dPdv);
fprintf(_fptr, "vn %f %f %f\n", N[0], N[1], N[2]);
}
_numNormals += numNewNormals;
}
void
ObjWriter::WriteVertexUVs(std::vector<float> const & uv) {
int numNewUVs = (int)uv.size() / 2;
for (int i = 0; i < numNewUVs; ++i) {
fprintf(_fptr, "vt %f %f\n", uv[i*2], uv[i*2+1]);
}
_numUVs += numNewUVs;
}
void
ObjWriter::WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool includeNormalIndices, bool includeUVIndices) {
int numNewFaces = (int)faceVertices.size() / faceSize;
int const * v = &faceVertices[0];
for (int i = 0; i < numNewFaces; ++i, v += faceSize) {
fprintf(_fptr, "f ");
for (int j = 0; j < faceSize; ++j) {
if (v[j] >= 0) {
// Remember Obj indices start with 1:
int vIndex = 1 + v[j];
if (includeNormalIndices && includeUVIndices) {
fprintf(_fptr, " %d/%d/%d", vIndex, vIndex, vIndex);
} else if (includeNormalIndices) {
fprintf(_fptr, " %d//%d", vIndex, vIndex);
} else if (includeUVIndices) {
fprintf(_fptr, " %d/%d", vIndex, vIndex);
} else {
fprintf(_fptr, " %d", vIndex);
}
}
}
fprintf(_fptr, "\n");
}
_numFaces += numNewFaces;
}
void
ObjWriter::WriteGroupName(char const * prefix, int index) {
fprintf(_fptr, "g %s%d\n", prefix ? prefix : "", index);
}
} // end namespace

View File

@@ -0,0 +1,11 @@
#
# Copyright 2021 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_bfr_tutorial(
bfr_tutorial_3_2
bfr_tutorial_3_2.cpp
)

View File

@@ -0,0 +1,381 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// 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;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,192 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include "../../../regression/common/far_utils.h"
#include <opensubdiv/far/topologyRefiner.h>
#include <opensubdiv/far/topologyDescriptor.h>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <sstream>
// Utilities local to this tutorial:
namespace tutorial {
using namespace OpenSubdiv;
//
// Create a TopologyRefiner from default geometry:
//
Far::TopologyRefiner *
dfltTopologyRefiner(std::vector<float> & posVector,
std::vector<float> & uvVector) {
//
// Default topology and positions for a cube:
//
int dfltNumFaces = 6;
int dfltNumVerts = 8;
int dfltNumUVs = 16;
int dfltFaceSizes[6] = { 4, 4, 4, 4, 4, 4 };
int dfltFaceVerts[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
float dfltPositions[8][3] = {{ -0.5f, -0.5f, 0.5f },
{ 0.5f, -0.5f, 0.5f },
{ -0.5f, 0.5f, 0.5f },
{ 0.5f, 0.5f, 0.5f },
{ -0.5f, 0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },
{ -0.5f, -0.5f, -0.5f },
{ 0.5f, -0.5f, -0.5f }};
int dfltFaceFVars[24] = { 9, 10, 14, 13,
4, 0, 1, 5,
5, 1, 2, 6,
6, 2, 3, 7,
10, 11, 15, 14,
8, 9, 13, 12 };
float dfltUVs[16][2] = {{ 0.05f, 0.05f },
{ 0.35f, 0.15f },
{ 0.65f, 0.15f },
{ 0.95f, 0.05f },
{ 0.05f, 0.35f },
{ 0.35f, 0.45f },
{ 0.65f, 0.45f },
{ 0.95f, 0.35f },
{ 0.05f, 0.65f },
{ 0.35f, 0.55f },
{ 0.65f, 0.55f },
{ 0.95f, 0.65f },
{ 0.05f, 0.95f },
{ 0.35f, 0.85f },
{ 0.65f, 0.85f },
{ 0.95f, 0.95f }};
posVector.resize(8 * 3);
std::memcpy(&posVector[0], dfltPositions, 8 * 3 * sizeof(float));
uvVector.resize(16 * 2);
std::memcpy(&uvVector[0], dfltUVs, 16 * 2 * sizeof(float));
//
// Initialize a Far::TopologyDescriptor, from which to create
// the Far::TopologyRefiner:
//
typedef Far::TopologyDescriptor Descriptor;
Descriptor::FVarChannel uvChannel;
uvChannel.numValues = dfltNumUVs;
uvChannel.valueIndices = dfltFaceFVars;
Descriptor topDescriptor;
topDescriptor.numVertices = dfltNumVerts;
topDescriptor.numFaces = dfltNumFaces;
topDescriptor.numVertsPerFace = dfltFaceSizes;
topDescriptor.vertIndicesPerFace = dfltFaceVerts;
topDescriptor.numFVarChannels = 1;
topDescriptor.fvarChannels = &uvChannel;
Sdc::SchemeType schemeType = Sdc::SCHEME_CATMARK;
Sdc::Options schemeOptions;
schemeOptions.SetVtxBoundaryInterpolation(
Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
schemeOptions.SetFVarLinearInterpolation(
Sdc::Options::FVAR_LINEAR_CORNERS_ONLY);
typedef Far::TopologyRefinerFactory<Descriptor> RefinerFactory;
Far::TopologyRefiner * topRefiner =
RefinerFactory::Create(topDescriptor,
RefinerFactory::Options(schemeType, schemeOptions));
assert(topRefiner);
return topRefiner;
}
//
// Create a TopologyRefiner from a specified Obj file:
//
Far::TopologyRefiner *
readTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
const char * filename = objFileName.c_str();
const Shape * shape = 0;
std::ifstream ifs(filename);
if (ifs) {
std::stringstream ss;
ss << ifs.rdbuf();
ifs.close();
std::string shapeString = ss.str();
shape = Shape::parseObj(
shapeString.c_str(), ConvertSdcTypeToShapeScheme(schemeType), false);
if (shape == 0) {
fprintf(stderr,
"Error: Cannot create Shape from Obj file '%s'\n", filename);
return 0;
}
} else {
fprintf(stderr, "Error: Cannot open Obj file '%s'\n", filename);
return 0;
}
Sdc::SchemeType sdcType = GetSdcType(*shape);
Sdc::Options sdcOptions = GetSdcOptions(*shape);
Far::TopologyRefiner * refiner = Far::TopologyRefinerFactory<Shape>::Create(
*shape, Far::TopologyRefinerFactory<Shape>::Options(sdcType, sdcOptions));
if (refiner == 0) {
fprintf(stderr,
"Error: Unable to construct TopologyRefiner from Obj file '%s'\n",
filename);
return 0;
}
int numVertices = refiner->GetNumVerticesTotal();
posVector.resize(numVertices * 3);
std::memcpy(&posVector[0], &shape->verts[0], 3*numVertices*sizeof(float));
uvVector.resize(0);
if (refiner->GetNumFVarChannels()) {
int numUVs = refiner->GetNumFVarValuesTotal(0);
uvVector.resize(numUVs * 2);
std::memcpy(&uvVector[0], &shape->uvs[0], 2 * numUVs*sizeof(float));
}
delete shape;
return refiner;
}
Far::TopologyRefiner *
createTopologyRefiner(std::string const & objFileName,
Sdc::SchemeType schemeType,
std::vector<float> & posVector,
std::vector<float> & uvVector) {
if (objFileName.empty()) {
return dfltTopologyRefiner(posVector, uvVector);
} else {
return readTopologyRefiner(objFileName, schemeType,
posVector, uvVector);
}
}
} // end namespace

View File

@@ -0,0 +1,176 @@
//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include <string>
#include <vector>
#include <cstdio>
#include <cmath>
#include <cassert>
// Utilities local to this tutorial:
namespace tutorial {
//
// Simple class to write vertex positions, normals and faces to a
// specified Obj file:
//
class ObjWriter {
public:
ObjWriter(std::string const &filename = 0);
~ObjWriter();
int GetNumVertices() const { return _numVertices; }
int GetNumFaces() const { return _numFaces; }
void WriteVertexPositions(std::vector<float> const & p, int size = 3);
void WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv);
void WriteVertexUVs(std::vector<float> const & uv);
void WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool writeNormalIndices = false,
bool writeUVIndices = false);
void WriteGroupName(char const * prefix, int index);
private:
void getNormal(float N[3], float const du[3], float const dv[3]) const;
private:
std::string _filename;
FILE * _fptr;
int _numVertices;
int _numNormals;
int _numUVs;
int _numFaces;
};
//
// Definitions ObjWriter methods:
//
ObjWriter::ObjWriter(std::string const &filename) :
_fptr(0), _numVertices(0), _numNormals(0), _numUVs(0), _numFaces(0) {
if (filename != std::string()) {
_fptr = fopen(filename.c_str(), "w");
if (_fptr == 0) {
fprintf(stderr, "Error: ObjWriter cannot open Obj file '%s'\n",
filename.c_str());
}
}
if (_fptr == 0) _fptr = stdout;
}
ObjWriter::~ObjWriter() {
if (_fptr != stdout) fclose(_fptr);
}
void
ObjWriter::WriteVertexPositions(std::vector<float> const & pos, int dim) {
assert(dim >= 2);
int numNewVerts = (int)pos.size() / dim;
float const * P = pos.data();
for (int i = 0; i < numNewVerts; ++i, P += dim) {
if (dim == 2) {
fprintf(_fptr, "v %f %f 0.0\n", P[0], P[1]);
} else {
fprintf(_fptr, "v %f %f %f\n", P[0], P[1], P[2]);
}
}
_numVertices += numNewVerts;
}
void
ObjWriter::getNormal(float N[3], float const du[3], float const dv[3]) const {
N[0] = du[1] * dv[2] - du[2] * dv[1];
N[1] = du[2] * dv[0] - du[0] * dv[2];
N[2] = du[0] * dv[1] - du[1] * dv[0];
float lenSqrd = N[0] * N[0] + N[1] * N[1] + N[2] * N[2];
if (lenSqrd <= 0.0f) {
N[0] = 0.0f;
N[1] = 0.0f;
N[2] = 0.0f;
} else {
float lenInv = 1.0f / std::sqrt(lenSqrd);
N[0] *= lenInv;
N[1] *= lenInv;
N[2] *= lenInv;
}
}
void
ObjWriter::WriteVertexNormals(std::vector<float> const & du,
std::vector<float> const & dv) {
assert(du.size() == dv.size());
int numNewNormals = (int)du.size() / 3;
float const * dPdu = &du[0];
float const * dPdv = &dv[0];
for (int i = 0; i < numNewNormals; ++i, dPdu += 3, dPdv += 3) {
float N[3];
getNormal(N, dPdu, dPdv);
fprintf(_fptr, "vn %f %f %f\n", N[0], N[1], N[2]);
}
_numNormals += numNewNormals;
}
void
ObjWriter::WriteVertexUVs(std::vector<float> const & uv) {
int numNewUVs = (int)uv.size() / 2;
for (int i = 0; i < numNewUVs; ++i) {
fprintf(_fptr, "vt %f %f\n", uv[i*2], uv[i*2+1]);
}
_numUVs += numNewUVs;
}
void
ObjWriter::WriteFaces(std::vector<int> const & faceVertices, int faceSize,
bool includeNormalIndices, bool includeUVIndices) {
int numNewFaces = (int)faceVertices.size() / faceSize;
int const * v = &faceVertices[0];
for (int i = 0; i < numNewFaces; ++i, v += faceSize) {
fprintf(_fptr, "f ");
for (int j = 0; j < faceSize; ++j) {
if (v[j] >= 0) {
// Remember Obj indices start with 1:
int vIndex = 1 + v[j];
if (includeNormalIndices && includeUVIndices) {
fprintf(_fptr, " %d/%d/%d", vIndex, vIndex, vIndex);
} else if (includeNormalIndices) {
fprintf(_fptr, " %d//%d", vIndex, vIndex);
} else if (includeUVIndices) {
fprintf(_fptr, " %d/%d", vIndex, vIndex);
} else {
fprintf(_fptr, " %d", vIndex);
}
}
}
fprintf(_fptr, "\n");
}
_numFaces += numNewFaces;
}
void
ObjWriter::WriteGroupName(char const * prefix, int index) {
fprintf(_fptr, "g %s%d\n", prefix ? prefix : "", index);
}
} // end namespace

View File

@@ -0,0 +1,51 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
macro(osd_add_far_tutorial NAME)
osd_add_executable(${NAME} "tutorials/far"
${ARGN}
$<TARGET_OBJECTS:sdc_obj>
$<TARGET_OBJECTS:vtr_obj>
$<TARGET_OBJECTS:far_obj>
)
install(TARGETS ${NAME} DESTINATION "${CMAKE_BINDIR_BASE}/tutorials")
endmacro()
set(TUTORIALS
tutorial_1_1
tutorial_1_2
tutorial_2_1
tutorial_2_2
tutorial_2_3
tutorial_3_1
tutorial_4_1
tutorial_4_2
tutorial_4_3
tutorial_5_1
tutorial_5_2
tutorial_5_3
)
foreach(tutorial ${TUTORIALS})
add_subdirectory("${tutorial}")
list(APPEND TUTORIAL_TARGETS "far_${tutorial}")
add_test(far_${tutorial} ${EXECUTABLE_OUTPUT_PATH}/far_${tutorial})
endforeach()
add_custom_target(far_tutorials DEPENDS ${TUTORIAL_TARGETS})
set_target_properties(far_tutorials
PROPERTIES
FOLDER "tutorials/far"
)

View File

@@ -0,0 +1,10 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_far_tutorial(
far_tutorial_1_1
far_tutorial_1_1.cpp
)

View File

@@ -0,0 +1,173 @@
//
// Copyright 2013 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial presents in a very succinct way the requisite steps to
// instantiate and refine a mesh with Far from simple topological data.
//
#include <opensubdiv/far/topologyDescriptor.h>
#include <opensubdiv/far/primvarRefiner.h>
#include <cstdio>
//------------------------------------------------------------------------------
// Vertex container implementation.
//
struct Vertex {
// Minimal required interface ----------------------
Vertex() { }
Vertex(Vertex const & src) {
_position[0] = src._position[0];
_position[1] = src._position[1];
_position[2] = src._position[2];
}
void Clear( void * =0 ) {
_position[0]=_position[1]=_position[2]=0.0f;
}
void AddWithWeight(Vertex const & src, float weight) {
_position[0]+=weight*src._position[0];
_position[1]+=weight*src._position[1];
_position[2]+=weight*src._position[2];
}
// Public interface ------------------------------------
void SetPosition(float x, float y, float z) {
_position[0]=x;
_position[1]=y;
_position[2]=z;
}
const float * GetPosition() const {
return _position;
}
private:
float _position[3];
};
//------------------------------------------------------------------------------
// Cube geometry from catmark_cube.h
static float g_verts[8][3] = {{ -0.5f, -0.5f, 0.5f },
{ 0.5f, -0.5f, 0.5f },
{ -0.5f, 0.5f, 0.5f },
{ 0.5f, 0.5f, 0.5f },
{ -0.5f, 0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },
{ -0.5f, -0.5f, -0.5f },
{ 0.5f, -0.5f, -0.5f }};
static int g_nverts = 8,
g_nfaces = 6;
static int g_vertsperface[6] = { 4, 4, 4, 4, 4, 4 };
static int g_vertIndices[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
using namespace OpenSubdiv;
//------------------------------------------------------------------------------
int main(int, char **) {
// Populate a topology descriptor with our raw data
typedef Far::TopologyDescriptor Descriptor;
Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK;
Sdc::Options options;
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
Descriptor desc;
desc.numVertices = g_nverts;
desc.numFaces = g_nfaces;
desc.numVertsPerFace = g_vertsperface;
desc.vertIndicesPerFace = g_vertIndices;
// Instantiate a Far::TopologyRefiner from the descriptor
Far::TopologyRefiner * refiner = Far::TopologyRefinerFactory<Descriptor>::Create(desc,
Far::TopologyRefinerFactory<Descriptor>::Options(type, options));
int maxlevel = 2;
// Uniformly refine the topology up to 'maxlevel'
refiner->RefineUniform(Far::TopologyRefiner::UniformOptions(maxlevel));
// Allocate a buffer for vertex primvar data. The buffer length is set to
// be the sum of all children vertices up to the highest level of refinement.
std::vector<Vertex> vbuffer(refiner->GetNumVerticesTotal());
Vertex * verts = &vbuffer[0];
// Initialize coarse mesh positions
int nCoarseVerts = g_nverts;
for (int i=0; i<nCoarseVerts; ++i) {
verts[i].SetPosition(g_verts[i][0], g_verts[i][1], g_verts[i][2]);
}
// Interpolate vertex primvar data
Far::PrimvarRefiner primvarRefiner(*refiner);
Vertex * src = verts;
for (int level = 1; level <= maxlevel; ++level) {
Vertex * dst = src + refiner->GetLevel(level-1).GetNumVertices();
primvarRefiner.Interpolate(level, src, dst);
src = dst;
}
{ // Output OBJ of the highest level refined -----------
Far::TopologyLevel const & refLastLevel = refiner->GetLevel(maxlevel);
int nverts = refLastLevel.GetNumVertices();
int nfaces = refLastLevel.GetNumFaces();
// Print vertex positions
int firstOfLastVerts = refiner->GetNumVerticesTotal() - nverts;
for (int vert = 0; vert < nverts; ++vert) {
float const * pos = verts[firstOfLastVerts + vert].GetPosition();
printf("v %f %f %f\n", pos[0], pos[1], pos[2]);
}
// Print faces
for (int face = 0; face < nfaces; ++face) {
Far::ConstIndexArray fverts = refLastLevel.GetFaceVertices(face);
// all refined Catmark faces should be quads
assert(fverts.size()==4);
printf("f ");
for (int vert=0; vert<fverts.size(); ++vert) {
printf("%d ", fverts[vert]+1); // OBJ uses 1-based arrays...
}
printf("\n");
}
}
delete refiner;
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,10 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_far_tutorial(
far_tutorial_1_2
far_tutorial_1_2.cpp
)

View File

@@ -0,0 +1,278 @@
//
// Copyright 2018 DreamWorks Animation LLC.
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial illustrates two different styles of defining classes for
// interpolating primvar data with the template methods in Far. The most
// common usage involves data of a fixed size, so the focus here is on an
// alternative supporting variable length data.
//
#include <opensubdiv/far/topologyDescriptor.h>
#include <opensubdiv/far/primvarRefiner.h>
#include <cstdio>
using namespace OpenSubdiv;
//
// Vertex data containers for interpolation:
// - Coord3 is fixed to support 3 floats
// - Coord2 is fixed to support 2 floats
// - CoordBuffer can support a specified number of floats
//
struct Coord3 {
Coord3() { }
Coord3(float x, float y, float z) { _xyz[0] = x, _xyz[1] = y, _xyz[2] = z; }
void Clear() { _xyz[0] = _xyz[1] = _xyz[2] = 0.0f; }
void AddWithWeight(Coord3 const & src, float weight) {
_xyz[0] += weight * src._xyz[0];
_xyz[1] += weight * src._xyz[1];
_xyz[2] += weight * src._xyz[2];
}
float const * Coords() const { return &_xyz[0]; }
private:
float _xyz[3];
};
struct Coord2 {
Coord2() { }
Coord2(float u, float v) { _uv[0] = u, _uv[1] = v; }
void Clear() { _uv[0] = _uv[1] = 0.0f; }
void AddWithWeight(Coord2 const & src, float weight) {
_uv[0] += weight * src._uv[0];
_uv[1] += weight * src._uv[1];
}
float const * Coords() const { return &_uv[0]; }
private:
float _uv[2];
};
struct CoordBuffer {
//
// The head of an external buffer and stride is specified on construction:
//
CoordBuffer(float * data, int size) : _data(data), _size(size) { }
CoordBuffer() : _data(0), _size(0) { }
void Clear() {
for (int i = 0; i < _size; ++i) {
_data[i] = 0.0f;
}
}
void AddWithWeight(CoordBuffer const & src, float weight) {
assert(src._size == _size);
for (int i = 0; i < _size; ++i) {
_data[i] += weight * src._data[i];
}
}
float const * Coords() const { return _data; }
//
// Defining [] to return a location elsewhere in the buffer is the key
// requirement to supporting interpolatible data of varying size
//
CoordBuffer operator[](int index) const {
return CoordBuffer(_data + index * _size, _size);
}
private:
float * _data;
int _size;
};
//
// Global cube geometry from catmark_cube.h
//
// Topology:
static int g_nverts = 8;
static int g_nfaces = 6;
static int g_vertsperface[6] = { 4, 4, 4, 4, 4, 4 };
static int g_vertIndices[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
// Primvar data:
static float g_verts[8][3] = {{ 0.0f, 0.0f, 1.0f },
{ 1.0f, 0.0f, 1.0f },
{ 0.0f, 1.0f, 1.0f },
{ 1.0f, 1.0f, 1.0f },
{ 0.0f, 1.0f, 0.0f },
{ 1.0f, 1.0f, 0.0f },
{ 0.0f, 0.0f, 0.0f },
{ 1.0f, 0.0f, 0.0f }};
//
// Creates Far::TopologyRefiner from raw geometry above (see tutorial_1_1 for
// more details)
//
static Far::TopologyRefiner *
createFarTopologyRefiner() {
typedef Far::TopologyDescriptor Descriptor;
Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK;
Sdc::Options options;
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
Descriptor desc;
desc.numVertices = g_nverts;
desc.numFaces = g_nfaces;
desc.numVertsPerFace = g_vertsperface;
desc.vertIndicesPerFace = g_vertIndices;
// Instantiate a Far::TopologyRefiner from the descriptor
Far::TopologyRefiner * refiner =
Far::TopologyRefinerFactory<Descriptor>::Create(desc,
Far::TopologyRefinerFactory<Descriptor>::Options(type, options));
return refiner;
}
//
// Overview of main():
// - create a Far::TopologyRefiner and uniformly refine it
// - allocate separate and combined data buffers for vertex positions and UVs
// - populate all refined data buffers and compare results
// - write the result in Obj format
//
// Disable warnings for exact floating point comparisons:
#ifdef __INTEL_COMPILER
#pragma warning disable 1572
#endif
int main(int, char **) {
// Instantiate a Far::TopologyRefiner from the global geometry:
Far::TopologyRefiner * refiner = createFarTopologyRefiner();
// Uniformly refine the topology up to 'maxlevel'
int maxlevel = 2;
refiner->RefineUniform(Far::TopologyRefiner::UniformOptions(maxlevel));
// Allocate and populate data buffers for vertex primvar data -- positions and
// UVs. We assign UV coordiantes by simply projecting/assigning XY values.
// The position and UV buffers use their associated data types, while the
// combined buffer uses 5 floats per vertex.
//
int numBaseVertices = g_nverts;
int numTotalVertices = refiner->GetNumVerticesTotal();
std::vector<Coord3> posData(numTotalVertices);
std::vector<Coord2> uvData(numTotalVertices);
int combinedStride = 3 + 2;
std::vector<float> combinedData(numTotalVertices * combinedStride);
for (int i = 0; i < numBaseVertices; ++i) {
posData[i] = Coord3(g_verts[i][0], g_verts[i][1], g_verts[i][2]);
uvData[i] = Coord2(g_verts[i][0], g_verts[i][1]);
float * coordCombined = &combinedData[i * combinedStride];
coordCombined[0] = g_verts[i][0];
coordCombined[1] = g_verts[i][1];
coordCombined[2] = g_verts[i][2];
coordCombined[3] = g_verts[i][0];
coordCombined[4] = g_verts[i][1];
}
// Interpolate vertex primvar data
Far::PrimvarRefiner primvarRefiner(*refiner);
Coord3 * posSrc = &posData[0];
Coord2 * uvSrc = & uvData[0];
CoordBuffer combinedSrc(&combinedData[0], combinedStride);
for (int level = 1; level <= maxlevel; ++level) {
int numLevelVerts = refiner->GetLevel(level-1).GetNumVertices();
Coord3 * posDst = posSrc + numLevelVerts;
Coord2 * uvDst = uvSrc + numLevelVerts;
CoordBuffer combinedDst = combinedSrc[numLevelVerts];
primvarRefiner.Interpolate(level, posSrc, posDst);
primvarRefiner.Interpolate(level, uvSrc, uvDst);
primvarRefiner.Interpolate(level, combinedSrc, combinedDst);
posSrc = posDst;
uvSrc = uvDst;
combinedSrc = combinedDst;
}
// Verify that the combined coords match the separate results:
for (int i = numBaseVertices; i < numTotalVertices; ++i) {
float const * posCoords = posData[i].Coords();
float const * uvCoords = uvData[i].Coords();
float const * combCoords = &combinedData[combinedStride * i];
assert(combCoords[0] == posCoords[0]);
assert(combCoords[1] == posCoords[1]);
assert(combCoords[2] == posCoords[2]);
assert(combCoords[3] == uvCoords[0]);
assert(combCoords[4] == uvCoords[1]);
}
//
// Output OBJ of the highest level refined:
//
Far::TopologyLevel const & refLastLevel = refiner->GetLevel(maxlevel);
int firstOfLastVerts = numTotalVertices - refLastLevel.GetNumVertices();
// Print vertex positions
printf("# Vertices:\n");
for (int vert = firstOfLastVerts; vert < numTotalVertices; ++vert) {
float const * pos = &combinedData[vert * combinedStride];
printf("v %f %f %f\n", pos[0], pos[1], pos[2]);
}
printf("# UV coordinates:\n");
for (int vert = firstOfLastVerts; vert < numTotalVertices; ++vert) {
float const * uv = &combinedData[vert * combinedStride] + 3;
printf("vt %f %f\n", uv[0], uv[1]);
}
// Print faces
int numFaces = refLastLevel.GetNumFaces();
printf("# Faces:\n");
for (int face = 0; face < numFaces; ++face) {
Far::ConstIndexArray fverts = refLastLevel.GetFaceVertices(face);
printf("f ");
for (int fvert = 0; fvert < fverts.size(); ++fvert) {
int objIndex = 1 + fverts[fvert]; // OBJ uses 1-based arrays...
printf("%d/%d ", objIndex, objIndex);
}
printf("\n");
}
delete refiner;
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,10 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_far_tutorial(
far_tutorial_2_1
far_tutorial_2_1.cpp
)

View File

@@ -0,0 +1,228 @@
//
// Copyright 2013 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// Building on tutorial 0, this example shows how to instantiate a simple mesh,
// refine it uniformly and then interpolate additional sets of primvar data.
//
#include <opensubdiv/far/topologyDescriptor.h>
#include <opensubdiv/far/primvarRefiner.h>
#include <cstdio>
//------------------------------------------------------------------------------
// Vertex container implementation.
//
// We are adding a per-vertex color attribute to our primvar data. While they
// are separate properties and exist in separate buffers (as when read from an
// Alembic file) they are both of the form float[3] and so we can use the same
// underlying type.
//
// While color and position may be the same, we'll make the color a "varying"
// primvar, e.g. it is constrained to being linearly interpolated between
// vertices, rather than smoothly like position and other vertex data.
//
struct Point3 {
// Minimal required interface ----------------------
Point3() { }
void Clear( void * =0 ) {
_point[0]=_point[1]=_point[2]=0.0f;
}
void AddWithWeight(Point3 const & src, float weight) {
_point[0]+=weight*src._point[0];
_point[1]+=weight*src._point[1];
_point[2]+=weight*src._point[2];
}
// Public interface ------------------------------------
void SetPoint(float x, float y, float z) {
_point[0]=x;
_point[1]=y;
_point[2]=z;
}
const float * GetPoint() const {
return _point;
}
private:
float _point[3];
};
typedef Point3 VertexPosition;
typedef Point3 VertexColor;
//------------------------------------------------------------------------------
// Cube geometry from catmark_cube.h
static float g_verts[8][3] = {{ -0.5f, -0.5f, 0.5f },
{ 0.5f, -0.5f, 0.5f },
{ -0.5f, 0.5f, 0.5f },
{ 0.5f, 0.5f, 0.5f },
{ -0.5f, 0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },
{ -0.5f, -0.5f, -0.5f },
{ 0.5f, -0.5f, -0.5f }};
// Per-vertex RGB color data
static float g_colors[8][3] = {{ 1.0f, 0.0f, 0.5f },
{ 0.0f, 1.0f, 0.0f },
{ 0.0f, 0.0f, 1.0f },
{ 1.0f, 1.0f, 1.0f },
{ 1.0f, 1.0f, 0.0f },
{ 0.0f, 1.0f, 1.0f },
{ 1.0f, 0.0f, 1.0f },
{ 0.0f, 0.0f, 0.0f }};
static int g_nverts = 8,
g_nfaces = 6;
static int g_vertsperface[6] = { 4, 4, 4, 4, 4, 4 };
static int g_vertIndices[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
using namespace OpenSubdiv;
static Far::TopologyRefiner * createFarTopologyRefiner();
//------------------------------------------------------------------------------
int main(int, char **) {
int maxlevel = 5;
Far::TopologyRefiner * refiner = createFarTopologyRefiner();
// Uniformly refine the topology up to 'maxlevel'
refiner->RefineUniform(Far::TopologyRefiner::UniformOptions(maxlevel));
// Allocate buffers for vertex primvar data.
//
// We assume we received the coarse data for the mesh in separate buffers
// from some other source, e.g. an Alembic file. Meanwhile, we want buffers
// for the last/finest subdivision level to persist. We have no interest
// in the intermediate levels.
//
// Determine the sizes for our needs:
int nCoarseVerts = g_nverts;
int nFineVerts = refiner->GetLevel(maxlevel).GetNumVertices();
int nTotalVerts = refiner->GetNumVerticesTotal();
int nTempVerts = nTotalVerts - nCoarseVerts - nFineVerts;
// Allocate and initialize the primvar data for the original coarse vertices:
std::vector<VertexPosition> coarsePosBuffer(nCoarseVerts);
std::vector<VertexColor> coarseClrBuffer(nCoarseVerts);
for (int i = 0; i < nCoarseVerts; ++i) {
coarsePosBuffer[i].SetPoint(g_verts[i][0], g_verts[i][1], g_verts[i][2]);
coarseClrBuffer[i].SetPoint(g_colors[i][0], g_colors[i][1], g_colors[i][2]);
}
// Allocate intermediate and final storage to be populated:
std::vector<VertexPosition> tempPosBuffer(nTempVerts);
std::vector<VertexPosition> finePosBuffer(nFineVerts);
std::vector<VertexColor> tempClrBuffer(nTempVerts);
std::vector<VertexColor> fineClrBuffer(nFineVerts);
// Interpolate all primvar data -- separate buffers can be populated on
// separate threads if desired:
VertexPosition * srcPos = &coarsePosBuffer[0];
VertexPosition * dstPos = &tempPosBuffer[0];
VertexColor * srcClr = &coarseClrBuffer[0];
VertexColor * dstClr = &tempClrBuffer[0];
Far::PrimvarRefiner primvarRefiner(*refiner);
for (int level = 1; level < maxlevel; ++level) {
primvarRefiner.Interpolate( level, srcPos, dstPos);
primvarRefiner.InterpolateVarying(level, srcClr, dstClr);
srcPos = dstPos, dstPos += refiner->GetLevel(level).GetNumVertices();
srcClr = dstClr, dstClr += refiner->GetLevel(level).GetNumVertices();
}
// Interpolate the last level into the separate buffers for our final data:
primvarRefiner.Interpolate( maxlevel, srcPos, finePosBuffer);
primvarRefiner.InterpolateVarying(maxlevel, srcClr, fineClrBuffer);
{ // Visualization with Maya : print a MEL script that generates colored
// particles at the location of the refined vertices (don't forget to
// turn shading on in the viewport to see the colors)
int nverts = nFineVerts;
// Output particle positions
printf("particle ");
for (int vert = 0; vert < nverts; ++vert) {
float const * pos = finePosBuffer[vert].GetPoint();
printf("-p %f %f %f\n", pos[0], pos[1], pos[2]);
}
printf(";\n");
// Set particle point size (20 -- very large)
printf("addAttr -is true -ln \"pointSize\" -at long -dv 20 particleShape1;\n");
// Add per-particle color attribute ('rgbPP')
printf("addAttr -ln \"rgbPP\" -dt vectorArray particleShape1;\n");
// Set per-particle color values from our primvar data
printf("setAttr \"particleShape1.rgbPP\" -type \"vectorArray\" %d ", nverts);
for (int vert = 0; vert < nverts; ++vert) {
float const * color = fineClrBuffer[vert].GetPoint();
printf("%f %f %f\n", color[0], color[1], color[2]);
}
printf(";\n");
}
delete refiner;
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------
// Creates Far::TopologyRefiner from raw geometry
//
// see tutorial_1_1 for more details
//
static Far::TopologyRefiner *
createFarTopologyRefiner() {
// Populate a topology descriptor with our raw data
typedef Far::TopologyDescriptor Descriptor;
Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK;
Sdc::Options options;
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
Descriptor desc;
desc.numVertices = g_nverts;
desc.numFaces = g_nfaces;
desc.numVertsPerFace = g_vertsperface;
desc.vertIndicesPerFace = g_vertIndices;
// Instantiate a Far::TopologyRefiner from the descriptor
Far::TopologyRefiner * refiner =
Far::TopologyRefinerFactory<Descriptor>::Create(desc,
Far::TopologyRefinerFactory<Descriptor>::Options(type, options));
return refiner;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,10 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_far_tutorial(
far_tutorial_2_2
far_tutorial_2_2.cpp
)

View File

@@ -0,0 +1,345 @@
//
// Copyright 2013 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// Building on tutorial 0, this example shows how to instantiate a simple mesh,
// refine it uniformly and then interpolate both 'vertex' and 'face-varying'
// primvar data.
// The resulting interpolated data is output as an 'obj' file, with the
// 'face-varying' data recorded in the uv texture layout.
//
#include <opensubdiv/far/topologyDescriptor.h>
#include <opensubdiv/far/primvarRefiner.h>
#include <cstdio>
//------------------------------------------------------------------------------
// Face-varying implementation.
//
//
struct Vertex {
// Minimal required interface ----------------------
Vertex() { }
Vertex(Vertex const & src) {
_position[0] = src._position[0];
_position[1] = src._position[1];
_position[2] = src._position[2];
}
void Clear( void * =0 ) {
_position[0]=_position[1]=_position[2]=0.0f;
}
void AddWithWeight(Vertex const & src, float weight) {
_position[0]+=weight*src._position[0];
_position[1]+=weight*src._position[1];
_position[2]+=weight*src._position[2];
}
// Public interface ------------------------------------
void SetPosition(float x, float y, float z) {
_position[0]=x;
_position[1]=y;
_position[2]=z;
}
const float * GetPosition() const {
return _position;
}
private:
float _position[3];
};
//------------------------------------------------------------------------------
// Face-varying container implementation.
//
// We are using a uv texture layout as a 'face-varying' primitive variable
// attribute. Because face-varying data is specified 'per-face-per-vertex',
// we cannot use the same container that we use for 'vertex' or 'varying'
// data. We specify a new container, which only carries (u,v) coordinates.
// Similarly to our 'Vertex' container, we add a minimalistic interpolation
// interface with a 'Clear()' and 'AddWithWeight()' methods.
//
struct FVarVertexUV {
// Minimal required interface ----------------------
void Clear() {
u=v=0.0f;
}
void AddWithWeight(FVarVertexUV const & src, float weight) {
u += weight * src.u;
v += weight * src.v;
}
// Basic 'uv' layout channel
float u,v;
};
struct FVarVertexColor {
// Minimal required interface ----------------------
void Clear() {
r=g=b=a=0.0f;
}
void AddWithWeight(FVarVertexColor const & src, float weight) {
r += weight * src.r;
g += weight * src.g;
b += weight * src.b;
a += weight * src.a;
}
// Basic 'color' layout channel
float r,g,b,a;
};
//------------------------------------------------------------------------------
// Cube geometry from catmark_cube.h
// 'vertex' primitive variable data & topology
static float g_verts[8][3] = {{ -0.5f, -0.5f, 0.5f },
{ 0.5f, -0.5f, 0.5f },
{ -0.5f, 0.5f, 0.5f },
{ 0.5f, 0.5f, 0.5f },
{ -0.5f, 0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },
{ -0.5f, -0.5f, -0.5f },
{ 0.5f, -0.5f, -0.5f }};
static int g_nverts = 8,
g_nfaces = 6;
static int g_vertsperface[6] = { 4, 4, 4, 4, 4, 4 };
static int g_vertIndices[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
// 'face-varying' primitive variable data & topology for UVs
static float g_uvs[14][2] = {{ 0.375, 0.00 },
{ 0.625, 0.00 },
{ 0.375, 0.25 },
{ 0.625, 0.25 },
{ 0.375, 0.50 },
{ 0.625, 0.50 },
{ 0.375, 0.75 },
{ 0.625, 0.75 },
{ 0.375, 1.00 },
{ 0.625, 1.00 },
{ 0.875, 0.00 },
{ 0.875, 0.25 },
{ 0.125, 0.00 },
{ 0.125, 0.25 }};
static int g_nuvs = 14;
static int g_uvIndices[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 9, 8,
1, 10, 11, 3,
12, 0, 2, 13 };
// 'face-varying' primitive variable data & topology for color
static float g_colors[24][4] = {{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 0.0, 0.0, 1.0},
{1.0, 0.0, 0.0, 1.0},
{1.0, 0.0, 0.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0}};
static int g_ncolors = 24;
static int g_colorIndices[24] = { 0, 3, 9, 6,
7, 10, 15, 12,
13, 16, 21, 18,
19, 22, 4, 1,
5, 23, 17, 11,
20, 2, 8, 14 };
using namespace OpenSubdiv;
//------------------------------------------------------------------------------
int main(int, char **) {
int maxlevel = 3;
typedef Far::TopologyDescriptor Descriptor;
Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK;
Sdc::Options options;
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
options.SetFVarLinearInterpolation(Sdc::Options::FVAR_LINEAR_NONE);
// Populate a topology descriptor with our raw data
Descriptor desc;
desc.numVertices = g_nverts;
desc.numFaces = g_nfaces;
desc.numVertsPerFace = g_vertsperface;
desc.vertIndicesPerFace = g_vertIndices;
int channelUV = 0;
int channelColor = 1;
// Create a face-varying channel descriptor
Descriptor::FVarChannel channels[2];
channels[channelUV].numValues = g_nuvs;
channels[channelUV].valueIndices = g_uvIndices;
channels[channelColor].numValues = g_ncolors;
channels[channelColor].valueIndices = g_colorIndices;
// Add the channel topology to the main descriptor
desc.numFVarChannels = 2;
desc.fvarChannels = channels;
// Instantiate a Far::TopologyRefiner from the descriptor
Far::TopologyRefiner * refiner =
Far::TopologyRefinerFactory<Descriptor>::Create(desc,
Far::TopologyRefinerFactory<Descriptor>::Options(type, options));
// Uniformly refine the topology up to 'maxlevel'
// note: fullTopologyInLastLevel must be true to work with face-varying data
{
Far::TopologyRefiner::UniformOptions refineOptions(maxlevel);
refineOptions.fullTopologyInLastLevel = true;
refiner->RefineUniform(refineOptions);
}
// Allocate and initialize the 'vertex' primvar data (see tutorial 2 for
// more details).
std::vector<Vertex> vbuffer(refiner->GetNumVerticesTotal());
Vertex * verts = &vbuffer[0];
for (int i=0; i<g_nverts; ++i) {
verts[i].SetPosition(g_verts[i][0], g_verts[i][1], g_verts[i][2]);
}
// Allocate and initialize the first channel of 'face-varying' primvar data (UVs)
std::vector<FVarVertexUV> fvBufferUV(refiner->GetNumFVarValuesTotal(channelUV));
FVarVertexUV * fvVertsUV = &fvBufferUV[0];
for (int i=0; i<g_nuvs; ++i) {
fvVertsUV[i].u = g_uvs[i][0];
fvVertsUV[i].v = g_uvs[i][1];
}
// Allocate & interpolate the 'face-varying' primvar data (colors)
std::vector<FVarVertexColor> fvBufferColor(refiner->GetNumFVarValuesTotal(channelColor));
FVarVertexColor * fvVertsColor = &fvBufferColor[0];
for (int i=0; i<g_ncolors; ++i) {
fvVertsColor[i].r = g_colors[i][0];
fvVertsColor[i].g = g_colors[i][1];
fvVertsColor[i].b = g_colors[i][2];
fvVertsColor[i].a = g_colors[i][3];
}
// Interpolate both vertex and face-varying primvar data
Far::PrimvarRefiner primvarRefiner(*refiner);
Vertex * srcVert = verts;
FVarVertexUV * srcFVarUV = fvVertsUV;
FVarVertexColor * srcFVarColor = fvVertsColor;
for (int level = 1; level <= maxlevel; ++level) {
Vertex * dstVert = srcVert + refiner->GetLevel(level-1).GetNumVertices();
FVarVertexUV * dstFVarUV = srcFVarUV + refiner->GetLevel(level-1).GetNumFVarValues(channelUV);
FVarVertexColor * dstFVarColor = srcFVarColor + refiner->GetLevel(level-1).GetNumFVarValues(channelColor);
primvarRefiner.Interpolate(level, srcVert, dstVert);
primvarRefiner.InterpolateFaceVarying(level, srcFVarUV, dstFVarUV, channelUV);
primvarRefiner.InterpolateFaceVarying(level, srcFVarColor, dstFVarColor, channelColor);
srcVert = dstVert;
srcFVarUV = dstFVarUV;
srcFVarColor = dstFVarColor;
}
{ // Output OBJ of the highest level refined -----------
Far::TopologyLevel const & refLastLevel = refiner->GetLevel(maxlevel);
int nverts = refLastLevel.GetNumVertices();
int nuvs = refLastLevel.GetNumFVarValues(channelUV);
int ncolors= refLastLevel.GetNumFVarValues(channelColor);
int nfaces = refLastLevel.GetNumFaces();
// Print vertex positions
int firstOfLastVerts = refiner->GetNumVerticesTotal() - nverts;
for (int vert = 0; vert < nverts; ++vert) {
float const * pos = verts[firstOfLastVerts + vert].GetPosition();
printf("v %f %f %f\n", pos[0], pos[1], pos[2]);
}
// Print uvs
int firstOfLastUvs = refiner->GetNumFVarValuesTotal(channelUV) - nuvs;
for (int fvvert = 0; fvvert < nuvs; ++fvvert) {
FVarVertexUV const & uv = fvVertsUV[firstOfLastUvs + fvvert];
printf("vt %f %f\n", uv.u, uv.v);
}
// Print colors
int firstOfLastColors = refiner->GetNumFVarValuesTotal(channelColor) - ncolors;
for (int fvvert = 0; fvvert < ncolors; ++fvvert) {
FVarVertexColor const & c = fvVertsColor[firstOfLastColors + fvvert];
printf("c %f %f %f %f\n", c.r, c.g, c.b, c.a);
}
// Print faces
for (int face = 0; face < nfaces; ++face) {
Far::ConstIndexArray fverts = refLastLevel.GetFaceVertices(face);
Far::ConstIndexArray fuvs = refLastLevel.GetFaceFVarValues(face, channelUV);
// all refined Catmark faces should be quads
assert(fverts.size()==4 && fuvs.size()==4);
printf("f ");
for (int vert=0; vert<fverts.size(); ++vert) {
// OBJ uses 1-based arrays...
printf("%d/%d ", fverts[vert]+1, fuvs[vert]+1);
}
printf("\n");
}
}
delete refiner;
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,10 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_far_tutorial(
far_tutorial_2_3
far_tutorial_2_3.cpp
)

View File

@@ -0,0 +1,513 @@
//
// Copyright 2013 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// NOTE: The following approaches are approximations to compute smooth normals,
// for highest fidelity patches should be used for positions and normals,
// which form the true limit surface.
//
// Building on tutorial 3, this example shows how to instantiate a simple mesh,
// refine it uniformly, interpolate both 'vertex' and 'face-varying'
// primvar data, and finally calculate approximated smooth normals.
// The resulting interpolated data is output in 'obj' format.
//
// Currently, this tutorial supports 3 methods to approximate smooth normals:
//
// CrossTriangle : Calculates smooth normals (accumulating per vertex) using
// 3 verts to generate 2 vectors. This approximation has
// trouble when working with quads (which can be non-planar)
// since it only takes into account half of each face.
//
// CrossQuad : Calculates smooth normals (accumulating per vertex)
// but this time, instead of taking into account only 3 verts
// it creates 2 vectors crossing the quad.
// This approximation builds upon CrossTriangle but takes
// into account the 4 verts of the face.
//
// Limit : Calculates the normals at the limit for each vert
// at the last level of subdivision.
// These are the true limit normals, however, in this example
// they are used with verts that are not at the limit.
// This can lead to new visual artifacts since the normals
// and the positions don't match. Additionally, this approach
// requires extra computation to calculate the limit normals.
// For this reason, we strongly suggest using
// limit positions with limit normals.
//
#include <opensubdiv/far/topologyDescriptor.h>
#include <opensubdiv/far/primvarRefiner.h>
#include <cstdio>
//------------------------------------------------------------------------------
// Math helpers.
//
//
// Returns the normalized version of the input vector
inline void
normalize(float *n) {
float rn = 1.0f/sqrtf(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]);
n[0] *= rn;
n[1] *= rn;
n[2] *= rn;
}
// Returns the cross product of \p v1 and \p v2.
void cross(float const *v1, float const *v2, float* vOut)
{
vOut[0] = v1[1] * v2[2] - v1[2] * v2[1];
vOut[1] = v1[2] * v2[0] - v1[0] * v2[2];
vOut[2] = v1[0] * v2[1] - v1[1] * v2[0];
}
//------------------------------------------------------------------------------
// Face-varying implementation.
//
//
struct Vertex {
// Minimal required interface ----------------------
Vertex() {
Clear();
}
Vertex(Vertex const & src) {
position[0] = src.position[0];
position[1] = src.position[1];
position[2] = src.position[2];
}
void Clear() {
position[0]=position[1]=position[2]=0.0f;
}
void AddWithWeight(Vertex const & src, float weight) {
position[0]+=weight*src.position[0];
position[1]+=weight*src.position[1];
position[2]+=weight*src.position[2];
}
// Public interface ------------------------------------
void SetPosition(float x, float y, float z) {
position[0]=x;
position[1]=y;
position[2]=z;
}
const float * GetPosition() const {
return position;
}
float position[3];
};
//------------------------------------------------------------------------------
// Face-varying container implementation.
//
// We are using a uv texture layout as a 'face-varying' primtiive variable
// attribute. Because face-varying data is specified 'per-face-per-vertex',
// we cannot use the same container that we use for 'vertex' or 'varying'
// data. We specify a new container, which only carries (u,v) coordinates.
// Similarly to our 'Vertex' container, we add a minimaliztic interpolation
// interface with a 'Clear()' and 'AddWithWeight()' methods.
//
struct FVarVertexUV {
// Minimal required interface ----------------------
void Clear() {
u=v=0.0f;
}
void AddWithWeight(FVarVertexUV const & src, float weight) {
u += weight * src.u;
v += weight * src.v;
}
// Basic 'uv' layout channel
float u,v;
};
struct FVarVertexColor {
// Minimal required interface ----------------------
void Clear() {
r=g=b=a=0.0f;
}
void AddWithWeight(FVarVertexColor const & src, float weight) {
r += weight * src.r;
g += weight * src.g;
b += weight * src.b;
a += weight * src.a;
}
// Basic 'color' layout channel
float r,g,b,a;
};
//------------------------------------------------------------------------------
// Cube geometry from catmark_cube.h
// 'vertex' primitive variable data & topology
static float g_verts[8][3] = {{ -0.5f, -0.5f, 0.5f },
{ 0.5f, -0.5f, 0.5f },
{ -0.5f, 0.5f, 0.5f },
{ 0.5f, 0.5f, 0.5f },
{ -0.5f, 0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },
{ -0.5f, -0.5f, -0.5f },
{ 0.5f, -0.5f, -0.5f }};
static int g_nverts = 8,
g_nfaces = 6;
static int g_vertsperface[6] = { 4, 4, 4, 4, 4, 4 };
static int g_vertIndices[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
// 'face-varying' primitive variable data & topology for UVs
static float g_uvs[14][2] = {{ 0.375, 0.00 },
{ 0.625, 0.00 },
{ 0.375, 0.25 },
{ 0.625, 0.25 },
{ 0.375, 0.50 },
{ 0.625, 0.50 },
{ 0.375, 0.75 },
{ 0.625, 0.75 },
{ 0.375, 1.00 },
{ 0.625, 1.00 },
{ 0.875, 0.00 },
{ 0.875, 0.25 },
{ 0.125, 0.00 },
{ 0.125, 0.25 }};
static int g_nuvs = 14;
static int g_uvIndices[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 9, 8,
1, 10, 11, 3,
12, 0, 2, 13 };
// 'face-varying' primitive variable data & topology for color
static float g_colors[24][4] = {{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 0.0, 0.0, 1.0},
{1.0, 0.0, 0.0, 1.0},
{1.0, 0.0, 0.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0}};
static int g_ncolors = 24;
static int g_colorIndices[24] = { 0, 3, 9, 6,
7, 10, 15, 12,
13, 16, 21, 18,
19, 22, 4, 1,
5, 23, 17, 11,
20, 2, 8, 14 };
using namespace OpenSubdiv;
// Approximation methods for smooth normal computations
enum NormalApproximation
{
CrossTriangle,
CrossQuad,
Limit
};
//------------------------------------------------------------------------------
int main(int argc, char ** argv) {
const int maxlevel = 2;
enum NormalApproximation normalApproximation = CrossTriangle;
// Parsing command line parameters to see if the user wants to use a
// specific method to calculate normals
for (int i = 1; i < argc; ++i) {
if (strstr(argv[i], "-limit")) {
normalApproximation = Limit;
} else if (!strcmp(argv[i], "-crossquad")) {
normalApproximation = CrossQuad;
} else if (!strcmp(argv[i], "-crosstriangle")) {
normalApproximation = CrossTriangle;
} else {
printf("Parameters : \n");
printf(" -crosstriangle : use the cross product of vectors\n");
printf(" generated from 3 verts (default).\n");
printf(" -crossquad : use the cross product of vectors\n");
printf(" generated from 4 verts.\n");
printf(" -limit : use normals calculated from the limit.\n");
return 0;
}
}
typedef Far::TopologyDescriptor Descriptor;
Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK;
Sdc::Options options;
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
options.SetFVarLinearInterpolation(Sdc::Options::FVAR_LINEAR_NONE);
// Populate a topology descriptor with our raw data
Descriptor desc;
desc.numVertices = g_nverts;
desc.numFaces = g_nfaces;
desc.numVertsPerFace = g_vertsperface;
desc.vertIndicesPerFace = g_vertIndices;
// Create a face-varying channel descriptor
const int numChannels = 2;
const int channelUV = 0;
const int channelColor = 1;
Descriptor::FVarChannel channels[numChannels];
channels[channelUV].numValues = g_nuvs;
channels[channelUV].valueIndices = g_uvIndices;
channels[channelColor].numValues = g_ncolors;
channels[channelColor].valueIndices = g_colorIndices;
// Add the channel topology to the main descriptor
desc.numFVarChannels = numChannels;
desc.fvarChannels = channels;
// Instantiate a Far::TopologyRefiner from the descriptor
Far::TopologyRefiner * refiner =
Far::TopologyRefinerFactory<Descriptor>::Create(desc,
Far::TopologyRefinerFactory<Descriptor>::Options(type, options));
// Uniformly refine the topolgy up to 'maxlevel'
// note: fullTopologyInLastLevel must be true to work with face-varying data
{
Far::TopologyRefiner::UniformOptions refineOptions(maxlevel);
refineOptions.fullTopologyInLastLevel = true;
refiner->RefineUniform(refineOptions);
}
// Allocate and initialize the 'vertex' primvar data (see tutorial 2 for
// more details).
std::vector<Vertex> vbuffer(refiner->GetNumVerticesTotal());
Vertex * verts = &vbuffer[0];
for (int i=0; i<g_nverts; ++i) {
verts[i].SetPosition(g_verts[i][0], g_verts[i][1], g_verts[i][2]);
}
// Allocate & initialize the first channel of 'face-varying' primvars (UVs)
std::vector<FVarVertexUV> fvBufferUV(refiner->GetNumFVarValuesTotal(channelUV));
FVarVertexUV * fvVertsUV = &fvBufferUV[0];
for (int i=0; i<g_nuvs; ++i) {
fvVertsUV[i].u = g_uvs[i][0];
fvVertsUV[i].v = g_uvs[i][1];
}
// Allocate & interpolate the 'face-varying' primvar data (colors)
std::vector<FVarVertexColor> fvBufferColor(refiner->GetNumFVarValuesTotal(channelColor));
FVarVertexColor * fvVertsColor = &fvBufferColor[0];
for (int i=0; i<g_ncolors; ++i) {
fvVertsColor[i].r = g_colors[i][0];
fvVertsColor[i].g = g_colors[i][1];
fvVertsColor[i].b = g_colors[i][2];
fvVertsColor[i].a = g_colors[i][3];
}
// Interpolate both vertex and face-varying primvar data
Far::PrimvarRefiner primvarRefiner(*refiner);
Vertex * srcVert = verts;
FVarVertexUV * srcFVarUV = fvVertsUV;
FVarVertexColor * srcFVarColor = fvVertsColor;
for (int level = 1; level <= maxlevel; ++level) {
Vertex * dstVert = srcVert + refiner->GetLevel(level-1).GetNumVertices();
FVarVertexUV * dstFVarUV = srcFVarUV + refiner->GetLevel(level-1).GetNumFVarValues(channelUV);
FVarVertexColor * dstFVarColor = srcFVarColor + refiner->GetLevel(level-1).GetNumFVarValues(channelColor);
primvarRefiner.Interpolate(level, srcVert, dstVert);
primvarRefiner.InterpolateFaceVarying(level, srcFVarUV, dstFVarUV, channelUV);
primvarRefiner.InterpolateFaceVarying(level, srcFVarColor, dstFVarColor, channelColor);
srcVert = dstVert;
srcFVarUV = dstFVarUV;
srcFVarColor = dstFVarColor;
}
// Approximate normals
Far::TopologyLevel const & refLastLevel = refiner->GetLevel(maxlevel);
int nverts = refLastLevel.GetNumVertices();
int nfaces = refLastLevel.GetNumFaces();
int firstOfLastVerts = refiner->GetNumVerticesTotal() - nverts;
std::vector<Vertex> normals(nverts);
// Different ways to approximate smooth normals
//
// For details check the description at the beginning of the file
if (normalApproximation == Limit) {
// Approximation using the normal at the limit with verts that are
// not at the limit
//
// For details check the description at the beginning of the file
std::vector<Vertex> fineLimitPos(nverts);
std::vector<Vertex> fineDu(nverts);
std::vector<Vertex> fineDv(nverts);
primvarRefiner.Limit(&verts[firstOfLastVerts], fineLimitPos, fineDu, fineDv);
for (int vert = 0; vert < nverts; ++vert) {
float const * du = fineDu[vert].GetPosition();
float const * dv = fineDv[vert].GetPosition();
float norm[3];
cross(du, dv, norm);
normals[vert].SetPosition(norm[0], norm[1], norm[2]);
}
} else if (normalApproximation == CrossQuad) {
// Approximate smooth normals by accumulating normal vectors computed as
// the cross product of two vectors generated by the 4 verts that
// form each quad
//
// For details check the description at the beginning of the file
for (int f = 0; f < nfaces; f++) {
Far::ConstIndexArray faceVertices = refLastLevel.GetFaceVertices(f);
// We will use the first three verts to calculate a normal
const float * v0 = verts[ firstOfLastVerts + faceVertices[0] ].GetPosition();
const float * v1 = verts[ firstOfLastVerts + faceVertices[1] ].GetPosition();
const float * v2 = verts[ firstOfLastVerts + faceVertices[2] ].GetPosition();
const float * v3 = verts[ firstOfLastVerts + faceVertices[3] ].GetPosition();
// Calculate the cross product between the vectors formed by v1-v0 and
// v2-v0, and then normalize the result
float normalCalculated [] = {0.0,0.0,0.0};
float a[3] = { v2[0]-v0[0], v2[1]-v0[1], v2[2]-v0[2] };
float b[3] = { v3[0]-v1[0], v3[1]-v1[1], v3[2]-v1[2] };
cross(a, b, normalCalculated);
normalize(normalCalculated);
// Accumulate that normal on all verts that are part of that face
for(int vInFace = 0; vInFace < faceVertices.size() ; vInFace++ ) {
int vertexIndex = faceVertices[vInFace];
normals[vertexIndex].position[0] += normalCalculated[0];
normals[vertexIndex].position[1] += normalCalculated[1];
normals[vertexIndex].position[2] += normalCalculated[2];
}
}
} else if (normalApproximation == CrossTriangle) {
// Approximate smooth normals by accumulating normal vectors computed as
// the cross product of two vectors generated by 3 verts of the quad
//
// For details check the description at the beginning of the file
for (int f = 0; f < nfaces; f++) {
Far::ConstIndexArray faceVertices = refLastLevel.GetFaceVertices(f);
// We will use the first three verts to calculate a normal
const float * v0 = verts[ firstOfLastVerts + faceVertices[0] ].GetPosition();
const float * v1 = verts[ firstOfLastVerts + faceVertices[1] ].GetPosition();
const float * v2 = verts[ firstOfLastVerts + faceVertices[2] ].GetPosition();
// Calculate the cross product between the vectors formed by v1-v0 and
// v2-v0, and then normalize the result
float normalCalculated [] = {0.0,0.0,0.0};
float a[3] = { v1[0]-v0[0], v1[1]-v0[1], v1[2]-v0[2] };
float b[3] = { v2[0]-v0[0], v2[1]-v0[1], v2[2]-v0[2] };
cross(a, b, normalCalculated);
normalize(normalCalculated);
// Accumulate that normal on all verts that are part of that face
for(int vInFace = 0; vInFace < faceVertices.size() ; vInFace++ ) {
int vertexIndex = faceVertices[vInFace];
normals[vertexIndex].position[0] += normalCalculated[0];
normals[vertexIndex].position[1] += normalCalculated[1];
normals[vertexIndex].position[2] += normalCalculated[2];
}
}
}
// Finally we just need to normalize the accumulated normals
for (int vert = 0; vert < nverts; ++vert) {
normalize(&normals[vert].position[0]);
}
{ // Output OBJ of the highest level refined -----------
// Print vertex positions
for (int vert = 0; vert < nverts; ++vert) {
float const * pos = verts[firstOfLastVerts + vert].GetPosition();
printf("v %f %f %f\n", pos[0], pos[1], pos[2]);
}
// Print vertex normals
for (int vert = 0; vert < nverts; ++vert) {
float const * pos = normals[vert].GetPosition();
printf("vn %f %f %f\n", pos[0], pos[1], pos[2]);
}
// Print uvs
int nuvs = refLastLevel.GetNumFVarValues(channelUV);
int firstOfLastUvs = refiner->GetNumFVarValuesTotal(channelUV) - nuvs;
for (int fvvert = 0; fvvert < nuvs; ++fvvert) {
FVarVertexUV const & uv = fvVertsUV[firstOfLastUvs + fvvert];
printf("vt %f %f\n", uv.u, uv.v);
}
// Print faces
for (int face = 0; face < nfaces; ++face) {
Far::ConstIndexArray fverts = refLastLevel.GetFaceVertices(face);
Far::ConstIndexArray fuvs = refLastLevel.GetFaceFVarValues(face, channelUV);
// all refined Catmark faces should be quads
assert(fverts.size()==4 && fuvs.size()==4);
printf("f ");
for (int vert=0; vert<fverts.size(); ++vert) {
// OBJ uses 1-based arrays...
printf("%d/%d/%d ", fverts[vert]+1, fuvs[vert]+1, fverts[vert]+1);
}
printf("\n");
}
}
delete refiner;
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,10 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_far_tutorial(
far_tutorial_3_1
far_tutorial_3_1.cpp
)

View File

@@ -0,0 +1,479 @@
//
// Copyright 2013 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial shows how to interface a high-level topology representation
// with Far for better efficiency. In tutorial 0, we showed how to instantiate
// topology from a simple face-vertex list. Here we will show how to take
// advantage of more complex data structures.
//
// Many client applications that manipulate geometry use advanced data structures
// such as half-edge, quad-edge or winged-edge in order to represent complex
// topological relationships beyond the usual face-vertex lists. We can take
// advantage of this information.
//
// Far provides an advanced interface that allows such a client application to
// communicate advanced component relationships directly and avoid having Far
// rebuilding them redundantly.
//
#include <opensubdiv/far/topologyRefinerFactory.h>
#include <opensubdiv/far/primvarRefiner.h>
#include <cstdio>
//------------------------------------------------------------------------------
using namespace OpenSubdiv;
//------------------------------------------------------------------------------
//
// For this tutorial, we provide the complete topological representation of a
// simple pyramid. In our case, we store it as a simple sequence of integers,
// with the understanding that client-code would provide a fully implemented
// data-structure such as quad-edges or winged-edges.
//
// Pyramid geometry from catmark_pyramid.h - extended for this tutorial
//
static int g_nverts = 5,
g_nedges = 8,
g_nfaces = 5;
// vertex positions
static float g_verts[5][3] = {{ 0.0f, 0.0f, 2.0f},
{ 0.0f, -2.0f, 0.0f},
{ 2.0f, 0.0f, 0.0f},
{ 0.0f, 2.0f, 0.0f},
{-2.0f, 0.0f, 0.0f}};
// number of vertices in each face
static int g_facenverts[5] = { 3, 3, 3, 3, 4 };
// index of face vertices
static int g_faceverts[16] = { 0, 1, 2,
0, 2, 3,
0, 3, 4,
0, 4, 1,
4, 3, 2, 1 };
// index of edge vertices (2 per edge)
static int g_edgeverts[16] = { 0, 1,
1, 2,
2, 0,
2, 3,
3, 0,
3, 4,
4, 0,
4, 1 };
// index of face edges
static int g_faceedges[16] = { 0, 1, 2,
2, 3, 4,
4, 5, 6,
6, 7, 0,
5, 3, 1, 7 };
// number of faces adjacent to each edge
static int g_edgenfaces[8] = { 2, 2, 2, 2, 2, 2, 2, 2 };
// index of faces incident to a given edge
static int g_edgefaces[16] = { 0, 3,
0, 4,
0, 1,
1, 4,
1, 2,
2, 4,
2, 3,
3, 4 };
// number of faces incident to each vertex
static int g_vertexnfaces[5] = { 4, 3, 3, 3, 3 };
// index of faces incident to each vertex
static int g_vertexfaces[25] = { 0, 1, 2, 3,
0, 3, 4,
0, 4, 1,
1, 4, 2,
2, 4, 3 };
// number of edges incident to each vertex
static int g_vertexnedges[5] = { 4, 3, 3, 3, 3 };
// index of edges incident to each vertex
static int g_vertexedges[25] = { 0, 2, 4, 6,
1, 0, 7,
2, 1, 3,
4, 3, 5,
6, 5, 7 };
// Edge crease sharpness
static float g_edgeCreases[8] = { 0.0f,
2.5f,
0.0f,
2.5f,
0.0f,
2.5f,
0.0f,
2.5f };
//------------------------------------------------------------------------------
//
// Because existing client-code may not provide an exact match for the
// topological queries required by Far's interface, we can provide a converter
// class. This can be particularly useful for instance if the client
// data-structure requires additional relationships to be mapped. For instance,
// half-edge representations do not store unique edge indices and it can be
// difficult to traverse edges or faces adjacent to a given vertex.
//
// Using an intermediate wrapper class allows us to leverage existing
// relationships information from a mesh, and generate the missing components
// temporarily.
//
// For a practical example, you can look at the file 'hbr_to_vtr.h' in the same
// tutorial directory. This example implements a 'OsdHbrConverter' class as a
// way of interfacing PRman's half-edge representation to Far.
//
struct Converter {
public:
Sdc::SchemeType GetType() const {
return Sdc::SCHEME_CATMARK;
}
Sdc::Options GetOptions() const {
Sdc::Options options;
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
return options;
}
int GetNumFaces() const { return g_nfaces; }
int GetNumEdges() const { return g_nedges; }
int GetNumVertices() const { return g_nverts; }
//
// Face relationships
//
int GetNumFaceVerts(int face) const { return g_facenverts[face]; }
int const * GetFaceVerts(int face) const { return g_faceverts+getCompOffset(g_facenverts, face); }
int const * GetFaceEdges(int face) const { return g_faceedges+getCompOffset(g_facenverts, face); }
//
// Edge relationships
//
int const * GetEdgeVertices(int edge) const { return g_edgeverts+edge*2; }
int GetNumEdgeFaces(int edge) const { return g_edgenfaces[edge]; }
int const * GetEdgeFaces(int edge) const { return g_edgefaces+getCompOffset(g_edgenfaces, edge); }
//
// Vertex relationships
//
int GetNumVertexEdges(int vert) const { return g_vertexnedges[vert]; }
int const * GetVertexEdges(int vert) const { return g_vertexedges+getCompOffset(g_vertexnedges, vert); }
int GetNumVertexFaces(int vert) const { return g_vertexnfaces[vert]; }
int const * GetVertexFaces(int vert) const { return g_vertexfaces+getCompOffset(g_vertexnfaces, vert); }
private:
int getCompOffset(int const * comps, int comp) const {
int ofs=0;
for (int i=0; i<comp; ++i) {
ofs += comps[i];
}
return ofs;
}
};
//------------------------------------------------------------------------------
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
namespace Far {
template <>
bool
TopologyRefinerFactory<Converter>::resizeComponentTopology(
TopologyRefiner & refiner, Converter const & conv) {
// Faces and face-verts
int nfaces = conv.GetNumFaces();
setNumBaseFaces(refiner, nfaces);
for (int face=0; face<nfaces; ++face) {
int nv = conv.GetNumFaceVerts(face);
setNumBaseFaceVertices(refiner, face, nv);
}
// Edges and edge-faces
int nedges = conv.GetNumEdges();
setNumBaseEdges(refiner, nedges);
for (int edge=0; edge<nedges; ++edge) {
int nf = conv.GetNumEdgeFaces(edge);
setNumBaseEdgeFaces(refiner, edge, nf);
}
// Vertices and vert-faces and vert-edges
int nverts = conv.GetNumVertices();
setNumBaseVertices(refiner, nverts);
for (int vert=0; vert<nverts; ++vert) {
int ne = conv.GetNumVertexEdges(vert),
nf = conv.GetNumVertexFaces(vert);
setNumBaseVertexEdges(refiner, vert, ne);
setNumBaseVertexFaces(refiner, vert, nf);
}
return true;
}
template <>
bool
TopologyRefinerFactory<Converter>::assignComponentTopology(
TopologyRefiner & refiner, Converter const & conv) {
using Far::IndexArray;
{ // Face relations:
int nfaces = conv.GetNumFaces();
for (int face=0; face<nfaces; ++face) {
IndexArray dstFaceVerts = getBaseFaceVertices(refiner, face);
IndexArray dstFaceEdges = getBaseFaceEdges(refiner, face);
int const * faceverts = conv.GetFaceVerts(face);
int const * faceedges = conv.GetFaceEdges(face);
for (int vert=0; vert<conv.GetNumFaceVerts(face); ++vert) {
dstFaceVerts[vert] = faceverts[vert];
dstFaceEdges[vert] = faceedges[vert];
}
}
}
{ // Edge relations
//
// Note: if your representation is unable to provide edge relationships
// (ex: half-edges), you can comment out this section and Far will
// automatically generate the missing information.
//
int nedges = conv.GetNumEdges();
for (int edge=0; edge<nedges; ++edge) {
// Edge-vertices:
IndexArray dstEdgeVerts = getBaseEdgeVertices(refiner, edge);
dstEdgeVerts[0] = conv.GetEdgeVertices(edge)[0];
dstEdgeVerts[1] = conv.GetEdgeVertices(edge)[1];
// Edge-faces
IndexArray dstEdgeFaces = getBaseEdgeFaces(refiner, edge);
for (int face=0; face<conv.GetNumEdgeFaces(face); ++face) {
dstEdgeFaces[face] = conv.GetEdgeFaces(edge)[face];
}
}
}
{ // Vertex relations
int nverts = conv.GetNumVertices();
for (int vert=0; vert<nverts; ++vert) {
// Vert-Faces:
IndexArray vertFaces = getBaseVertexFaces(refiner, vert);
//LocalIndexArray vertInFaceIndices = getBaseVertexFaceLocalIndices(refiner, vert);
for (int face=0; face<conv.GetNumVertexFaces(vert); ++face) {
vertFaces[face] = conv.GetVertexFaces(vert)[face];
}
// Vert-Edges:
IndexArray vertEdges = getBaseVertexEdges(refiner, vert);
//LocalIndexArray vertInEdgeIndices = getBaseVertexEdgeLocalIndices(refiner, vert);
for (int edge=0; edge<conv.GetNumVertexEdges(vert); ++edge) {
vertEdges[edge] = conv.GetVertexEdges(vert)[edge];
}
}
}
populateBaseLocalIndices(refiner);
return true;
};
template <>
bool
TopologyRefinerFactory<Converter>::assignComponentTags(
TopologyRefiner & refiner, Converter const & conv) {
// arbitrarily sharpen the 4 bottom edges of the pyramid to 2.5f
for (int edge=0; edge<conv.GetNumEdges(); ++edge) {
setBaseEdgeSharpness(refiner, edge, g_edgeCreases[edge]);
}
return true;
}
#ifdef _MSC_VER
template <>
void
TopologyRefinerFactory<Converter>::reportInvalidTopology(
TopologyError /* errCode */, char const * msg, Converter const& /* mesh */) {
//
// Optional topology validation error reporting:
// This method is called whenever the factory encounters topology validation
// errors. By default, nothing is reported
//
Warning(msg);
}
template <>
bool
TopologyRefinerFactory<Converter>::assignFaceVaryingTopology(
TopologyRefiner & /* refiner */, Converter const & /* conv */) {
// Because of the way MSVC++ specializes templated functions, we had to
// remove the default stubs in Far::TopologyRefinerFactory. In this
// example, no face-varying data is being added, but we still need to
// implement a template specialization or MSVC++ linker fails.
return true;
}
#endif
} // namespace Far
} // namespace OPENSUBDIV_VERSION
} // namespace OpenSubdiv
//------------------------------------------------------------------------------
//
// Vertex container implementation.
//
struct Vertex {
// Minimal required interface ----------------------
Vertex() { }
Vertex(Vertex const & src) {
_position[0] = src._position[0];
_position[1] = src._position[1];
_position[2] = src._position[2];
}
void Clear( void * =0 ) {
_position[0]=_position[1]=_position[2]=0.0f;
}
void AddWithWeight(Vertex const & src, float weight) {
_position[0]+=weight*src._position[0];
_position[1]+=weight*src._position[1];
_position[2]+=weight*src._position[2];
}
// Public interface ------------------------------------
void SetPosition(float x, float y, float z) {
_position[0]=x;
_position[1]=y;
_position[2]=z;
}
const float * GetPosition() const {
return _position;
}
private:
float _position[3];
};
//------------------------------------------------------------------------------
int main(int, char **) {
Converter conv;
Far::TopologyRefiner * refiner =
Far::TopologyRefinerFactory<Converter>::Create(conv,
Far::TopologyRefinerFactory<Converter>::Options(conv.GetType(), conv.GetOptions()));
int maxlevel = 5;
// Uniformly refine the topology up to 'maxlevel'
refiner->RefineUniform(Far::TopologyRefiner::UniformOptions(maxlevel));
// Allocate a buffer for vertex primvar data. The buffer length is set to
// be the sum of all children vertices up to the highest level of refinement.
std::vector<Vertex> vbuffer(refiner->GetNumVerticesTotal());
Vertex * verts = &vbuffer[0];
// Initialize coarse mesh positions
int nCoarseVerts = g_nverts;
for (int i=0; i<nCoarseVerts; ++i) {
verts[i].SetPosition(g_verts[i][0], g_verts[i][1], g_verts[i][2]);
}
// Interpolate vertex primvar data
Far::PrimvarRefiner primvarRefiner(*refiner);
Vertex * src = verts;
for (int level = 1; level <= maxlevel; ++level) {
Vertex * dst = src + refiner->GetLevel(level-1).GetNumVertices();
primvarRefiner.Interpolate(level, src, dst);
src = dst;
}
{ // Output OBJ of the highest level refined -----------
Far::TopologyLevel const & refLastLevel = refiner->GetLevel(maxlevel);
int nverts = refLastLevel.GetNumVertices();
int nfaces = refLastLevel.GetNumFaces();
// Print vertex positions
int firstOfLastVerts = refiner->GetNumVerticesTotal() - nverts;
for (int vert = 0; vert < nverts; ++vert) {
float const * pos = verts[firstOfLastVerts + vert].GetPosition();
printf("v %f %f %f\n", pos[0], pos[1], pos[2]);
}
// Print faces
for (int face = 0; face < nfaces; ++face) {
Far::ConstIndexArray fverts = refLastLevel.GetFaceVertices(face);
// all refined Catmark faces should be quads
assert(fverts.size()==4);
printf("f ");
for (int vert=0; vert<fverts.size(); ++vert) {
printf("%d ", fverts[vert]+1); // OBJ uses 1-based arrays...
}
printf("\n");
}
}
delete refiner;
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,446 @@
//
// Copyright 2013 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
#include <hbr/mesh.h>
#include <hbr/bilinear.h>
#include <hbr/loop.h>
#include <hbr/catmark.h>
#include <hbr/vertexEdit.h>
#include <hbr/cornerEdit.h>
#include <hbr/holeEdit.h>
#include <opensubdiv/far/topologyRefinerFactory.h>
#include <typeinfo>
#include <cassert>
//------------------------------------------------------------------------------
inline bool
compareType(std::type_info const & t1, std::type_info const & t2) {
if (t1==t2) {
return true;
}
// On some systems, distinct instances of \c type_info objects compare equal if
// their name() functions return equivalent strings. On other systems, distinct
// type_info objects never compare equal. The latter can cause problems in the
// presence of plugins loaded without RTLD_GLOBAL, because typeid(T) returns
// different \c type_info objects for the same T in the two plugins.
for (char const * p1 = t1.name(), *p2 = t2.name(); *p1 == *p2; ++p1, ++p2)
if (*p1 == '\0')
return true;
return false;
}
//------------------------------------------------------------------------------
// Translates subdivision options from Hbr to SdcOptions
//
template <class T>
static OpenSubdiv::SdcType
getSdcOptions( OpenSubdiv::HbrMesh<T> const & mesh, OpenSubdiv::SdcOptions * options) {
typedef OpenSubdiv::SdcOptions SdcOptions;
typedef OpenSubdiv::HbrMesh<T> HMesh;
typedef OpenSubdiv::HbrSubdivision<T> HSubdiv;
typedef OpenSubdiv::HbrCatmarkSubdivision<T> HCatmarkSubdiv;
OpenSubdiv::SdcType type;
SdcOptions::TriangleSubdivision tris=SdcOptions::TRI_SUB_NORMAL;
HSubdiv const * subdivision = mesh.GetSubdivision();
if (compareType(typeid(*subdivision), typeid(OpenSubdiv::HbrBilinearSubdivision<T>))) {
type = OpenSubdiv::TYPE_BILINEAR;
} else if (compareType(typeid(*subdivision), typeid(OpenSubdiv::HbrCatmarkSubdivision<T>))) {
type = OpenSubdiv::TYPE_CATMARK;
HCatmarkSubdiv const * catmarkSudiv = dynamic_cast<HCatmarkSubdiv const *>(subdivision);
switch (catmarkSudiv->GetTriangleSubdivisionMethod()) {
case HCatmarkSubdiv::k_Normal: tris=SdcOptions::TRI_SUB_NORMAL; break;
case HCatmarkSubdiv::k_Old: tris=SdcOptions::TRI_SUB_OLD; break;
case HCatmarkSubdiv::k_New: tris=SdcOptions::TRI_SUB_NEW; break;
}
} else if (compareType(typeid(*subdivision), typeid(OpenSubdiv::HbrLoopSubdivision<T>))) {
type = OpenSubdiv::TYPE_LOOP;
} else
assert(0);
OpenSubdiv::SdcOptions::VtxBoundaryInterpolation vvbi;
switch (mesh.GetInterpolateBoundaryMethod()) {
case HMesh::k_InterpolateBoundaryNone: vvbi=SdcOptions::VTX_BOUNDARY_NONE; break;
case HMesh::k_InterpolateBoundaryEdgeOnly: vvbi=SdcOptions::VTX_BOUNDARY_EDGE_ONLY; break;
case HMesh::k_InterpolateBoundaryEdgeAndCorner: vvbi=SdcOptions::VTX_BOUNDARY_EDGE_AND_CORNER; break;
default:
assert(0);
}
OpenSubdiv::SdcOptions::FVarBoundaryInterpolation fvbi;
switch (mesh.GetInterpolateBoundaryMethod()) {
case HMesh::k_InterpolateBoundaryNone: fvbi=SdcOptions::FVAR_BOUNDARY_BILINEAR; break;
case HMesh::k_InterpolateBoundaryEdgeOnly: fvbi=SdcOptions::FVAR_BOUNDARY_EDGE_ONLY; break;
case HMesh::k_InterpolateBoundaryEdgeAndCorner: fvbi=SdcOptions::FVAR_BOUNDARY_EDGE_AND_CORNER; break;
case HMesh::k_InterpolateBoundaryAlwaysSharp: fvbi=SdcOptions::FVAR_BOUNDARY_ALWAYS_SHARP; break;
default:
assert(0);
}
OpenSubdiv::SdcOptions::CreasingMethod creaseMethod;
switch (subdivision->GetCreaseSubdivisionMethod()) {
case HSubdiv::k_CreaseNormal: creaseMethod=OpenSubdiv::SdcOptions::CREASE_UNIFORM; break;
case HSubdiv::k_CreaseChaikin: creaseMethod=OpenSubdiv::SdcOptions::CREASE_CHAIKIN; break;
};
options->SetVtxBoundaryInterpolation(vvbi);
options->SetFVarBoundaryInterpolation(fvbi);
options->SetCreasingMethod(creaseMethod);
options->SetTriangleSubdivision(tris);
options->SetNonManifoldInterpolation(OpenSubdiv::SdcOptions::NON_MANIFOLD_NONE);
return type;
}
//------------------------------------------------------------------------------
// The HbrConverter is used to specialize FarTopologyRefinerFactory for the Hbr rep.
//
// Hbr is a half-edge topo rep, which by definition does not index edges
// uniquely. To remedy the problem, the converter uses a std::map to connect
// Hbr's half-edge pointers to unique indices.
//
// This remapping code is provided as an example of efficient implementation of
// the translation from an arbitrary topo rep to Vtr.
//
// Even though Vtr is capable of re-generating edge and vertex relationships on
// its own, this requires costly work that may be redundant if these relationships
// can be translated from the host rep.
//
template <class T> class HbrConverter {
public:
// Constructor
HbrConverter(OpenSubdiv::HbrMesh<T> const & hmesh) : _hmesh(hmesh) {
_nfaces = _hmesh.GetNumFaces();
_nverts = _hmesh.GetNumVertices();
_type = getSdcOptions<OpenSubdiv::OsdVertex>(_hmesh, &_options);
}
// Returns the type of mesh (Bilinear, Catmark, Loop)
OpenSubdiv::SdcType const & GetType() const {
return _type;
}
// Returns subdivision options
OpenSubdiv::SdcOptions const & GetOptions() const {
return _options;
}
// The HbrMesh being converted
OpenSubdiv::HbrMesh<T> const & GetHbrMesh() const {
return _hmesh;
}
// Number of faces in the mesh (cached for efficiency)
int GetNumFaces() const {
return _nfaces;
}
// Number of vertices in the mesh (cached for efficiency)
int GetNumVertices() const {
return _nverts;
}
// Number of edges in the mesh
int GetNumEdges() const {
return (int)_edgeset.size();
}
// Returns a pointer to the Hbr halfege of index 'idx'
OpenSubdiv::HbrHalfedge<T> const * GetEdge(int idx) const {
return _edgeids[idx];
}
typedef std::map<OpenSubdiv::HbrHalfedge<T> const *, int> EdgeMap;
// A map of unique edges between vertices
EdgeMap & GetEdges() {
return _edgeset;
}
// Must be called after the edge map has been populated
void FinishEdgeMap() const {
// Fudge const-ness because resizeComponentTopology() passes the converter
// as a const. The alternative is to add an iteration loop over Hbr before,
// which would waste time.
EdgeVec * edges = const_cast<EdgeVec *>(&_edgeids);
edges->resize(_edgeset.size());
for (typename EdgeMap::const_iterator it=_edgeset.begin(); it!=_edgeset.end(); ++it) {
(*edges)[it->second] = it->first;
}
}
// Returns a unique edge index for a given half-edge
int GetEdgeIndex(OpenSubdiv::HbrHalfedge<T> const * e) const {
assert(e);
typename EdgeMap::const_iterator it = _edgeset.find(e);
if (it==_edgeset.end()) {
assert(e->GetOpposite());
it = _edgeset.find(e->GetOpposite());
}
assert(it!=_edgeset.end());
return it->second;
}
// Returns the edgeVertIndex for a given Hbr halfedge 'e' and vertex 'v'
int GetEdgeVertIndex(OpenSubdiv::HbrHalfedge<T> const * e, OpenSubdiv::HbrVertex<T> const * v) const {
assert(e && v);
if (_edgeset.find(e)==_edgeset.end()) {
e = e->GetOpposite();
assert(e);
}
return (v==e->GetOrgVertex() ? 0:1);
}
private:
typedef std::vector<OpenSubdiv::HbrHalfedge<T> const *> EdgeVec;
OpenSubdiv::SdcType _type;
OpenSubdiv::SdcOptions _options;
OpenSubdiv::HbrMesh<T> const & _hmesh;
int _nfaces,
_nverts;
EdgeMap _edgeset;
EdgeVec _edgeids;
};
typedef HbrConverter<OpenSubdiv::OsdVertex> OsdHbrConverter;
typedef OpenSubdiv::HbrMesh<OpenSubdiv::OsdVertex> OsdHbrMesh;
typedef OpenSubdiv::HbrSubdivision<OpenSubdiv::OsdVertex> OsdHbrSubdivision;
typedef OpenSubdiv::HbrVertex<OpenSubdiv::OsdVertex> OsdHbrVertex;
typedef OpenSubdiv::HbrFace<OpenSubdiv::OsdVertex> OsdHbrFace;
typedef OpenSubdiv::HbrHalfedge<OpenSubdiv::OsdVertex> OsdHbrHalfedge;
namespace OpenSubdiv {
namespace OPENSUBDIV_VERSION {
template <>
void
FarTopologyRefinerFactory<OsdHbrConverter>::resizeComponentTopology(
FarTopologyRefiner & refiner, OsdHbrConverter const & conv) {
OsdHbrMesh const & hmesh = conv.GetHbrMesh();
int nfaces = hmesh.GetNumFaces(),
nverts = hmesh.GetNumVertices();
OsdHbrConverter::EdgeMap & edges = const_cast<OsdHbrConverter &>(conv).GetEdges();
assert(edges.size()==0);
// Faces and face-verts
setNumBaseFaces(refiner, nfaces);
for (int i=0; i<nfaces; ++i) {
OsdHbrFace const * f = hmesh.GetFace(i);
int nv = f->GetNumVertices();
assert(nv==4); // temporary until n-gons are supported
setNumBaseFaceVertices(refiner, i, nv);
for (int j=0; j<nv; ++j) {
// index Hbr edge pointers in the map
OsdHbrHalfedge const * e = f->GetEdge(j);
if (e->IsBoundary() || (e->GetRightFace()->GetID()>f->GetID())) {
int id = (int)edges.size();
edges[e] = id;
}
}
}
conv.FinishEdgeMap();
// Edges and edge-faces
setNumBaseEdges(refiner, (int)edges.size());
for (int i=0; i!=conv.GetNumEdges(); ++i) {
OsdHbrHalfedge const * e = conv.GetEdge(i);
setNumBaseEdgeFaces(refiner, i, e->GetRightFace() ? 2 : 1);
}
// Vertices and vert-faces and vert-edges
setNumBaseVertices(refiner, nverts);
for (int i=0; i<nverts; ++i) {
OsdHbrVertex * v = hmesh.GetVertex(i);
class GatherOperator : public OpenSubdiv::HbrHalfedgeOperator<OpenSubdiv::OsdVertex> {
OsdHbrVertex const * _v;
public:
int vertEdgeCount,
vertFaceCount;
GatherOperator(OsdHbrVertex const * v) : _v(v), vertEdgeCount(0), vertFaceCount(0) { }
virtual void operator() (OsdHbrHalfedge &e) {
if (e.GetOrgVertex()==_v && e.GetFace())
++vertFaceCount;
++vertEdgeCount;
}
};
GatherOperator op(v);
v->ApplyOperatorSurroundingEdges(op);
setNumBaseVertexEdges(refiner, i, op.vertEdgeCount);
setNumBaseVertexFaces(refiner, i, op.vertFaceCount);
}
}
template <>
void
FarTopologyRefinerFactory<OsdHbrConverter>::assignComponentTopology(
FarTopologyRefiner & refiner, OsdHbrConverter const & conv) {
typedef FarTopologyRefiner::Index Index;
typedef FarTopologyRefiner::IndexArray IndexArray;
typedef FarTopologyRefiner::LocalIndex LocalIndex;
OsdHbrMesh const & hmesh = conv.GetHbrMesh();
OsdHbrConverter::EdgeMap & edges = const_cast<OsdHbrConverter &>(conv).GetEdges();
{ // Face relations:
int nfaces = getNumBaseFaces(refiner);
for (int i=0; i < nfaces; ++i) {
IndexArray dstFaceVerts = getBaseFaceVertices(refiner, i);
IndexArray dstFaceEdges = getBaseFaceEdges(refiner, i);
OsdHbrFace * f = hmesh.GetFace(i);
for (int j = 0; j < dstFaceVerts.size(); ++j) {
dstFaceVerts[j] = (int)f->GetVertex(j)->GetID();
dstFaceEdges[j] = conv.GetEdgeIndex(f->GetEdge(j));
}
}
}
{ // Edge relations
for (OsdHbrConverter::EdgeMap::const_iterator it=edges.begin(); it!=edges.end(); ++it) {
OsdHbrHalfedge const * e = it->first;
int eidx = it->second;
// Edge-vertices:
IndexArray dstEdgeVerts = getBaseEdgeVertices(refiner, eidx);
dstEdgeVerts[0] = e->GetOrgVertex()->GetID();
dstEdgeVerts[1] = e->GetDestVertex()->GetID();
// Edge-faces
IndexArray dstEdgeFaces = getBaseEdgeFaces(refiner, eidx);
dstEdgeFaces[0] = e->GetLeftFace()->GetID();
// half-edges only have 2 faces incident to an edge (no non-manifold)
if (e->GetRightFace()) {
dstEdgeFaces[1] = e->GetRightFace()->GetID();
}
}
}
{ // Vert relations
for (int i=0; i<getNumBaseVertices(refiner); ++i) {
OsdHbrVertex const * v = hmesh.GetVertex(i);
// The Hbr operator gathers the indices of the faces and edges incident
// to a vertex and populates the refiner topological relationships.
class GatherOperator : public OpenSubdiv::HbrHalfedgeOperator<OpenSubdiv::OsdVertex> {
OsdHbrConverter const & _conv;
OsdHbrVertex const * _v;
Index * _dstVertFaces,
* _dstVertEdges;
LocalIndex * _dstVertInFaceIndices,
* _dstVertInEdgeIndices;
public:
GatherOperator(FarTopologyRefiner & refiner, OsdHbrConverter const & conv,
OsdHbrVertex const * v, int idx) : _conv(conv), _v(v) {
_dstVertFaces = getBaseVertexFaces(refiner, idx).begin(),
_dstVertEdges = getBaseVertexEdges(refiner, idx).begin();
_dstVertInFaceIndices = getBaseVertexFaceLocalIndices(refiner, idx).begin(),
_dstVertInEdgeIndices = getBaseVertexEdgeLocalIndices(refiner, idx).begin();
}
virtual void operator() (OsdHbrHalfedge &e) {
OsdHbrFace * f=e.GetFace();
if (f && (e.GetOrgVertex()==_v)) {
*_dstVertFaces++ = f->GetID();
for (int j=0; j<f->GetNumVertices(); ++j) {
if (f->GetVertex(j)==_v) {
*_dstVertInFaceIndices++ = j;
break;
}
}
}
*_dstVertEdges++ = _conv.GetEdgeIndex(&e);
*_dstVertInEdgeIndices++ = _conv.GetEdgeVertIndex(&e, _v);
}
};
GatherOperator op(refiner, conv, v, i);
v->ApplyOperatorSurroundingEdges(op);
}
}
}
template <>
void
FarTopologyRefinerFactory<OsdHbrConverter>::assignComponentTags(
FarTopologyRefiner & refiner, OsdHbrConverter const & conv) {
OsdHbrMesh const & hmesh = conv.GetHbrMesh();
OsdHbrConverter::EdgeMap & edges = const_cast<OsdHbrConverter &>(conv).GetEdges();
// Initialize edge sharpness
for (OsdHbrConverter::EdgeMap::const_iterator it=edges.begin(); it!=edges.end(); ++it) {
OsdHbrHalfedge const * e = it->first;
float sharpness = e->GetSharpness();
if (e->GetOpposite()) {
sharpness = std::max(sharpness, e->GetOpposite()->GetSharpness());
}
setBaseEdgeSharpness(refiner, it->second, sharpness);
}
// Initialize vertex sharpness
for (int i=0; i<getNumBaseVertices(refiner); ++i) {
setBaseVertexSharpness(refiner, i, hmesh.GetVertex(i)->GetSharpness());
}
// XXXX Initialize h-edits
}
} // namespace OPENSUBDIV_VERSION
} // namespace OpenSubdiv

View File

@@ -0,0 +1,10 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_far_tutorial(
far_tutorial_4_1
far_tutorial_4_1.cpp
)

View File

@@ -0,0 +1,168 @@
//
// Copyright 2013 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial shows how to create and manipulate Far::StencilTable. We use
// the factorized stencils to interpolate vertex primvar data buffers.
//
#include <opensubdiv/far/topologyDescriptor.h>
#include <opensubdiv/far/stencilTable.h>
#include <opensubdiv/far/stencilTableFactory.h>
#include <cstdio>
#include <cstring>
//------------------------------------------------------------------------------
// Vertex container implementation.
//
struct Vertex {
// Minimal required interface ----------------------
Vertex() { }
Vertex(Vertex const & src) {
_position[0] = src._position[0];
_position[1] = src._position[1];
_position[2] = src._position[2];
}
void Clear( void * =0 ) {
_position[0]=_position[1]=_position[2]=0.0f;
}
void AddWithWeight(Vertex const & src, float weight) {
_position[0]+=weight*src._position[0];
_position[1]+=weight*src._position[1];
_position[2]+=weight*src._position[2];
}
// Public interface ------------------------------------
void SetPosition(float x, float y, float z) {
_position[0]=x;
_position[1]=y;
_position[2]=z;
}
float const * GetPosition() const {
return _position;
}
private:
float _position[3];
};
//------------------------------------------------------------------------------
// Cube geometry from catmark_cube.h
static float g_verts[24] = {-0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f };
static int g_nverts = 8,
g_nfaces = 6;
static int g_vertsperface[6] = { 4, 4, 4, 4, 4, 4 };
static int g_vertIndices[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
using namespace OpenSubdiv;
static Far::TopologyRefiner * createTopologyRefiner();
//------------------------------------------------------------------------------
int main(int, char **) {
// Generate a Far::TopologyRefiner (see tutorial_1_1 for details).
Far::TopologyRefiner * refiner = createTopologyRefiner();
// Uniformly refine the topology up to 'maxlevel'.
int maxlevel = 3;
refiner->RefineUniform(Far::TopologyRefiner::UniformOptions(maxlevel));
// Use the Far::StencilTable factory to create discrete stencil table
// note: we only want stencils for the highest refinement level.
Far::StencilTableFactory::Options options;
options.generateIntermediateLevels=false;
options.generateOffsets=true;
Far::StencilTable const * stencilTable =
Far::StencilTableFactory::Create(*refiner, options);
// Allocate vertex primvar buffer (1 stencil for each vertex)
int nstencils = stencilTable->GetNumStencils();
std::vector<Vertex> vertexBuffer(nstencils);
// Quick & dirty re-cast of the primvar data from our cube
// (this is where you would drive shape deformations every frame)
Vertex * controlValues = reinterpret_cast<Vertex *>(g_verts);
{ // This section would be applied every frame after control vertices have
// been moved.
// Apply stencils on the control vertex data to update the primvar data
// of the refined vertices.
stencilTable->UpdateValues(controlValues, &vertexBuffer[0]);
}
{ // Visualization with Maya : print a MEL script that generates particles
// at the location of the refined vertices
printf("particle ");
for (int i=0; i<(int)vertexBuffer.size(); ++i) {
float const * pos = vertexBuffer[i].GetPosition();
printf("-p %f %f %f\n", pos[0], pos[1], pos[2]);
}
printf("-c 1;\n");
}
delete refiner;
delete stencilTable;
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------
static Far::TopologyRefiner *
createTopologyRefiner() {
// Populate a topology descriptor with our raw data.
typedef Far::TopologyDescriptor Descriptor;
Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK;
Sdc::Options options;
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
Descriptor desc;
desc.numVertices = g_nverts;
desc.numFaces = g_nfaces;
desc.numVertsPerFace = g_vertsperface;
desc.vertIndicesPerFace = g_vertIndices;
// Instantiate a Far::TopologyRefiner from the descriptor.
return Far::TopologyRefinerFactory<Descriptor>::Create(desc,
Far::TopologyRefinerFactory<Descriptor>::Options(type, options));
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,10 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_far_tutorial(
far_tutorial_4_2
far_tutorial_4_2.cpp
)

View File

@@ -0,0 +1,222 @@
//
// Copyright 2013 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial shows how to create and manipulate both 'vertex' and 'varying'
// Far::StencilTable to interpolate 2 primvar data buffers: vertex positions and
// vertex colors.
//
#include <opensubdiv/far/topologyDescriptor.h>
#include <opensubdiv/far/stencilTable.h>
#include <opensubdiv/far/stencilTableFactory.h>
#include <cstdio>
#include <cstring>
//------------------------------------------------------------------------------
// Vertex container implementation.
//
struct Vertex {
// Minimal required interface ----------------------
Vertex() { }
Vertex(Vertex const & src) {
_data[0] = src._data[0];
_data[1] = src._data[1];
_data[2] = src._data[2];
}
void Clear( void * =0 ) {
_data[0]=_data[1]=_data[2]=0.0f;
}
void AddWithWeight(Vertex const & src, float weight) {
_data[0]+=weight*src._data[0];
_data[1]+=weight*src._data[1];
_data[2]+=weight*src._data[2];
}
// Public interface ------------------------------------
float const * GetData() const {
return _data;
}
private:
float _data[3];
};
//------------------------------------------------------------------------------
// Cube geometry from catmark_cube.h
static float g_verts[24] = {-0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f };
// Per-vertex RGB color data
static float g_colors[24] = { 1.0f, 0.0f, 0.5f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 0.0f,
0.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f };
static int g_nverts = 8,
g_nfaces = 6;
static int g_vertsperface[6] = { 4, 4, 4, 4, 4, 4 };
static int g_vertIndices[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
using namespace OpenSubdiv;
static Far::TopologyRefiner * createTopologyRefiner();
//------------------------------------------------------------------------------
int main(int, char **) {
// Generate a Far::TopologyRefiner (see tutorial_1_1 for details).
Far::TopologyRefiner * refiner = createTopologyRefiner();
// Uniformly refine the topology up to 'maxlevel'.
int maxlevel = 4;
refiner->RefineUniform(Far::TopologyRefiner::UniformOptions(maxlevel));
int nverts = refiner->GetLevel(maxlevel).GetNumVertices();
// Use the Far::StencilTable factory to create discrete stencil table
Far::StencilTableFactory::Options options;
options.generateIntermediateLevels=false; // only the highest refinement level.
options.generateOffsets=true;
//
// Vertex primvar data
//
// Create stencils table for 'vertex' interpolation
options.interpolationMode=Far::StencilTableFactory::INTERPOLATE_VERTEX;
Far::StencilTable const * vertexStencils =
Far::StencilTableFactory::Create(*refiner, options);
assert(nverts==vertexStencils->GetNumStencils());
// Allocate vertex primvar buffer (1 stencil for each vertex)
std::vector<Vertex> vertexBuffer(vertexStencils->GetNumStencils());
// Use the cube vertex positions as 'vertex' primvar data
Vertex * vertexCVs = reinterpret_cast<Vertex *>(g_verts);
//
// Varying primvar data
//
// Create stencils table for 'varying' interpolation
options.interpolationMode=Far::StencilTableFactory::INTERPOLATE_VARYING;
Far::StencilTable const * varyingStencils =
Far::StencilTableFactory::Create(*refiner, options);
assert(nverts==varyingStencils->GetNumStencils());
// Allocate varying primvar buffer (1 stencil for each vertex)
std::vector<Vertex> varyingBuffer(varyingStencils->GetNumStencils());
// Use per-vertex array of RGB colors as 'varying' primvar data
Vertex * varyingCVs = reinterpret_cast<Vertex *>(g_colors);
delete refiner;
//
// Apply stencils (in frame loop)
//
{ // This section would be applied every frame after control vertices have
// been moved.
// Apply stencils on the control vertex data to update the primvar data
// of the refined vertices.
vertexStencils->UpdateValues(vertexCVs, &vertexBuffer[0]);
varyingStencils->UpdateValues(varyingCVs, &varyingBuffer[0]);
}
{ // Visualization with Maya : print a MEL script that generates particles
// at the location of the refined vertices
printf("particle ");
for (int vert=0; vert<(int)nverts; ++vert) {
float const * pos = vertexBuffer[vert].GetData();
printf("-p %f %f %f\n", pos[0], pos[1], pos[2]);
}
printf("-c 1;\n");
// Set particle point size (20 -- very large)
printf("addAttr -is true -ln \"pointSize\" -at long -dv 20 particleShape1;\n");
// Add per-particle color attribute ('rgbPP')
printf("addAttr -ln \"rgbPP\" -dt vectorArray particleShape1;\n");
// Set per-particle color values from our 'varying' primvar data
printf("setAttr \"particleShape1.rgbPP\" -type \"vectorArray\" %d ", nverts);
for (int vert=0; vert<nverts; ++vert) {
float const * color = varyingBuffer[vert].GetData();
printf("%f %f %f\n", color[0], color[1], color[2]);
}
printf(";\n");
}
delete vertexStencils;
delete varyingStencils;
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------
static Far::TopologyRefiner *
createTopologyRefiner() {
// Populate a topology descriptor with our raw data.
typedef Far::TopologyDescriptor Descriptor;
Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK;
Sdc::Options options;
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
Descriptor desc;
desc.numVertices = g_nverts;
desc.numFaces = g_nfaces;
desc.numVertsPerFace = g_vertsperface;
desc.vertIndicesPerFace = g_vertIndices;
// Instantiate a Far::TopologyRefiner from the descriptor.
Far::TopologyRefiner * refiner =
Far::TopologyRefinerFactory<Descriptor>::Create(desc,
Far::TopologyRefinerFactory<Descriptor>::Options(type, options));
return refiner;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,10 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_far_tutorial(
far_tutorial_4_3
far_tutorial_4_3.cpp
)

View File

@@ -0,0 +1,222 @@
//
// Copyright 2013 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial shows how to create and manipulate table of cascading stencils.
//
// We initialize a Far::TopologyRefiner with a cube and apply uniform
// refinement. We then use a Far::StencilTableFactory to generate a stencil
// table. We set the factory Options to not factorize intermediate levels,
// thus giving a table of "cascading" stencils.
//
// We then apply the stencils to the vertex position primvar data, and insert
// a hierarchical edit at level 1. This edit is smoothed by the application
// of the subsequent stencil cascades.
//
// The results are dumped into an OBJ file that shows the intermediate levels
// of refinement of the original cube.
//
#include <opensubdiv/far/topologyDescriptor.h>
#include <opensubdiv/far/stencilTable.h>
#include <opensubdiv/far/stencilTableFactory.h>
#include <cstdio>
#include <cstring>
//------------------------------------------------------------------------------
// Vertex container implementation.
//
struct Vertex {
// Minimal required interface ----------------------
Vertex() { }
Vertex(Vertex const & src) {
_position[0] = src._position[0];
_position[1] = src._position[1];
_position[2] = src._position[2];
}
void Clear( void * =0 ) {
_position[0]=_position[1]=_position[2]=0.0f;
}
void AddWithWeight(Vertex const & src, float weight) {
_position[0]+=weight*src._position[0];
_position[1]+=weight*src._position[1];
_position[2]+=weight*src._position[2];
}
// Public interface ------------------------------------
void SetPosition(float x, float y, float z) {
_position[0]=x;
_position[1]=y;
_position[2]=z;
}
float const * GetPosition() const {
return _position;
}
float * GetPosition() {
return _position;
}
private:
float _position[3];
};
//------------------------------------------------------------------------------
// Cube geometry from catmark_cube.h
static float g_verts[24] = {-0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f };
static int g_nverts = 8,
g_nfaces = 6;
static int g_vertsperface[6] = { 4, 4, 4, 4, 4, 4 };
static int g_vertIndices[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
using namespace OpenSubdiv;
static Far::TopologyRefiner * createTopologyRefiner();
//------------------------------------------------------------------------------
int main(int, char **) {
// Generate a Far::TopologyRefiner (see tutorial_1_1 for details).
Far::TopologyRefiner * refiner = createTopologyRefiner();
// Uniformly refine the topology up to 'maxlevel'.
int maxlevel = 4;
refiner->RefineUniform(Far::TopologyRefiner::UniformOptions(maxlevel));
// Use the Far::StencilTable factory to create cascading stencil table
// note: we want stencils for each refinement level
// "cascade" mode is achieved by setting "factorizeIntermediateLevels"
// to false
Far::StencilTableFactory::Options options;
options.generateIntermediateLevels=true;
options.factorizeIntermediateLevels=false;
options.generateOffsets=true;
Far::StencilTable const * stencilTable =
Far::StencilTableFactory::Create(*refiner, options);
std::vector<Vertex> vertexBuffer(refiner->GetNumVerticesTotal()-g_nverts);
Vertex * destVerts = &vertexBuffer[0];
int start = 0, end = 0; // stencil batches for each level of subdivision
for (int level=0; level<maxlevel; ++level) {
int nverts = refiner->GetLevel(level+1).GetNumVertices();
Vertex const * srcVerts = reinterpret_cast<Vertex *>(g_verts);
if (level>0) {
srcVerts = &vertexBuffer[start];
}
start = end;
end += nverts;
stencilTable->UpdateValues(srcVerts, destVerts, start, end);
// apply 2 hierarchical edits on level 1 vertices
if (level==1) {
float * pos = destVerts[start+5].GetPosition();
pos[1] += 0.5f;
pos = destVerts[start+20].GetPosition();
pos[0] += 0.25f;
}
}
{ // Output OBJ of the highest level refined -----------
Vertex * verts = &vertexBuffer[0];
// Print vertex positions
for (int level=1, firstvert=0; level<=maxlevel; ++level) {
Far::TopologyLevel const & refLevel = refiner->GetLevel(level);
printf("g level_%d\n", level);
int nverts = refLevel.GetNumVertices();
for (int vert=0; vert<nverts; ++vert) {
float const * pos = verts[vert].GetPosition();
printf("v %f %f %f\n", pos[0], pos[1], pos[2]);
}
verts += nverts;
// Print faces
for (int face=0; face<refLevel.GetNumFaces(); ++face) {
Far::ConstIndexArray fverts = refLevel.GetFaceVertices(face);
// all refined Catmark faces should be quads
assert(fverts.size()==4);
printf("f ");
for (int vert=0; vert<fverts.size(); ++vert) {
printf("%d ", fverts[vert]+firstvert+1); // OBJ uses 1-based arrays...
}
printf("\n");
}
firstvert+=nverts;
}
}
delete refiner;
delete stencilTable;
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------
static Far::TopologyRefiner *
createTopologyRefiner() {
// Populate a topology descriptor with our raw data.
typedef Far::TopologyDescriptor Descriptor;
Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK;
Sdc::Options options;
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
Descriptor desc;
desc.numVertices = g_nverts;
desc.numFaces = g_nfaces;
desc.numVertsPerFace = g_vertsperface;
desc.vertIndicesPerFace = g_vertIndices;
// Instantiate a Far::TopologyRefiner from the descriptor.
return Far::TopologyRefinerFactory<Descriptor>::Create(desc,
Far::TopologyRefinerFactory<Descriptor>::Options(type, options));
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,10 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_far_tutorial(
far_tutorial_5_1
far_tutorial_5_1.cpp
)

View File

@@ -0,0 +1,321 @@
//
// Copyright 2013 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial shows how to interpolate surface limits at arbitrary
// parametric locations using feature adaptive Far::PatchTables.
//
// The evaluation of the limit surface at arbitrary locations requires the
// adaptive isolation of topological features. This process converts the
// input polygonal control cage into a collection of bi-cubic patches.
//
// We can then evaluate the patches at random parametric locations and
// obtain analytical positions and tangents on the limit surface.
//
// The results are dumped into a MEL script that draws 'streak' particle
// systems that show the tangent and bi-tangent at the random samples locations.
//
#include <opensubdiv/far/topologyDescriptor.h>
#include <opensubdiv/far/primvarRefiner.h>
#include <opensubdiv/far/patchTableFactory.h>
#include <opensubdiv/far/patchMap.h>
#include <opensubdiv/far/ptexIndices.h>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <cfloat>
using namespace OpenSubdiv;
typedef double Real;
// pyramid geometry from catmark_pyramid_crease0.h
static int const g_nverts = 5;
static Real const g_verts[24] = { 0.0f, 0.0f, 2.0f,
0.0f, -2.0f, 0.0f,
2.0f, 0.0f, 0.0f,
0.0f, 2.0f, 0.0f,
-2.0f, 0.0f, 0.0f, };
static int const g_vertsperface[5] = { 3, 3, 3, 3, 4 };
static int const g_nfaces = 5;
static int const g_faceverts[16] = { 0, 1, 2,
0, 2, 3,
0, 3, 4,
0, 4, 1,
4, 3, 2, 1 };
static int const g_ncreases = 4;
static int const g_creaseverts[8] = { 4, 3, 3, 2, 2, 1, 1, 4 };
static float const g_creaseweights[4] = { 3.0f, 3.0f, 3.0f, 3.0f };
// Creates a Far::TopologyRefiner from the pyramid shape above
static Far::TopologyRefiner * createTopologyRefiner();
//------------------------------------------------------------------------------
// Vertex container implementation.
//
struct Vertex {
// Minimal required interface ----------------------
Vertex() { }
void Clear( void * =0 ) {
point[0] = point[1] = point[2] = 0.0f;
}
void AddWithWeight(Vertex const & src, Real weight) {
point[0] += weight * src.point[0];
point[1] += weight * src.point[1];
point[2] += weight * src.point[2];
}
Real point[3];
};
//------------------------------------------------------------------------------
// Limit frame container implementation -- this interface is not strictly
// required but follows a similar pattern to Vertex.
//
struct LimitFrame {
void Clear( void * =0 ) {
point[0] = point[1] = point[2] = 0.0f;
deriv1[0] = deriv1[1] = deriv1[2] = 0.0f;
deriv2[0] = deriv2[1] = deriv2[2] = 0.0f;
}
void AddWithWeight(Vertex const & src,
Real weight, Real d1Weight, Real d2Weight) {
point[0] += weight * src.point[0];
point[1] += weight * src.point[1];
point[2] += weight * src.point[2];
deriv1[0] += d1Weight * src.point[0];
deriv1[1] += d1Weight * src.point[1];
deriv1[2] += d1Weight * src.point[2];
deriv2[0] += d2Weight * src.point[0];
deriv2[1] += d2Weight * src.point[1];
deriv2[2] += d2Weight * src.point[2];
}
Real point[3],
deriv1[3],
deriv2[3];
};
//------------------------------------------------------------------------------
int main(int, char **) {
// Generate a Far::TopologyRefiner (see tutorial_1_1 for details).
Far::TopologyRefiner * refiner = createTopologyRefiner();
// Patches are constructed from adaptively refined faces, but the processes
// of constructing the PatchTable and of applying adaptive refinement have
// historically been separate. Adaptive refinement is applied purely to
// satisfy the needs of the desired PatchTable, so options associated with
// adaptive refinement should be derived from those specified for the
// PatchTable. This is not a strict requirement, but it will avoid
// problems arising from specifying/coordinating the two independently
// (especially when dealing with face-varying patches).
// Initialize options for the PatchTable:
//
// Choose patches adaptively refined to level 3 since the sharpest crease
// in the shape is 3.0f (in g_creaseweights[]), and include the inf-sharp
// crease option just to illustrate the need to syncronize options.
//
int maxPatchLevel = 3;
Far::PatchTableFactory::Options patchOptions(maxPatchLevel);
patchOptions.SetPatchPrecision<Real>();
patchOptions.useInfSharpPatch = true;
patchOptions.generateVaryingTables = false;
patchOptions.endCapType =
Far::PatchTableFactory::Options::ENDCAP_GREGORY_BASIS;
// Initialize corresonding options for adaptive refinement:
Far::TopologyRefiner::AdaptiveOptions adaptiveOptions(maxPatchLevel);
bool assignAdaptiveOptionsExplicitly = false;
if (assignAdaptiveOptionsExplicitly) {
adaptiveOptions.useInfSharpPatch = true;
} else {
// Be sure patch options were intialized with the desired max level
adaptiveOptions = patchOptions.GetRefineAdaptiveOptions();
}
assert(adaptiveOptions.useInfSharpPatch == patchOptions.useInfSharpPatch);
// Apply adaptive refinement and construct the associated PatchTable to
// evaluate the limit surface:
refiner->RefineAdaptive(adaptiveOptions);
Far::PatchTable const * patchTable =
Far::PatchTableFactory::Create(*refiner, patchOptions);
// Compute the total number of points we need to evaluate the PatchTable.
// Approximations at irregular or extraordinary features require the use
// of additional points associated with the patches that are referred to
// as "local points" (i.e. local to the PatchTable).
int nRefinerVertices = refiner->GetNumVerticesTotal();
int nLocalPoints = patchTable->GetNumLocalPoints();
// Create a buffer to hold the position of the refined verts and
// local points, then copy the coarse positions at the beginning.
std::vector<Vertex> verts(nRefinerVertices + nLocalPoints);
std::memcpy(&verts[0], g_verts, g_nverts*3*sizeof(Real));
// Adaptive refinement may result in fewer levels than the max specified.
int nRefinedLevels = refiner->GetNumLevels();
// Interpolate vertex primvar data : they are the control vertices
// of the limit patches (see tutorial_1_1 for details)
Far::PrimvarRefinerReal<Real> primvarRefiner(*refiner);
Vertex * src = &verts[0];
for (int level = 1; level < nRefinedLevels; ++level) {
Vertex * dst = src + refiner->GetLevel(level-1).GetNumVertices();
primvarRefiner.Interpolate(level, src, dst);
src = dst;
}
// Evaluate local points from interpolated vertex primvars.
if (nLocalPoints) {
patchTable->GetLocalPointStencilTable<Real>()->UpdateValues(
&verts[0], &verts[nRefinerVertices]);
}
// Create a Far::PatchMap to help locating patches in the table
Far::PatchMap patchmap(*patchTable);
// Create a Far::PtexIndices to help find indices of ptex faces.
Far::PtexIndices ptexIndices(*refiner);
// Generate random samples on each ptex face
int nsamplesPerFace = 200,
nfaces = ptexIndices.GetNumFaces();
std::vector<LimitFrame> samples(nsamplesPerFace * nfaces);
srand( static_cast<int>(2147483647) );
Real pWeights[20], dsWeights[20], dtWeights[20];
for (int face=0, count=0; face<nfaces; ++face) {
for (int sample=0; sample<nsamplesPerFace; ++sample, ++count) {
Real s = (Real)rand()/(Real)RAND_MAX,
t = (Real)rand()/(Real)RAND_MAX;
// Locate the patch corresponding to the face ptex idx and (s,t)
Far::PatchTable::PatchHandle const * handle =
patchmap.FindPatch(face, s, t);
assert(handle);
// Evaluate the patch weights, identify the CVs and compute the limit frame:
patchTable->EvaluateBasis(*handle, s, t, pWeights, dsWeights, dtWeights);
Far::ConstIndexArray cvs = patchTable->GetPatchVertices(*handle);
LimitFrame & dst = samples[count];
dst.Clear();
for (int cv=0; cv < cvs.size(); ++cv) {
dst.AddWithWeight(verts[cvs[cv]], pWeights[cv], dsWeights[cv], dtWeights[cv]);
}
}
}
{ // Visualization with Maya : print a MEL script that generates particles
// at the location of the limit vertices
int nsamples = (int)samples.size();
printf("file -f -new;\n");
// Output particle positions for the tangent
printf("particle -n deriv1 ");
for (int sample=0; sample<nsamples; ++sample) {
Real const * pos = samples[sample].point;
printf("-p %f %f %f\n", pos[0], pos[1], pos[2]);
}
printf(";\n");
// Set per-particle direction using the limit tangent (display as 'Streak')
printf("setAttr \"deriv1.particleRenderType\" 6;\n");
printf("setAttr \"deriv1.velocity\" -type \"vectorArray\" %d ",nsamples);
for (int sample=0; sample<nsamples; ++sample) {
Real const * tan1 = samples[sample].deriv1;
printf("%f %f %f\n", tan1[0], tan1[1], tan1[2]);
}
printf(";\n");
// Output particle positions for the bi-tangent
printf("particle -n deriv2 ");
for (int sample=0; sample<nsamples; ++sample) {
Real const * pos = samples[sample].point;
printf("-p %f %f %f\n", pos[0], pos[1], pos[2]);
}
printf(";\n");
printf("setAttr \"deriv2.particleRenderType\" 6;\n");
printf("setAttr \"deriv2.velocity\" -type \"vectorArray\" %d ",nsamples);
for (int sample=0; sample<nsamples; ++sample) {
Real const * tan2 = samples[sample].deriv2;
printf("%f %f %f\n", tan2[0], tan2[1], tan2[2]);
}
printf(";\n");
// Exercise to the reader : cross tangent & bi-tangent for limit
// surface normal...
// Force Maya DAG update to see the result in the viewport
printf("currentTime -edit `currentTime -q`;\n");
printf("select deriv1Shape deriv2Shape;\n");
}
delete refiner;
delete patchTable;
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------
static Far::TopologyRefiner *
createTopologyRefiner() {
typedef Far::TopologyDescriptor Descriptor;
Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK;
Sdc::Options options;
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
Descriptor desc;
desc.numVertices = g_nverts;
desc.numFaces = g_nfaces;
desc.numVertsPerFace = g_vertsperface;
desc.vertIndicesPerFace = g_faceverts;
desc.numCreases = g_ncreases;
desc.creaseVertexIndexPairs = g_creaseverts;
desc.creaseWeights = g_creaseweights;
// Instantiate a Far::TopologyRefiner from the descriptor.
Far::TopologyRefiner * refiner =
Far::TopologyRefinerFactory<Descriptor>::Create(desc,
Far::TopologyRefinerFactory<Descriptor>::Options(type, options));
return refiner;
}

View File

@@ -0,0 +1,11 @@
#
# Copyright 2018 DreamWorks Animation LLC.
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_far_tutorial(
far_tutorial_5_2
far_tutorial_5_2.cpp
$<TARGET_OBJECTS:regression_common_obj>
)

View File

@@ -0,0 +1,643 @@
//
// Copyright 2018 DreamWorks Animation LLC.
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial shows how to manage the limit surface of a potentially
// large mesh by creating groups of patches for selected faces of the
// mesh. Familiarity with construction and evaluation of a PatchTable
// is assumed (see tutorial_5_1).
//
// When the patches for a mesh do not need to be retained for further
// use, e.g. when simply computing points for a tessellation, the time
// and space required to construct a single large PatchTable can be
// considerable. By constructing, evaluating and discarding smaller
// PatchTables for subsets of the mesh, the high transient memory cost
// can be avoided when computed serially. When computed in parallel,
// there may be little memory savings, but the construction time can
// then be distributed.
//
// This tutorial creates simple geometry (currently a lattice of cubes)
// that can be expanded in complexity with a simple multiplier. The
// collection of faces are then divided into a specified number of groups
// from which patches will be constructed and evaluated. A simple
// tessellation (a triangle fan around the midpoint of each face) is then
// written in Obj format to the standard output.
//
#include "../../../regression/common/arg_utils.h"
#include "../../../regression/common/far_utils.h"
#include <opensubdiv/far/topologyDescriptor.h>
#include <opensubdiv/far/primvarRefiner.h>
#include <opensubdiv/far/patchTableFactory.h>
#include <opensubdiv/far/patchMap.h>
#include <opensubdiv/far/ptexIndices.h>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <sstream>
using namespace OpenSubdiv;
using Far::Index;
//
// Global utilities in this namespace are not relevant to the tutorial.
// They simply serve to construct some default geometry to be processed
// in the form of a TopologyRefiner and vector of vertex positions.
//
namespace {
//
// Simple structs for (x,y,z) position and a 3-tuple for the set
// of vertices of a triangle:
//
struct Pos {
Pos() { }
Pos(float x, float y, float z) { p[0] = x, p[1] = y, p[2] = z; }
Pos operator+(Pos const & op) const {
return Pos(p[0] + op.p[0], p[1] + op.p[1], p[2] + op.p[2]);
}
// Clear() and AddWithWeight() required for interpolation:
void Clear( void * =0 ) { p[0] = p[1] = p[2] = 0.0f; }
void AddWithWeight(Pos const & src, float weight) {
p[0] += weight * src.p[0];
p[1] += weight * src.p[1];
p[2] += weight * src.p[2];
}
float p[3];
};
typedef std::vector<Pos> PosVector;
struct Tri {
Tri() { }
Tri(int a, int b, int c) { v[0] = a, v[1] = b, v[2] = c; }
int v[3];
};
typedef std::vector<Tri> TriVector;
//
// Functions to populate the topology and geometry arrays with simple
// shapes that we can multiply to increase complexity:
//
void
appendDefaultPrimitive(Pos const & origin,
std::vector<int> & vertsPerFace,
std::vector<Index> & faceVerts,
std::vector<Pos> & positionsPerVert) {
// Local topology and position of a cube centered at origin:
static float const cubePositions[8][3] = { { -0.5f, -0.5f, -0.5f },
{ -0.5f, 0.5f, -0.5f },
{ -0.5f, 0.5f, 0.5f },
{ -0.5f, -0.5f, 0.5f },
{ 0.5f, -0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },
{ 0.5f, 0.5f, 0.5f },
{ 0.5f, -0.5f, 0.5f } };
static int const cubeFaceVerts[6][4] = { { 0, 3, 2, 1 },
{ 4, 5, 6, 7 },
{ 0, 4, 7, 3 },
{ 1, 2, 6, 5 },
{ 0, 1, 5, 4 },
{ 3, 7, 6, 2 } };
// Identify the next vertex before appending vertex positions:
int baseVertex = (int) positionsPerVert.size();
for (int i = 0; i < 8; ++i) {
float const * p = cubePositions[i];
positionsPerVert.push_back(origin + Pos(p[0], p[1], p[2]));
}
// Append number of verts-per-face and face-vertices for each face:
for (int i = 0; i < 6; ++i) {
vertsPerFace.push_back(4);
for (int j = 0; j < 4; ++j) {
faceVerts.push_back(baseVertex + cubeFaceVerts[i][j]);
}
}
}
void
createDefaultGeometry(int multiplier,
std::vector<int> & vertsPerFace,
std::vector<Index> & faceVerts,
std::vector<Pos> & positionsPerVert) {
// Default primitive is currently a cube:
int const vertsPerPrimitive = 8;
int const facesPerPrimitive = 6;
int const faceVertsPerPrimitive = 24;
int nPrimitives = multiplier * multiplier * multiplier;
positionsPerVert.reserve(nPrimitives * vertsPerPrimitive);
vertsPerFace.reserve(nPrimitives * facesPerPrimitive);
faceVerts.reserve(nPrimitives * faceVertsPerPrimitive);
for (int x = 0; x < multiplier; ++x) {
for (int y = 0; y < multiplier; ++y) {
for (int z = 0; z < multiplier; ++z) {
appendDefaultPrimitive(
Pos((float)x * 2.0f, (float)y * 2.0f, (float)z * 2.0f),
vertsPerFace, faceVerts, positionsPerVert);
}
}
}
}
//
// Create a TopologyRefiner from default geometry created above:
//
Far::TopologyRefiner *
createTopologyRefinerDefault(int multiplier,
PosVector & posVector) {
std::vector<int> topVertsPerFace;
std::vector<Index> topFaceVerts;
createDefaultGeometry(
multiplier, topVertsPerFace, topFaceVerts, posVector);
typedef Far::TopologyDescriptor Descriptor;
Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK;
Sdc::Options options;
options.SetVtxBoundaryInterpolation(
Sdc::Options::VTX_BOUNDARY_EDGE_AND_CORNER);
Descriptor desc;
desc.numVertices = (int) posVector.size();
desc.numFaces = (int) topVertsPerFace.size();
desc.numVertsPerFace = &topVertsPerFace[0];
desc.vertIndicesPerFace = &topFaceVerts[0];
// Instantiate a Far::TopologyRefiner from the descriptor.
Far::TopologyRefiner * refiner =
Far::TopologyRefinerFactory<Descriptor>::Create(desc,
Far::TopologyRefinerFactory<Descriptor>::Options(
type, options));
if (refiner == 0) {
exit(EXIT_FAILURE);
}
bool dumpDefaultGeometryToObj = false;
if (dumpDefaultGeometryToObj) {
int nVerts = (int) posVector.size();
for (int i = 0; i < nVerts; ++i) {
float const * p = posVector[i].p;
printf("v %f %f %f\n", p[0], p[1], p[2]);
}
int const * fVerts = &topFaceVerts[0];
int nFaces = (int) topVertsPerFace.size();
for (int i = 0; i < nFaces; ++i) {
printf("f");
for (int j = 0; j < topVertsPerFace[i]; ++j) {
printf(" %d", 1 + *fVerts++);
}
printf("\n");
}
exit(EXIT_SUCCESS);
}
return refiner;
}
//
// Create a TopologyRefiner from a specified Obj file:
// geometry created internally:
//
Far::TopologyRefiner *
createTopologyRefinerFromObj(std::string const & objFileName,
Sdc::SchemeType schemeType,
PosVector & posVector) {
const char * filename = objFileName.c_str();
const Shape * shape = 0;
std::ifstream ifs(filename);
if (ifs) {
std::stringstream ss;
ss << ifs.rdbuf();
ifs.close();
std::string shapeString = ss.str();
shape = Shape::parseObj(shapeString.c_str(),
ConvertSdcTypeToShapeScheme(schemeType), false);
if (shape == 0) {
fprintf(stderr, "Error: Cannot create Shape "
"from .obj file '%s'\n", filename);
return 0;
}
} else {
fprintf(stderr, "Error: Cannot open .obj file '%s'\n", filename);
return 0;
}
Sdc::SchemeType sdcType = GetSdcType(*shape);
Sdc::Options sdcOptions = GetSdcOptions(*shape);
Far::TopologyRefiner * refiner =
Far::TopologyRefinerFactory<Shape>::Create(*shape,
Far::TopologyRefinerFactory<Shape>::Options(
sdcType, sdcOptions));
if (refiner == 0) {
fprintf(stderr, "Error: Unable to construct TopologyRefiner "
"from .obj file '%s'\n", filename);
return 0;
}
int numVertices = refiner->GetNumVerticesTotal();
posVector.resize(numVertices);
std::memcpy(&posVector[0].p[0], &shape->verts[0],
numVertices * 3 * sizeof(float));
delete shape;
return refiner;
}
} // end namespace
//
// The PatchGroup bundles objects used to create and evaluate a sparse set
// of patches. Its construction creates a PatchTable and all other objects
// necessary to evaluate patches associated with the specified subset of
// faces provided. A simple method to tessellate a specified face is
// provided.
//
// Note that, since the data buffers for the base level and refined levels
// are separate (we want to avoid copying primvar data for the base level
// of a potentially large mesh), that patch evaluation needs to account
// for the separation when combining control points.
//
struct PatchGroup {
PatchGroup(Far::PatchTableFactory::Options patchOptions,
Far::TopologyRefiner const & baseRefinerArg,
Far::PtexIndices const & basePtexIndicesArg,
std::vector<Pos> const & basePositionsArg,
std::vector<Index> const & baseFacesArg);
~PatchGroup();
void TessellateBaseFace(int face, PosVector & tessPoints,
TriVector & tessTris) const;
// Const reference members:
Far::TopologyRefiner const & baseRefiner;
Far::PtexIndices const & basePtexIndices;
std::vector<Pos> const & basePositions;
std::vector<Index> const & baseFaces;
// Members constructed to evaluate patches:
Far::PatchTable * patchTable;
Far::PatchMap * patchMap;
int patchFaceSize;
std::vector<Pos> localPositions;
};
PatchGroup::PatchGroup(Far::PatchTableFactory::Options patchOptions,
Far::TopologyRefiner const & baseRefinerArg,
Far::PtexIndices const & basePtexIndicesArg,
std::vector<Pos> const & basePositionsArg,
std::vector<Index> const & baseFacesArg) :
baseRefiner(baseRefinerArg),
basePtexIndices(basePtexIndicesArg),
basePositions(basePositionsArg),
baseFaces(baseFacesArg) {
// Create a local refiner (sharing the base level), apply adaptive
// refinement to the given subset of base faces, and construct a patch
// table (and its associated map) for the same set of faces:
//
Far::ConstIndexArray groupFaces(&baseFaces[0], (int)baseFaces.size());
Far::TopologyRefiner *localRefiner =
Far::TopologyRefinerFactory<Far::TopologyDescriptor>::Create(
baseRefiner);
localRefiner->RefineAdaptive(
patchOptions.GetRefineAdaptiveOptions(), groupFaces);
patchTable = Far::PatchTableFactory::Create(*localRefiner, patchOptions,
groupFaces);
patchMap = new Far::PatchMap(*patchTable);
patchFaceSize =
Sdc::SchemeTypeTraits::GetRegularFaceSize(baseRefiner.GetSchemeType());
// Compute the number of refined and local points needed to evaluate the
// patches, allocate and interpolate. This varies from tutorial_5_1 in
// that the primvar buffer for the base vertices is separate from the
// refined vertices and local patch points (which must also be accounted
// for when evaluating the patches).
//
int nBaseVertices = localRefiner->GetLevel(0).GetNumVertices();
int nRefinedVertices = localRefiner->GetNumVerticesTotal() - nBaseVertices;
int nLocalPoints = patchTable->GetNumLocalPoints();
localPositions.resize(nRefinedVertices + nLocalPoints);
if (nRefinedVertices) {
Far::PrimvarRefiner primvarRefiner(*localRefiner);
Pos const * src = &basePositions[0];
Pos * dst = &localPositions[0];
for (int level = 1; level < localRefiner->GetNumLevels(); ++level) {
primvarRefiner.Interpolate(level, src, dst);
src = dst;
dst += localRefiner->GetLevel(level).GetNumVertices();
}
}
if (nLocalPoints) {
patchTable->GetLocalPointStencilTable()->UpdateValues(
&basePositions[0], nBaseVertices, &localPositions[0],
&localPositions[nRefinedVertices]);
}
delete localRefiner;
}
PatchGroup::~PatchGroup() {
delete patchTable;
delete patchMap;
}
void
PatchGroup::TessellateBaseFace(int face, PosVector & tessPoints,
TriVector & tessTris) const {
// Tesselate the face with points at the midpoint of the face and at
// each corner, and triangles connecting the midpoint to each edge.
// Irregular faces require an aribrary number of corners points, but
// all are at the origin of the child face of the irregular base face:
//
float const quadPoints[5][2] = { { 0.5f, 0.5f },
{ 0.0f, 0.0f },
{ 1.0f, 0.0f },
{ 1.0f, 1.0f },
{ 0.0f, 1.0f } };
float const triPoints[4][2] = { { 0.5f, 0.5f },
{ 0.0f, 0.0f },
{ 1.0f, 0.0f },
{ 0.0f, 1.0f } };
float const irregPoints[4][2] = { { 1.0f, 1.0f },
{ 0.0f, 0.0f } };
// Determine the topology of the given base face and the resulting
// tessellation points and faces to generate:
//
int baseFace = baseFaces[face];
int faceSize = baseRefiner.GetLevel(0).GetFaceVertices(baseFace).size();
bool faceIsIrregular = (faceSize != patchFaceSize);
int nTessPoints = faceSize + 1;
int nTessFaces = faceSize;
tessPoints.resize(nTessPoints);
tessTris.resize(nTessFaces);
// Compute the mid and corner points -- remember that for an irregular
// face, we must reference the individual ptex faces for each corner:
//
int ptexFace = basePtexIndices.GetFaceId(baseFace);
int numBaseVerts = (int) basePositions.size();
for (int i = 0; i < nTessPoints; ++i) {
// Choose the (s,t) coordinate from the fixed tessellation:
float const * st = faceIsIrregular ? irregPoints[i != 0]
: ((faceSize == 4) ? quadPoints[i] : triPoints[i]);
// Locate the patch corresponding to the face ptex idx and (s,t)
// and evaluate:
int patchFace = ptexFace;
if (faceIsIrregular && (i > 0)) {
patchFace += i - 1;
}
Far::PatchTable::PatchHandle const * handle =
patchMap->FindPatch(patchFace, st[0], st[1]);
assert(handle);
float pWeights[20];
patchTable->EvaluateBasis(*handle, st[0], st[1], pWeights);
// Identify the patch cvs and combine with the evaluated weights --
// remember to distinguish cvs in the base level:
Far::ConstIndexArray cvIndices = patchTable->GetPatchVertices(*handle);
Pos & pos = tessPoints[i];
pos.Clear();
for (int cv = 0; cv < cvIndices.size(); ++cv) {
int cvIndex = cvIndices[cv];
if (cvIndex < numBaseVerts) {
pos.AddWithWeight(basePositions[cvIndex],
pWeights[cv]);
} else {
pos.AddWithWeight(localPositions[cvIndex - numBaseVerts],
pWeights[cv]);
}
}
}
// Assign triangles connecting the midpoint of the base face to the
// points computed at the ends of each of its edges:
//
for (int i = 0; i < nTessFaces; ++i) {
tessTris[i] = Tri(0, 1 + i, 1 + ((i + 1) % faceSize));
}
}
//
// Command line arguments parsed to provide run-time options:
//
class Args {
public:
std::string inputObjFile;
Sdc::SchemeType schemeType;
int geoMultiplier;
int maxPatchDepth;
int numPatchGroups;
bool noTessFlag;
bool noOutputFlag;
public:
Args(int argc, char ** argv) :
inputObjFile(),
schemeType(Sdc::SCHEME_CATMARK),
geoMultiplier(10),
maxPatchDepth(3),
numPatchGroups(10),
noTessFlag(false),
noOutputFlag(false) {
// Parse and assign standard arguments and Obj files:
ArgOptions args;
args.Parse(argc, argv);
maxPatchDepth = args.GetLevel();
schemeType = ConvertShapeSchemeToSdcType(args.GetDefaultScheme());
const std::vector<const char *> objFiles = args.GetObjFiles();
if (!objFiles.empty()) {
for (size_t i = 1; i < objFiles.size(); ++i) {
fprintf(stderr,
"Warning: .obj file '%s' ignored\n", objFiles[i]);
}
inputObjFile = std::string(objFiles[0]);
}
// Parse remaining arguments specific to this example:
const std::vector<const char *> &rargs = args.GetRemainingArgs();
for (size_t i = 0; i < rargs.size(); ++i) {
if (!strcmp(rargs[i], "-groups")) {
if (++i < rargs.size()) numPatchGroups = atoi(rargs[i]);
} else if (!strcmp(rargs[i], "-mult")) {
if (++i < rargs.size()) geoMultiplier = atoi(rargs[i]);
} else if (!strcmp(rargs[i], "-notess")) {
noTessFlag = true;
} else if (!strcmp(rargs[i], "-nooutput")) {
noOutputFlag = true;
} else {
fprintf(stderr, "Warning: Argument '%s' ignored\n", rargs[i]);
}
}
}
private:
Args() { }
};
//
// Load command line arguments and geometry, then divide the mesh into groups
// of faces from which to create and tessellate patches:
//
int
main(int argc, char **argv) {
Args args(argc, argv);
//
// Create or load the base geometry (command line arguments allow a
// .obj file to be specified). In addition to the TopologyRefiner
// and set of positions for the base vertices, a set of PtexIndices is
// also required to evaluate patches, so build it here once for use
// elsewhere:
//
std::vector<Pos> basePositions;
Far::TopologyRefiner * baseRefinerPtr = args.inputObjFile.empty() ?
createTopologyRefinerDefault(args.geoMultiplier, basePositions) :
createTopologyRefinerFromObj(args.inputObjFile, args.schemeType,
basePositions);
assert(baseRefinerPtr);
Far::TopologyRefiner & baseRefiner = *baseRefinerPtr;
Far::PtexIndices basePtexIndices(baseRefiner);
//
// Determine the sizes of the patch groups specified -- there will be
// two sizes that differ by one to account for unequal division:
//
int numBaseFaces = baseRefiner.GetNumFacesTotal();
int numPatchGroups = args.numPatchGroups;
if (numPatchGroups > numBaseFaces) {
numPatchGroups = numBaseFaces;
} else if (numPatchGroups < 1) {
numPatchGroups = 1;
}
int lesserGroupSize = numBaseFaces / numPatchGroups;
int numLargerGroups = numBaseFaces - (numPatchGroups * lesserGroupSize);
//
// Define the options used to construct the patches for each group.
// Unless suppressed, a tessellation in Obj format will also be printed
// to standard output, so keep track of the vertex indices.
//
Far::PatchTableFactory::Options patchOptions(args.maxPatchDepth);
patchOptions.generateVaryingTables = false;
patchOptions.shareEndCapPatchPoints = false;
patchOptions.endCapType =
Far::PatchTableFactory::Options::ENDCAP_GREGORY_BASIS;
int objVertCount = 0;
PosVector tessPoints;
TriVector tessFaces;
for (int i = 0; i < numPatchGroups; ++i) {
//
// Initialize a vector with a group of base faces from which to
// create and evaluate patches:
//
Index minFace = i * lesserGroupSize + std::min(i, numLargerGroups);
Index maxFace = minFace + lesserGroupSize + (i < numLargerGroups);
std::vector<Far::Index> baseFaces(maxFace - minFace);
for (int face = minFace; face < maxFace; ++face) {
baseFaces[face - minFace] = face;
}
//
// Declare a PatchGroup and tessellate its base faces -- generating
// vertices and faces in Obj format to standard output:
//
PatchGroup patchGroup(patchOptions,
baseRefiner, basePtexIndices, basePositions, baseFaces);
if (args.noTessFlag) continue;
if (!args.noOutputFlag) {
printf("g patchGroup_%d\n", i);
}
for (int j = 0; j < (int) baseFaces.size(); ++j) {
patchGroup.TessellateBaseFace(j, tessPoints, tessFaces);
if (!args.noOutputFlag) {
int nVerts = (int) tessPoints.size();
for (int k = 0; k < nVerts; ++k) {
float const * p = tessPoints[k].p;
printf("v %f %f %f\n", p[0], p[1], p[2]);
}
int nTris = (int) tessFaces.size();
int vBase = 1 + objVertCount;
for (int k = 0; k < nTris; ++k) {
int const * v = tessFaces[k].v;
printf("f %d %d %d\n",
vBase + v[0], vBase + v[1], vBase + v[2]);
}
objVertCount += nVerts;
}
}
}
delete baseRefinerPtr;
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,11 @@
#
# Copyright 2020 DreamWorks Animation LLC.
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
osd_add_far_tutorial(
far_tutorial_5_3
far_tutorial_5_3.cpp
$<TARGET_OBJECTS:regression_common_obj>
)

View File

@@ -0,0 +1,562 @@
//
// Copyright 2020 DreamWorks Animation LLC.
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial shows how to use a Far::LimitStenciTable to repeatedly
// and efficiently evaluate a set of points (and optionally derivatives)
// on the limit surface.
//
// A LimitStencilTable derives from StencilTable but is specialized to
// factor the evaluation of limit positions and derivatives into stencils.
// This allows a set of limit properties to be efficiently recomputed in
// response to changes to the vertices of the base mesh. Constructing
// the different kinds of StencilTables can have a high cost, so whether
// that cost is worth it will depend on your usage (e.g. if points are
// only computed once, using stencil tables is typically not worth the
// added cost).
//
// Any points on the limit surface can be identified for evaluation. In
// this example we create a crude tessellation similar to tutorial_5_2.
// The midpoint of each face and points near the corners of the face are
// evaluated and a triangle fan connects them.
//
#include "../../../regression/common/arg_utils.h"
#include "../../../regression/common/far_utils.h"
#include <opensubdiv/far/topologyDescriptor.h>
#include <opensubdiv/far/patchTableFactory.h>
#include <opensubdiv/far/stencilTableFactory.h>
#include <opensubdiv/far/ptexIndices.h>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <sstream>
using namespace OpenSubdiv;
using Far::Index;
//
// Global utilities in this namespace are not relevant to the tutorial.
// They simply serve to construct some default geometry to be processed
// in the form of a TopologyRefiner and vector of vertex positions.
//
namespace {
//
// Simple structs for (x,y,z) position and a 3-tuple for the set
// of vertices of a triangle:
//
struct Pos {
Pos() { }
Pos(float x, float y, float z) { p[0] = x, p[1] = y, p[2] = z; }
Pos operator+(Pos const & op) const {
return Pos(p[0] + op.p[0], p[1] + op.p[1], p[2] + op.p[2]);
}
// Clear() and AddWithWeight() required for interpolation:
void Clear( void * =0 ) { p[0] = p[1] = p[2] = 0.0f; }
void AddWithWeight(Pos const & src, float weight) {
p[0] += weight * src.p[0];
p[1] += weight * src.p[1];
p[2] += weight * src.p[2];
}
float p[3];
};
typedef std::vector<Pos> PosVector;
struct Tri {
Tri() { }
Tri(int a, int b, int c) { v[0] = a, v[1] = b, v[2] = c; }
int v[3];
};
typedef std::vector<Tri> TriVector;
//
// Functions to populate the topology and geometry arrays a simple
// shape whose positions may be transformed:
//
void
createCube(std::vector<int> & vertsPerFace,
std::vector<Index> & faceVertsPerFace,
std::vector<Pos> & positionsPerVert) {
// Local topology and position of a cube centered at origin:
static float const cubePositions[8][3] = { { -0.5f, -0.5f, -0.5f },
{ -0.5f, 0.5f, -0.5f },
{ -0.5f, 0.5f, 0.5f },
{ -0.5f, -0.5f, 0.5f },
{ 0.5f, -0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },
{ 0.5f, 0.5f, 0.5f },
{ 0.5f, -0.5f, 0.5f } };
static int const cubeFaceVerts[6][4] = { { 0, 3, 2, 1 },
{ 4, 5, 6, 7 },
{ 0, 4, 7, 3 },
{ 1, 2, 6, 5 },
{ 0, 1, 5, 4 },
{ 3, 7, 6, 2 } };
// Initialize verts-per-face and face-vertices for each face:
vertsPerFace.resize(6);
faceVertsPerFace.resize(24);
for (int i = 0; i < 6; ++i) {
vertsPerFace[i] = 4;
for (int j = 0; j < 4; ++j) {
faceVertsPerFace[i*4+j] = cubeFaceVerts[i][j];
}
}
// Initialize vertex positions:
positionsPerVert.resize(8);
for (int i = 0; i < 8; ++i) {
float const * p = cubePositions[i];
positionsPerVert[i] = Pos(p[0], p[1], p[2]);
}
}
//
// Create a TopologyRefiner from default geometry created above:
//
Far::TopologyRefiner *
createTopologyRefinerDefault(PosVector & posVector) {
std::vector<int> topVertsPerFace;
std::vector<Index> topFaceVerts;
createCube(topVertsPerFace, topFaceVerts, posVector);
typedef Far::TopologyDescriptor Descriptor;
Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK;
Sdc::Options options;
options.SetVtxBoundaryInterpolation(
Sdc::Options::VTX_BOUNDARY_EDGE_AND_CORNER);
Descriptor desc;
desc.numVertices = (int) posVector.size();
desc.numFaces = (int) topVertsPerFace.size();
desc.numVertsPerFace = &topVertsPerFace[0];
desc.vertIndicesPerFace = &topFaceVerts[0];
// Instantiate a Far::TopologyRefiner from the descriptor.
Far::TopologyRefiner * refiner =
Far::TopologyRefinerFactory<Descriptor>::Create(desc,
Far::TopologyRefinerFactory<Descriptor>::Options(type,options));
assert(refiner);
return refiner;
}
//
// Create a TopologyRefiner from a specified Obj file:
// geometry created internally:
//
Far::TopologyRefiner *
createTopologyRefinerFromObj(std::string const & objFileName,
Sdc::SchemeType schemeType,
PosVector & posVector) {
const char * filename = objFileName.c_str();
const Shape * shape = 0;
std::ifstream ifs(filename);
if (ifs) {
std::stringstream ss;
ss << ifs.rdbuf();
ifs.close();
std::string shapeString = ss.str();
shape = Shape::parseObj(shapeString.c_str(),
ConvertSdcTypeToShapeScheme(schemeType), false);
if (shape == 0) {
fprintf(stderr,
"Error: Cannot create Shape from .obj file '%s'\n",
filename);
return 0;
}
} else {
fprintf(stderr, "Error: Cannot open .obj file '%s'\n", filename);
return 0;
}
Sdc::SchemeType sdcType = GetSdcType(*shape);
Sdc::Options sdcOptions = GetSdcOptions(*shape);
Far::TopologyRefiner * refiner =
Far::TopologyRefinerFactory<Shape>::Create(*shape,
Far::TopologyRefinerFactory<Shape>::Options(
sdcType, sdcOptions));
if (refiner == 0) {
fprintf(stderr, "Error: Unable to construct TopologyRefiner "
"from .obj file '%s'\n", filename);
return 0;
}
int numVertices = refiner->GetNumVerticesTotal();
posVector.resize(numVertices);
std::memcpy(&posVector[0].p[0], &shape->verts[0],
numVertices * 3 * sizeof(float));
delete shape;
return refiner;
}
//
// Simple function to export an Obj file for the limit points -- which
// provides a simple tessllation similar to tutorial_5_2.
//
int writeToObj(
Far::TopologyLevel const & baseLevel,
std::vector<Pos> const & vertexPositions,
int nextObjVertexIndex) {
for (size_t i = 0; i < vertexPositions.size(); ++i) {
float const * p = vertexPositions[i].p;
printf("v %f %f %f\n", p[0], p[1], p[2]);
}
//
// Connect the sequences of limit points (center followed by corners)
// into triangle fans for each base face:
//
for (int i = 0; i < baseLevel.GetNumFaces(); ++i) {
int faceSize = baseLevel.GetFaceVertices(i).size();
int vCenter = nextObjVertexIndex + 1;
int vCorner = vCenter + 1;
for (int k = 0; k < faceSize; ++k) {
printf("f %d %d %d\n",
vCenter, vCorner + k, vCorner + ((k + 1) % faceSize));
}
nextObjVertexIndex += faceSize + 1;
}
return nextObjVertexIndex;
}
} // end namespace
//
// Command line arguments parsed to provide run-time options:
//
class Args {
public:
std::string inputObjFile;
Sdc::SchemeType schemeType;
int maxPatchDepth;
int numPoses;
Pos poseOffset;
bool deriv1Flag;
bool noPatchesFlag;
bool noOutputFlag;
public:
Args(int argc, char ** argv) :
inputObjFile(),
schemeType(Sdc::SCHEME_CATMARK),
maxPatchDepth(3),
numPoses(0),
poseOffset(1.0f, 0.0f, 0.0f),
deriv1Flag(false),
noPatchesFlag(false),
noOutputFlag(false) {
// Parse and assign standard arguments and Obj files:
ArgOptions args;
args.Parse(argc, argv);
maxPatchDepth = args.GetLevel();
schemeType = ConvertShapeSchemeToSdcType(args.GetDefaultScheme());
const std::vector<const char *> objFiles = args.GetObjFiles();
if (!objFiles.empty()) {
for (size_t i = 1; i < objFiles.size(); ++i) {
fprintf(stderr,
"Warning: .obj file '%s' ignored\n", objFiles[i]);
}
inputObjFile = std::string(objFiles[0]);
}
// Parse remaining arguments specific to this example:
const std::vector<const char *> &rargs = args.GetRemainingArgs();
for (size_t i = 0; i < rargs.size(); ++i) {
if (!strcmp(rargs[i], "-d1")) {
deriv1Flag = true;
} else if (!strcmp(rargs[i], "-nopatches")) {
noPatchesFlag = true;
} else if (!strcmp(rargs[i], "-poses")) {
if (++i < rargs.size()) numPoses = atoi(rargs[i]);
} else if (!strcmp(rargs[i], "-offset")) {
if (++i < rargs.size()) poseOffset.p[0] = (float)atof(rargs[i]);
if (++i < rargs.size()) poseOffset.p[1] = (float)atof(rargs[i]);
if (++i < rargs.size()) poseOffset.p[2] = (float)atof(rargs[i]);
} else if (!strcmp(rargs[i], "-nooutput")) {
noOutputFlag = true;
} else {
fprintf(stderr, "Warning: Argument '%s' ignored\n", rargs[i]);
}
}
}
private:
Args() { }
};
//
// Assemble the set of locations for the limit points. The resulting
// vector of LocationArrays can contain arbitrary locations on the limit
// surface -- with multiple locations for the same patch grouped into a
// single array.
//
// In this case, for each base face, coordinates for the center and its
// corners are specified -- from which we will construct a triangle fan
// providing a crude tessellation (similar to tutorial_5_2).
//
typedef Far::LimitStencilTableFactory::LocationArray LocationArray;
int assembleLimitPointLocations(Far::TopologyRefiner const & refiner,
std::vector<LocationArray> & locations) {
//
// Coordinates for the center of the face and its corners (slightly
// inset). Unlike most of the public interface for patches, the
// LocationArray refers to parameteric coordinates as (s,t), so that
// convention will be followed here.
//
// Note that the (s,t) coordinates in a LocationArray are referred to
// by reference. The memory holding these (s,t) values must persist
// while the LimitStencilTable is constructed -- the arrays here are
// declared as static for that purpose.
//
static float const quadSCoords[5] = { 0.5f, 0.05f, 0.95f, 0.95f, 0.05f };
static float const quadTCoords[5] = { 0.5f, 0.05f, 0.05f, 0.95f, 0.95f };
static float const triSCoords[4] = { 0.33f, 0.05f, 0.95f, 0.05f };
static float const triTCoords[4] = { 0.33f, 0.05f, 0.00f, 0.95f };
static float const irregSCoords[2] = { 1.0f, 0.05f };
static float const irregTCoords[2] = { 1.0f, 0.05f };
//
// Since these are references to patches to be evaluated, we require
// use of the Ptex indices to identify the top-most parameterized
// patch, which is essential to dealing with non-quad faces (in the
// case of Catmark).
//
Far::TopologyLevel const & baseLevel = refiner.GetLevel(0);
Far::PtexIndices basePtexIndices(refiner);
int regFaceSize = Sdc::SchemeTypeTraits::GetRegularFaceSize(
refiner.GetSchemeType());
//
// For each base face, simply refer to the (s,t) arrays for regular quad
// and triangular patches with a single LocationArray. Otherwise, for
// irregular faces, the corners of the face come from different patches
// and so must be referenced in separate LocationArrays.
//
locations.clear();
int numLimitPoints = 0;
for (int i = 0; i < baseLevel.GetNumFaces(); ++i) {
int baseFaceSize = baseLevel.GetFaceVertices(i).size();
int basePtexId = basePtexIndices.GetFaceId(i);
bool faceIsRegular = (baseFaceSize == regFaceSize);
if (faceIsRegular) {
// All coordinates are on the same top-level patch:
LocationArray loc;
loc.ptexIdx = basePtexId;
loc.numLocations = baseFaceSize + 1;
if (baseFaceSize == 4) {
loc.s = quadSCoords;
loc.t = quadTCoords;
} else {
loc.s = triSCoords;
loc.t = triTCoords;
}
locations.push_back(loc);
} else {
// Center coordinate is on the first sub-patch while those on
// near the corners are on each successive sub-patch:
LocationArray loc;
loc.numLocations = 1;
for (int j = 0; j <= baseFaceSize; ++j) {
bool isPerimeter = (j > 0);
loc.ptexIdx = basePtexId + (isPerimeter ? (j-1) : 0);
loc.s = &irregSCoords[isPerimeter];
loc.t = &irregTCoords[isPerimeter];
locations.push_back(loc);
}
}
numLimitPoints += baseFaceSize + 1;
}
return numLimitPoints;
}
//
// Load command line arguments and geometry, build the LimitStencilTable
// for a set of points on the limit surface and compute those points for
// several orientations of the mesh:
//
int
main(int argc, char **argv) {
Args args(argc, argv);
//
// Create or load the base geometry (command line arguments allow a
// .obj file to be specified), providing a TopologyRefiner and a set
// of base vertex positions to work with:
//
std::vector<Pos> basePositions;
Far::TopologyRefiner * refinerPtr = args.inputObjFile.empty() ?
createTopologyRefinerDefault(basePositions) :
createTopologyRefinerFromObj(args.inputObjFile, args.schemeType,
basePositions);
assert(refinerPtr);
Far::TopologyRefiner & refiner = *refinerPtr;
Far::TopologyLevel const & baseLevel = refiner.GetLevel(0);
//
// Use of LimitStencilTable requires either explicit or implicit use
// of a PatchTable. A PatchTable is not required to construct a
// LimitStencilTable -- one will be constructed internally for use
// and discarded -- but explicit construction is recommended to control
// the many legacy options for PatchTable, rather than relying on
// internal defaults. Adaptive refinement is required in both cases
// to indicate the accuracy of the patches.
//
// Note that if a TopologyRefiner and PatchTable are not used for
// any other purpose than computing the limit points, that specifying
// the subset of faces containing those limit points in the adaptive
// refinement and PatchTable construction can avoid unnecessary
// overhead.
//
Far::PatchTable * patchTablePtr = 0;
if (args.noPatchesFlag) {
refiner.RefineAdaptive(
Far::TopologyRefiner::AdaptiveOptions(args.maxPatchDepth));
} else {
Far::PatchTableFactory::Options patchOptions(args.maxPatchDepth);
patchOptions.useInfSharpPatch = true;
patchOptions.generateLegacySharpCornerPatches = false;
patchOptions.generateVaryingTables = false;
patchOptions.generateFVarTables = false;
patchOptions.endCapType =
Far::PatchTableFactory::Options::ENDCAP_GREGORY_BASIS;
refiner.RefineAdaptive(patchOptions.GetRefineAdaptiveOptions());
patchTablePtr = Far::PatchTableFactory::Create(refiner, patchOptions);
assert(patchTablePtr);
}
//
// Assemble the set of locations for the limit points. For each base
// face, coordinates for the center and its corners are specified --
// from which we will construct a triangle fan providing a crude
// tessellation (similar to tutorial_5_2).
//
std::vector<LocationArray> locations;
int numLimitPoints = assembleLimitPointLocations(refiner, locations);
//
// Construct a LimitStencilTable from the refiner, patch table (optional)
// and the collection of limit point locations. Stencils can optionally
// be created for computing dervatives -- the default is to compute 1st
// derivative stencils, so be sure to disable that if not necessary:
//
Far::LimitStencilTableFactory::Options limitOptions;
limitOptions.generate1stDerivatives = args.deriv1Flag;
Far::LimitStencilTable const * limitStencilTablePtr =
Far::LimitStencilTableFactory::Create(refiner, locations,
0, // optional StencilTable for the refined points
patchTablePtr, // optional PatchTable
limitOptions);
assert(limitStencilTablePtr);
Far::LimitStencilTable const & limitStencilTable = *limitStencilTablePtr;
//
// Apply the constructed LimitStencilTable to compute limit positions
// from the base level vertex positions. This is trivial if computing
// all positions in one invokation. The UpdateValues method (and those
// for derivatives) are overloaded to optionally accept a subrange of
// indices to distribute the computation:
//
std::vector<Pos> limitPositions(numLimitPoints);
limitStencilTable.UpdateValues(basePositions, limitPositions);
// Call with the optional subrange:
limitStencilTable.UpdateValues(basePositions, limitPositions,
0, numLimitPoints / 2);
limitStencilTable.UpdateValues(basePositions, limitPositions,
(numLimitPoints / 2) + 1, numLimitPoints);
// Write vertices and faces in Obj format for the original limit points:
int objVertCount = 0;
if (!args.noOutputFlag) {
printf("g base_mesh\n");
objVertCount = writeToObj(baseLevel, limitPositions, objVertCount);
}
//
// Recompute the limit points and output faces for different "poses" of
// the original mesh -- in this case simply translated. Also optionally
// compute 1st derivatives (though they are not used here):
//
std::vector<Pos> posePositions(basePositions);
std::vector<Pos> limitDu(args.deriv1Flag ? numLimitPoints : 0);
std::vector<Pos> limitDv(args.deriv1Flag ? numLimitPoints : 0);
for (int i = 0; i < args.numPoses; ++i) {
// Trivially transform the base vertex positions and re-compute:
for (size_t j = 0; j < basePositions.size(); ++j) {
posePositions[j] = posePositions[j] + args.poseOffset;
}
limitStencilTable.UpdateValues(posePositions, limitPositions);
if (args.deriv1Flag) {
limitStencilTable.UpdateDerivs(posePositions, limitDu, limitDv);
}
if (!args.noOutputFlag) {
printf("\ng pose_%d\n", i);
objVertCount = writeToObj(baseLevel, limitPositions, objVertCount);
}
}
delete refinerPtr;
delete patchTablePtr;
delete limitStencilTablePtr;
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,27 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
set(TUTORIALS
tutorial_0
tutorial_1
tutorial_2
)
foreach(tutorial ${TUTORIALS})
add_subdirectory("${tutorial}")
list(APPEND TUTORIAL_TARGETS "hbr_${tutorial}")
endforeach()
add_custom_target(hbr_tutorials DEPENDS ${TUTORIAL_TARGETS})
set_target_properties(hbr_tutorials
PROPERTIES
FOLDER "tutorials/hbr"
)

View File

@@ -0,0 +1,17 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
set(SOURCE_FILES
hbr_tutorial_0.cpp
)
osd_add_executable(hbr_tutorial_0 "tutorials/hbr"
${SOURCE_FILES}
)
install(TARGETS hbr_tutorial_0 DESTINATION "${CMAKE_BINDIR_BASE}/tutorials")

View File

@@ -0,0 +1,145 @@
//
// Copyright 2013 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial presents in a very succinct way the requisite steps to
// instantiate an Hbr mesh from simple topological data.
//
#include <opensubdiv/hbr/mesh.h>
#include <opensubdiv/hbr/catmark.h>
#include <cstdio>
//------------------------------------------------------------------------------
// Vertex container implementation.
//
// The HbrMesh<T> class is a templated interface that expects a vertex class to
// perform interpolation on arbitrary vertex data.
//
// For the template specialization of the HbrMesh interface to be met, our
// Vertex object to implement a minimal set of constructors and member
// functions.
//
// Since we are not going to subdivide the mesh, the struct presented here has
// been left minimalistic. The only customization added to our container was to
// provide storage and accessors for the position of a 3D vertex.
//
struct Vertex {
// Hbr minimal required interface ----------------------
Vertex() { }
Vertex(int /*i*/) { }
Vertex(Vertex const & src) {
_position[0] = src._position[0];
_position[1] = src._position[1];
_position[2] = src._position[2];
}
void Clear( void * =0 ) { }
void AddWithWeight(Vertex const &, float ) { }
void AddVaryingWithWeight(Vertex const &, float) { }
// Public interface ------------------------------------
void SetPosition(float x, float y, float z) {
_position[0]=x;
_position[1]=y;
_position[2]=z;
}
const float * GetPosition() const {
return _position;
}
private:
float _position[3];
};
typedef OpenSubdiv::HbrMesh<Vertex> Hmesh;
typedef OpenSubdiv::HbrFace<Vertex> Hface;
typedef OpenSubdiv::HbrVertex<Vertex> Hvertex;
typedef OpenSubdiv::HbrHalfedge<Vertex> Hhalfedge;
//------------------------------------------------------------------------------
// Pyramid geometry from catmark_pyramid.h
static float verts[5][3] = {{ 0.0f, 0.0f, 2.0f},
{ 0.0f, -2.0f, 0.0f},
{ 2.0f, 0.0f, 0.0f},
{ 0.0f, 2.0f, 0.0f},
{-2.0f, 0.0f, 0.0f}};
static int nverts = 5,
nfaces = 5;
static int facenverts[5] = { 3, 3, 3, 3, 4 };
static int faceverts[16] = { 0, 1, 2,
0, 2, 3,
0, 3, 4,
0, 4, 1,
4, 3, 2, 1 };
//------------------------------------------------------------------------------
int main(int, char **) {
// Create a subdivision scheme (Catmull-Clark here)
OpenSubdiv::HbrCatmarkSubdivision<Vertex> * catmark =
new OpenSubdiv::HbrCatmarkSubdivision<Vertex>();
// Create an empty Hbr mesh
Hmesh * hmesh = new Hmesh(catmark);
// Populate the vertices
Vertex v;
for (int i=0; i<nverts; ++i) {
// Primitive variable data must be set here: in our case we set
// the 3D position of the vertex.
v.SetPosition(verts[i][0], verts[i][1], verts[i][2]);
// Add the vertex to the mesh.
hmesh->NewVertex(i, v);
}
// Create the topology
int * fv = faceverts;
for (int i=0; i<nfaces; ++i) {
int nv = facenverts[i];
hmesh->NewFace(nv, fv, 0);
fv+=nv;
}
// Set subdivision options
//
// By default vertex interpolation is set to "none" on boundaries, which
// can produce un-expected results, so we change it to "edge-only".
//
hmesh->SetInterpolateBoundaryMethod(Hmesh::k_InterpolateBoundaryEdgeOnly);
// Call 'Finish' to finalize the data structures before using the mesh.
hmesh->Finish();
printf("Created a pyramid with %d faces and %d vertices.\n",
hmesh->GetNumFaces(), hmesh->GetNumVertices());
delete hmesh;
delete catmark;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,17 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
set(SOURCE_FILES
hbr_tutorial_1.cpp
)
osd_add_executable(hbr_tutorial_1 "tutorials/hbr"
${SOURCE_FILES}
)
install(TARGETS hbr_tutorial_1 DESTINATION "${CMAKE_BINDIR_BASE}/tutorials")

View File

@@ -0,0 +1,180 @@
//
// Copyright 2013 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial shows how to safely create Hbr meshes from arbitrary topology.
// Because Hbr is a half-edge data structure, it cannot represent non-manifold
// topology. Ensuring that the geometry used is manifold is a requirement to use
// Hbr safely. This tutorial presents some simple tests to detect inappropriate
// topology.
//
#include <opensubdiv/hbr/mesh.h>
#include <opensubdiv/hbr/catmark.h>
#include <cstdio>
//------------------------------------------------------------------------------
struct Vertex {
// Hbr minimal required interface ----------------------
Vertex() { }
Vertex(int /*i*/) { }
Vertex(Vertex const & src) {
_position[0] = src._position[0];
_position[1] = src._position[1];
_position[2] = src._position[2];
}
void Clear( void * =0 ) { }
void AddWithWeight(Vertex const &, float ) { }
void AddVaryingWithWeight(Vertex const &, float) { }
// Public interface ------------------------------------
void SetPosition(float x, float y, float z) {
_position[0]=x;
_position[1]=y;
_position[2]=z;
}
const float * GetPosition() const {
return _position;
}
private:
float _position[3];
};
typedef OpenSubdiv::HbrMesh<Vertex> Hmesh;
typedef OpenSubdiv::HbrFace<Vertex> Hface;
typedef OpenSubdiv::HbrVertex<Vertex> Hvertex;
typedef OpenSubdiv::HbrHalfedge<Vertex> Hhalfedge;
//------------------------------------------------------------------------------
// Non-manifold geometry from catmark_fan.h
//
// o
// /|
// / |
// / |
// / |
// o |
// | f2 |
// | |
// o--------+----o------------o
// / | / /
// / | / /
// / f0 | / f1 /
// / |/ /
// o------------ o------------o
//
// The shared edge of a fan is adjacent to 3 faces, and therefore non-manifold.
//
static float verts[8][3] = {{-1.0, 0.0, -1.0},
{-1.0, 0.0, 0.0},
{ 0.0, 0.0, 0.0},
{ 0.0, 0.0, -1.0},
{ 1.0, 0.0, 0.0},
{ 1.0, 0.0, -1.0},
{ 0.0, 1.0, 0.0},
{ 0.0, 1.0, -1.0}};
static int nverts = 8,
nfaces = 3;
static int facenverts[3] = { 4, 4, 4 };
static int faceverts[12] = { 0, 1, 2, 3,
3, 2, 4, 5,
3, 2, 6, 7 };
//------------------------------------------------------------------------------
int main(int, char **) {
OpenSubdiv::HbrCatmarkSubdivision<Vertex> * catmark =
new OpenSubdiv::HbrCatmarkSubdivision<Vertex>();
Hmesh * hmesh = new Hmesh(catmark);
Vertex v;
for (int i=0; i<nverts; ++i) {
v.SetPosition(verts[i][0], verts[i][1], verts[i][2]);
hmesh->NewVertex(i, v);
}
// Create the topology
int * fv = faceverts;
for (int i=0; i<nfaces; ++i) {
int nv = facenverts[i];
bool valid = true;
for(int j=0;j<nv;j++) {
Hvertex const * origin = hmesh->GetVertex(fv[j]),
* destination = hmesh->GetVertex(fv[(j+1)%nv]);
Hhalfedge const * opposite = destination->GetEdge(origin);
// Make sure that the vertices exist in the mesh
if (origin==NULL || destination==NULL) {
printf(" An edge was specified that connected a nonexistent vertex\n");
valid=false;
break;
}
// Check for a degenerate edge
if (origin == destination) {
printf(" An edge was specified that connected a vertex to itself\n");
valid=false;
break;
}
// Check that no more than 2 faces are adjacent to the edge
if (opposite && opposite->GetOpposite() ) {
printf(" A non-manifold edge incident to more than 2 faces was found\n");
valid=false;
break;
}
// Check that the edge is unique and oriented properly
if (origin->GetEdge(destination)) {
printf(" An edge connecting two vertices was specified more than once."
" It's likely that an incident face was flipped\n");
valid=false;
break;
}
}
if (valid) {
hmesh->NewFace(nv, fv, 0);
} else {
printf(" Skipped face %d\n", i);
}
fv+=nv;
}
hmesh->SetInterpolateBoundaryMethod(Hmesh::k_InterpolateBoundaryEdgeOnly);
hmesh->Finish();
printf("Created a fan with %d faces and %d vertices.\n",
hmesh->GetNumFaces(), hmesh->GetNumVertices());
delete hmesh;
delete catmark;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,17 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
set(SOURCE_FILES
hbr_tutorial_2.cpp
)
osd_add_executable(hbr_tutorial_2 "tutorials/hbr"
${SOURCE_FILES}
)
install(TARGETS hbr_tutorial_2 DESTINATION "${CMAKE_BINDIR_BASE}/tutorials")

View File

@@ -0,0 +1,247 @@
//
// Copyright 2013 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial shows how to subdivide uniformly a simple Hbr mesh. We are
// building upon previous tutorials and assuming a fully instantiated mesh:
// we start with an HbrMesh pointer initialized from the same pyramid shape
// used in hbr_tutorial_0.
//
// We then apply the Refine() function sequentially to all the faces in the
// mesh to generate several levels of uniform subdivision. The resulting data
// is then dumped to the terminal in Wavefront OBJ format for inspection.
//
#include <opensubdiv/hbr/mesh.h>
#include <opensubdiv/hbr/catmark.h>
#include <cassert>
#include <cstdio>
//------------------------------------------------------------------------------
//
// For this tutorial, we have to flesh out the Vertex class further. Note that now
// the copy constructor, Clear() and AddwithWeight() methods have been
// implemented to interpolate our float3 position data.
//
// This vertex specialization pattern leaves client-code free to implement
// arbitrary vertex primvar data schemes (or none at all to conserve efficiency)
//
struct Vertex {
// Hbr minimal required interface ----------------------
Vertex() { }
Vertex(int /*i*/) { }
Vertex(Vertex const & src) {
_position[0] = src._position[0];
_position[1] = src._position[1];
_position[2] = src._position[2];
}
void Clear( void * =0 ) {
_position[0]=_position[1]=_position[2]=0.0f;
}
void AddWithWeight(Vertex const & src, float weight) {
_position[0]+=weight*src._position[0];
_position[1]+=weight*src._position[1];
_position[2]+=weight*src._position[2];
}
void AddVaryingWithWeight(Vertex const &, float) { }
// Public interface ------------------------------------
void SetPosition(float x, float y, float z) {
_position[0]=x;
_position[1]=y;
_position[2]=z;
}
const float * GetPosition() const {
return _position;
}
private:
float _position[3];
};
typedef OpenSubdiv::HbrMesh<Vertex> Hmesh;
typedef OpenSubdiv::HbrFace<Vertex> Hface;
typedef OpenSubdiv::HbrVertex<Vertex> Hvertex;
typedef OpenSubdiv::HbrHalfedge<Vertex> Hhalfedge;
Hmesh * createMesh();
//------------------------------------------------------------------------------
int main(int, char **) {
Hmesh * hmesh = createMesh();
int maxlevel=2, // 2 levels of subdivision
firstface=0, // marker to the first face index of level 2
firstvertex=0; // marker to the first vertex index of level 2
// Refine the mesh to 'maxlevel'
for (int level=0; level<maxlevel; ++level) {
// Total number of faces in the mesh, across all levels
//
// Note: this function iterates over the list of faces and can be slow
int nfaces = hmesh->GetNumFaces();
if (level==(maxlevel-1)) {
// Save our vertex marker
firstvertex = hmesh->GetNumVertices();
}
// Iterate over the faces of the current level of subdivision
for (int face=firstface; face<nfaces; ++face) {
Hface * f = hmesh->GetFace(face);
// Note: hole tags would have to be dealt with here.
f->Refine();
}
// Save our face index marker for the next level
firstface = nfaces;
}
{ // Output OBJ of the highest level refined -----------
// Print vertex positions
int nverts = hmesh->GetNumVertices();
for (int vert=firstvertex; vert<nverts; ++vert) {
float const * pos = hmesh->GetVertex(vert)->GetData().GetPosition();
printf("v %f %f %f\n", pos[0], pos[1], pos[2]);
}
// Print faces
for (int face=firstface; face<hmesh->GetNumFaces(); ++face) {
Hface * f = hmesh->GetFace(face);
assert(f->GetNumVertices()==4 );
printf("f ");
for (int vert=0; vert<4; ++vert) {
// OBJ uses 1-based arrays
printf("%d ", f->GetVertex(vert)->GetID() - firstvertex + 1);
}
printf("\n");
}
}
}
//------------------------------------------------------------------------------
// Creates an Hbr mesh
//
// see hbr_tutorial_0 and hbr_tutorial_1 for more details
//
Hmesh *
createMesh() {
// Pyramid geometry from catmark_pyramid.h
static float verts[5][3] = {{ 0.0f, 0.0f, 2.0f},
{ 0.0f, -2.0f, 0.0f},
{ 2.0f, 0.0f, 0.0f},
{ 0.0f, 2.0f, 0.0f},
{-2.0f, 0.0f, 0.0f}};
static int nverts = 5,
nfaces = 5;
static int facenverts[5] = { 3, 3, 3, 3, 4 };
static int faceverts[16] = { 0, 1, 2,
0, 2, 3,
0, 3, 4,
0, 4, 1,
4, 3, 2, 1 };
OpenSubdiv::HbrCatmarkSubdivision<Vertex> * catmark =
new OpenSubdiv::HbrCatmarkSubdivision<Vertex>();
Hmesh * hmesh = new Hmesh(catmark);
// Populate the vertices
Vertex v;
for (int i=0; i<nverts; ++i) {
v.SetPosition(verts[i][0], verts[i][1], verts[i][2]);
hmesh->NewVertex(i, v);
}
// Create the topology
int * fv = faceverts;
for (int i=0; i<nfaces; ++i) {
int nv = facenverts[i];
bool valid = true;
for(int j=0;j<nv;j++) {
Hvertex const * origin = hmesh->GetVertex(fv[j]),
* destination = hmesh->GetVertex(fv[(j+1)%nv]);
Hhalfedge const * opposite = destination->GetEdge(origin);
// Make sure that the vertices exist in the mesh
if (origin==NULL || destination==NULL) {
printf(" An edge was specified that connected a nonexistent vertex\n");
valid=false;
break;
}
// Check for a degenerate edge
if (origin == destination) {
printf(" An edge was specified that connected a vertex to itself\n");
valid=false;
break;
}
// Check that no more than 2 faces are adjacent to the edge
if (opposite && opposite->GetOpposite() ) {
printf(" A non-manifold edge incident to more than 2 faces was found\n");
valid=false;
break;
}
// Check that the edge is unique and oriented properly
if (origin->GetEdge(destination)) {
printf(" An edge connecting two vertices was specified more than once."
" It's likely that an incident face was flipped\n");
valid=false;
break;
}
}
if (valid) {
hmesh->NewFace(nv, fv, 0);
} else {
printf(" Skipped face %d\n", i);
}
fv+=nv;
}
hmesh->SetInterpolateBoundaryMethod(Hmesh::k_InterpolateBoundaryEdgeOnly);
hmesh->Finish();
return hmesh;
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,25 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
set(TUTORIALS
tutorial_0
)
foreach(tutorial ${TUTORIALS})
add_subdirectory("${tutorial}")
list(APPEND TUTORIAL_TARGETS "osd_${tutorial}")
endforeach()
add_custom_target(osd_tutorials DEPENDS ${TUTORIAL_TARGETS})
set_target_properties(osd_tutorials
PROPERTIES
FOLDER "tutorials/osd"
)

View File

@@ -0,0 +1,22 @@
#
# Copyright 2013 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://opensubdiv.org/license.
#
set(SOURCE_FILES
osd_tutorial_0.cpp
)
osd_add_executable(osd_tutorial_0 "tutorials/osd"
${SOURCE_FILES}
)
target_link_libraries(osd_tutorial_0
osd_static_cpu
)
install(TARGETS osd_tutorial_0 DESTINATION "${CMAKE_BINDIR_BASE}/tutorials")

View File

@@ -0,0 +1,148 @@
//
// Copyright 2013 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://opensubdiv.org/license.
//
//------------------------------------------------------------------------------
// Tutorial description:
//
// This tutorial demonstrates the manipulation of Osd Evaluator and
// BufferDescriptor.
//
#include <opensubdiv/far/topologyDescriptor.h>
#include <opensubdiv/far/stencilTableFactory.h>
#include <opensubdiv/osd/cpuEvaluator.h>
#include <opensubdiv/osd/cpuVertexBuffer.h>
#include <cstdio>
#include <cstring>
//------------------------------------------------------------------------------
// Cube geometry from catmark_cube.h
static float g_verts[24] = {-0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f};
static int g_nverts = 8,
g_nfaces = 6;
static int g_vertsperface[6] = { 4, 4, 4, 4, 4, 4 };
static int g_vertIndices[24] = { 0, 1, 3, 2,
2, 3, 5, 4,
4, 5, 7, 6,
6, 7, 1, 0,
1, 7, 5, 3,
6, 0, 2, 4 };
using namespace OpenSubdiv;
static Far::TopologyRefiner const * createTopologyRefiner(int maxlevel);
//------------------------------------------------------------------------------
int main(int, char **) {
int maxlevel=2,
nCoarseVerts=0,
nRefinedVerts=0;
//
// Setup phase
//
Far::StencilTable const * stencilTable = NULL;
{ // Setup Far::StencilTable
Far::TopologyRefiner const * refiner = createTopologyRefiner(maxlevel);
// Setup a factory to create FarStencilTable (for more details see
// Far tutorials)
Far::StencilTableFactory::Options options;
options.generateOffsets=true;
options.generateIntermediateLevels=false;
stencilTable = Far::StencilTableFactory::Create(*refiner, options);
nCoarseVerts = refiner->GetLevel(0).GetNumVertices();
nRefinedVerts = stencilTable->GetNumStencils();
// We are done with Far: cleanup table
delete refiner;
}
// Setup a buffer for vertex primvar data:
Osd::CpuVertexBuffer * vbuffer =
Osd::CpuVertexBuffer::Create(3, nCoarseVerts + nRefinedVerts);
//
// Execution phase (every frame)
//
{
// Pack the control vertex data at the start of the vertex buffer
// and update every time control data changes
vbuffer->UpdateData(g_verts, 0, nCoarseVerts);
Osd::BufferDescriptor srcDesc(0, 3, 3);
Osd::BufferDescriptor dstDesc(nCoarseVerts*3, 3, 3);
// Launch the computation
Osd::CpuEvaluator::EvalStencils(vbuffer, srcDesc,
vbuffer, dstDesc,
stencilTable);
}
{ // Visualization with Maya : print a MEL script that generates particles
// at the location of the refined vertices
printf("particle ");
float const * refinedVerts = vbuffer->BindCpuBuffer() + 3*nCoarseVerts;
for (int i=0; i<nRefinedVerts; ++i) {
float const * vert = refinedVerts + 3*i;
printf("-p %f %f %f\n", vert[0], vert[1], vert[2]);
}
printf("-c 1;\n");
}
delete stencilTable;
delete vbuffer;
}
//------------------------------------------------------------------------------
static Far::TopologyRefiner const *
createTopologyRefiner(int maxlevel) {
// Populate a topology descriptor with our raw data
typedef Far::TopologyDescriptor Descriptor;
Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK;
Sdc::Options options;
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
Descriptor desc;
desc.numVertices = g_nverts;
desc.numFaces = g_nfaces;
desc.numVertsPerFace = g_vertsperface;
desc.vertIndicesPerFace = g_vertIndices;
// Instantiate a FarTopologyRefiner from the descriptor
Far::TopologyRefiner * refiner =
Far::TopologyRefinerFactory<Descriptor>::Create(desc,
Far::TopologyRefinerFactory<Descriptor>::Options(type, options));
// Uniformly refine the topology up to 'maxlevel'
refiner->RefineUniform(Far::TopologyRefiner::UniformOptions(maxlevel));
return refiner;
}
//------------------------------------------------------------------------------