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