Animation in HOOPS Publish

HOOPS Publish is able to export key frame animation in 3D PDF documents. This feature allows you to use JavaScript to direct the animation using familiar controls. Attributes of the geometric model which can be animated are appearance, transform, and camera. If you are already familiar with building 3D PDF documents programmatically, you should be able to get running quickly as animation is built into the Artwork Publish object.

In order to organize complex animations efficiently, the animation interface is broken into a hierarchy:

Note the objects with the [array] tag. This indicates these objects are inserted as arrays of multiple objects in order to support complex, multi-layered animations.

Overview

At the lowest level, the animation interface is built around one main data structure, A3DPDFAnimKeyFrameData. This structure represents the state of the model at a specific key frame. The idea is to build multiple A3DPDFAnimKeyFrameData structures and order them sequentially, which allows HOOPS Publish to interpolate the animated object between key frames. You should adjust the parameters of each key frame data structure so it describes the model at that frame. You can combine the attributes of appearance, transform, and camera into the same key frame.

Key frames are collected to form a key frame array. The array is then used as a component of a motion data object. The motion object, A3DPDFAnimMotion, defines movement on a set of target objects. If you have a scene with multiple animated objects, you need to create a corresponding motion object for each of them.

All motion objects are then collected to form an animation object. Animation objects also encapsulate other high-level parameters such as number of frames per second. Finally, the animation object is bound to the Publish Artwork object.

JavaScript

Although the HOOPS Publish interface language is C++, all generated documents are able to store embedded JavaScript. The purpose of JavaScript in this context is to interact with the model and control the animation. The procedure is roughly as follows:

  1. User begins creating a 3D PDF, possibly containing JavaScript logic for interactive widgets such as buttons
  2. As part of the Artwork, user creates an animation for the PDF using C++
  3. Upon generating the document, HOOPS Publish automatically translates the C++ instructions and embeds them in the document as additional JavaScript
  4. User may inspect or edit the JavaScript before the 3D PDF is finalized (see reference manual entry for A3DPDF3DArtworkEditAnimationJavascript)

Further details about using JavaScript are provided after the first example.

Building an animation

Perhaps the best way to learn about the implementation details of the Publish animation API is to inspect a few examples. There are three examples in this guide:

  1. Moving the camera
  2. Transformations
  3. Adjusting appearance

In the first example, we will demonstrate how to move the camera around a model in order to create an orbiting effect. As noted earlier, Publish animations are hierarchical and are typically built from the bottom up. Therefore, in step 1, we will start by building the key frames.

Example 1 - Moving the camera

This example will create an animation based on two key frames. There are two parts to each key frame - the key frame object itself, as well as its associated data (in this case, camera position). The data for each key frame is stored in an object of the type A3DPDFAnimKeyFrameData while the encapsulating object is of type A3DPDFAnimKeyFrame.

Step 1: Create the basic structures

// we will create an animation based on two key frames
// so, two A3DPDFAnimKeyFrames are allocated
A3DPDFAnimKeyFrame *pKeyFrame[2];
// each key frame will have data associated with it
// an A3DPDFAnimKeyFrameData object holds that data
A3DPDFAnimKeyFrameData sKeyFrameData;
// initialize key frame objects

Step 2: Set up the data for the first key frame

In order for the animation API to work, you must define the properties for each key frame. The following code snippet sets up the starting position and orientation of the camera.

// key frame time seconds
sKeyFrameData.m_dTime=0;
// this will be a camera interpolation, so we set the mask accordingly
// here, we use the camera data structure to set
// the position and orientation of the camera
// for the first key frame
sCameraData.m_sPosition.m_dX=100;
sCameraData.m_sPosition.m_dY=0;
sCameraData.m_sPosition.m_dZ=0;
sCameraData.m_sTarget.m_dX=0;
sCameraData.m_sTarget.m_dY=0;
sCameraData.m_sTarget.m_dZ=0;
// compute the up vector (not a Publish function)
ComputeUpVector(sCameraData.m_sPosition, sCameraData.m_sTarget, sCameraData.m_sUpVector, 0);
sCameraData.m_dFieldOfView=30;
sCameraData.m_dZoomFactor=0.001;
// bind the new camera data structure to the key frame's camera
sKeyFrameData.m_psCameraData=&sCameraData;

Now that all data for the first key frame is created, we need to bind it to the animation framework. This is done by calling A3DPDFAnimKeyFrameCreate. Notice that we are binding it to the 0th element of the key frame array. Change this as needed for additional key frames.

A3DPDFAnimKeyFrameCreate(&sKeyFrameData, &pKeyFrame[0]);

Step 3: Set up the data for the second key frame

In the second key frame, the camera position and zoom changes, but the target remains the same. The objects used here are identical to the previous step, but the field values are changed to reflect the desired key frame.

sKeyFrameData.m_dTime=4.5;
sCameraData.m_sPosition.m_dX=0;
sCameraData.m_sPosition.m_dY=100;
sCameraData.m_sPosition.m_dZ=0;
sCameraData.m_sTarget.m_dX=0;
sCameraData.m_sTarget.m_dY=0;
sCameraData.m_sTarget.m_dZ=0;
stComputeUpVector(sCameraData.m_sPosition, sCameraData.m_sTarget, sCameraData.m_sUpVector, 0);
sCameraData.m_dFieldOfView=35;
sCameraData.m_dZoomFactor=0.0001;
// bind this camera data to the second key frame
A3DPDFAnimKeyFrameCreate(&sKeyFrameData, &pKeyFrame[1]);

Step 4: Set up the motion parameters

Now that the key frames are created, you must next assign the motion parameters. These parameters control things like the number of key frames to expect, whether or not there is a starting delay, or whether the animation will repeat. This example uses two key frames, loops, and will start immediately.

sMotionData.m_bRepeat=true; // loop the animation
sMotionData.m_dTimeOffset=0; // start animation at time 0
sMotionData.m_iNumKeyFrames=2; // 2 key frames will be used
sMotionData.m_ppKeyFrames=pKeyFrame; // key frame data
sMotionData.m_iNumTargets=0; // see note below
// now, create the motion object using the parameters set above
A3DPDFAnimMotionCreate2(&sMotionData, &pMotion);

NOTE: The motion target is the object that will be animated. In this case, we are animating only the camera. The camera is not part of the model file, so it is not addressable. Therefore, the number of targets is zero. The target field is used in the other animation types. See this subsection for more information.

Step 5: Build the animation object

The animation object, A3DPDFAnimation, is the final encapsulating object of an animation. This object controls number of frames per second and contains any motion objects from the previous step.

A3DPDFAnimationData sAnimationData;
sAnimationData.m_iFramesPerSecond=20;
sAnimationData.m_iNumAnimationMotions=1;
sAnimationData.m_pcName="My first animation";
sAnimationData.m_ppAnimationMotions=&pMotion;
A3DPDFAnimationCreate(&sAnimationData, &pAnim);

Step 6: Add the completed object to the Artwork

The animation object becomes part of the normal Artwork object used when exporting HOOPS Publish PDF documents.

A3DPDF3DArtwork* p3DArtwork;
A3DPDF3DArtworkData2 s3DArtworkData;
s3DArtworkData.m_pStream = pStream;
s3DArtworkData.m_bKeepNativeDefaultView=false;
s3DArtworkData.m_bUseRuntimeDisplaySettings=false;
s3DArtworkData.m_bActivatePMICrossHighlight=false;
s3DArtworkData.m_iNumberOfAnimations=1;
s3DArtworkData.m_ppAnimations=&pAnim;
A3DPDF3DArtworkCreate2(pDoc, &s3DArtworkData, &p3DArtwork);

Note on interpolations

The interpolation mask, sKeyFrameData.m_iInterpolationMask, lets HOOPS Publish know what type of interpolation your animation should use. In the last example, we set this field to kA3DPDFInterpolateCamera to indicate we were animating the camera. Other values allow you to interpolate the appearance or matrix (see examples 2 and 3, below).

If you do not want smooth interpolation, set the interpolation mask field to 0. This will force an abrupt transition at the next key frame.

Interacting with animations

Before continuing with further examples, we will spend some time discussing how to interact with the animation. The animations generated by HOOPS Publish can be controlled with JavaScript embedded within the PDF document. In most cases, the PDF page defines buttons to play and pause the animation. Other buttons may play a minimal portion of the animation, or just a subset of animations.

The primary functions that can be called externally are as follows:

playSequence(start, end, fncallbackStart, fncallbackStop)
Description: This function should be called to play the portion of the animation between the start time start and the ending time end. Use start=0 and end=-1 to play the entire animation.

Parameters: start - The time, relative to the animation, from which you would like the animation to begin playing. A value of 0 indicates the beginning of the animation.
end - The time, relative to the animation, from which you would like the animation to end.
fncallbackStart - A callback function that is called at the beginning of the sequence.
fncallbackStop - A callback function that is called at the end of the sequence.

Returns: none
restartSequence()
Description: This function should be called to reset the current sequence and restart it from the beginning. The current sequence is defined by the last call to playSequence.

Parameters: none<br>
Returns: none

playAnims(start, end, fncallbackStart, fncallbackStop)
Description: This function is defined the same way as playSequence, except it works at the animation level instead of with time. For example, if your Artwork contains multiple independent animations, you may use this function to play a subset of those animations. Animations are indexed between index start and end, inclusive.

Parameters: start - The animation from which you would like to begin playing. A value of 0 indicates the first animation.
end - The animation at which you would like to end.
fncallbackStart - A callback function that is called at the beginning of the sequence.
fncallbackStop - A callback function that is called at the end of the sequence.
Returns: none
restartAnims()
Description: This function should be called to reset the current sequence and restart it from the beginning. The current sequence is defined by the last call to playSequence.

Parameters: none<br>
Returns: none

These interactions are demonstrated in the package sample area - samples\publish\publishsource\AnimWorkinstruction.

Example 2 - Transformation

This example shows how to build the key frames for an animation which makes a cube turn on its z-axis. The process for building the animation is almost the same as the previous example. The main difference is setting up the key frame data - since we are dealing with a transformation instead of adjusting the camera position, we will adjust the model matrix.

First key frame

// we are going to interpolate the position to
// get a smooth transition between each keyframe
// build first key frame
stLoadIdentityMatrix(sTransformData.m_adMatrix);
double adQuat[4];
adQuat[0]=0.000000;
adQuat[1]=0.000000;
adQuat[2]=0.000000;
adQuat[3]=1.000000;
stQuaternionToMatrix(adQuat, sTransformData.m_adMatrix);
sKeyFrameData.m_psTransformationData=&sTransformData;
A3DPDFAnimKeyFrameCreate(&sKeyFrameData, &pKeyFrame[0]);

Second keyframe: rotation of 90 degrees around z

sKeyFrameData.m_dTime=2;
adQuat[2]=0.7071; // 90 degree rotation
adQuat[3]=0.7071;
stQuaternionToMatrix(adQuat, sTransformData.m_adMatrix);
A3DPDFAnimKeyFrameCreate(&sKeyFrameData, &pKeyFrame[1]);

Third keyframe: rotation of 90 degrees around z again, the result is a rotation of 180 degrees

sKeyFrameData.m_dTime=4;
double adRot[16];
stLoadIdentityMatrix(adRot);
stQuaternionToMatrix(adQuat, adRot);
stMatrixMatrixMult(sTransformData.m_adMatrix, adRot);
A3DPDFAnimKeyFrameCreate(&sKeyFrameData, &pKeyFrame[2]);

Fourth keyframe: rotation of 90 degrees around z again, the result is a rotation of 270 degrees

sKeyFrameData.m_dTime=6;
stMatrixMatrixMult(sTransformData.m_adMatrix, adRot);
sKeyFrameData.m_psTransformationData=&sTransformData;
A3DPDFAnimKeyFrameCreate(&sKeyFrameData, &pKeyFrame[3]);

Fifth keyframe: rotation of 90 degrees around z again, the result is a rotation of 360 degrees, back to the first position

sKeyFrameData.m_dTime=8;
stMatrixMatrixMult(sTransformData.m_adMatrix, adRot);
sKeyFrameData.m_psTransformationData=&sTransformData;
A3DPDFAnimKeyFrameCreate(&sKeyFrameData, &pKeyFrame[4]);

Motion object

Lastly, when building the motion object, be sure to account for all five key frames:

sMotionData.m_iNumKeyFrames=5;

For this type of animation, it is also necessary to specify which object will be animated. This object is called the target object, and it is specified by a PRC entity and its father product occurrence. If you don't already have a reference to these target data, you can use the function A3DPDFGetEntitiesFromName to traverse your model in order to find it. The target is specifed in the motion object.

sMotionData.m_ppTargets=&pTarget;
sMotionData.m_iNumTargets=1; // specify the number of targets

Example 3 - Adjusting appearance over time

In this example, we are going to create an animation which changes the color and transparency of the model.

// set the flags for the interpolating mask
// ...
A3DPDFAnimKeyFrame *pKeyFrame[6];
// first keyframe: the cube starts from red, and fully opaque
sAppearanceData.m_sColor.m_dRed=1;
sAppearanceData.m_sColor.m_dGreen=0;
sAppearanceData.m_sColor.m_dBlue=0;
sAppearanceData.m_dOpacity=1.;
sAppearanceData.m_eRenderingStyle=kA3DPDFRenderingSolid; // rendering style cannot be interpolated, it will be set at the time of the keyframe
sKeyFrameData.m_psAppearanceData=&sAppearanceData;
A3DPDFAnimKeyFrameCreate(&sKeyFrameData, &pKeyFrame[0]);
// second keyframe: the cube is turning to green, and becomes less opaque (or more transparent)
sKeyFrameData.m_dTime=2.1;
sAppearanceData.m_sColor.m_dRed=0;
sAppearanceData.m_sColor.m_dGreen=1;
sAppearanceData.m_sColor.m_dBlue=0;
sAppearanceData.m_dOpacity=0.8;
sKeyFrameData.m_psAppearanceData=&sAppearanceData;
A3DPDFAnimKeyFrameCreate(&sKeyFrameData, &pKeyFrame[1]);
// third keyframe: the cube is turning from green to blue, and becomes again less opaque (or more transparent)
sKeyFrameData.m_dTime=4.5;
sAppearanceData.m_sColor.m_dRed=0.;
sAppearanceData.m_sColor.m_dGreen=0;
sAppearanceData.m_sColor.m_dBlue=1;
sAppearanceData.m_dOpacity=0.6;
sKeyFrameData.m_psAppearanceData=&sAppearanceData;
A3DPDFAnimKeyFrameCreate(&sKeyFrameData, &pKeyFrame[2]);

Like the previous transformation example, you also need to specify the animation target in the motion object.

top_level:1 prog_guide:2