Constructing the baseline scene
 
 
 

Method CreateScene() constructs a scene consisting of a marker, a camera pointed at the marker, and a light.

Naming the take which will contain the animation

In live action filmmaking, a “take” refers to a single version of the filming of a scene. Directors can film multiple takes, and refer to them as “take one”, “take two”, etc.

In FBX, a take contains all the animation curves necessary to define the animation for a 3D object. An FBX scene can have multiple takes. This means that the 3D objects in the scene can be animated differently in each take.

A take is a concept used by MotionBuilder and FBX, but not used by many 3D tools including 3ds Max and Maya. Of the file formats supported by FBX SDK 2010, only FBX files can contain multiple takes. Other file formats can contain animation of course, but they cannot store explicit take information, and they can only contain animation equivalent to one FBX take.

KFbxNode’s methods and properties that manage takes are inherited from KFbxTakeNodeContainer:

In FBX SDK, a take is a container for animation data. Any scene with animation must contain at least one take. CubeCreator’s scene has only one take.

A take is identified by its name:

    // set the take name
    gTakeName = "Take camera animation";

A scene may have many takes, but only one of them can be the current take. When an FBX scene is exported to a file format that does not support takes, only the current take is exported.

Even though CubeCreator’s scene has only one take, we explicitly set it as the current take:

    // set this take name as the current take
    gScene->SetCurrentTake(gTakeName.Buffer());

Even though we have a name for our take, we haven’t actually created it yet. Nor have we assigned any animation to the take. We’ll do both in Adding animation to the camera.

NoteFor an example of an FBX file with several takes of animation, see humanoid.fbx, located in \examples\viewscene. You can use FBX for QuickTime to cycle through the takes. Or, you can use ViewScene itself (see ViewScene).

Creating a marker to be used as a reference point

Markers are usually not rendered, i.e., they are usually not visible in a 3D view of a scene. But markers can be useful to the animator or (in this case) the programmer as a reference point.

CubeCreator creates a marker that will be used as the point of interest of the camera, and as the location of the first cube.

Here is the code in CreateScene():

    // create a marker
    KFbxNode* lMarker = CreateMarker(gSdkManager, "Marker");

And here is the code in CreateMarker():

// Create a marker to use a point of interest for the camera.
KFbxNode* CreateMarker(KFbxSdkManager* pSdkManager, char* pName)
{

First, we use the SDK Manager to allocate memory for the KFbxMarker object:

    KFbxMarker* lMarker = KFbxMarker::Create(pSdkManager,pName);

Then, we allocate the memory for the KFbxNode object:

    KFbxNode* lNode = KFbxNode::Create(pSdkManager,pName);

We tell the node that its contents are the marker:

    lNode->SetNodeAttribute(lMarker);

Finally, we return the node that contains the marker:

    return lNode;
}

Creating a camera

CreateScene() calls CreateCamera() to create a node whose node attribute is a camera:

    // create a camera
 KFbxNode* lCamera = CreateCamera(gSdkManager, "Camera");
...
// Create a camera.
KFbxNode* CreateCamera(KFbxSdkManager* pSdkManager, char* pName)
{

First we instantiate a camera object, using the SDK Manager to manage memory:

    KFbxCamera* lCamera = KFbxCamera::Create(pSdkManager,pName);

Then we adjust the settings of the camera so that it “films” in a format suitable for (non-high-definition) television :

    // Set camera properties for classic TV projection with aspect
    // ratio 4:3
    lCamera->SetFormat(KFbxCamera::eNTSC);

Next we instantiate a node object:

    KFbxNode* lNode = KFbxNode::Create(pSdkManager,pName);

We assign the camera to the node:

    lNode->SetNodeAttribute(lCamera);

Finally, we return the node that contains the camera:

    return lNode;
}

Creating one texture available to all cubes

CreateScene() calls CreateTexture() to create a texture object that is available for use by any of the cubes. The texture object is global:

KFbxTexture* gTexture = NULL;
...
    // create a single texture shared by all cubes
    CreateTexture(gSdkManager);
...
// Create a global texture for cube.
void CreateTexture(KFbxSdkManager* pSdkManager)
{
     gTexture = KFbxTexture::Create(pSdkManager,"Diffuse Texture");
     // Resource file must be in the application’s directory.
     KString lTexPath = gAppPath + "\\Crate.jpg";
     // Set texture properties.
     gTexture->SetFileName(lTexPath.Buffer());
     gTexture->SetTextureUse(KFbxTexture::eSTANDARD);
     gTexture->SetMappingType(KFbxTexture::eUV);
     gTexture->SetMaterialUse(KFbxTexture::eMODEL_MATERIAL);
     gTexture->SetSwapUV(false);
     gTexture->SetTranslation(0.0, 0.0);
     gTexture->SetScale(1.0, 1.0);
     gTexture->SetRotation(0.0, 0.0);
}
NoteThe texture file crate.jpg must be available to CubeCreator at run time, and available to the renderer (e.g., FBX for QuickTime) of the file exported by CubeCreator. See:

Embedding media files in an FBX file

When you run CubeCreator, you can save the scene in the file format of your choice and in the folder of your choice.

If you save the scene as a binary FBX file, CubeCreator embeds the texture file crate.jpg in the FBX file. This ensures that if you send your FBX file to a co-worker, the FBX file will contain all files referenced by the scene.

In general, you can embed any kind of media file in a binary FBX file—providing you set the appropriate export option. Here is how we do it in CubeCreator.

When the user clicks the Save to button, CubeCreator calls SaveScene(), passing true as the value of pEmbedMedia:

// to save a scene to an FBX file
bool Export(
            const char* pFilename, 
            int pFileFormat
            )
...
    KFbxStreamOptionsFbxWriter* lExportOptions=
    KFbxStreamOptionsFbxWriter::Create(pSdkManager, "");
    if (pSdkManager->GetIOPluginRegistry()->WriterIsFBX(pFileFormat))
 {
         // Set the export states.
         // By default, the export states are always set to true except for
         // the option eEXPORT_TEXTURE_AS_EMBEDDED. The code below
         // shows how to change these states.
         IOSREF.SetBoolProp(EXP_FBX_MATERIAL, true);
         IOSREF.SetBoolProp(EXP_FBX_TEXTURE, true);
         IOSREF.SetBoolProp(EXP_FBX_EMBEDDED, pEmbedMedia);
         IOSREF.SetBoolProp(EXP_FBX_SHAPE, true);
...
 }

Processing FBX files that contain embedded media

Applications that render or otherwise process FBX files must access the textures and other media files.

If media files are embedded in a binary FBX file, when you import the file into the application, the FBX SDK importer will extract the media from the FBX file and copy them to a directory. By default:

Processing scene files with references to media

ASCII FBX files, OBJ files, and other FBX-supported file formats cannot contain embedded media. Moreover, you can create binary FBX files that need media files, but set the export option so that the files are not embedded. In these cases, the file will retain the addresses of the media files.

Then you must make sure that FBX for QuickTime (or 3ds Max, Maya, or any other application that you use to render or otherwise process the scene) can find those media.

Here is how FBX SDK searches for each non-embedded media file in, for example, an ASCII FBX file:

    // Resource file must be in the application's directory.
    KString lTexPath = gAppPath + "\\Crate.jpg";
...
    gTexture->SetFileName(lTexPath.Buffer());

Creating an array of materials to be shared

CreateScene() calls CreateMaterial() to create one material that will be applied to each face of the cube. These materials are available for use by all the cubes in the scene.

We’ll use a Phong shader for the materials.

KFbxSurfacePhong* gMaterial = NULL;
...
    // create a material shared by all faces of all cubes
    CreateMaterial(gSdkManager);
...
// Create global material for cube.
void CreateMaterial(KFbxSdkManager* pSdkManager)
{
   KString lMaterialName = "material";
   KString lShadingName = "Phong";
   fbxDouble3 lBlack(0.0, 0.0, 0.0);
   fbxDouble3 lRed(1.0, 0.0, 0.0);
   fbxDouble3 lDiffuseColor(0.75, 0.75, 0.0);
   gMaterial = KFbxSurfacePhong::Create(pSdkManager,  lMaterialName.Buffer());

The following properties are used by the Phong shader to calculate the color for each pixel in the material:

   // Generate primary and secondary colors.
   gMaterial->GetEmissiveColor() .Set(lBlack);
   gMaterial->GetAmbientColor() .Set(lRed);
   gMaterial->GetDiffuseColor() .Set(lDiffuseColor);
   gMaterial->GetTransparencyFactor() .Set(40.5);
   gMaterial->GetShadingModel() .Set(lShadingName);
   gMaterial->GetShininess() .Set(0.5);
}

Pointing the camera at the marker

CreateScene() calls SetCameraPointOfInterest() to set the marker as the target (i.e., point of interest) of the camera. This means that even if the camera and/or the target moves, the camera will continue to point at the target.

    // set the camera point of interest on the marker
    SetCameraPointOfInterest(lCamera, lMarker);

lMarker and lCamera are pointers to KFbxNode objects, not pointers to KFbxMarker and KFbxCamera objects. Recall that each node (i.e., KFbxNode object) contains a pointer to its node attribute object (or null): the node attribute object is the “contents” of the node object. In this case:

// Set target of the camera.
void SetCameraPointOfInterest(KFbxNode* pCamera, KFbxNode* pPointOfInterest)
{
    // Set the camera to always point at this node.
    pCamera->SetTarget(pPointOfInterest);
}

Setting the position of the marker

Here we set the marker’s translation, rotation, and scaling (the TRS properties):

    // set the marker position
    SetMarkerDefaultPosition(lMarker);
...
// Set marker default position.
void SetMarkerDefaultPosition(KFbxNode* pMarker)
{
    // The marker is positioned above the origin. There is no
    // rotation and no scaling.
    pMarker->LclTranslation.Set(KFbxVector4(0.0, 40.0, 0.0));
    pMarker->LclRotation.Set(KFbxVector4(0.0, 0.0, 0.0));
    pMarker->LclScaling.Set(KFbxVector4(1.0, 1.0, 1.0));
}

LclTranslation.Set() sets the position of a node—in this case, the camera. Note that:

Similarly, the rotation and scaling of a node are also defaults and are also relative to the parent.

Setting the position of the camera

    // set the camera position
	    SetCameraDefaultPosition(lCamera);
...
// Compute the camera position.
void SetCameraDefaultPosition(KFbxNode* pCamera)
{
    // set the initial camera position
    KFbxVector4 lCameraLocation(0.0, 200.0, -100.0); 
    pCamera->LclTranslation.Set(lCameraLocation);
}

Adding animation to the camera

    // animate the camera
    AnimateCamera(lCamera, gTakeName);
// The camera move on X and Y axis.
void AnimateCamera(KFbxNode* pCamera, KString pTakeName)
{

Declare an animation curve (FCurve), a time object, and an animation key index. The key index is 0 for the first key, 1 for the second key, and so forth.

    KFCurve* lCurve = NULL;
    KTime lTime;
    int lKeyIndex = 0;

Here, we create one take node for this scene. A node (KFbxNode object) is a container for take nodes. A take node (KFbxTakeNode object) is in turn a container for animation data. Animation data are stored as function curves, usually called FCurves.

We give the take node the name that we set in Naming the take which will contain the animation.

    pCamera->CreateTakeNode(pTakeName.Buffer());

A scene can have many takes. Only one of these takes can be the current take. CubeCreator’s scene has only one take, but we explicitly set it as the current take.

    pCamera->SetCurrentTakeNode(pTakeName.Buffer());

The next statement does the following:

    pCamera->LclTranslation.GetKFCurveNode(true, pTakeName.Buffer());

GetKFCurveNode may not be an obvious name for a method to create FCurves, but if true is its first parameter, it will create a set of FCurves if the node (in this case, pCamera) does not already have FCurves. For local translation, the default values for the X, Y, and Z channels are 0, 0, and 0.

There are three FCurves each for translation: one for translation along the X-axis, one for translation along the Y-axis, and one for translation along the Z-axis. Similarly, there are three FCurves each for rotation and scaling.

Here are some defined symbols from kfcurvenode.h (located in \include\fbxfilessdk\components\kfcurve\):

    #define KFCURVENODE_TRANSFORM "Transform"
    #define KFCURVENODE_T "T"
    #define KFCURVENODE_T_X "X"
    #define KFCURVENODE_T_Y "Y"
    #define KFCURVENODE_T_Z "Z"
    #define KFCURVENODE_R "R"
    #define KFCURVENODE_R_X "X"
    #define KFCURVENODE_R_Y "Y"
    #define KFCURVENODE_R_Z "Z"
    #define KFCURVENODE_R_W "W"
    #define KFCURVENODE_S "S"
    #define KFCURVENODE_S_X "X"
    #define KFCURVENODE_S_Y "Y"
    #define KFCURVENODE_S_Z "Z"

Here is how AnimateCamera() sets up the animation curve for translation along the X-axis. We start by getting the animation curve with its default values:

     // X translation.
     lCurve = pCamera->LclTranslation.GetKFCurve(KFCURVENODE_T_X, pTakeName.Buffer());
     if (lCurve)
     {

The animation begins immediately (at 0.0 seconds), and ends at 20.0 seconds: 0.0 and 20.0 are keyframe values.

During that time, any node that is animated by this curve (in this case, the camera node) will move from its initial displacement along the X-axis (0.0 units, the third parameter of Keyset() ), to its final displacement (500.0 units).

The velocity of the displacement will be the same at the beginning of the animation as at the end (KFCURVE_INTERPOLATION_LINEAR).

     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, 500.0);
     lCurve->KeyModifyEnd();
 }

The code is similar for translation along the Y-axis. There is no code for the Z-axis: accordingly, the camera will not move along the Z-axis. Nor is there rotation or scaling for the Z-axis.

Build the initial scene graph

CubeCreator builds a scene with only two nodes, the camera and the marker. These nodes are both direct children of the scene’s root node.

    // build a minimum scene graph
    KFbxNode* lRootNode = gScene->GetRootNode();
    lRootNode->AddChild(lMarker);
    lRootNode->AddChild(lCamera);

CubeCreator’s UI will display this tree to the user, then allow the user to add cubes to it.

Set the camera as the scene’s default camera

Since a scene can have many cameras, FBX SDK allows you to switch from camera to camera. The default camera is the camera that will be used when the scene’s animation begins.

Even though CubeCreator’s scene has only one camera, we must explicitly set it as the default camera:

    // set camera switcher as the default camera
    gScene->GetGlobalCameraSettings().SetDefaultCamera((char *)lCamera->GetName());