Saving Patches in Open Cascade

220 views
Skip to first unread message

Rajendra Pardeshi

unread,
Jan 30, 2022, 4:55:05 PM1/30/22
to OpenSubdiv Forum
Hi Folks,

I am trying to save patches from the subdivision using Open Cascade for one of my app that I am trying to build for subdivision surfaces. This is pretty similar to the thread on this forum - https://groups.google.com/g/opensubdiv/c/ujdolv75a4Q/m/djyq6xIqAAAJ

But I am not quite getting the result and I am not sure what I am missing.
The overall shape of the output looks correct but surfaces are protruding into each other and they are not "smooth" at boundaries. So it looks like I am not able to query the patches right or something is wrong in converting the patches into the "Geom_BezierSurface" from Open Cascade. If you could just point me where I could start looking, that would be really helpful. I have spent some time on this but not getting anywhere. 

Here is what my output looks like right now with 160 faces created.

opensubdiv.png

If I just render two faces, here is the output:

opensubdivtwofaces.png

and here is my code:

#include "Geom_BezierSurface.hxx"

#include <opensubdiv/far/topologyDescriptor.h>
#include <opensubdiv/far/primvarRefiner.h>
#include <opensubdiv/far/patchTableFactory.h>
#include <opensubdiv/far/patchMap.h>
#include <opensubdiv/far/ptexIndices.h>

#include <cassert>
#include <cstdio>
#include <cstring>
#include <cfloat>

using namespace OpenSubdiv;

typedef double Real;

static int const g_nverts = 5;
static double const g_verts[24] = { 0.0f,   0.0f, 20.0f,
                                    0.0f, -20.0f,  0.0f,
                                   20.0f,   0.0f,  0.0f,
                                    0.0f,  20.0f,  0.0f,
                                  -20.0f,   0.0f,  0.0f, };


static int const g_vertsperface[5] = { 3, 3, 3, 3, 4 };

static int const g_nfaces = 5;
static int const g_faceverts[16] = { 0, 1, 2,
                                     0, 2, 3,
                                     0, 3, 4,
                                     0, 4, 1,
                                     4, 3, 2, 1 };

static int const g_ncreases = 4;
static int const g_creaseverts[8] = { 4, 3, 3, 2, 2, 1, 1, 4 };
static float const g_creaseweights[4] = { 3.0f, 3.0f, 3.0f, 3.0f };

// Creates a Far::TopologyRefiner from the pyramid shape above
static Far::TopologyRefiner* createTopologyRefiner();

static Far::TopologyRefiner* createTopologyRefiner()
{


    typedef Far::TopologyDescriptor Descriptor;

    Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK;

    Sdc::Options options;
    options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);

    Descriptor desc;
    desc.numVertices = g_nverts;
    desc.numFaces = g_nfaces;
    desc.numVertsPerFace = g_vertsperface;
    desc.vertIndicesPerFace = g_faceverts;
    desc.numCreases = g_ncreases;
    desc.creaseVertexIndexPairs = g_creaseverts;
    desc.creaseWeights = g_creaseweights;

    // Instantiate a FarTopologyRefiner from the descriptor.
    Far::TopologyRefiner* refiner =
        Far::TopologyRefinerFactory<Descriptor>::Create(desc,
            Far::TopologyRefinerFactory<Descriptor>::Options(type, options));

    return refiner;
}

//------------------------------------------------------------------------------
// Vertex container implementation.
//
struct Vertex {

    // Minimal required interface ----------------------
    Vertex() { }

    void Clear(void* = 0) {
        point[0] = point[1] = point[2] = 0.0f;
    }

    void AddWithWeight(Vertex const& src, Real weight) {
        point[0] += weight * src.point[0];
        point[1] += weight * src.point[1];
        point[2] += weight * src.point[2];
    }

    Real point[3];
};

void CModelingDoc::OnFace()
{
    // Generate a FarTopologyRefiner (see far_tutorial_0 for details).
    Far::TopologyRefiner* refiner = createTopologyRefiner();

    // Adaptively refine the topology with an isolation level capped at 3
    // because the sharpest crease in the shape is 3.0f (in g_creaseweights[])
    int maxIsolation = 3;
    refiner->RefineAdaptive(
        Far::TopologyRefiner::AdaptiveOptions(maxIsolation));

    // Generate a set of Far::PatchTable that we will use to evaluate the
    // surface limit
    Far::PatchTableFactory::Options patchOptions;
    patchOptions.endCapType =
        Far::PatchTableFactory::Options::ENDCAP_GREGORY_BASIS;

    Far::PatchTable const* patchTable =
        Far::PatchTableFactory::Create(*refiner, patchOptions);

    // Compute the total number of points we need to evaluate patchtable.
    // we use local points around extraordinary features.
    int nRefinerVertices = refiner->GetNumVerticesTotal();
    int nLocalPoints = patchTable->GetNumLocalPoints();

    // Create a buffer to hold the position of the refined verts and
    // local points, then copy the coarse positions at the beginning.
    std::vector<Vertex> verts(nRefinerVertices + nLocalPoints);
    memcpy(&verts[0], g_verts, g_nverts * 3 * sizeof(double));

    // Adaptive refinement may result in fewer levels than maxIsolation.
    int nRefinedLevels = refiner->GetNumLevels();

    // Interpolate vertex primvar data : they are the control vertices
    // of the limit patches (see far_tutorial_0 for details)
    Vertex* src = &verts[0];
    for (int level = 1; level < nRefinedLevels; ++level)
    {
        Vertex* dst = src + refiner->GetLevel(level - 1).GetNumVertices();
        Far::PrimvarRefiner(*refiner).Interpolate(level, src, dst);
        src = dst;
    }

    // Evaluate local points from interpolated vertex primvars.
    patchTable->ComputeLocalPointValues(&verts[0], &verts[nRefinerVertices]);

    std::vector<TopoDS_Face> mySurfaces;

    // Loop through each patch and save out 4x4 vertices each
    int na = patchTable->GetNumPatchArrays();
    bool error;
    for (int i = 0; i < na; i++)
    {
        Far::PatchDescriptor pd = patchTable->GetPatchArrayDescriptor(i);
        if (pd == 6) // Type::REGULAR
        {
            Far::ConstIndexArray arraycvs = patchTable->GetPatchArrayVertices(i);
            int np = patchTable->GetNumPatches(i);

            for (int patch = 0; patch < np; patch++)
            {
                Far::ConstIndexArray cvs = patchTable->GetPatchVertices(i, patch);
                int cvCount = cvs.size();
                TColgp_Array2OfPnt surfVerts(1, 4, 1, 4);

                for (int cv = 0; cv < cvCount; cv++)
                {
                    int division = (int)((cv + 1) / 4);
                    int remainder = (cv + 1) % 4;
                    int firstIndex = remainder == 0 ? division : division + 1;
                    int secondIndex = remainder == 0 ? 4 : remainder;
                    surfVerts.SetValue(firstIndex, secondIndex, gp_Pnt(verts[cvs[cv]].point[0], verts[cvs[cv]].point[1], verts[cvs[cv]].point[2]));
                }

                Handle(Geom_BezierSurface) BZ1 =
                    new Geom_BezierSurface(surfVerts);

                TopoDS_Face newFace = BRepBuilderAPI_MakeFace(BZ1, Precision::Confusion());
                mySurfaces.push_back(newFace);
            }
        }
    }

    for (int i = 0; i < mySurfaces.size(); i++)
    {
        Quantity_NameOfColor myColor = static_cast<Quantity_NameOfColor>((i % 505) + 1);
        Handle(AIS_Shape) myFace = new AIS_Shape(mySurfaces[i]);
        myAISContext->SetColor(myFace, myColor, Standard_False);
        myAISContext->SetMaterial(myFace, Graphic3d_NOM_PLASTIC, Standard_False);
        myAISContext->Display(myFace, Standard_False);
    }
}

Rajendra Pardeshi

unread,
Jan 30, 2022, 5:01:41 PM1/30/22
to OpenSubdiv Forum
I am attaching result in one color if that helps

withonecolor.png

Rajendra Pardeshi

unread,
Feb 1, 2022, 4:48:12 PM2/1/22
to OpenSubdiv Forum
Apologies for bugging you guys, mostly, this is a very naive  issue but I am still struggling with this. I am new to the OpenSubdiv library.

When I looked at the surfaces today, I was able to see that the Bezier patches that I am creating has overlapping control points for adjacent patches. So I think I am correctly getting the control polygon for patches but when creating surface/face from it in OpenCascade, it is not creating smooth surface. I am doubtful though and not sure if I am on correct path as far as my understand goes.

Also, this brings a very basic question to my mind - the "Limit surface" that documentation talks about, will I get that surface when I use patch table and create Bezier surfaces from the Bezier patches that patch table gives me? Or is that "Limit Surface" evaluation completely different than the surface that I will get from the Bezier patches from patch table? Also, do I HAVE to do Adaptive refinement for generating patch table and getting Bezier patches? Does Uniform refinement not give me patch table and Bezier patches? As I understand it, the Adaptive refinement is just subset of Uniform refinement to save on cost - instead of refining all faces, we only refine those that are around extraordinary vertices - is this correct understanding? And if this is the case, Uniform refinement also should produce patch table and Bezier surface right?

I am really trying to understand this since this has really intrigued me. I would really appreciate the clarifications you can provide, this not might be something of a very deep level stuff for most of you folks but I am just starting out on this.

Thanks

BTW, I am trying with cube example now.

2022-02-02 02_58_21-TopologyTransformations - [Make face ].png

2022-02-02 02_59_54-TopologyTransformations - [Make face ].png

Bruce Malone

unread,
Feb 1, 2022, 11:44:08 PM2/1/22
to Rajendra Pardeshi, OpenSubdiv Forum
Rajendra,

I only skimmed your code, and I am not familiar with OpenCascade. But let me attempt to answer some questions and point you in the right direction. Anyone, feel free to correct me.

Your use of adaptive refinement is correct.

Bezier mathematics are incorrect per patch: rather, they are based on a b-spline with a uniform knot vector. This is standard with subdivision. So instead of, for example, [0 0 0 0 1 1 1 1] (for a Bezier) you might use the knot vector [0 1 2 3 4 5 6 7]. Note that in the enum in far\patchDescriptor.h is no type for Bezier but for b-spline. However, if I recall correctly, they make an adjustment to their b-spline basis function at boundaries. I don't recall implications of this off the top of my head, but it is all open-source and can be figured out.

The basis functions are found in far\patchBasis.h -- see the one for b-spline. I note that the file does have Bezier basis functions; I'm not sure where they are used in OpenSubdiv.

Your code used the Gregory end cap, I think. For b-spline patch extraction, I believe you'll want the regular end cap. Make sure you are getting back 16 cvs per patch.

Each face is composed of several patches. 

Bruce


--
You received this message because you are subscribed to the Google Groups "OpenSubdiv Forum" group.
To unsubscribe from this group and stop receiving emails from it, send an email to opensubdiv+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/opensubdiv/1bf211b4-89fb-4a9e-9f0b-870800900d11n%40googlegroups.com.


--

Bruce Malone
Troy, MI  48085
801-228-8428

Rajendra Pardeshi

unread,
Feb 2, 2022, 2:07:36 PM2/2/22
to OpenSubdiv Forum
Thank you Bruce very much!!!
I could resolve the issue with your suggestion. I should have used b-spline instead of Bezier. I don't know where I got the idea to use Bezier; I thought I read it in documentation but I am not seeing it. I am getting nice surface now, attaching a screenshot below.

The surface is not smooth at the extraordinary vertices though. The surfaces do not meet at the edges tangentially. I think these are Gregogy patches. And I am approximating them with the option ENDCAP_BSPLINE_BASIS. So I was wondering, for modelling purposes, is there a way to get smooth surfaces from Gregogy patches? Looking at the control points of Gregogy patches, I do not see how that can be converted to a surface supported by modelling kernels. So does Pixar has any plan to work on this problem - to give a patch that can be converted to supported entities in modelling packages? I am not sure how feasible it would be to get such a patch.

Also, another question on the math behind this - so the control points we get for B-spline surface from the patch table, if we create a mesh of all the control points for all patches, we get a cage - is that cage same as we would get when applied subdivision to the starting cage? Basically, does patch table has a geometry algorithm in it? Or is it just arranging vertices created by refinement to create an array of control points? I think patch table just gives us indices of vertices and we get the geometry from the vertices created by refinement so it looks like patch table does not have geometry algorithm. Just confirming my understanding... And would Adaptive and Uniform subdivision give me geometrically different cage? I get that topologically, Adaptive has fewer vertices and faces but geometrically, is Adaptive subdivision a subset of Uniform subdivision. And why does Uniform refinement not give B-Spline patches?

Thanks
Rajendra

When I used the option to hide tangent edges in Solidworks, it showed the subd surface that I created like this...
2022-02-02 17_45_35-Amazon AppStream 2.0.png

Showing zebra analysis
2022-02-02 17_44_41-Amazon AppStream 2.0.png

Showing non-smooth edges at extraordinary vertex
zebra.png

David G Yu

unread,
Feb 2, 2022, 4:13:24 PM2/2/22
to Rajendra Pardeshi, OpenSubdiv Forum
Hi Rajendra,

It looks like you are making good progress, and the follow up questions you are asking are good ones!

- Your observation is correct that using ENDCAP_BSPLINE_BASIS will give you point continuity but not tangent continuity.
- Using ENDCAP_GREGORY_BASIS will generate patches with both point and normal continuity.
- Both of these methods only approximate the limit surface, increasing the adaptive refinement level will increase the fidelity of the approximation.

For interchange of assets (whether between users or applications), you will get the best results by:
- exchanging the base mesh along with any tag data and indicate that it is a subdivision surface.
- this allows the receiver or receiving application (modeller, renderer, etc) evaluate the mesh with full fidelity.
- alternatively you can exchange a tessellation of the refined subdivision surface, e.g. after refining, triangulating, and evaluating the mesh points and mesh data.
- this allows the receiver to deal with the mesh consistently according to the fidelity of your refinement.

Exchanging patches produced by OpenSubdiv (or any other tool which produces a patched approximation of a subdivision limit surface) is challenging.
- you will probably need to include extra data for each patch in order to help the receiver interpret the patches correctly.
- you will have to assume that the receiver knows how to correctly evaluate boundary edge encodings, etc., in addition to possibly being able to evaluate Gregory Basis patches.

Your questions about uniform vs adaptive refinement are good ones too:
- right, the patch table is fundamentally topological and is independent of the geometric point positions of the mesh.
- you can definitely construct parametric patches from the points produced by uniform refinement.
- this is straightforward for regular parts of the mesh, but you still have to resolve what to do with resulting irregular vertices.
- you are correct, that the patches produced by adaptive refinement are defined by a sparse set of the points produced by uniform refinement.
- with adaptive refinement those points typically correspond to points from multiple different levels of uniform refinement.
- so to evaluate those patches you also need the extra data for each such patch so that you can evaluate them correctly.

Hope that helps!
-David

Rajendra Pardeshi

unread,
Feb 3, 2022, 5:00:05 PM2/3/22
to David G Yu, OpenSubdiv Forum
Thank you David. Lots of clarifications from your mail. It is really good to get confirmation for my understanding because some of the math is beyond what my brain can stomach.

Just want to say that this is an amazing library that you folks have open sourced. It feels like we should have kernel on top of this for geometry modelling (I am focused/biased on modelling because that is my background but I see that the library has amazing animation use cases and that is its primary target)

Above clarifications opened a few more questions for me:
1. If both the methods produce approximations of limit surface, how is the limit surface calculated by StencilTable? It is not an approximation right? I see that it is using EvalBasisBSpline for ENDCAP_BSPLINE_BASIS so it is basically treating this as a B Spline. And I am guessing the vertices that it applies this on will be patch table control points? So where is the difference between surfaces that the Stencil table evaluates as limit surface and the surface that I created using B Spline from OpenCascade? I am surely missing something and want to know the missing link. Is it the Gregory Basis patches? But you say that even ENDCAP_GREGORY_BASIS will generate patches that are approximations. Is the difference (between limit surface and approximations that patches give us) only at the boundary edges? How about a mesh without extraordinary vertices? Does it exist? Will it be an exact match with limit surface?
2. What do you mean by normal continuity? Is it curvature continuity? C2 continuity?
3. I am not entirely sure what you mean by interchange of assets. So are you saying that I should store the tag with base mesh so that my receiver app goes to OpenSubdiv for evaluating that mesh whenever it wants to render it?
4. About alternative approach of tessellation, does OpenSubdiv provide tessellation? If it does not and i have to do it, My understanding of your comment is as following - can you please validate it
    a) refine the mesh to appropriate level to generate the required level of finer tessellation
    b) use Stencil table and get the points on limit surface corresponding to the final refined level mesh vertices. (How do I get "s and t" for these points? If I go by Patch table to get the mesh vertices, I would get duplicate vertices right? So I can get the most refined level and get all its vertices and try to find the point on limit surfaces for this vertex so do I need "s and t" for      this?
5. I do not want to exchange patches with the app. I want to convert the patches to a surface that my kernel (OpenCascade in this case) can understand. Because even if I exchange patches and evaluate Gregory Basis patches and deal with boundary edge conditions, it is still not an entity that my kernel can understand. And if I want to, for example, cut a hole                    (boolean subtract) through this surface/patch using another geometry type that is a native of my kernel (OpenCascade), how do I do that? Basically, I want the output of OpenSubdiv to interact with other entities of my kernel - become a first party entity of Kernel. Otherwise I will have to write a kernel for subdivision surface itself which I do not think I am up for :)

I really appreciate the time you take to answer the questions. Thank you.

Regards,
Rajendra

BTW, I was able to get the surface using uniform refinement and I did it for coarse mesh as well - I find that amazing. And I saw there is an option in code for this

//  Option to be made public in future:
    bool options_generateNonLinearUniformPatches = false;

Is there a reason it is not yet public? Is it not release-ready? Any issues if I enable it?

2022-02-04 01_06_18-Amazon AppStream 2.0.png

Rajendra Pardeshi

unread,
Feb 9, 2022, 8:46:53 PM2/9/22
to OpenSubdiv Forum
Maybe for most of folks this is a set naïve of questions, but since I am just starting out with this library it would make a huge difference for me to get clarifications. And I find the library quite interesting in what it is doing. So would be studying it further. Please feel free to share your thoughts on above questions.

Thanks
Rajendra.

Reply all
Reply to author
Forward
0 new messages