mirror of
https://github.com/PixarAnimationStudios/OpenSubdiv
synced 2024-12-22 16:00:07 +00:00
Addition of Bfr interface (2 of 4): tutorials/bfr
This commit is contained in:
parent
a1c7be7c8e
commit
8cbc059e6b
@ -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
|
||||
)
|
||||
|
64
tutorials/bfr/CMakeLists.txt
Normal file
64
tutorials/bfr/CMakeLists.txt
Normal 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"
|
||||
)
|
39
tutorials/bfr/tutorial_1_1/CMakeLists.txt
Normal file
39
tutorials/bfr/tutorial_1_1/CMakeLists.txt
Normal 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")
|
||||
|
259
tutorials/bfr/tutorial_1_1/bfr_tutorial_1_1.cpp
Normal file
259
tutorials/bfr/tutorial_1_1/bfr_tutorial_1_1.cpp
Normal 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;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
209
tutorials/bfr/tutorial_1_1/meshLoader.h
Normal file
209
tutorials/bfr/tutorial_1_1/meshLoader.h
Normal 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
|
193
tutorials/bfr/tutorial_1_1/objWriter.h
Normal file
193
tutorials/bfr/tutorial_1_1/objWriter.h
Normal 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
|
39
tutorials/bfr/tutorial_1_2/CMakeLists.txt
Normal file
39
tutorials/bfr/tutorial_1_2/CMakeLists.txt
Normal 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")
|
||||
|
269
tutorials/bfr/tutorial_1_2/bfr_tutorial_1_2.cpp
Normal file
269
tutorials/bfr/tutorial_1_2/bfr_tutorial_1_2.cpp
Normal 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;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
209
tutorials/bfr/tutorial_1_2/meshLoader.h
Normal file
209
tutorials/bfr/tutorial_1_2/meshLoader.h
Normal 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
|
193
tutorials/bfr/tutorial_1_2/objWriter.h
Normal file
193
tutorials/bfr/tutorial_1_2/objWriter.h
Normal 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
|
39
tutorials/bfr/tutorial_1_3/CMakeLists.txt
Normal file
39
tutorials/bfr/tutorial_1_3/CMakeLists.txt
Normal 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")
|
||||
|
340
tutorials/bfr/tutorial_1_3/bfr_tutorial_1_3.cpp
Normal file
340
tutorials/bfr/tutorial_1_3/bfr_tutorial_1_3.cpp
Normal 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;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
209
tutorials/bfr/tutorial_1_3/meshLoader.h
Normal file
209
tutorials/bfr/tutorial_1_3/meshLoader.h
Normal 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
|
193
tutorials/bfr/tutorial_1_3/objWriter.h
Normal file
193
tutorials/bfr/tutorial_1_3/objWriter.h
Normal 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
|
39
tutorials/bfr/tutorial_1_4/CMakeLists.txt
Normal file
39
tutorials/bfr/tutorial_1_4/CMakeLists.txt
Normal 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")
|
||||
|
392
tutorials/bfr/tutorial_1_4/bfr_tutorial_1_4.cpp
Normal file
392
tutorials/bfr/tutorial_1_4/bfr_tutorial_1_4.cpp
Normal 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;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
209
tutorials/bfr/tutorial_1_4/meshLoader.h
Normal file
209
tutorials/bfr/tutorial_1_4/meshLoader.h
Normal 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
|
193
tutorials/bfr/tutorial_1_4/objWriter.h
Normal file
193
tutorials/bfr/tutorial_1_4/objWriter.h
Normal 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
|
39
tutorials/bfr/tutorial_2_1/CMakeLists.txt
Normal file
39
tutorials/bfr/tutorial_2_1/CMakeLists.txt
Normal 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")
|
||||
|
391
tutorials/bfr/tutorial_2_1/bfr_tutorial_2_1.cpp
Normal file
391
tutorials/bfr/tutorial_2_1/bfr_tutorial_2_1.cpp
Normal 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;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
209
tutorials/bfr/tutorial_2_1/meshLoader.h
Normal file
209
tutorials/bfr/tutorial_2_1/meshLoader.h
Normal 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
|
193
tutorials/bfr/tutorial_2_1/objWriter.h
Normal file
193
tutorials/bfr/tutorial_2_1/objWriter.h
Normal 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
|
39
tutorials/bfr/tutorial_2_2/CMakeLists.txt
Normal file
39
tutorials/bfr/tutorial_2_2/CMakeLists.txt
Normal 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")
|
||||
|
419
tutorials/bfr/tutorial_2_2/bfr_tutorial_2_2.cpp
Normal file
419
tutorials/bfr/tutorial_2_2/bfr_tutorial_2_2.cpp
Normal 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;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
209
tutorials/bfr/tutorial_2_2/meshLoader.h
Normal file
209
tutorials/bfr/tutorial_2_2/meshLoader.h
Normal 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
|
193
tutorials/bfr/tutorial_2_2/objWriter.h
Normal file
193
tutorials/bfr/tutorial_2_2/objWriter.h
Normal 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
|
40
tutorials/bfr/tutorial_3_1/CMakeLists.txt
Normal file
40
tutorials/bfr/tutorial_3_1/CMakeLists.txt
Normal 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")
|
||||
|
342
tutorials/bfr/tutorial_3_1/bfr_tutorial_3_1.cpp
Normal file
342
tutorials/bfr/tutorial_3_1/bfr_tutorial_3_1.cpp
Normal 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;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
276
tutorials/bfr/tutorial_3_1/customSurfaceFactory.cpp
Normal file
276
tutorials/bfr/tutorial_3_1/customSurfaceFactory.cpp
Normal 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;
|
||||
}
|
144
tutorials/bfr/tutorial_3_1/customSurfaceFactory.h
Normal file
144
tutorials/bfr/tutorial_3_1/customSurfaceFactory.h
Normal 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();
|
||||
}
|
209
tutorials/bfr/tutorial_3_1/meshLoader.h
Normal file
209
tutorials/bfr/tutorial_3_1/meshLoader.h
Normal 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
|
193
tutorials/bfr/tutorial_3_1/objWriter.h
Normal file
193
tutorials/bfr/tutorial_3_1/objWriter.h
Normal 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
|
Loading…
Reference in New Issue
Block a user