Adding a cube to the scene
 
 
 

Setting the cube’s name, position, and rotation

CreateCube() is triggered when the CubeCreator user clicks the Add Cube button. It does the following:

// create a new cube
void CreateCube(bool pWithMaterial, bool pWithTexture, bool pAnimate)
{
    // make a new cube name
    KString lCubeName = "Cube number ";
    lCubeName += KString(gCubeNumber);
    // create a new cube
    CreateCubeDetailed( lCubeName.Buffer(),
        gCubeXPos,
        gCubeYPos,
        gCubeZPos,
        gCubeRotationAxis,
        pWithMaterial,
        pWithTexture,
        pAnimate
        );
    // compute for next cube creation
    gCubeNumber++; // cube number
    // set next pos
    if(gCubeXPos >= 0.0)
    {
        gCubeXPos += 50.0;
        gCubeXPos *= -1.0;
        gCubeRotationAxis++; // change rotation axis
    }
    else
    {
        gCubeXPos *= -1.0;
    }
    // go up
    gCubeYPos += 30.0;
    if(gCubeRotationAxis > 2) gCubeRotationAxis = 0; // cube rotation
}

Applying the user’s specifications to the cube

CreateCube() calls CreateCubeDetailed(), which does the following:

// create a new cube
void CreateCubeDetailed( char* pCubeName,
                        double pX,
                        double pY,
                        double pZ,
                        int pRotateAxe,
                        bool pWithMaterial,
                        bool pWithTexture,
                        bool pAnimate
                        )
{
    KFbxNode* lCube = CreateCubeMesh(gSdkManager, pCubeName);
    // set the cube position
    lCube->LclTranslation.Set(KFbxVector4(pX, pY, pZ));
    if(pAnimate)
    {
        AnimateCube(lCube, gTakeName, pRotateAxe);
    }
    if(pWithTexture)
    {
        // use already created texture
        AddTexture( lCube->GetMesh() );
    }
    if(pWithMaterial)
    {
        // use already created materials
        AddMaterials(lCube->GetMesh());
    }
    gScene->GetRootNode()->AddChild(lCube);
}

Setting the cube’s position in the scene

Here, we set the cube’s position:

// Set the cube position.
void SetCubePosition(KFbxNode* pCube, double pX, double pY, double pZ)
{
    pCube->LclTranslation.Set(KFbxVector4(pX, pY, pZ));
}

Since CubeCreator’s cubes have only the root node as a parent, the translation set by LclTranslation.Set() is relative to the scene’s origin (0, 0, 0).

Since CubeCreator never applies to a cube any animation that involves translation, this default position is not overridden by animation.

Accordingly, SetCubePosition() sets the cube’s actual position in the scene: the translation specified is relative to the scene’s origin.

Creating a mesh for the cube

CubeCreator shows you how to create a cube as a KFbxMesh object, one vertex at a time.

KFbxNode* CreateCubeMesh(KFbxSdkManager* pSdkManager, char* pName)
{
    int i, j;
    KFbxMesh* lMesh = KFbxMesh::Create(pSdkManager,pName);

Defining the cube’s vertices and the normals

Here we define eight points in 3D space that define the eight corners of a cube. These corners are, more generally, the control points (vertices) of the mesh that we are creating:

    KFbxVector4 lControlPoint0(-50, 0, 50);
    KFbxVector4 lControlPoint1(50, 0, 50);
    KFbxVector4 lControlPoint2(50, 100, 50);
    KFbxVector4 lControlPoint3(-50, 100, 50);
    KFbxVector4 lControlPoint4(-50, 0, -50);
    KFbxVector4 lControlPoint5(50, 0, -50);
    KFbxVector4 lControlPoint6(50, 100, -50);
    KFbxVector4 lControlPoint7(-50, 100, -50);

Here we define six normals, one for each face of the cube:

    KFbxVector4 lNormalXPos(1, 0, 0);
    KFbxVector4 lNormalXNeg(-1, 0, 0);
    KFbxVector4 lNormalYPos(0, 1, 0);
    KFbxVector4 lNormalYNeg(0, -1, 0);
    KFbxVector4 lNormalZPos(0, 0, 1);
    KFbxVector4 lNormalZNeg(0, 0, -1);

Defining the cube’s faces

Here we tell the mesh object that it needs 24 control points, four control points for each of the cube’s six faces:

    // Create control points.
    lMesh->InitControlPoints(24);

Here we get a pointer to the mesh’s array of 24 control points:

    KFbxVector4* lControlPoints = lMesh->GetControlPoints();

Here we set the values of the mesh’s control point array, one face at a time:

    lControlPoints[0] = lControlPoint0;
    lControlPoints[1] = lControlPoint1;
    lControlPoints[2] = lControlPoint2;
    lControlPoints[3] = lControlPoint3;
 
    lControlPoints[4] = lControlPoint1;
    lControlPoints[5] = lControlPoint5;
    lControlPoints[6] = lControlPoint6;
    lControlPoints[7] = lControlPoint2;
 
    lControlPoints[8] = lControlPoint5;
    lControlPoints[9] = lControlPoint4;
    lControlPoints[10] = lControlPoint7;
    lControlPoints[11] = lControlPoint6;
 
    lControlPoints[12] = lControlPoint4;
    lControlPoints[13] = lControlPoint0;
    lControlPoints[14] = lControlPoint3;
    lControlPoints[15] = lControlPoint7;
 
    lControlPoints[16] = lControlPoint3;
    lControlPoints[17] = lControlPoint2;
    lControlPoints[18] = lControlPoint6;
    lControlPoints[19] = lControlPoint7;
 
    lControlPoints[20] = lControlPoint1;
    lControlPoints[21] = lControlPoint0;
    lControlPoints[22] = lControlPoint4;
    lControlPoints[23] = lControlPoint5;

Using layer 0 to store a normal to each face

In FBX SDK, a mesh can have multiple layers. A layer is a container for texture files, normals, UV coordinates, etc. A layer may contain a layer element to store texture files, another to store normals, a third to store diffuse materials, and a fourth to store specular materials.

Unless you need more than one layer (for some advanced purpose), always use Layer 0.

NoteMotionBuilder, FBX for QuickTime, and the FBX plugins for 3ds Max and Maya only process normals and materials stored in Layer 0.

In this section of code, we will assign the normals that we created earlier in a layer element for normals, and we will assign that layer element to Layer 0 of the mesh.

We start by creating Layer 0 if it does not already exist:

    // Set the normals on Layer 0.
    KFbxLayer* lLayer = lMesh->GetLayer(0);
    if (lLayer == NULL)
    {
        lMesh->CreateLayer();
        lLayer = lMesh->GetLayer(0);
    }

Here we assign normals to each polygon in our mesh. Normals are vectors that are (normally) perpendicular to a polygon, and which face “out”. Since our mesh is a cube, the faces of the cube are squares (four-sided polygons), we assign one normal to each vertex of each face.

A layer element is composed of two arrays, called DirectArray and IndexArray:

We start by creating the DirectArray for the normals.

    // We want to have one normal for each vertex (or control point),
    // so we set the mapping mode to eBY_CONTROL_POINT.
    KFbxLayerElementNormal* lLayerElementNormal= KFbxLayerElementNormal::Create(lMesh, "");
 
    lLayerElementNormal->SetMappingMode(KFbxLayerElement::eBY_CONTROL_POINT);
 
    // Set the normal values for every control point.
    lLayerElementNormal->SetReferenceMode(KFbxLayerElement::eDIRECT);

The normals for each face of the cube will face the same direction. Accordingly, the first four vertices, which refer to the first face, are assigned the same normal (lNormalZpos); the second four vectors, which refer to the second face, are assigned the same normal (lNormalXpos); etc.

GetDirectArray() returns a pointer to a DirectArray for normals. Add() appends a vector to the end of the array.

    lLayerElementNormal->GetDirectArray().Add(lNormalZPos);
    lLayerElementNormal->GetDirectArray().Add(lNormalZPos);
    lLayerElementNormal->GetDirectArray().Add(lNormalZPos);
    lLayerElementNormal->GetDirectArray().Add(lNormalZPos);
 
    lLayerElementNormal->GetDirectArray().Add(lNormalXPos);
    lLayerElementNormal->GetDirectArray().Add(lNormalXPos);
    lLayerElementNormal->GetDirectArray().Add(lNormalXPos);
    lLayerElementNormal->GetDirectArray().Add(lNormalXPos);
 
    lLayerElementNormal->GetDirectArray().Add(lNormalZNeg);
    lLayerElementNormal->GetDirectArray().Add(lNormalZNeg);
    lLayerElementNormal->GetDirectArray().Add(lNormalZNeg);
    lLayerElementNormal->GetDirectArray().Add(lNormalZNeg);
 
    lLayerElementNormal->GetDirectArray().Add(lNormalXNeg);
    lLayerElementNormal->GetDirectArray().Add(lNormalXNeg);
    lLayerElementNormal->GetDirectArray().Add(lNormalXNeg);
    lLayerElementNormal->GetDirectArray().Add(lNormalXNeg);
 
    lLayerElementNormal->GetDirectArray().Add(lNormalYPos);
    lLayerElementNormal->GetDirectArray().Add(lNormalYPos);
    lLayerElementNormal->GetDirectArray().Add(lNormalYPos);
    lLayerElementNormal->GetDirectArray().Add(lNormalYPos);
 
    lLayerElementNormal->GetDirectArray().Add(lNormalYNeg);
    lLayerElementNormal->GetDirectArray().Add(lNormalYNeg);
    lLayerElementNormal->GetDirectArray().Add(lNormalYNeg);
    lLayerElementNormal->GetDirectArray().Add(lNormalYNeg);

We’ve finished assigning normals to the DirectArray of normals. Now we assign the DirectArray to the layer (Layer 0).

    lLayer->SetNormals(lLayerElementNormal);

Creating a layer element for the materials and textures

For each cube, CubeCreator allows the user to apply one material and one texture to each of the faces. This section of code does all the necessary low-level setup so that CreateCubeDetailed() can apply the material and texture in a few lines of code (see Applying the user’s specifications to the cube).

Here we create an array of polygon vertex numbers that we’ll use below.

    // Array of polygon vertices.
    int lPolygonVertices[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,12, 13,
        14, 15, 16, 17, 18, 19, 20, 21, 22, 23 };

CubeCreator uses only one texture, but we still need to use a DirectArray to store the pointer to that texture. The DirectArray will need only one element, i.e., element 0, for that texture.

FBX SDK supports various texture channels, including diffuse, specular, ambient, and shininess. We’re going to use the diffuse channel: that allows us to set the color of each face of the cube.

Here, we create the DirectArray for textures:

    // Set texture mapping for diffuse channel.
    KFbxLayerElementTexture* lTextureDiffuseLayer=KFbxLayerElementTexture::Create(pMesh, "Diffuse Texture");
    lTextureDiffuseLayer-  >SetMappingMode(KFbxLayerElement::eBY_POLYGON);

This time, we are going to use the IndexArray as well as the DirectArray.

    lTextureDiffuseLayer->SetReferenceMode(KFbxLayerElement::eINDEX_TO_DIRECT);

Here we point the mesh’s layer to the object that contains the DirectArray and IndexArray for textures:

    if(pMesh->GetLayer(0))
        pMesh->Getlayer(0)->SetDiffuseTextures(lTextureDiffuseLayer);
    else
        return;

To correctly map the texture onto the mesh, we need the UV coordinates on the same channel (i.e., diffuse texture). FBX SDK will pair the texture data with the UV coordinates of the same given channel.

    // Create UV for Diffuse channel.
    KFbxLayerElementUV* lUVDiffuseLayer =
    KFbxLayerElementUV::Create(lMesh, "DiffuseUV");
    lUVDiffuseLayer->SetMappingMode(KFbxLayerElement::eBY_POLYGON_VERTEX);

We are going to use IndexArray to store index numbers for DirectArray:

    lUVDiffuseLayer->SetReferenceMode(KFbxLayerElement::eINDEX_TO_DIRECT);
    lLayer->SetUVs(lUVDiffuseLayer, KFbxLayerElement::eDIFFUSE_TEXTURES);

Since we may apply a material to the faces, we also need a layer element for materials:

    // Set material mapping.
    KFbxLayerElementMaterial* lMaterialLayer=KFbxLayerElementMaterial::Create(pMesh, "");
    lMaterialLayer->SetMappingMode(KFbxLayerElement::eBY_POLYGON);
    lMaterialLayer->SetReferenceMode(KFbxLayerElement::eINDEX_TO_DIRECT);
    if(pMesh->GetLayer(0))
        pMesh->Getlayer(0)->SetMaterials(lMaterialLayer);
    else
        return;

These four vectors define UV coordinates for mapping the the texture onto the face of a cube. UV coordinates are normalized to range from 0.0 to 1.0: the vectors below will map the texture so that it covers the entire surface of the face.

    KFbxVector2 lVectors0(0, 0);
    KFbxVector2 lVectors1(1, 0);
    KFbxVector2 lVectors2(1, 1);
    KFbxVector2 lVectors3(0, 1);

Our DirectArray for diffuse UV coordinates has four elements, one for each coordinate:

    lUVDiffuseLayer->GetDirectArray().Add(lVectors0);
    lUVDiffuseLayer->GetDirectArray().Add(lVectors1);
    lUVDiffuseLayer->GetDirectArray().Add(lVectors2);
    lUVDiffuseLayer->GetDirectArray().Add(lVectors3);

This IndexArray has 24 elements, one for each of the six vertices of the four faces of the cube. We use it to index the DirectArray of four UV coordinates.

    //Now we have set the UVs as eINDEX_TO_DIRECT reference and in eBY_POLYGON_VERTEX mapping mode
    //we must update the size of the index array.
 lUVDiffuseLayer->GetIndexArray().SetCount(24);

This IndexArray has has six elements, one for each face of a cube. We use it to index the DirectArray of textures defined in lTextureDiffuseLayer.

    //We are in eBY_POLYGON, so there’s only need for 6 index (a cube has 6 polygons).
 lTextureDiffuseLayer->GetIndexArray().SetCount(6);

For each of the six faces of the cube:

    // Create polygons. Assign texture and texture UV indices.
    for(i = 0; i < 6; i++)
    {
    // all faces of the cube have the same texture
    lMesh->BeginPolygon(-1, -1, -1, false);) 
    for(j = 0; j < 4; j++)
    {
    // Control point index
    lMesh->AddPolygon(lPolygonVertices[i*4 + j]);
         // update the index array of the UVs that map the texture to the face
         lUVDiffuseLayer->GetIndexArray().SetAt(i*4+j, j);
     }
     lMesh->EndPolygon ();
 }

Now we have finished creating the KFbxMesh object. We create a KFbxNode object to contain the mesh, and return the node to the calling program.

    // create a KFbxNode
    KFbxNode* lNode = KFbxNode::Create(pSdkManager,pName);
 
    // set the node attribute
    lNode->SetNodeAttribute(lMesh);
 
    // set the shading mode to view texture
    lNode->SetShadingMode(KFbxNode::eTEXTURE_SHADING);
 
    // rescale the cube
    lNode->LclScaling.Set(KFbxVector4(0.3, 0.3, 0.3));
 
    // return the KFbxNode
    return lNode;
}

Saving memory by sharing a mesh among multiple nodes

Every time the user adds a cube, CubeCreator creates a new KFbxMesh object and a new KFbxNode object containing that mesh. That’s two objects for each cube, not to mention all the layer objects, layer element objects, and so forth. If the user needs hundreds or thousands of cubes, the memory requirements could be significant.

Imagine that you need a program where all cubes looked alike, but you needed many thousands of them. You could save memory by creating one KFbxMesh object when the program starts up. Then, every time you needed a new cube, you create a new KFbxNode object, then point that node to the one mesh.

Similarly, you can save memory by having multiple nodes share textures, materials, takes, animation curves, etc.

Adding animation to a cube

CreateScene() calls CreateCube(), which calls CreateCubeDetailed(), which calls AnimateCube() only if the user has selected Animation in the UI:

    if(pAnimate)
    {
        AnimateCube(lCube, gTakeName, pRotateAxe);
    }

You can animate meshes, NURBS, lights, cameras—any instance of a subclass of KFbxNodeAttribute. See Adding animation to the camera.

// The cube rotate on X or Y or Z.
void AnimateCube(KFbxNode* pCube, KString pTakeName, int pRotAxe)
{
    KFCurve* lCurve = NULL;
    KTime lTime;
    int lKeyIndex = 0;
 
    pCube->CreateTakeNode(pTakeName.Buffer());
    pCube->SetCurrentTakeNode(pTakeName.Buffer());
    pCube->LclRotation.GetKFCurveNode(true, pTakeName.Buffer());
 
    if(pRotAxe == 0)
        lCurve = pCube->LclRotation.GetKFCurve(KFCURVENODE_R_X,
 pTakeName.Buffer());
    else if(pRotAxe == 1)
         lCurve = pCube->LclRotation.GetKFCurve(KFCURVENODE_R_Y,
 pTakeName.Buffer());
    else if(pRotAxe == 2)
         lCurve = pCube->LclRotation.GetKFCurve(KFCURVENODE_R_Z, pTakeName.Buffer());
 
    if (lCurve)
    {
        lCurve->KeyModifyBegin();
 
        lTime.SetSecondDouble(0.0);
        lKeyIndex = lCurve->KeyAdd(lTime);
        lCurve->KeySet(lKeyIndex, lTime, 0.0, KFCURVE_INTERPOLATION_LINEAR);
 
        lTime.SetSecondDouble(20.0);
        lKeyIndex = lCurve->KeyAdd(lTime);
        lCurve->KeySet(lKeyIndex, lTime, -3500, KFCURVE_INTERPOLATION_LINEAR);
        lCurve->KeyModifyEnd();
    }
}

Adding a texture to a cube

CreateScene() calls CreateCube(), which calls CreateCubeDetailed(), which calls AddTexture() only if the user has selected Texture in the UI:

void AddTexture(KFbxMesh* pMesh)
{
    // The Layer 0 and the KFbxLayerElementTexture has already been created
    // in the CreateCube function.
    pMesh->GetLayer(0)->GetDiffuseTextures()->
    GetDirectArray().Add(gTexture);
}

Adding a material to a cube

CreateScene() calls CreateCube(), which calls CreateCubeDetailed(), which calls AddMaterials() only if the user has selected Materials in the UI:

void AddMaterials(KFbxMesh* pMesh)
{
    int i;
 
    //get the node of mesh, add material for it.
    KFbxNode* lNode = pMesh->GetNode();
    if(lNode == NULL)
        return;
 
    // cube has 6 faces with the same material
    for (i = 0; i < 6; i++ )
    {
        lNode->AddMaterial(gMaterial);
    }
}