PixarAnimationStudios / OpenSubdiv

An Open-Source subdivision surface library.
graphics.pixar.com/opensubdiv
Other
2.87k stars 558 forks source link

Question on getting base face Id from limit surface patches for non-quad base faces. #1121

Closed VadiMysore closed 5 years ago

VadiMysore commented 5 years ago

Hi there! I have an octagonal solid control cage as shown below. It has 10 faces with two non-quad faces, one each at the top and at the bottom.

image

After adaptive subdividing it to one level, I get the following limit surface. It has 48 faces.

image

Now, I want to group the resulting 48 patches on a per control cage face basis, essentially, a map from each control cage face to the patches it gives rise to in the limiting surface.

If the control cage comprised all quads, I get 4 patches per control cage face, which is as expected. Non-quad faces in the control cage will introduce an indirection, though. Here is the code I have that appears to account for both quad and non-quad control cage faces but may not be correct. Can someone please verify whether what I am doing is correct?

Far::PatchMap patchmap(*patchTable);

int numPatchArrays = patchTable->GetNumPatchArrays();

for (int array = 0; array < numPatchArrays; array++) { Far::PatchDescriptor patchDescriptor = patchTable->GetPatchArrayDescriptor(array);

if (patchDescriptor == Far::PatchDescriptor::REGULAR)
{
             for (int patch = 0; patch < numPatches; patch++)
     {
        Far::Index parentFaceId = -1;

                     Far::PatchParam patchParam = patchTable->GetPatchParam(array, patch);

        int faceId = patchParam.GetFaceId();

                    if (!patchParam.NonQuadRoot() &&
             faceId < meshInfo.m_facets.size())
        {
            PK_FACE_t initialQuadFace = meshInfo.m_facets[faceId].mesh;

            faceToFacePatchSet[initialQuadFace].insert(patch);
        }
                    else 
                    {
                            parentFaceId = refiner->GetLevel(1).GetFaceParentFace(faceId);

            if (parentFaceId < meshInfo.m_facets.size())
            {
                PK_FACE_t initialQuadFace = meshInfo.m_facets[parentFaceId].mesh;

                faceToFacePatchSet[initialQuadFace].insert(patch);
            }
                    }
             }
    }

}

Doing the above leaves out the top and bottom non-quad control cage faces out of the map and ends up adding the top 8 and bottom 8 patches to some of the side control cage face entries.

I somehow think that this is incorrect.

@barfowl suggested using ptexIndices towards this in my previous discussion with him on this issue. I do not know how to use this feature. Can someone give me a straight forward code snippet to do this?

One general issue I have with OpenSubdiv is that there are many nuances like the one above and when an API could have done all this, the users are left to understand the innards and implementation details of OpenSubdiv to get the information they want. Also, there are no good, clear examples for such features for the users to follow. I hope this situation can be improved.

Thanks!

jilliene commented 5 years ago

Filed as internal issue #OSD-292

barfowl commented 5 years ago

If you are going to pick apart the PatchTable in the presence of non-quads, then you will unfortunately need to make use of -- or at least have some understanding of -- the Far::PtexIndices class which adds a level of indirection between base faces and the patches that result from them. There is no direct mapping between the patches and the base faces from which they descend in this case, so you are left to construct it if that is what you need.

The "face ID" in Far::PatchParam will not refer to the index of the base face of your mesh if non-quads are present -- instead it refers to the "Ptex face". This is the top-most parameterizable quad in the subdivision hierarchy from which one or more quad patches may be descended. Ptex is a texturing scheme used elsewhere for polygonal meshes and since OpenSubdiv shares the same quad-based parameterization of all faces, that name was adopted. The most common usage of PtexIndices is associated with the PatchMap for dealing with patch parameterizations, but your use case differs.

For non-quad faces, an N-sided face subdivided once with Catmull-Clark results in N quad sub faces which correspond to N sequential "ptex faces". The Far::PtexIndices class provides a mapping from a given base face index to the first of its ptex indices.

To get the first Ptex face for a base face (though you probably won't need to):

//  Construct/initialize the PtexIndices for your base mesh:
Far::PtexIndices ptexIndices(topologyRefiner);

//  For some base face index:
int ptexFaceIndex = ptexIndices.GetFaceID(baseFaceIndex);

If your 8-sided base face has a ptex face index of K, all patches that are derived from ptex faces [K,K+7] inclusive are descendants of that base face.

Probably the simplest thing to do in your case is build a vector to invert the base-face to ptex-face relationship that the Far::PtexIndices class defines, e.g. roughly the following:

std::vector<int> ptexFaceToBaseFace;

for (int baseFace = 0; baseFace < baseLevel.GetNumFaces(); ++baseFace) {
    int numBaseFaceVerts = baseLevel.GetFaceVertices(baseFace).size();

    int numPtexFaces = (numBaseFaceVerts == 4) ? 1 : numBaseFaceVerts;
    for (int j = 0; j < numPtexFaces ++j) {
        ptexFaceToBaseFace.push_back(baseFace);
    }
}
assert(ptexFaceToBaseFace.size() == ptexIndices.GetNumFaces());

Then you can simply use the Far::PatchParam::GetFaceId() as an index to trivially identify the base face of all patches:

for (int patch = 0; patch < numPatches; ++patch) {
    Far::Index parentFaceId = ptexFaceToBaseFace[patchParam.GetFaceId()];

    // ...
}

I won't make any attempt to defend the use of PtexIndices here. It was introduced before my involvement in OpenSubdiv and I have been advocating for simpler alternatives to avoid it ever since. Unfortunately it is deeply ingrained in the Far::PatchParam, so we cannot easily change it while supporting all clients that have adapted to it. And given how deeply ingrained it is, providing an alternative is going to have to wait until the next major release.

As I have said before, you are trying to disassemble a data structure that was intended to be used for other purposes, so there are many aspects of the interface that are not well suited to your task.

VadiMysore commented 5 years ago

@barfowl - Hi Barry! Thanks for the detailed response. After much research into this, I had figured out the response myself. But could not post my response here in time. My solution is exactly the same as your suggestions. Thanks for your effort in responding to me in such details.

My solution was based on this unfinished documentation on PTex Faces I found somehow on OpenSubdiv documentation.

http://graphics.pixar.com/opensubdiv/docs/using_osd_textures.html

It gave me a clue as to how to prepare a map of ptex face ids to control cage faces. I tried this on various cases including the one I took as an example in my original posting. My request to OpenSubdiv was to document it better, in the least. That would have helped me in formulating my logic to get the information I wanted.

Thanks again for your explanation. This issue may now be closed as a solution has been found.

barfowl commented 5 years ago

Glad to hear you arrived at the same solution.

And we are in agreement about the poor documentation in this area. The page you cited is actually long dead and was not being linked by any active pages, so it was recently removed. I did just add references to the images that it used in the Patch Parameterization section of the Far Overview. So that minor improvement will accompany the next release.