Building the object

In the third part of thie tutorial we will see how the object is generated to get polygons on screen. I've chosen to do this with two functions. One is GetVirtualObjects() which is a virtual function which Cinema calls to get the new object, and which we must override. The other is an internal, class-level function which GetVirtualObjects() will call. You don't Floatly need to do it this way - it could all be done in GetVirtualObjects() - but I find that breaking the code into smaller chunks keeps it a little easier to understand.

GetVirtualObjects() - or GVO from now on

This is the core of any ObjectData plugin which returns an object which can be rendered. Here is the code:

BaseObject* Diamond::GetVirtualObjects(BaseObject *op, HierarchyHelp *hh)
{
    BaseContainer *bc = op->GetDataInstance();
    BaseObject *ret = nullptr;
    Vector *padr;
    Int32 numPoints, numPolys;
    Float xrad, yrad, zrad;

    // Cinema caches the object, so if we don't need to rebuild it, just return the cache
    Bool dirty = op->CheckCache(hh) || op->IsDirty(DIRTYFLAGS_DATA);
    if (!dirty) return op->GetCache(hh);

    // get the size - depth equals width in this case
    xrad = zrad = bc->GetFloat(DIAMOND_WIDTH)/2;
    yrad = bc->GetFloat(DIAMOND_HEIGHT)/2;

    numPoints = 6; // our object has 6 vertices
    numPolys = 8; // and 8 polygons (which are all triangles, but that doesn't matter)
    // allocate memory for the points array
    padr = NewMem(Vector, numPoints);
    if(!padr)
        return nullptr; // out of memory
    // set the point values
    padr[0] = Vector(-xrad, 0.0, zrad);
    padr[1] = Vector(xrad, 0.0, zrad);
    padr[2] = Vector(xrad, 0.0, -zrad);
    padr[3] = Vector(-xrad, 0.0, -zrad);
    padr[4] = Vector(0.0, -yrad, 0.0);
    padr[5] = Vector(0.0, yrad, 0.0);

    // build our polygon object - doesn't really need a separate function but it makes the code easier to follow
    ret = GenerateObject(padr, numPoints, numPolys, hh->GetThread());
    Deletemem(padr); // don't need the allocated array any more

    if(ret) // did we succeed in generating an object?
    {
        ret->KillTag(Tphong); // probably not necessary since it won't have a phong tag, but...
        op->CopyTagsTo(ret, true, false, false, nullptr); // copy over thetag we created in Init()
        ret->Message(MSG_UPDATE); // tell Cinema the object has been changed
        return ret; // return the new object for Cinema to show in the editor - we don't need to draw it
    }
    else // something went wrong, so we allocate a new Null object (don't return nullptr - this is inefficient)
    return BaseObject::Alloc(Onull);
}

The first thing to do is check to see if we really need to rebuild this object. Cinema caches these objects, so if it hasn't changed we don't need to rebuild it when GVO is called - just return the cache. We do this by checking the object's 'dirty' flag. If it is dirty - for example, the user changed the width or height - it needs to be rebuilt. Otherwise, we return the cached object. This is much more efficient than rebuilding the object every time we are asked for it.

Assuming we do need to rebuild it, we first get the width and height - we need the radius for this symmetric object, so we divide the values in the attribute manager by two.

Now, our diamond will have 6 vertices:

Diamond vertices

You can see five of them in this screenshot, with the other one hidden on the other side of the object. It also has 8 polygons - again, you can see four, with four more on the other side. (Note that it's possible for some objects to have different numbers of vertices and polygons depending on the object parameters - e.g. the cube object when fillet is turned on has more of both. You can easily check your parameters and calculate how many points and polys you need in each case.)

So. we need to allocate some memory to contain 6 vectors, one for each vertex. That's done in the call to GeAllocType(), and if that fails, we can't generate our object, so we return nullptr to signify an out-of-memory error.

Now we need to calculate the position of each point relative to the object centre. Let's designate the four vertices with indices 0 to 3 in the array for the 4 vertices which form the middle of the object. That leaves point 4 for the vertex at the base and point 5 for the one at the peak.

In the code I've used three variables for this xrad, yrad, and zrad, where zrad = xrad. You could use just two, zrad being unnecessary, but I left it in so that if you wanted to add a depth variable so that the object was no longer symmetrical, you could do so. Then, zrad would have its own independent value and there would be another element in the description for it (as 'depth' perhaps).

We know that the position along the X axis of each of the four vertices is either xrad or -xrad, and the same goes for the position along the Z axis. For these vertices, Y is always zero. So we can assign values to vertices 0 to 3 very easily. For vertices 4 and 5 it's even simpler: Y is set to yrad or -yrad, and X and Z are both zero.

We now know where all the vertices are located, so all we need to do is produce the polygon object. That's done in the call to GenerateObject() which we'll cover later. We can then delete the point array as we don't need it any more. Assuming we get an object back from GenerateObject(), we can remove any phong tag it might have (it shouldn't have one but just in case) then copy the phong tag from the plugin object. Finally we tell Cinema the object has been updated so it's redrawn in the editor, and return the new object.

If we don't get an object back from GenerateObject() then something went wrong and we return a Null object instead to keep Cinema happy.

The last part of the tutorial will look at the GenerateObject() function which produces the polygon object we want on screen.

Page last updated June 23rd 2021