Addition of Bfr interface (2 of 4): tutorials/bfr

This commit is contained in:
Barry Fowler 2022-08-02 20:40:48 -07:00
parent a1c7be7c8e
commit 8cbc059e6b
32 changed files with 5987 additions and 0 deletions

View File

@ -31,11 +31,14 @@ 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,64 @@
#
# Copyright 2021
#
# Licensed under the Apache License, Version 2.0 (the "Apache License")
# with the following modification; you may not use this file except in
# compliance with the Apache License and the following modification to it:
# Section 6. Trademarks. is deleted and replaced with:
#
# 6. Trademarks. This License does not grant permission to use the trade
# names, trademarks, service marks, or product names of the Licensor
# and its affiliates, except as required to comply with Section 4(c) of
# the License and to reproduce the content of the NOTICE file.
#
# You may obtain a copy of the Apache License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the Apache License with the above modification is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the Apache License for the specific
# language governing permissions and limitations under the Apache License.
#
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>
)
install(TARGETS ${NAME} DESTINATION "${CMAKE_BINDIR_BASE}/tutorials")
endmacro()
set(TUTORIALS
tutorial_1_1
tutorial_1_2
tutorial_1_3
tutorial_1_4
tutorial_2_1
tutorial_2_2
tutorial_3_1
)
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,39 @@
#
# Copyright 2021 Pixar
#
# Licensed under the Apache License, Version 2.0 (the "Apache License")
# with the following modification; you may not use this file except in
# compliance with the Apache License and the following modification to it:
# Section 6. Trademarks. is deleted and replaced with:
#
# 6. Trademarks. This License does not grant permission to use the trade
# names, trademarks, service marks, or product names of the Licensor
# and its affiliates, except as required to comply with Section 4(c) of
# the License and to reproduce the content of the NOTICE file.
#
# You may obtain a copy of the Apache License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the Apache License with the above modification is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the Apache License for the specific
# language governing permissions and limitations under the Apache License.
#
set(SOURCE_FILES
bfr_tutorial_1_1.cpp
)
osd_add_executable(bfr_tutorial_1_1 "tutorials/bfr"
${SOURCE_FILES}
$<TARGET_OBJECTS:sdc_obj>
$<TARGET_OBJECTS:vtr_obj>
$<TARGET_OBJECTS:far_obj>
$<TARGET_OBJECTS:bfr_obj>
$<TARGET_OBJECTS:regression_common_obj>
)
install(TARGETS bfr_tutorial_1_1 DESTINATION "${CMAKE_BINDIR_BASE}/tutorials")

View File

@ -0,0 +1,259 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
//------------------------------------------------------------------------------
// 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,209 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "../../../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,193 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include <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,39 @@
#
# Copyright 2021 Pixar
#
# Licensed under the Apache License, Version 2.0 (the "Apache License")
# with the following modification; you may not use this file except in
# compliance with the Apache License and the following modification to it:
# Section 6. Trademarks. is deleted and replaced with:
#
# 6. Trademarks. This License does not grant permission to use the trade
# names, trademarks, service marks, or product names of the Licensor
# and its affiliates, except as required to comply with Section 4(c) of
# the License and to reproduce the content of the NOTICE file.
#
# You may obtain a copy of the Apache License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the Apache License with the above modification is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the Apache License for the specific
# language governing permissions and limitations under the Apache License.
#
set(SOURCE_FILES
bfr_tutorial_1_2.cpp
)
osd_add_executable(bfr_tutorial_1_2 "tutorials/bfr"
${SOURCE_FILES}
$<TARGET_OBJECTS:sdc_obj>
$<TARGET_OBJECTS:vtr_obj>
$<TARGET_OBJECTS:far_obj>
$<TARGET_OBJECTS:bfr_obj>
$<TARGET_OBJECTS:regression_common_obj>
)
install(TARGETS bfr_tutorial_1_2 DESTINATION "${CMAKE_BINDIR_BASE}/tutorials")

View File

@ -0,0 +1,269 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
//------------------------------------------------------------------------------
// 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,209 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "../../../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,193 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include <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,39 @@
#
# Copyright 2021 Pixar
#
# Licensed under the Apache License, Version 2.0 (the "Apache License")
# with the following modification; you may not use this file except in
# compliance with the Apache License and the following modification to it:
# Section 6. Trademarks. is deleted and replaced with:
#
# 6. Trademarks. This License does not grant permission to use the trade
# names, trademarks, service marks, or product names of the Licensor
# and its affiliates, except as required to comply with Section 4(c) of
# the License and to reproduce the content of the NOTICE file.
#
# You may obtain a copy of the Apache License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the Apache License with the above modification is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the Apache License for the specific
# language governing permissions and limitations under the Apache License.
#
set(SOURCE_FILES
bfr_tutorial_1_3.cpp
)
osd_add_executable(bfr_tutorial_1_3 "tutorials/bfr"
${SOURCE_FILES}
$<TARGET_OBJECTS:sdc_obj>
$<TARGET_OBJECTS:vtr_obj>
$<TARGET_OBJECTS:far_obj>
$<TARGET_OBJECTS:bfr_obj>
$<TARGET_OBJECTS:regression_common_obj>
)
install(TARGETS bfr_tutorial_1_3 DESTINATION "${CMAKE_BINDIR_BASE}/tutorials")

View File

@ -0,0 +1,340 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
//------------------------------------------------------------------------------
// 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,209 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "../../../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,193 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include <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,39 @@
#
# Copyright 2022 Pixar
#
# Licensed under the Apache License, Version 2.0 (the "Apache License")
# with the following modification; you may not use this file except in
# compliance with the Apache License and the following modification to it:
# Section 6. Trademarks. is deleted and replaced with:
#
# 6. Trademarks. This License does not grant permission to use the trade
# names, trademarks, service marks, or product names of the Licensor
# and its affiliates, except as required to comply with Section 4(c) of
# the License and to reproduce the content of the NOTICE file.
#
# You may obtain a copy of the Apache License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the Apache License with the above modification is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the Apache License for the specific
# language governing permissions and limitations under the Apache License.
#
set(SOURCE_FILES
bfr_tutorial_1_4.cpp
)
osd_add_executable(bfr_tutorial_1_4 "tutorials/bfr"
${SOURCE_FILES}
$<TARGET_OBJECTS:sdc_obj>
$<TARGET_OBJECTS:vtr_obj>
$<TARGET_OBJECTS:far_obj>
$<TARGET_OBJECTS:bfr_obj>
$<TARGET_OBJECTS:regression_common_obj>
)
install(TARGETS bfr_tutorial_1_4 DESTINATION "${CMAKE_BINDIR_BASE}/tutorials")

View File

@ -0,0 +1,392 @@
//
// Copyright 2022 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
//------------------------------------------------------------------------------
// 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,209 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "../../../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,193 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include <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,39 @@
#
# Copyright 2022 Pixar
#
# Licensed under the Apache License, Version 2.0 (the "Apache License")
# with the following modification; you may not use this file except in
# compliance with the Apache License and the following modification to it:
# Section 6. Trademarks. is deleted and replaced with:
#
# 6. Trademarks. This License does not grant permission to use the trade
# names, trademarks, service marks, or product names of the Licensor
# and its affiliates, except as required to comply with Section 4(c) of
# the License and to reproduce the content of the NOTICE file.
#
# You may obtain a copy of the Apache License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the Apache License with the above modification is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the Apache License for the specific
# language governing permissions and limitations under the Apache License.
#
set(SOURCE_FILES
bfr_tutorial_2_1.cpp
)
osd_add_executable(bfr_tutorial_2_1 "tutorials/bfr"
${SOURCE_FILES}
$<TARGET_OBJECTS:sdc_obj>
$<TARGET_OBJECTS:vtr_obj>
$<TARGET_OBJECTS:far_obj>
$<TARGET_OBJECTS:bfr_obj>
$<TARGET_OBJECTS:regression_common_obj>
)
install(TARGETS bfr_tutorial_2_1 DESTINATION "${CMAKE_BINDIR_BASE}/tutorials")

View File

@ -0,0 +1,391 @@
//
// Copyright 2022 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
//------------------------------------------------------------------------------
// 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 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;
}
//
// 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.
//
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,209 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "../../../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,193 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include <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,39 @@
#
# Copyright 2021 Pixar
#
# Licensed under the Apache License, Version 2.0 (the "Apache License")
# with the following modification; you may not use this file except in
# compliance with the Apache License and the following modification to it:
# Section 6. Trademarks. is deleted and replaced with:
#
# 6. Trademarks. This License does not grant permission to use the trade
# names, trademarks, service marks, or product names of the Licensor
# and its affiliates, except as required to comply with Section 4(c) of
# the License and to reproduce the content of the NOTICE file.
#
# You may obtain a copy of the Apache License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the Apache License with the above modification is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the Apache License for the specific
# language governing permissions and limitations under the Apache License.
#
set(SOURCE_FILES
bfr_tutorial_2_2.cpp
)
osd_add_executable(bfr_tutorial_2_2 "tutorials/bfr"
${SOURCE_FILES}
$<TARGET_OBJECTS:sdc_obj>
$<TARGET_OBJECTS:vtr_obj>
$<TARGET_OBJECTS:far_obj>
$<TARGET_OBJECTS:bfr_obj>
$<TARGET_OBJECTS:regression_common_obj>
)
install(TARGETS bfr_tutorial_2_2 DESTINATION "${CMAKE_BINDIR_BASE}/tutorials")

View File

@ -0,0 +1,419 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
//------------------------------------------------------------------------------
// 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>
// 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() { }
};
//
// Local helpers for the main tessellation function that follows:
//
namespace {
inline bool
DoEdgeVertexIndicesIncrease(int edgeInFace, ConstIndexArray & faceVerts) {
int v0InFace = edgeInFace;
int v1InFace = (v0InFace == (faceVerts.size()-1)) ? 0 : (v0InFace+1);
return (faceVerts[v0InFace] < faceVerts[v1InFace]);
}
} // 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 used shared points at vertices and edges of the mesh.
// These are computed and used as encountered by the faces -- rather than
// computing all shared vertex and edge points at once (which is more
// amenable to threading).
//
// This method has the advantage of only constructing face Surfaces once
// per face, but requires additional book-keeping, and accesses memory
// less coherently (making threading more difficult).
//
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 posSurface;
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);
//
// 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<int> sharedVertexPointIndex(baseLevel.GetNumVertices(), -1);
std::vector<int> sharedEdgePointIndex(baseLevel.GetNumEdges(), -1);
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, &posSurface)) {
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 numTessCoords = tessPattern.GetNumCoords();
outCoords.resize(numTessCoords * 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(posSurface.GetNumPatchPoints() * pointSize);
outPos.resize(numTessCoords * pointSize);
outDu.resize(numTessCoords * pointSize);
outDv.resize(numTessCoords * pointSize);
posSurface.PreparePatchPoints(meshVertexPositions.data(), pointSize,
facePatchPoints.data(), pointSize);
//
// Evaluate the sample points of the Tessellation:
//
// First we 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 index buffer for later use. The interior points
// are trivially computed after the boundary is dealt with.
//
// Identify the boundary and interior coords and initialize the
// buffer for the potentially shared boundary points:
//
int numBoundaryCoords = tessPattern.GetNumBoundaryCoords();
int numInteriorCoords = numTessCoords - 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 boundary index buffer in the process:
//
float * patchPointData = facePatchPoints.data();
int boundaryIndex = 0;
int numFacePointsEvaluated = 0;
for (int i = 0; i < fVerts.size(); ++i) {
// Evaluate and/or retrieve the shared point for the vertex:
{
int & vertPointIndex = sharedVertexPointIndex[fVerts[i]];
if (vertPointIndex < 0) {
vertPointIndex = numMeshPointsEvaluated ++;
float const * uv = &tessBoundaryCoords[boundaryIndex*2];
int k = (numFacePointsEvaluated ++) * pointSize;
posSurface.Evaluate(uv, patchPointData, pointSize,
&outPos[k], &outDu[k], &outDv[k]);
}
tessBoundaryIndices[boundaryIndex++] = vertPointIndex;
}
// Evaluate and/or retrieve all shared points for the edge:
int N = options.tessUniformRate - 1;
if (N) {
// Be careful to respect ordering of the edge and its
// points when both evaluating and identifying indices:
bool edgeIsNotReversed = DoEdgeVertexIndicesIncrease(i, fVerts);
int iOffset = edgeIsNotReversed ? 0 : (N - 1);
int iDelta = edgeIsNotReversed ? 1 : -1;
int & edgePointIndex = sharedEdgePointIndex[fEdges[i]];
if (edgePointIndex < 0) {
edgePointIndex = numMeshPointsEvaluated;
float const * uv = &tessBoundaryCoords[boundaryIndex*2];
int iNext = numFacePointsEvaluated + iOffset;
for (int j = 0; j < N; ++j, iNext += iDelta, uv += 2) {
int k = iNext * pointSize;
posSurface.Evaluate(uv, patchPointData, pointSize,
&outPos[k], &outDu[k], &outDv[k]);
}
numFacePointsEvaluated += N;
numMeshPointsEvaluated += N;
}
int iNext = edgePointIndex + iOffset;
for (int j = 0; j < N; ++j, iNext += iDelta) {
tessBoundaryIndices[boundaryIndex++] = iNext;
}
}
}
//
// 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 k = i * pointSize;
posSurface.Evaluate(uv, patchPointData, pointSize,
&outPos[k], &outDu[k], &outDv[k]);
}
numFacePointsEvaluated += numInteriorCoords;
numMeshPointsEvaluated += numInteriorCoords;
}
//
// Remember to trim/resize the buffers 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 - numTessCoords;
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,209 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "../../../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,193 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include <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,40 @@
#
# Copyright 2021 Pixar
#
# Licensed under the Apache License, Version 2.0 (the "Apache License")
# with the following modification; you may not use this file except in
# compliance with the Apache License and the following modification to it:
# Section 6. Trademarks. is deleted and replaced with:
#
# 6. Trademarks. This License does not grant permission to use the trade
# names, trademarks, service marks, or product names of the Licensor
# and its affiliates, except as required to comply with Section 4(c) of
# the License and to reproduce the content of the NOTICE file.
#
# You may obtain a copy of the Apache License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the Apache License with the above modification is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the Apache License for the specific
# language governing permissions and limitations under the Apache License.
#
set(SOURCE_FILES
bfr_tutorial_3_1.cpp
customSurfaceFactory.cpp
)
osd_add_executable(bfr_tutorial_3_1 "tutorials/bfr"
${SOURCE_FILES}
$<TARGET_OBJECTS:sdc_obj>
$<TARGET_OBJECTS:vtr_obj>
$<TARGET_OBJECTS:far_obj>
$<TARGET_OBJECTS:bfr_obj>
$<TARGET_OBJECTS:regression_common_obj>
)
install(TARGETS bfr_tutorial_3_1 DESTINATION "${CMAKE_BINDIR_BASE}/tutorials")

View File

@ -0,0 +1,342 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
//------------------------------------------------------------------------------
// 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,276 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "./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,144 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include <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,209 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "../../../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,193 @@
//
// Copyright 2021 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include <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