Skip to content

Subclassing the View Object

James Walker edited this page Sep 1, 2019 · 2 revisions

The objects submitted to rendering/bounding/picking/writing loops are often groups which contain many other objects nested within. It is often useful to be able to intercept as it is just about to be rendered (or picked, etc.) in order to cull certain objects or modify their handling. This can be accomplished by defining a subclass of the view object.

The Geom Test sample uses a view subclass in order to display vertex or face normals of geometries. To accomplish this well enough for the example, it is only necessary to override one method, the one that submits a retained object to the renderer.

Writing a Metahandler

Every subclass must have a metahandler, a function that receives an integer indicating a kind of method, and returns a function pointer.

#!cpp
static TQ3XFunctionPointer
viewMetaHandler( TQ3XMethodType      methodType )
{
    TQ3XFunctionPointer    theMethod = NULL;

    switch (methodType)
    {
        case kQ3XMethodTypeViewSubmitRetainedRender:
            theMethod = (TQ3XFunctionPointer) submitForRenderMethod;
            break;
    }

    return theMethod;
}

If you need to override more methods, you would of course add more cases to the switch. The list of method types, and their corresponding function prototypes, can be found in the QuesaExtension.h public header.

Registering the Subclass

Before we can create an instance of the subclass, we must register it. In the Geom Test example, we need several static variables:

#!cpp
static TQ3ObjectType                sMyViewType = 0;
static TQ3XObjectClass                sMyViewClass = NULL;
static TQ3XViewSubmitRetainedMethod    sSubRenderStandardMethod = NULL;

The variable sMyViewType serves as a numerical identifier for the class, in functions such as Q3Object_GetLeafType and Q3Object_IsType. The object class pointer, sMyViewClass, will be needed in order to create an instance of the class. The function pointer sSubRenderStandardMethod is the view method before it was overridden.

Here is where we register the subclass:

#!cpp

if (sMyViewClass == NULL)
{
    // Get a function pointer to the method of the standard view object
    TQ3XObjectClass viewClass = Q3XObjectHierarchy_FindClassByType( kQ3ObjectTypeView );
    sSubRenderStandardMethod = (TQ3XViewSubmitRetainedMethod)
        Q3XObjectClass_GetMethod( viewClass,
        kQ3XMethodTypeViewSubmitRetainedRender );

    // Register the subclass
    sMyViewClass = Q3XObjectHierarchy_RegisterClass( kQ3ObjectTypeView,
        &sMyViewType, \"ToggleNormalView\", viewMetaHandler, NULL, 0, 0 );
}

The class name string, here shown as "ToggleNormalView", must be unique. If you want to ensure uniqueness, you may want to use a reverse DNS scheme.

Implementing the Override

Obviously, you must implement each override method mentioned in the metahandler. Here is the method from Geom Test:

#!cpp

static TQ3Status submitForRenderMethod( TQ3ViewObject inView, TQ3Object theObject )
{
    TQ3Status    theStatus = (*sSubRenderStandardMethod)( inView, theObject );
    
    if (Q3Object_IsType( theObject, kQ3GeometryTypeTriMesh ))
    {
        if (gShowVertexNormals)
        {
            displayVertexNormals( theObject, inView );
        }
        if (gShowFaceNormals)
        {
            displayFaceNormals( theObject, inView );
        }
    }
    
    return theStatus;
}

In this case, we call the standard submit method, then perform additional processing. In other cases, you might do something before calling the standard method, or you might skip the call to the standard method for certain objects.

In our example, we only handle TriMesh geometries. You might think that you would need to do more work for each other type of geometry, but not so. Every 2D geometry type (cone, ellipsoid, NURBS patch, etc.) is internally cached as one or more TriMesh, and it is these TriMeshes that are ultimately submitted to the renderer.

Creating an Instance

To create an instance of the view subclass, instead of calling Q3View_New(), you need to use the class pointer:

#!cpp

TQ3ViewObject theView = Q3XObjectHierarchy_NewObject( sMyViewClass, NULL );