Adding a cube to the scene
 
 
 

Setting the cube’s name, position, and rotation

CreateCube()is triggered when the CubeCreator user clicks 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()callsCreateCubeDetailed(), 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);
 SetCubePosition(lCube, 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 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 byLclTranslation.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 have contain a layer element to store texture files, another to store normals, a third to store diffuse materials, 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 thatCreateCubeDetailed()can apply the material and texture inn a few lines of code (seeApplying 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 only uses one texture, but we still need to use a DirectArray to store the pointer to that texture. The DirectArray will only need one element, i.e., element 0, for that texture.

FBX SDK supports various texture channels, including diffuse, specular, ambient, 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(lMesh, "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:

 lLayer->SetDiffuseTextures(lTextureDiffuseLayer);

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(lMesh, "");
 lMaterialLayer->SetMappingMode(KFbxLayerElement::eBY_POLYGON);
 lMaterialLayer->SetReferenceMode(KFbxLayerElement::eINDEX_TO_DIRECT);
 lLayer->SetMaterials(lMaterialLayer);

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 inlTextureDiffuseLayer.

 //in the same way we with Textures,
 //but we are in eBY_POLYGON and as we are doing a cube,
 //we should have 6 polygons (1 for each faces of the cube)
 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++)
 {
 // different material but same texture for each faces
 lMesh->BeginPolygon(i, 0, -1, false);) 
 // here we set the the index array for each channel
 lTextureDiffuseLayer->GetIndexArray().SetAt(i,0);
 for(j = 0; j < 4; j++)
 {
 // Control point index
 lMesh->AddPolygon(lPolygonVertices[i*4 + j]);
 // Now we have to update the index array of the UVs for diffuse, ambient and emissive
 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()callsCreateCube(), which callsCreateCubeDetailed(), which callsAnimateCube()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. SeeAdding 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()callsCreateCube(), which callsCreateCubeDetailed(), which callsAddTexture()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()callsCreateCube(), which callsCreateCubeDetailed(), which callsAddMaterials()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);
 }
}