CreateScene()constructs a scene consisting of a marker, a camera pointed at the marker, and a light.
MethodNaming 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 2009.1, 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 current take name
gScene->SetCurrentTake(gTakeName.Buffer());
Adding animation to the camera.
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 inCreating 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.
CreateScene():
Here is the code in// create a marker
KFbxNode* lMarker = CreateMarker(gSdkManager, "Marker");
CreateMarker():
And here is the code in// 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;
}
CreateScene()callsCreateCamera()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 (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()callsCreateTexture()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 is in the application 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);
}
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.
crate.jpgin 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.
If you save the scene as a binary FBX file, CubeCreator embeds the texture fileIn 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.
SaveScene(), passingtrueas the value ofpEmbedMedia:
When the user clicks to Save to button, CubeCreator calls// to save a scene to a FBX file
bool SaveScene(KFbxSdkManager* pSdkManager, KFbxDocument* pScene, const char* pFilename, int pFileFormat, bool pEmbedMedia)
...
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 KFBXSTREAMOPT_FBX_EMBEDDED. The code below
// shows how to change these states.
lExportOptions->SetOption(KFBXSTREAMOPT_FBX_MATERIAL, true);
lExportOptions->SetOption(KFBXSTREAMOPT_FBX_TEXTURE, true);
lExportOptions->SetOption(KFBXSTREAMOPT_FBX_EMBEDDED, pEmbedMedia);
lExportOptions->SetOption(KFBXSTREAMOPT_FBX_MEDIA, 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()callsCreateMaterials()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
CreateMaterials(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()callsSetCameraPointOfInterest()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);
lMarkerandlCameraare 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 (ornull): 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 above the origin.
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 onetake nodefor 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.
Naming the take which will contain the animation.
We give the take node the name that we set inpCamera->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());
GetKFCurveNodemay not be an obvious name for a method to create FCurves, but iftrueis 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.
kfcurvenode.h(located in\include\kfcurve\):
Here are some defined symbols from#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"
AnimateCamera()sets up the animation curve for translation along the X-axis. We start by getting the animation curve with its default values:
Here is how// 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.
Keyset()), to its final displacement (500.0 units).
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 ofKFCURVE_INTERPOLATION_LINEAR).
The velocity of the displacement will be the same at the beginning of the animation as at the end (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.
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 as the default camera
gScene->GetGlobalCameraSettings().SetDefaultCamera(
(char *)lCamera->GetName());