far_tutorial_2_3.cpp

far_tutorial_2_3.cpp


https://github.com/PixarAnimationStudios/OpenSubdiv/blob/master/tutorials/far/tutorial_2_3/far_tutorial_2_3.cpp


System Message: WARNING/2 (/construction/graphics/opensubdiv/.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");
            }
        }
    }
    //------------------------------------------------------------------------------