SGX Runtime API

The SGX Runtime API is a thread-safe C API used to load optimized in-game files created by the SGX Runtime Compiler.

The SGX Runtime is organized in the facefx directory. The API is defined in a single header file, facefx.h, found inside the root directory. Any static or dynamic link libraries are found in the bin directory, organized into subdirectories for the various target platforms (e.g. facefx/bin/windows or facefx/bin/macos). Additionally, the directories are further organized based on Debug or Release configuration, and, sometimes, as in the case for Windows, by compiler version (e.g. facefx/bin/windows/vs17/Release/x64). For platform support, please see SGX Runtime Platform Support.

In this section you will learn the basics of the Runtime API such as loading resources, playing animations, and processing animation frames. Before we get started, it is important to note that the Runtime API does not load files directly; instead, it relies solely on memory buffers. Not every API function is documented here. Comprehensive documentation for the Runtime API can be found in the manual, located at facefx/doc/pdf/manual.pdf, and in the API reference.

Error Handling

The Runtime uses a simple system for handling errors. Every API function returns an FxResult value that indicates if an error occurred or if the function completed successfully. All negative values are errors while positive values indicate success (but may also indicate a warning). FX_SUCCEEDED is a simple convenience macro for checking if an FxResult value indicates success. The FxResult values relevant for the SGX Runtime are:

FX_SUCCESS

The operation succeeded without error.

FX_ERROR_INVALID_ARGUMENT

One or more arguments were invalid.

FX_ERROR_DATA

The supplied data is either invalid or not Runtime data.

FX_ERROR_INCOMPATIBLE_VERSION

The version specified in the data block does not match the current Runtime version. You will need to recompile the source files to get up-to-date data formats.

FX_ERROR_INCOMPATIBLE_TYPE

The data type specified in the data block does not match the type of data that the Runtime expects.

You can get this error if you try to create a handle to a memory block that contains data of another type. For example, if you have a memory block containing animation data, the Runtime will not let you create a bone set or an actor handle to that data.

FX_ERROR_SIZE

The data size specified in the data block does not match the size of the data that was passed into the Runtime.

Each data block knows its own size, so if you pass a data block into the Runtime and specify and incorrect size, you will see this error.

FX_ERROR_RANGE

One or more arguments to a Runtime function were not within the permitted range.

FX_ERROR_VALIDATION_FAILED

The 32-bit CRC checksum stored in the data block does not match the computed 32-bit CRC value for the data block.

Each data block stores a 32-bit CRC checksum to detect corruption. If you create a handle to a data block, specify that you would like to run an integrity check, and the computed 32-bit CRC does not match the stored checksum, you will see this error. If you see this error, you have some form of memory or file corruption. You can also see this error in Debug mode if you happen to free or write over the data memory while it is in use.

FX_ERROR_INCOMPATIBLE_HANDLE

One or more of the supplied handles are not compatible with each other. To fully understand this error, you need to know a bit more about Runtime handles. Please be sure to read the Handles section, which goes into more detail about what this error indicates.

FX_ERROR_ZOMBIE_HANDLE

The handle is currently in the process of being destroyed and cannot be used.

If you destroy a handle in thread 1, the handle is marked as pending destruction and cannot be used in thread 2. You’ll see this error if some other thread has marked the handle as pending, or in the process of, destruction and you attempt to use the handle.

FX_ERROR_NOT_PERMITTED

The attempted operation is not permitted. For example, there are no free animation channels to satisfy the animation playback request, or a handle is currently in use in another thread and cannot be destroyed.

FX_ERROR_UNKNOWN

An unknown internal error. If you see this, please contact us.

Memory Management

Every API function that will internally allocate memory takes a memory allocation callbacks object (sometimes referred to as a memory context) that defines how memory is allocated and freed. When a handle is created in the Runtime, it carries its memory context with it throughout its lifetime, and that context is used every time the handle needs to allocate or free memory. The API functions for handle destruction do not take a memory context object because they used the context that was cached during creation. You do not have to use the same memory context for every handle. You can give each character its own memory context and use that context for all handles related to the character; or, you could have a context for each level or cinematic – you’re free to organize your memory allocation any way you like. Here is the definition of the memory allocation callbacks structure:

C
typedef struct FxAllocationCallbacks {
  PFN_fxAllocationFunction  pfnAllocation;
  PFN_fxFreeFunction        pfnFree;
  void                     *pUserData;
} FxAllocationCallbacks;

pfnAllocation and pFnFree are function pointers that perform memory allocation and deallocation operations, respectively:

C
typedef void *(FXAPI_CALL *PFN_fxAllocationFunction)(
  size_t  size,
  size_t  alignment,
  void   *pUserData
);

typedef void (FXAPI_CALL *PFN_fxFreeFunction)(
  void   *pMemory,
  size_t  alignment,
  void   *pUserData
);

PFN_fxAllocationFunction functions take a size_t parameter that specifies the number of bytes to allocate and a size_t parameter that specifies the requested alignment of the memory to allocate, a void* user data pointer, and return the allocated memory with the requested alignment. A value of 0 for alignment indicates that the alignment is unspecified – use the system default or your memory allocator’s default alignment. Any other value will specify an alignment.

PFN_fxFreeFunction functions take a void* pointer to the memory to free, a size_t parameter that specifies the alignment of the memory to free, a void* user data pointer, and return nothing. The alignment parameter obeys the same rules as specified for PFN_fxAllocationFunction functions.

pUserData is a void* pointer that is passed into every call to pfnAllocation and pfnFree. If you have no need for this facility, you can simply ignore it.

Strings

The Runtime does not use strings internally. Instead, during data compilation, all strings are turned into 64-bit identifier values. It can be useful to generate a 64-bit identifier from a string when using the Runtime. fxCreateIds() does just that:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxCreateIds(
  const char **pStrings,
  uint64_t    *pIds,
  size_t       count
);

fxCreateIds() creates Runtime ids from an array of NULL terminated strings. pStrings is the array of strings from which to create the ids. It must not be NULL and all strings must be NULL terminated. pIds is an array of uint64_t that is populated with the created is. It must not be NULL and must be the same length as pStrings. count is the length of the pStrings and pIds arrays.

You can use this function to create ids for the tracks or bones you may need, which avoids string comparisons. You can also instruct the compiler to create an .ffxids file which contains a list of all strings and their ids that the compiler generated, which is useful for debugging or debug displays.

Handles

Handles are opaque pointers to Runtime data and, with the exception of animation handles, mutable state. Handles form the backbone of the Runtime. There are four types of handles: bone sets, actors, animations, and frame states.

The most important piece of information you need to know about using handles in the Runtime is that you cannot mix handles to unrelated data – the Runtime prevents this. When you compile a source file, its compiled data is broken into blocks of data that you can load and create handles to: an actor, a bone set, and animations. Each source file will mark its data blocks with a globally unique identifier (GUID), and the Runtime enforces that all handles used together have matching GUIDs. This GUID uniquely identifies the face graph structure used during compilation, meaning a bone set, an actor, and animations only work together if they were compiled from the same source file or from a source file containing an identical face graph. To learn more about the compilation process, please refer to the Compiler section.

Handle Creation and Destruction

Every handle has a creation function and a destruction function, and these functions follow a pattern, regardless of the handle type:

C
FXAPI_ATTR
FxResult
FXAPI_CALL
fxHandleNameCreate(
  const void*                  pData,
  size_t                       dataSize,
  FxBoolean                    bValidateData,
  HandleType*                  pHandle,
  const FxAllocationCallbacks* pAllocator
);

FXAPI_ATTR
FxResult
FXAPI_CALL
fxHandleNameDestroy(
  HandleType*  pHandle,
  const void** ppData,
  size_t*      pDataSize
);

In the above example, HandleName and HandleType are placeholders for the name and type of the handle. For example, in the case of animation handles, Animation and FxAnimation.

When creating a handle, the Runtime needs a pointer to a constant memory buffer that contains the raw bytes of the data as well as the size of that memory buffer. It is very important to note that this memory buffer must remain valid throughout the handle’s lifetime. Once you give the Runtime a memory buffer, you must not modify or free that buffer until after you have destroyed the handle to it. You are actually creating a handle to that particular memory buffer.

Handle creation also takes a bValidateData flag that, if non-zero, will instruct the Runtime to perform a 32-bit CRC check on the supplied memory buffer. For your convenience, there are two defines to make this parameter more readable in your code:

  • FX_DATA_VALIDATION_ON

  • FX_DATA_VALIDATION_OFF

If there are any options that are specific to a particular handle type, they will always be specified after the bValidateData parameter.

Finally, handle creation needs a pointer to type HandleType that will receive the newly created handle. In addition, it needs to know about the allocator to be used to create the handle and any subsequent memory allocations the handle may require.

The only required parameter to handle destruction is a pointer to the handle to be destroyed. Optionally, the handle destruction API can pass back to you a pointer to the original memory buffer that was passed into handle creation, along with its size. The last two parameters can be NULL if you have been keeping track of the memory buffer yourself.

Once the handle has been successfully destroyed, you are then allowed to free the memory buffer you passed into the Runtime when you originally created the handle.

Handle Types

There are several types of handles in the Runtime:

  • Actor

  • Bone Set

  • Animation

  • Frame State

Actor, Bone Set, and Animation handles will be briefly covered here while Frame State handles will be covered in the Playback section below. More detailed coverage can be found in the manual.

Actor

Actor handles contain all of the animation target definitions for the character. An animation target definition corresponds to a “target node” in the original face graph; that is, a morph target node, bone pose node, material parameter node, or generic target node. Other node types, such as combiner nodes, and links are not present.

The definition of the actor handle creation function is:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxActorCreate(
  const void                  *pData,
  size_t                       dataSize,
  FxBoolean                    bValidateData,
  size_t                       channelCount,
  FxActor                     *pActor,
  const FxAllocationCallbacks *pAllocator
);

Notice the fourth parameter to fxActorCreate(). This is the channelCount and specifies the number of animation channels you would like the actor handle to contain. This value must be greater than zero, but it will likely be rare for you to need more than one animation channel.

Destruction is trivial and follows the pattern outlined in the Handle Creation and Destruction section above.

The most common operation on actor handles is retrieving the actor’s tracks:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxActorGetTracks(
  FxActor   actor,
  uint64_t *pTracks,
  size_t   *pTrackCount
);

actor is the actor handle, pTracks is an array of uint64_t that will hold the retrieved track ids, and pTrackCount is a pointer to the number of elements contained in the pTracks array. If pTracks is NULL, pTrackCount is set to the number of tracks contained in actor; otherwise, it must be equal to the number of tracks contained in actor. pTrackCount must not be NULL.

It’s also possible to get the name of the actor (in the form of an id). For SGX, this will be the name of the .k file (e.g. for Aiko.k, the name will be Aiko):

C
FXAPI_ATTR FxResult FXAPI_CALL
fxActorGetId(
  FxActor   actor,
  uint64_t *pId
);

Bone Set

Bone set handles contain all the information pertaining to a character’s bones. This includes the rest pose and all bone poses. Bone set data is stored separately from the actor so that an actor can make use of more than one bone set.

The definition of the bone set handle creation function is:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxBoneSetCreate(
  const void                  *pData,
  size_t                       dataSize,
  FxBoolean                    bValidateData,
  FxBoneSetFlags               flags,
  FxBoneSet                   *pBoneSet,
  const FxAllocationCallbacks *pAllocator
);

Destruction is trivial and follows the pattern outlined in the Handle Creation and Destruction section above.

Notice the fourth parameter to fxBoneSetCreate(). This is a special parameter required during bone set handle creation that tells the Runtime what type of bone transforms we would like to use with the bone set. To tell the Runtime to use full transforms, pass FX_BONESET_FULL_XFORMS. Full bone transforms are just that: full bone transforms, including the rest pose component. This is the most common case, but you can also pass FX_BONESET_OFFSET_XFORMS_BIT to tell the Runtime you would like to use offset transforms. Offset bone transforms are bone transforms without the rest pose component (i.e. they are offsets from the rest pose). You cannot change the type of bone transforms after the bone set handle has been created – all operations on that handle will always use the specified bone transform type.

The most common operation on bone set handles is retrieving the bone set’s bones:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxBoneSetGetBones(
  FxBoneSet  boneSet,
  uint64_t  *pBones,
  size_t    *pBoneCount
);

boneSet is the bone set handle, pBones is an array of uint64_t that will hold the retrieved bone ids, and pBoneCount is a pointer to the number of elements contained in the pBones array. If pBones is NULL, pBoneCount is set to the number of bones contained in boneSet; otherwise, it must be equal to the number of bones contained in boneSet. pBoneCount must not be NULL.

Animation

Animation handles contain all of the animation keys and events for a particular animation. Animation handles are read only and allocate no other memory during creation; the only memory taken by an animation handle is the memory used for its memory buffer.

The definition of the animation handle creation function is:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxAnimationCreate(
  const void                  *pData,
  size_t                       dataSize,
  FxBoolean                    bValidateData,
  FxAnimation                 *pAnimation,
  const FxAllocationCallbacks *pAllocator
);

Destruction is trivial and follows the pattern outlined in the Handle Creation and Destruction section above.

Animations in the Runtime are used only for playback – they are read only memory buffers that simply contain the animation data for playback. As such, you really do not need to know anything about what is inside an animation’s memory buffer; hence, the lack of query API functions for animations. However, there are times when knowing the start and end times of an animation, and thus its duration, is of use:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxAnimationGetBounds(
  FxAnimation  animation,
  float       *pAnimationStartTime,
  float       *pAnimationEndTime
);

fxAnimationGetBounds() will fill out pAnimationStartTime and pAnimationEndTime with the animation’s start and end time. These times are always returned in seconds.

It is important to note that the start time retrieved from the animation may actually be negative. When audio starts very soon in the audio file, the animation generation algorithms can place keys before “time zero.” It takes some measurable time for the lips to get into position to make the appropriate sound, and if the sound starts immediately, there will be some “preamble” section of the animation with negative time keys. This is why FX_CHANNEL_START_AUDIO_BIT exists. The Runtime will begin playing the animation – including this “preamble” – and then inform you when the audio should start. See the Playback section below for more details.

Animations have a name in the Runtime (stored as an id). It’s possible to retrieve the animation’s name with the fxAnimationGetId() API:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxAnimationGetId(
  FxAnimation  animation,
  uint64_t    *pId
);

Unlike an actor’s id, an animation’s id does not directly correspond to the animation’s file name (e.g. Example.event will not automatically result in the id Example). Instead, an animation’s id corresponds to the concatenation of the animation’s containing group name and the actual animation name, with a forward slash ( / ) separating the two. For SGX, the animation’s group name comes from the containing folder during compilation. That is, the directory passed via --events-dir. If --recurse is used and subdirectories contain .event files, that structure is preserved in the animation names. The exception is that .event files located in the “root” of the events directory go into the Default animation group.

To understand how this naming scheme works, consider a directory structure such as:

  • C:\Game\Hero\SGX_Events\Opening.event

  • C:\Game\Hero\SGX_Events\LevelOne\Intro.event

  • C:\Game\Hero\SGX_Events\LevelOne\Cutscene\Monologue.event

If you invoked the compiler with C:\Game\Hero\SGX_Events as --events-dir and used the --recurse flag, all three events will be compiled: Opening.event, Intro.event, and Monologue.event. Opening would be placed in the Default group since it is at the root of the tree, Intro would be placed in the LevelOne group, and Monologue would be placed in the LevelOne/Cutscene group. The animation names would therefore be:

  • Default/Opening

  • LevelOne/Intro

  • LevelOne/Cutscene/Monologue

Note that in the LevelOne/Cutscene/Monologuecase, the compiler output would be Hero/LevelOne_ASCII47_Cutscene/Monologue.ffxanim and the invalid-chars warning will be issued. For more details on how the compiler organizes its output, please see the Compiler chapter.

Handle Compatibility

As detailed in the Handles section above, it is forbidden to mix incompatible handles. If you need to check for compatibility prior to using the handles, the Runtime API provides several functions to facilitate such checks:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxActorCheckCompatibilityWithBoneSet(
  FxActor   actor,
  FxBoneSet boneSet
);

FXAPI_ATTR FxResult FXAPI_CALL
fxActorCheckCompatibilityWithAnimation(
  FxActor     actor,
  FxAnimation animation
);

FXAPI_ATTR FxResult FXAPI_CALL
fxActorCheckCompatibilityWithFrameState(
  FxActor      actor,
  FxFrameState frameState
);

These functions check the given actor and handle pairs for compatibility. If the handles are compatible, no error is returned.

Playback

The animation playback system in the Runtime is very simple to use. It consists of only a few functions for play, stop, pause, and resume.

Play

To play an animation, you simply need a handle to the animation and a handle to the actor on which to play it:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxActorPlayAnimation(
  FxActor      actor,
  FxAnimation  animation,
  size_t      *pChannelIndex
);

pChannelIndex is a pointer to a size_t that specifies the channel to play on and that will receive the channel index that animation was assigned in actor. If it is not NULL the animation will attempt to play on the specified channel if that channel is free. If it is not NULL and the value is set to FX_CHANNEL_ANY the animation will be played on the first free channel. You can pass NULL if you don’t care about channels or only have one channel. Passing NULL has the same behaviour as FX_CHANNEL_ANY. pChannelIndex is only modified if it is not NULL and the function succeeds.

Pause

To pause animation playback in the Runtime, you need to specify the actor handle on which animation playback is to be paused, as well as the current time, in seconds. The current time should be the current frame time; that is, the time you pass when processing frames. This parameter is used so that when animation playback is resumed the Runtime knows how long the animation was paused and can adjust its internal offsets:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxActorPauseAnimation(
  FxActor actor,
  float   currentTime
);

Resume

To resume animation playback in the Runtime, you need to specify the actor handle on which animation playback is to be resumed, as well as the current time, in seconds. The current time should be the current frame time; that is, the time you pass when processing frames. This parameter is used so that the Runtime knows how long the animation was paused and can adjust its internal offsets:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxActorResumeAnimation(
  FxActor actor,
  float   currentTime
);

Stop

To stop animation playback, you simply need a handle to the actor on which to stop the animation:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxActorStopAnimation(
  FxActor actor,
  size_t  channelIndex
);

To stop all animation playback on the actor, fxActorStopAnimation() needs the actor handle and FX_CHANNEL_ANY passed as the channelIndex parameter. To stop animation playback on a specific channel, pass a valid channel number for channelIndex.

Frame States

Frame states are objects that contain per frame state related to an actor. An actor’s frame state is contained in an FxFrameStatehandle.

In order to process frames on an actor and pull out results, you will need a frame state object, and each actor should have its own frame state object. It is important to note that you should not be creating and destroying these frame state objects every frame. Instead, you should create the frame state for an actor when you create the actor handle, and you should destroy it along with the actor handle.

To create a frame state handle, use fxFrameStateCreate() and pass the actor handle:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxFrameStateCreate(
  FxActor                      actor,
  FxFrameState                *pFrameState,
  const FxAllocationCallbacks *pAllocator
);

To destroy a frame state handle, simply use fxFrameStateDestroy():

C
FXAPI_ATTR FxResult FXAPI_CALL
fxFrameStateDestroy(
  FxFrameState *pFrameState
);

Processing Frames

For every actor handle in your game, you need to tell the Runtime to process the current frame, for every frame in your game. Processing a frame ensures that any playing animations are updated and that the actor state is fully updated for that frame. Processing a frame is a single function call. There are four such function calls, each doing something slightly different. This document will focus on the most common frame processing function you will need. The other more obscure functions are fully covered in the manual. You should only process a given frame once – do not process the same frame multiple times, and especially not in different ways.

The frame processing functions take a currentTime parameter. This is the current game time of the game engine, in seconds. This game time should always be increasing from frame to frame; that is, time is always marching forward. This time has nothing to do with any currently playing animation. It is the global game engine time. This parameter is needed so that the Runtime knows how much time has elapsed since the last frame and can update its animations – and its own understanding of time – accordingly.

“Normal” frames are by far the most common type of frame you will process. For the vast majority of users, this will be the only type of frame processing you will have to perform:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxActorProcessFrame(
  FxActor      actor,
  FxFrameState frameState,
  float        currentTime
);

To process a normal frame, you simply need the actor handle to process, the actor’s frame state handle, and the current game time, in seconds. Once fxActorProcessFrame() completes successfully, frameStatewill be updated for the current frame and can be used with the other frame update and query functions.

Retrieving a frame’s channel flags

After the current frame has been processed, it is time to start retrieving information from the frame state. One of the most important pieces of information to retrieve is the frame’s channel flags. Channel flags are integers, one per channel contains in the actor, that contain the state of each channel for the current frame.

These channel flags will tell you if the channel is active for the current frame, if it is paused, if you should start the audio for an animation on the current frame, or if the animation just stopped this frame:

C
typedef enum FxChannelFlagBits {
  FX_CHANNEL_INACTIVE        = 0,
  FX_CHANNEL_ACTIVE_BIT      = 0x00000001,
  FX_CHANNEL_PLAYING_BIT     = 0x00000002,
  FX_CHANNEL_PAUSED_BIT      = 0x00000004,
  FX_CHANNEL_START_AUDIO_BIT = 0x00000008,
  FX_CHANNEL_EVENT_FIRED_BIT = 0x00000010,
  FX_CHANNEL_FINISHED_BIT    = 0x00000020
} FxChannelFlagBits;

FX_CHANNEL_ACTIVE_BIT

When this bit is set it indicates that the channel is currently active and has an animation in the channel.

FX_CHANNEL_PLAYING_BIT

This bit is set only when the active bit is set. It indicates that the animation in the channel is currently playing (not paused).

FX_CHANNEL_PAUSED_BIT

This bit is set only when the active bit is set. It indicates that the animation in the channel is currently paused.

FX_CHANNEL_START_AUDIO_BIT

This bit is set only when the active bit is set. It indicates that the animation playing in the channel is requesting that any audio that corresponds to it be started this frame. You should not start the audio for an animation as soon as you start the animation playing because there are likely “negative time” keys in the animation (keys whose time is less than zero seconds). This is because the face needs a certain amount of time to get into position before emitting sound. The Runtime treats time zero seconds as the de factor time that sound starts emitting. Therefore, once the animation has reached time zero seconds, it will indicate to you via this bit that the audio should be started.

FX_CHANNEL_EVENT_FIRED_BIT

This bit is set only when the active bit is set. It indicates that the animation playing in the channel has fired events on this frame. This is currently unused in the SGX context.

FX_CHANNEL_FINISHED_BIT

This be is set only when the active bit is set. It indicates that the animation in the channel has just finished playing on this frame.

To read the channel flags for the current frame, call fxFrameStateGetChannelFlags():

C
FXAPI_ATTR FxResult FXAPI_CALL
fxFrameStateGetChannelFlags(
  FxFrameState    frameState,
  FxChannelFlags *pFlags,
  size_t          flagCount
);

fxFrameStateGetChannelFlags() consumes data computed for the current frame, so it should be called after the current frame has been processed. pFlags is an array of FxChannelFlags that will hold the channel flags, and it must not be NULL. flagCount is the number of elements contained in the pFlags array and it must be equal to the number of channels in frameState.

Each animation channel will report its current status as an FxChannelFlags in the pFlags array. A value of FX_CHANNEL_INACTIVE indicates that the channel is inactive this frame (no animation is currently in the channel). If the channel is active the FX_CHANNEL_ACTIVE_BIT bit will be set. Multiple flags may be set simultaneously. For example, usually the FX_CHANNEL_PLAYING_BIT bit is set along with the FX_CHANNEL_ACTIVE_BIT bit. When the animation request that its audio be started this frame, the following bits will be set: FX_CHANNEL_ACTIVE_BIT, FX_CHANNEL_PLAYING_BIT, and FX_CHANNEL_START_AUDIO_BIT. If the animation finished playing on this frame the FX_CHANNEL_ACTIVE_BIT and FX_CHANNEL_FINISHED_BIT bits will be set.

Retrieving a frame’s track values

In certain cases, you may be interested in a particular track’s value. For example, you may need to control a morph target or material parameter property in your game engine. Retrieving track values from a frame state is easy:

C
FXAPI_ATTR FxResult FXAPI_CALL
fxFrameStateGetTrackValues(
  FxFrameState  frameState,
  float        *pValues,
  size_t        valueCount
);

fxFrameStateGetTrackValues() consumes data computed for the current frame, so it should be called after the current frame has been processed. pValues is an array of float that will hold the requested track values, and it must not be NULL. It must also contain the same number of elements as there are tracks in the actor. valueCount is the number of elements contained in the pValues array.

Calculating and applying a frame’s bone transforms

If you are using bones for facial animation, you now need to instruct the Runtime to use the current frame state to calculate the current frame’s bone transforms. A bone transform is defined as:

C
typedef struct FxBoneTransform {
  FxVector3    translation;
  FxQuaternion rotation;
  FxVector3    scale;
} FxBoneTransform;

An FxBoneTransform is made up of three-dimensional vector translation and scale components (x, y, z) and a quaternion rotation component. Take particular note of the ordering of the components of the quaternion. The w component is first, giving the ordering (w, x, y, z), which m ay not match your own engine’s ordering:

C
typedef struct FxVector3 {
  float x;
  float y;
  float z;
} FxVector3;

typedef struct FxQuaternion {
  float w;
  float x;
  float y;
  float z;
} FxQuaternion;

Each bone transform is given in parent space, meaning that each bone’s transform is relative to its parent bone. Due to legacy FaceFX backwards compatibility reasons, however, the w component of the quaternion rotation is negated from the animation package’s value (so if, for example, Maya has w set to 1.0, the Runtime will have w set to -1.0).

To calculate the current frame’s bone transforms, we need to use fxFrameStateComputeBoneTransforms():

C
FXAPI_ATTR FxResult FXAPI_CALL
fxFrameStateComputeBoneTransforms(
  FxBoneSet        boneSet,
  FxFrameState     frameState,
  FxBoneTransform *pTransforms,
  size_t           transformCount
);

Pass the bone set handle, the frame state handle, and an array of FxBoneTransform, along with the number of elements in that array. It is important to note that the array must contain the same number of elements as there are bones in the bone set handle, otherwise the function call will fail.

Once the frame’s bone transforms have been calculated you can apply them to your character’s skeleton. How you do that is dependent on your game engine and how it deals with modifying skeletons. The important information to remember is that the bone transforms are in parent space, and the quaternion’s w component has been negated.

One last note on bone transforms: the calculated bone transforms depend on the value you passed for the flags parameter in your original call to fxBoneSetCreate(). If you passed FX_BONESET_FULL_XFORMS then these calculated bone transforms will include the rest pose’s contribution. If you passed FX_BONESET_OFFSET_XFORMS_BIT, they will not contain any contribution from the rest pose.

Retrieving a frame’s channel times

You may also be interested in a channel’s current playback time. Channel times are float (reported in seconds), one per channel contained in the actor handle, that contain the current playback time of each channel for the current frame.

It is important to note that only active channels will have a valid channel time. If the channel’s flag has the active bit set, then the corresponding channel time will be valid and within the range specified by the currently playing animation’s bounds.

Get the channel times with a call to fxFrameStateGetChannelTimes():

C
FXAPI_ATTR FxResult FXAPI_CALL
fxFrameStateGetChannelTimes(
  FxFrameState  frameState,
  float        *pTimes,
  size_t        timesCount
);