Using Behaviors

This section describes Microsoft® DirectAnimation® behaviors and how to use them. The basic class in DirectAnimation is a behavior. See DirectAnimation Behaviors, Creating Behaviors, and Displaying Behaviors for an overview of behaviors.

The topic Reacting to Events describes reactive and non-reactive behaviors, events, picking, and all forms of the Until function.

The topic More Ways to Use Behaviors describes many useful ways to use behaviors, including reaction to events, sequencing, switching, time substitution, integrals and derivatives, and more.

This section contains the following topics.

DirectAnimation is composed of several tightly integrated technologies. Different media, such as two-dimensional (2-D) and three-dimensional (3-D) images, video, and audio can be used in a single application, and all are handled in much the same way. Once you learn how to manipulate one type of media (for example, images), you can apply the same techniques to other media types, such as geometries or sound, simplifying media integration.

DirectAnimation provides the following services.

DirectAnimation Behaviors

DirectAnimation supports a set of classes and functions (and Java classes and methods) that are used to construct time-varying, interactive behaviors.

DirectAnimation can perform sequences of operations on images, geometry, or sounds. One of the most important features of the media operations available in DirectAnimation is that animations are completely encapsulated (completely defined within DirectAnimation) and composable (the result of one operation is operated on by the next operation and so on). This provides flexibility, expressiveness, and power with a relatively small number of base operations.

Any element of these compositions can be animated and made interactive.

The following table summarizes the behaviors available in DirectAnimation.

CategoryScripting class/Java classDescription
any DAArray/ArrayBvr Arrays of behaviors that are all the same type. Objects can be selected from the array based on a time-varying index.
2-D/3-D DABbox2/Bbox2Bvr and DABbox3/Bbox3Bvr The DABbox2 and Bbox2Bvr behaviors represent two-dimensional bounding boxes. Extracting a bounding box from an image results in a box that encloses all the tangible parts of the image.

The DABbox3 and Bbox3Bvr behaviors represent three-dimensional bounding boxes. Extracting a bounding box from a geometry results in a box that encloses all the tangible parts of the geometry.

logic DABoolean/BooleanBvr Animated TRUE/FALSE values. Traditional Boolean operations such as And, Not, and Or are available. KeyState returns a Boolean object that is true when the specified key is down, and false when the key is up.
2-D/3-D DAColor/ColorBvr Animated color values in an abstract color space. The run time supports all color bit depths. These behaviors can be constructed by supplying three animated numbers (DANumbers or NumberBvrs) that are interpreted as either Red/Green/Blue or as Hue/Saturation/Lightness components.
3-D DACamera/CameraBvr Movie camera to turn a 3-D scene into an animated image. All camera attributes can be animated. The camera is used with the Render function on DAGeometry to project a geometry onto an image. There are two basic types of cameras: one for parallel projection and one for perspective projection. Both types of cameras are oriented so that they point in the negative z direction, with positive y considered up. Both types of cameras have a near clipping plane. This plane (a product of Z-buffered rendering) specifies the closest distance at which an object is visible. Objects closer than the near clipping plane cannot be seen.

Cameras are manipulated through standard three-dimensional transform operations. These transforms modify the camera characteristics and also position and orient the camera.

text DAFontStyle/FontStyleBvr Specifies the font face, size, color, and attributes (such as bold or italic). The functions and methods of these behaviors are similar to the HTML tags specifying the font characteristics.
3-D DAGeometry/GeometryBvr 3-D geometry, the primary 3-D data type. Can be created by importing .X files; by applying transformations to existing geometries; by applying material properties to geometries; by combining multiple geometries; by embedding sound into 3-D; by specifying 3-D lights; by applying DAImages and ImageBvrs behaviors as textures; by varying opacity, and so on. Given a DACamera or CameraBvr, a geometry can be projected onto an image.
2-D/3-D DAImage/ImageBvr Animated images, including cell animation, bitmaps, vector-graphic animations, animated 3-D projections, partially transparent images, and animated textures for 3-D animated geometry. Images can be constructed by importing GIF, BMP, JPEG, and PNG files. Importing a movie (such as an MPEG or AVI file) also constructs an image. Images can be constructed as solid color images, by rendering text into an image; by projecting a 3-D geometry with a camera into an image; by stroking a line with a path behavior; and by creating an image by smoothly varying colors across the face of a polygon. Java AWT Images can also be used to construct a DirectAnimation ImageBvr.

Images can also be created by overlaying two existing images; by applying an opacity value to an existing image; by clipping an image to a matte behavior; bycropping an image to a box; by tiling an image; and by applying a DATransform2 or Transform2Bvr to an image. Any image, even if it is time-varying and interactive, can serve as a texture on a DAGeometry or GeometryBvr.

line DALineStyle/LineStyleBvr, DAEndStyle/EndStyleBvr, DAJoinStyle/JoinStyleBvr, and DADashStyle/DashStyleBvr Specify how lines are drawn. DALineStyle provides for dashing, line width, line end, and line join styles when a DAPath2 is being stroked into a DAImage. DAEndStyle defines the shape applied to the end of line segments when they are drawn. DAJoinStyle defines the shape of joints between intersecting line segments in a path. DADashStyle defines whether lines are drawn as a continuous stroke or as a sequence of dashes.
2-D DAMatte/MatteBvr Stencils for creating animated cutouts in images. A matte can be constructed by filling in a DAPath2 or Path2Bvr behavior, by constructing it from a text outline, and by performing logical operations (such as union, intersection, and difference) on other mattes.
sound DAMicrophone/MicrophoneBvr Used to make sounds seem to have been recorded in a 3-D space. This behavior is used with the RenderSound function on a geometry to extract a 3-D volume of sound from that geometry and produce a DASound behavior.
2-D DAMontage/MontageBvr A layered set of animated images with depth ordering, sometimes called 2.5-D images. A montage is constructed by providing an image and a potentially time-varying number as the depth value, and by uniting the montages together. The Render function resolves all the depths and constructs a DAImage.
2-D DANumber/NumberBvr Time-varying numbers. For example, the ColorRgb function takes three static numbers (doubles) as parameters for the red, green, and blue values, while the ColorRgbAnim function takes three potentially time-varying DANumbers as parameters.

A Java NumberBvr can be constructed by using the toBvr method on a Java number. Many arithmetic methods are available. These include addition, subtraction, multiplication, division, exponentiation, sine, cosine, random-number sequences, integrals, and derivatives. Additionally, the time-varying seeds for animation, LocalTime and GlobalTime are DANumbers.

2-D DAPath2/Path2Bvr Animated 2-D vector graphics (2-D geometry). These behaviors represent lines (one-dimensional paths) through two-dimensional space. Paths have a direction and can be open or closed. A path can be constructed as a series of connected line segments; as a B-spline curve; as a concatenation of other paths; or as a 2-D transformation of another path. Closed paths can be filled to create images.
2-D DAPoint2/Point2Bvr, DAVector2/Vector2Bvr Animated 2-D points and directed magnitudes. Both points and vectors can be constructed by supplying animated numbers (DANumbers or NumberBvrs) that can be interpreted either as Cartesian coordinates or polar coordinates. Available operations include subtracting points to yield a vector, adding a vector to a point, determining the distance between points, creating a point from a B-spline, and taking the derivative of either a point or a vector.
3-D DAPoint3/Point3Bvr, DAVector3/Vector3Bvr Animated 3-D points and directed magnitudes. These behaviors are the 3-D analogs of DAPoint2/Point2Bvr and DAVector3/Vector3Bvr behaviors.
sound,
3-D
DASound/SoundBvr The main sound data type, which can have animated properties such as gain. Sound can be embedded in a geometry to occupy a 3-D space and then rendered with a microphone. Sound can be created by importing MIDI, WAV, and video files, by applying methods that affect gain, phase, frequency, and pan, or by mixing existing sounds. Also, limited audio synthesis is supported.
text DAString/StringBvr Strings of characters. The JScript syntax for creating a DAString is:
   AnimStr = m.DAString("string");

StringBvrs can be constructed from Java strings. Simple operations such as concatenation (ConcatString or the Java concat) are available. For example:

   MyStr = m.ConcatString("My", " String");
2-D DATransform2/Transform2Bvr Animated scaling, rotation, shearing, and translation in 2-D. Transformations can be constructed from translations, uniform and non-uniform scales, rotations, 3 by 2 matrices, and composition of other transforms.
3-D DATransform3/Transform3Bvr Animated scaling, rotation, shearing, and translation in 3-D. These behaviors are the 3-D analogs of DATransform2 and Transform2Bvr.
any DATuple/TupleBvr Arrays of behaviors that can be different types. Objects can be selected from the list based on an index.

Remember, all attributes of all behavior types can be animated and can react to events.

Creating Behaviors

Once you have declared your DAViewerControl object, as shown in the section on Scripting, you assign a variable to the object (attached to either the PixelLibrary or the MeterLibrary, depending on the units you want to use). Through the variable (m in the following example), you access functions and properties in the DAStatics library. You use these functions and properties to construct behavior types.

    m = DAViewer.PixelLibrary;
    im = m.SolidColorImage(m.Red);

Compound behaviors are produced by operations that take behaviors and produce new modified behaviors from them, rather than by modifying the original behavior. For example:

   mySound = m.ImportSound("file://c:/dxmedia/media/sound/clock1.mp2").Sound;

  // Loop the imported sound with the position-varying pan for 12 seconds, then 
  // silence the sound.
  DAViewer.Sound = m.Until(mySound.Loop().PanAnim(panFac),m.Timer(12),m.Silence);  
 

Java behavior types are created by declaring a behavior and assigning it a value, as shown in the following code:

     ImageBvr im = solidColorImage(red);

Compound Java behaviors are produced in the same way as compound scripting behaviors. For example:

  
      URL geomBase = buildURL(getImportBase(),"file:/c:/DxM/Media/geometry/");
      GeometryBvr geom = importGeometry(buildURL(geomBase,"cube.x"));
      GeometryBvr geom2 = geom.diffuseColor(red);

In this case, the original behavior geom is cube. The new behavior geom2 is a red cube.

Alternatively, because each of these methods produces a value, you could combine them into the following statement:

     GeometryBvr geom2 = importGeometry(buildURL(geomBase,"cube.x")).diffuseColor(red);

Displaying Behaviors

In DirectAnimation, only sound and image behaviors are actually rendered or displayed. The final animation consists of some combination of image behaviors and sound behaviors.

The DAViewerControl class displays DirectAnimation behaviors in an HTML page. Here is an example of how it is specified:


<DIV ID=controlDiv>
<OBJECT ID="DAViewer"
        STYLE="position:absolute; left:10; top:10;width:450;height:450"
        CLASSID="CLSID:B6FFC24C-7E13-11D0-9B47-00C04FC2F51D">
</OBJECT>
</DIV>

The actual behavior to be rendered is set as the Image property or Sound property of DAViewerControl. The rendering is invoked with the DAViewerControl subroutine Start(). Whatever Image property or Sound property the DAViewerControl object has when started determines what will be rendered. For example, the following code displays a solid blue screen:

   m = DAViewer.MeterLibrary;
   DAViewer.Image = m.SolidColorImage(m.Blue);
   DAViewer.Start();

In Java, you must provide an instance of the Model class to view behaviors. The Model class method createModel builds the behaviors to be viewed.

Applications override the Model class and implement the createModel method. This method creates the set of behaviors to be displayed and calls the setImage and setSound methods on the image and sound behaviors that are intended to be viewed.

The DirectAnimation system is responsible for invoking the createModel method.

Behaviors are run as soon as the model starts.

The following code shows how to use the Model class to construct a blue image.

public class RedImg extends Model {
  // Create the image behavior that will be displayed 
  public void createModel(BvrsToRun blist) {
    ImageBvr im = solidColorImage(blue);
    setImage(im);
    }
}

Java behaviors that are not part of the model can also be run when the model is started by calling createModel with the BvrsToRun parameter. For example:

public class OutsideBehavior extends Model {
    public void createModel(BvrsToRun blist)) {
        ...
    }
}

The behaviors in blist will be run when the model is started.

Adding Behaviors

The DAViewerControl displays a DirectAnimation model in an HTML page. The DAViewerControl method, AddBehaviorToRun, adds behaviors to the control that are not specified in the model. When the system starts the model, it retrieves these additional behaviors and runs them with the same start time as the behaviors included in the model.

As an example, consider the following VBScript sample, which displays the local time as a text value.

<HTML>
<BODY>
<CENTER><INPUT TYPE="TEXT" NAME="txtInp" VALUE=""></CENTER>
<OBJECT ID="DAControl" WIDTH=400 HEIGHT=300
CLASSID="CLSID:B6FFC24C-7E13-11D0-9B47-00C04FC2F51D">
</OBJECT>
</HTML>
</BODY>

<OBJECT ID="DAControl" WIDTH=400 HEIGHT=300
CLASSID="CLSID:B6FFC24C-7E13-11D0-9B47-00C04FC2F51D">
</OBJECT>

<SCRIPT LANGUAGE="VBScript">
Sub window_onLoad
   Set m = DAControl.MeterLibrary
   Set timeVal = m.Localtime
   Set timeVal = timeVal.AnimateProperty( "txtInp.value", "VBScript", false, .01 )
   DAControl.AddBehaviorToRun( timeVal )
   DAControl.Start
End Sub
</SCRIPT>

Reacting to Events

This section includes the following topics.

Non-Reactive Behaviors

All behaviors can potentially vary with time, react to events, and interact with the user. Non-reactive behaviors are those that are constant or time-varying (but do not react to events).

Constant behaviors are not time-varying. In the previous example in Creating Behaviors, the rotated cube used behaviors that were constant. Using the LocalTime behavior value in the Rotate3Degrees method, you can construct a new red cube to spin around the y-axis at a rate of one degree per second, as shown in the following code:

mediaBase = "..\\..\\..\\..\\media\\";
geoBase = mediaBase + "geometry\\";
Geo1 = m.ImportGeometry(geoBase + "cube.x").
		DiffuseColor(m.Red).Geo2.Transform(m.Rotate3Degrees(m.YVector3, m.LocalTime));

The behavior value LocalTime (and the Java equivalent localTime) allows animation to be injected into behaviors. It is a number-valued behavior (DANumber or Java NumberBvr) that starts at time 0 and increases at the rate of one unit per second. It can be used as an argument to any behavior-constructing method that takes a number-valued behavior as an argument to construct a time-varying behavior. For example, consider the following statements:

Sawtooth = m.Mod(m.LocalTime, 1);
MyCol = m.ColorRgb(Sawtooth, 0, 0);

The first creates a number behavior that goes from 0 to 1, 0 to 1, 0 to 1, and so on, by taking the modulus of the ever-increasing LocalTime value and 1. It then uses this value to create a time-varying color whose red component is that number behavior.

Animations constructed using LocalTime are unaffected by fluctuations in the frame rate of the DirectAnimation implementation. This means that the Sawtooth behavior will go from 0 to 1 in a period of 1 second, no matter how many frames are actually generated. This is a very important feature of DirectAnimation because it allows applications to be constructed independently of the hardware on which they will ultimately run.

There are other forms of time-varying input, such as MousePosition that can add interactivity to behaviors. The MousePosition (and Java mouseposition) behavior, for example, provides the continuous 2-D world-coordinate position of the mouse. It can be used, for example, as an argument to a translation function for tracking the mouse.

Reactive Behaviors

Non-reactive behaviors change with time but do not change as the result of discrete events. However, being able to change as a result of events (either internal or external) is important for truly useful behaviors, especially in interactive animations.

Events are discussed in the section DirectAnimation Events. The general data type for events is DAEvent and the Java DXMEvent. This topic discusses creating behaviors that switch from one behavior to another based on events.

Events

DirectAnimation events come from a number of sources, including:

This section gives an overview of the following event topics.

Key and Button Events

Events can be produced when the user presses a mouse button or keyboard key. For example, consider the following JScript code:

Bvr1 = m.Until (initial_behavior, m.LeftButtonDown, second_behavior);

This defines Bvr1 as an initial behavior until the left mouse button is pressed, then Bvr1 becomes the second behavior.

You can trigger from an event produced when the user presses a specific key. For example:

Bvr2 = m.Until (initial_behavior, m.KeyDown(27), second_behavior);

This defines Bvr2 as an initial behavior until the ESC key is pressed, then Bvr2 becomes the second behavior.

You can also determine the state (whether it is pressed or not) of a mouse button or key with the LeftButtonState, RightButtonState, and KeyState functions. You can then trigger on that state using the Predicate event, as shown in the following JScript code:

Bvr3 = m.Until (initial_behavior, m.Predicate(m.LeftButtonState), second_behavior)

Timer Events

DirectAnimation supports the explicit specification of timer events, based upon LocalTime. For example, the following expression causes a behavior to switch from red to blue two seconds after it starts:

m.Until(m.Red, m.Timer(2), m.Blue)

The value given to timer can be any number-valued behavior.

Application-Triggered Events

Applications often receive events through means other than DirectAnimation. Examples include GUI elements and incoming network data. An application-triggered event is a subclass of the DAEvent type (DXMEvent Java type), and supports an additional trigger method. These events can be constructed with the new operator, placed into reactive behaviors, and triggered at any time by the application. For instance, the following Java example turns a cube from red to blue upon some external application event:

  URL geomBase = buildURL(getImportBase(),"file:/c:/DxM/Media/geometry/");
  GeometryBvr cube = importGeometry(buildURL(geomBase,"cube.x"));
  AppTriggeredEvent appEvent = new AppTriggeredEvent();
  ColorBvr col = (ColorBvr)until(red, appEvent, blue);
  GeometryBvr coloredCube = cube.diffuseColor(col);
//... elsewhere, when the application receives the proper event...
appEvent.trigger();
//... now coloredCube will turn blue (assuming it's running)

Note that the trigger method is an immediate method. The trigger occurs when the program executes the statement containing the trigger call.

Compound Events

You can combine events in a variety of simple ways. The AndEvent function takes two events and creates a third that occurs only when both constituent events occur simultaneously. The event resulting from OrEvent occurs when either of the constituent events occur (or both occur). The event resulting from NotEvent occurs whenever the constituent event does not occur.

The AndEvent function always produces a pair of event data. Each element of the pair is the event data from one of the constituent events. The OrEvent function produces the event data of whichever event caused OrEvent to trigger. The event data from NotEvent is undefined because an event not occurring doesn't produce any information.

The AttachData Function

DirectAnimation events support an AttachData function that takes an event and produces a new event. The new event occurs at the same time as the original event, but its data is now the data that has been specified in the call to AttachData. This allows an application to associate arbitrary client data with an event and know that it will be delivered to the notifier when the event occurs.

This method can be used in conjunction with UntilEx and OrEvent to allow a behavior to switch to one of several new behaviors, depending on which event occurred. For instance, if you want a behavior that is red until the left mouse button is pressed (in which case it turns green) or the right mouse button is pressed (in which case it turns yellow), you could use the following code:

GreenLeft = m.LeftButtonDown.AttachData(m.Green);
YellowRight = m.RightButtonDown.AttachData(m.Yellow);
MyCol = m.UntilEx(m.Red, m.OrEvent(GreenLeft, YellowRight);

Because AttachData can take any behavior, the application can associate any type of data with events, rather than only behaviors.

Event Data

It is often necessary to know more about an event than just its occurrence. This is why some events also produce data. For instance, a pick event provides information about the location of the intersection. To give applications the ability to access this data, DirectAnimation for Java calls the Notify function. This function creates a new event that occurs when the original event occurs. It then calls the notifier and uses the result as its event data. It is the responsibility of the application to retrieve the necessary data.

Event Data from Picking

The pick event returns data that can be accessed through the PickEvent property of the DAPickableResult class or from within an Until or UntilEx function. The data returned is a DAPair behavior whose first element is the intersection point of the event, and whose second element is a vector perpendicular to the surface of the picked object. Both of these pieces of data are supplied in the local coordinate system of the image or geometry used to construct a pickable image object (with the DAImage property Pickable) or pickable geometry object (with the DAGeometry property Pickable). This is needed to enable images and geometries to retain their interactivity independent of the context into which they are embedded. For example, an interactive image that is replicated three times, each time with a different scale, should retain its interactivity in all three contexts.

The sample in DXMedia\Samples\Multimedia\DAnim\VBScript\Showcase\Pick3.html demonstrates picking.

The samples GeometryDrag and ImageDrag in DXMedia\Samples\Multimedia\DAnim\Java\Templates\ directory show Java code that creates a geometry and an image that can be picked and, when picked, follows (or is dragged by) the mouse. When released, the geometry or image stays where it was released, waiting for the next pick. Because the event data is returned in the local modeling coordinates of the image itself, the resultant images can be used under any modeling transformation.

Because the draggable operation is so common, dragging utilities are provided in the DXMedia\Samples\Multimedia\DAnim\Java\Utility directory.

Event Detection

Descriptions of the Predicate function, which creates an event from a Boolean, might give the impression that this event is fired anytime the event occurs. In general, this is not possible, due to the possibilities of temporal aliasing. Therefore, DirectAnimation places implementation-specific restrictions on the form of Boolean that will successfully and consistently trigger events. For example, the event denoted by m.Predicate(m.EQ(m.Sin(m.LocalTime), 0)) is expected to fire exactly when Sin(LocalTime) is 0. However, in reality, this event will rarely, if ever, fire. This is because of the sample-driven nature of detecting predicate events. If the implementation doesn't sample at exactly the right time, the event will be missed. Thus, applications should use inequality events, such as m.Predicate(m.GTE(m.Sin(m.LocalTime), 0)) to test for events on continuous behaviors. It is this imprecision in sampling events that is the motivation for the specialized timer event.

There is a subtlety associated with imprecise sampling. If a notifier is invoked using Notify waiting for the above event, the event time will not generally be the exact time that the event became true. If precise timing is a requirement, the event time should not be used directly. Instead, the inaccuracy must be compensated for in some application-specific manner.

Picking

In DirectAnimation the Pickable property of DAImage and DAGeometry objects provides a simple mechanism by which picking (or hit detection) on images and geometry results in DirectAnimation events. In Java, the same mechanism is supported by the classes PickableImage and PickableGeometry. Applications can use these events as they would any other event. Additionally, information such as where on the object the pick occurred is provided with the resulting event data.

The support for image and geometry hit detection is identical.

Hit detection works even when geometry is embedded into an image, and the image is in turn textured onto a geometry.

Images and geometry, even when embedded, retain their interactive nature.

If an application needs to be informed that the mouse is over a particular image, it constructs a DAPickableResult object by invoking the Pickable property on an image or geometry. It can then use the DAPickableResult property PickEvent. If the mouse is directly on top of a pickable image or geometry (without intervening images or geometries) then the event fires. It is possible for a pickable object to appear multiple times in a scene. However, each time the mouse is on top of any of these images, the event occurs. To distinguish multiple uses of an image, and provide separate events based upon separate uses, the application must construct multiple pickable objects. For example:

    pim1 = image.Pickable;
    pim2 = image.Pickable;

A Simple Picking Example

This JScript example demonstrates how to make an image pickable, meaning it reacts to an event when the mouse is within its borders. In this example, the square cycles between blue and red when the mouse is within its borders and the left button is down. To view the sample, click on the "Show Sample" button; to view the source code, click on the "Show Sample Code" button.

Simple Picking Sample

The following simple Java example imports a GIF image and a piece of text rendered on top of the GIF image. The text changes when the mouse is over the GIF image.

URL imageBase =buildURL(getImportBase(),"../../Media/image/");
ImageBvr gif = importImage(buildURL(imageBase,"picture.gif"));

// Grab image, make pickable, grab the event.
PickableImage pGif = new PickableImage(gif);
DXMEvent ev = pGif.getPickEvent();

// Make the string change when mouse is over the image
StringBvr str =(StringBvr) until(toBvr("not picked"), ev, 
  toBvr("picked"));

// Render the string to an image, and overlay on top of the
// pickable GIF.
ImageBvr  textIm = simpleText(str).render();
ImageBvr  result = overlay(textIm, pGif.getImageBvr());

Note that the pick event occurs when the mouse is directly over the image (and no other detectable images occlude its visibility.) A mouse button press is not required to fire the event. The effect is analogous to an HTML hotspot. To ensure that the event fires only when the mouse button is pressed, use the event constructed by andEvent(leftButtonDown, ev).

The Until Function

This section discusses the Until function (Java until method), which enables developers to incorporate interactive elements into their animations. Here is an example of how to create a behavior that is red until the left mouse button is pressed, and then turns to blue:

Col = m.Until(m.Red, m.LeftButtonDown, m.Blue);

In general, Until takes a behavior, an event, and another behavior. It produces a new behavior. This new behavior is the first behavior until the event occurs. It then becomes the second behavior. Because Until takes behaviors and returns a behavior, it can be nested, as the following example demonstrates:

Col = m.Until(m.Red, m.LeftButtonDown, 
                             m.Until(m.Blue, m.LeftButtonDown, m.Green));

In this example, the resulting behavior is red until the button is pressed. It is then blue until the button is pressed again, and then it is green.

Note that the result is cast to the proper subclass of Behavior, because Until is defined on the Behavior superclass.

The previous example could have been written as:

Col1 = m.Until(m.Blue, m.LeftButtonDown, m.Green)
Col2 = m.Until(m.Red, m.LeftButtonDown, Col1);

Looking at the example in this form raises the following question: "When the LeftButtonDown event occurs, Col2 changes from red to Col1, but Col1 doesn't change from blue to green. Why not? Both of them appear to be waiting for the LeftButtonDown event, so why doesn't the first occurrence of this event cause them both to change?"

The reason the example works properly is because of the way Until works. Consider the following example:

b1 = m.Until(b2, ev, b3);

Behaviors are run automatically by the system when needed. Assume that behavior b1 is started by the system with a start time of t0. This causes b2 to be started at t0 also, and causes the system to start looking for the first occurrence of the event ev after t0. The system does not start behavior b3. When the event does occur (call this time te), the system starts behavior b3 at time te and stops looking for event ev.

In DirectAnimation, global time is ever-increasing, but local time for each behavior starts at 0 when the behavior is started. For example, if behavior b3 starts two seconds after b2, it will start with its own local time of 0.

Consider the original example:

Col1 = m.Until(m.Blue, m.LeftButtonDown, m.Green)
Col2 = m.Until(m.Red, m.LeftButtonDown, Col1);

When Col2 is started, the system looks for the LeftButtonDown event in Col2, but not in Col1. When the Col1 behavior starts, the system looks for the LeftButtonDown event in Col1.

The UntilEx Function

The UntilEx function (untilEx Java method) takes two parameters, a behavior and an event with a behavior associated with it: UntilEx(InitialBehavior, Event). The resulting behavior will have the initial behavior until the event happens, then will transition to whatever behavior the event produces.

There are several ways to associate a behavior with an event, including the AttachData, Notify, and Snapshot functions (and the Java attachData, notifyEvent, and snapshotEvent methods).

The AttachData function enables you to associate data with an event. The data associated with the event can be a behavior. In the following example, the behavior is red until a mouse button is pressed. It then turns either green or yellow, depending on which event happens first.

GreenLeft = m.LeftButtonDown.AttachData(m.Green);
YellowRight = m.RightButtonDown.AttachData(m.Yellow);
MyCol = m.UntilEx(m.Red, m.OrEvent(GreenLeft, YellowRight);

The Notify function calls the notifier when an event occurs, and uses the result as event data.

The Snapshot function takes a behavior as a parameter. When the event to which it is attached occurs, it samples the behavior and returns it as a constant behavior. In the following example, the value of n is LocalTime until the left mouse button is pressed. It then becomes whatever the value of LocalTime was when the event occurred:

n=m.Until(m.LocalTime, m.LeftButtonDown.Snapshot(m.LocalTime));

The UntilNotify and UntilNotifyScript Functions

The DAStatics UntilNotify and UntilNotifyScript functions (and the Java untilNotify method) provide a delay mechanism for using event time data to construct the behavior that occurs after the event occurs. At the time of the event, a user-constructed notification method is invoked and passes the current behavior and the event data. This is somewhat like a user callback. The user code (called the notification method) uses the passed current behavior and event data to construct and return the behavior that occurs after the event. UntilNotify constructs a behavior that changes upon the given event to the behavior specified in the DAUntilNotifier Notify method. The UntilNotifyScript method constructs a behavior that changes upon the given event to the behavior in the specified script procedure.

The JScript samples in samples/multimedia/danim/jscript/templates/Dragimg.html and samples/multimedia/danim/jscript/showcase/draggeo.html demonstrate the use of UntilNotifyScript. The C++ sample in samples/multimedia/danim/c++/showcase/BvrHookCntrl demonstrates the use of UntilNotify.

When Does Until Change Behaviors?

This subsection discusses a subtlety present in the DirectAnimation event model.

Consider the statement:

until(b1, ev, b2);

If the event ev occurs at time te, the b2 behavior will not be sampled until the first sample time strictly greater than te. If this rule were not followed, many cyclically defined behaviors would be infinitely recursive.

Consider this example, which is a red behavior until LocalTime is greater than or equal to 2, then is a green behavior:

m.Until(m.Red, m.Predicate(m.GTE(m.LocalTime, 2)), m.Green)

If this behavior is sampled at local time 1.8, the result will be red. At local time 2.1, the predicate will hold true, but the DirectAnimation implementation assumes that the event time is 2.1 (because this is the time the implementation became aware of the event). Because of the "strictly greater than" rule described above, the result will still be red. Not until the next sampling, for example at local time 2.4, will the result be green.

If the application wants to ensure that situations like this react more accurately, it can use the timer method, and rewrite the example as:

m.Until(m.Red, m.Predicate(m.Timer(2)), m.Green)

In Java, this statement would be:

until(red, timer(toBvr(2)), green) 

Until and LocalTime

The Until function and the LocalTime behavior starts at 0 and increases at the rate of 1 unit per second. Because Until starts its component behaviors at different times, the use of LocalTime within an Until function allows for the creation of distinct local timelines. For example:

sine = m.Sin(m.LocalTime);
slope = m.LocalTime;
sineSlope = m.Until(sine, m.Timer(5), slope); 

The sineSlope behavior behaves like the sine behavior until just after time 5 (in its local time line). It then behaves like the slope behavior. The following is a chart of the running value of sineSlope graphed against its local time line.

Sine/Slope graph

Any instances of LocalTime in a behavior will start at 0 when that behavior is running, including when the behavior is transitioned to as the result of an event.

This local timeline property of LocalTime is important because it allows behaviors to operate in their own local timelines. When placed in any Until function, started at any time, they will follow their prescribed behavior.

This form of Until doesn't describe every situation that might arise when designing a behavior that reacts to events. Specifically, this form only works when the component behaviors can be completely described when the resultant behavior is defined; for example, that the color becomes green when the button is pressed.

However, consider a stopwatch implemented as a number behavior representing a timer that starts counting from 0 and increases 1 unit per second until the left button is pressed. At that time, the behavior is permanently set to the value that existed when the button was pressed. This can be accomplished with the Snapshot function and the UntilEx function, as shown in the following example:

Stopwatch=m.Until(m.LocalTime, m.LeftButtonDown.Snapshot(m.LocalTime));

More Ways to Use Behaviors

This section describes how to use behaviors in different situations for different results. It includes the following topics:

Results of Operations on Behaviors

A common feature throughout the behavior classes is that operations generally take behaviors and produce new behaviors from them, rather than modifying the original inputs. For example, consider the expression:

Cube.Transform(xform)

This does not change the specified cube. Instead, it produces a new geometry that is a transform applied to the original cube.

The following example creates a red cube rotated around the y-axis by 60 degrees:

mediaBase = "..\\..\\..\\..\\media\\";
geoBase = mediaBase + "geometry\\";
Geo1 = m.ImportGeometry(geoBase + "cube.x");
Geo2 = Geo1.DiffuseColor(m.Red);
Geo3 = Geo2.Transform(m.Rotate3Degrees(m.YVector3, 60));

Alternatively, because each of these functions produce a value, they could all be combined into the following statement:

Geo1= m.ImportGeometry(geoBase + "cube.x").
		DiffuseColor(m.Red).Transform(m.Rotate3Degrees(m.YVector3, 60));

It is important to remember that these calls always create new values and never modify the existing values. For example, consider the following code:

Geo1 = m.ImportGeometry(geoBase + "cube.x");
//Incorrect Usage
Geo1.DiffuseColor(m.Red);
Geo1.Transform(m.Rotate3Degrees(m.YVector3, 60));

This code will not create a cube, make that cube red, then rotate that cube. Instead, Geo1 remains the original, imported cube. The second and third lines are useless because they don't do anything with their return values. The results of those operations are inaccessible.

Within the DirectAnimation implementation, new values may or may not actually be constructed. However, from the developer's point of view they always are. Additionally, when new values are constructed they are very inexpensive in terms of system resources. Generally, they are represented by a pointer to the old value and the data for the new attribute.

Some Defaults When Constructing Behaviors

In DirectAnimation, methods for constructing media data types (images, geometry, points, and so on) use the following defaults:

This approach minimizes the replication of extraneous parameters. You don't need to define image imports, simple text, or light constants to take a position and size parameter. This both simplifies and aids consistency in animations.

When Are Behaviors Run?

This section lists the ways behaviors are run, and how the start time that becomes associated with the running behavior is determined. The different ways of running behaviors are:

The RunOnce Function

Certain applications need to be able to reference a running behavior without the behavior restarting when events occur. Consider two animated, time-varying images (perhaps movies) and an application that plays the first movie from its beginning for 10 seconds, then, over the course of the next two seconds, fades from it to the second movie, then continues playing the second movie. Assuming the existence of a Fade operation (which can be constructed easily from overlay and opacity), a first attempt might look like this:

Incorrect Usage

wrongResult = m.Until(movie1, 
                  m.Timer(10),
                  m.Until(Fade(movie1, movie2),
                        m.Timer(2),
                        movie2);

Unfortunately this doesn't work. The problem is that after the first event the movie1 behavior will be started again, resulting in seconds 1 and 2 of movie1 fading, rather than seconds 11 and 12. Also, after the second event the movie2 behavior also starts again, replaying seconds 1 and 2 rather than continuing on from the end of 2 seconds.

This particular application calls for a behavior that, once running, is always referred to without restarting. This means that after the initial start, no new behaviors are created. The RunOnce function satisfies this requirement. With RunOnce, the fader example can be expressed as:

movie1Once = movie1.RunOnce();
movie2Once = movie2.RunOnce();
faderMovie = m.Until(movie1Once, m.Timer(10), m.Until(Fade(movie1Once, movie2Once), 
          m.Timer(2), movie2Once);

In this example, movie1Once and movie2Once are constructed as behaviors that are not yet running, but once they are started, subsequent references to these behaviors will return to the behavior already running.

Java toBvr Method

The toBvr method converts a double precision Java number into a constant NumberBvr behavior. This conversion is necessary because the DirectAnimation for Java methods generally take arguments that are behaviors and not Java constant numbers. The toBvr method exists for all core Java types that have DirectAnimation equivalents. For example, it converts Boolean types and java.lang.String types to BooleanBvr and StringBvr respectively.

The only time that toBvr is used is when converting a Java number, Boolean, or String to a DirectAnimation type. It is not used for constants such as red, yVector3, and origin2, because these are defined as constant behaviors.

Constructing Cyclic Java Behaviors

Often there is a need to construct a behavior that cycles through some number of values when an event occurs. The Cycler object solves this problem. It is a subclass of the Behavior class. Here is an example that cycles through the three sounds upon each leftButtonDown event:

URL soundBase =buildURL(getImportBase(),"../../Media/sounds/");
SoundBvr snd1 = importSound(buildURL(soundBase,"do.wav"), null);
SoundBvr snd2 = importSound(buildURL(soundBase,"re.wav"), null);
SoundBvr snd3 = importSound(buildURL(soundBase,"mi.wav"), null);

//Create an array of sounds
Behavior[]snds = {snd1, snd2, snd3};
Cycler cycle = new Cycler(snds, leftButtonDown);
SoundBvr currentSnd = (SoundBvr)(cycle.getBvr());

Note that each component behavior gets started each time there is a transition to it.

The Cycler Class entry in the Java Reference describes Cycler.

The Cycler utility can be constructed easily in Scripting classes using Until and uninitialized behaviors.

Sequencing

Sequencing enables you to sequence behaviors with finite durations. Consider the following JScript sample which sequences through a set of labels. You can see how sequencing is used in animation by viewing the sample DXMedia\Samples\Multimedia\DAnim\JScript\Templates\Sequencing.

labels = new Array("First", "Second", "Third", "Fourth", "Fifth",
                      "Sixth", "Seventh", "Repeat the Sequence Forever");
   
durations = new Array(tDur, tDur, p1, sDur, p2, tDur, tDur, p1, sDur, p2, 1);

// Construct a sequence of labels 
len = labels.length;
label = m.DAString("").Duration(0);
for (i=0; i<len; i++) 
   label = m.Sequence(label, m.DAString(labels[i]).Duration(durations[i])); 

font = m.DefaultFont.Color(m.Blue); 
textImg = m.TextImage(label, font);

Note that the last duration of one second is for the last string, which will stay forever since it is the end of the sequence.

Switching

Sometimes there is a need to modify behaviors that is not dependent on events. The SwitchTo function fills this need. A switchable object is created with the ModifiableBehavior function and is given an initial behavior. When SwitchTo is called with another behavior, the modifiable behavior switches to the provided behavior. Here is an example:

// Create a modifiable color with the initial value blue. 
col = m.ModifiableBehavior(m.Red)
im = m.SolidColorImage(col);
//... somewhere else in the program ...
col.SwitchTo(m.Blue);

Anywhere that col was used will turn from red to blue when SwitchTo is called. Calls to SwitchTo must carry arguments of the same behavior type as the initial behavior. The behavior switches when the program executes the statement containing the SwitchTo call.

Integrals and Derivatives

DirectAnimation supports explicit construction of the integrals and derivatives of certain types with respect to time. Integration can be performed on values of type DANumber with the Integral function and on DAVector2 and DAVector3 types with the IntegralVector2 and IntegralVector3 functions. Derivatives can be taken on all these types and also on DAPoint2 and DAPoint3 with the Derivative, DerivativeVector2, DerivativeVector3, DerivativePoint2, and DerivativePoint3 functions.

When an integral behavior begins, it starts building a conceptually continuous summation of values from that start time.

For example, the following JScript code creates a rotating ball that stops and starts on every left mouse button click. Rotate3RateDegrees takes an implicit LocalTime parameter. This parameter is replaced by the integral when SubstituteTime is called. The changingRate variable is a velocity that cycles between 0 and 1. The integral of that velocity increases gradually. This integral is used as the angle of rotation for the geometry.

Ball = m.ImportImage("ball.gif");
RollingBall = Ball.Transform(m.Rotate3RateDegrees(m.Vector3(1,5,3), 540));
changingRate = new ActiveXObject("DirectAnimation.DANumber");
changingRate.Init(m.Until(m.DANumber(0), m.LeftButtonDown,
		m.Until(m.DANumber(1), m.LeftButtonDown, changingRate)));
finalImg = RollingBall.SubstituteTime(m.Integral(changingRate));

The following Java code creates a spinning cube that stops and starts on every left mouse button click.

URL mediaBase =buildURL(getImportBase(),"../../Media/");
GeometryBvr geo = importGeometry(buildURL(mediaBase,"cube.x"));
NumberBvr[] zeroAndOne = { toBvr(0), toBvr(1) };
Cycler cyc = new Cycler(zeroAndOne, leftButtonDown);
NumberBvr angularVelocity = (NumberBvr)(cyc.getBvr());
NumberBvr angle = integral(angularVelocity);
GeometryBvr spinningGeo = geo.transform(rotate(zVector3, angle));

The Extract Function

For behavior types that have primitive equivalents, (DABoolean, DANumber, and DAString, and the Java types BooleanBvr, NumberBvr, and StringBvr), a value in the underlying type is often needed. For instance, you might need an actual float in order to call out to a C++ or Java routine. The Extract function (Java extract method) exists for these classes of behaviors, takes no arguments, and is expected to be called on a behavior that is actually constant.

The Cond Function

The Cond function enables the construction of a behavior out of a Boolean behavior and two other behaviors. The value of the resultant behavior at any point in time is equal to the value of one of the two other behaviors. Which value is chosen is determined by the value of the Boolean behavior. This is a behavior-level conditional.

The following example creates a behavior, y, which is either another behavior, x, or 1.0 if x is greater than 0.5:

x = m.Sin(M.LocalTime);
y = m.Cond(m.GTE(x, 0.5), 1.0, x);

Time Substitution

By default, the LocalTime behavior starts at 0 and increases at the rate of one unit per second. However, this behavior can be modified. DirectAnimation supports time substitution. Time substitution creates a new behavior from an existing behavior and a number behavior. In the new behavior, the number behavior replaces all occurrences of LocalTime that were found in the original behavior. This includes behaviors where LocalTime is implicit, such as in imported movies. Time substitution allows behaviors to be time-scaled so that they can, for example, run faster or slower, be time-shifted to start at a different time, or be frozen at a particular point in time. This can be used to accelerate and decelerate animation objects.

The SubstituteTime function takes the following form:

newBvr = origBvr.SubstituteTime(newTime)

The parameter newTime is a DANumber behavior that gets substituted for LocalTime in the origBvr behavior. The value of newBvr at time t is found by taking the value of newTime at time t, and using that as LocalTime in evaluating the behavior origBvr.

Here are some simple examples:

//Create an original behavior: a point moving
//one unit in the x direction per second, starting at 0
pt1 = m.Point2(m.LocalTime, 0);

//Create a new behavior moving 0.5 units per second
//Do this by replacing localTime with LocalTime/2
pt2 = pt2.SubstituteTime(m.Div(m.LocalTime, 2);

//Create a new behavior moving 1 unit per second 
//starting at time 33, by replacing LocalTime with 
//LocalTime + 33
pt3 = pt1.SubstituteTime(m.Add(m.LocalTime, 33);


//Create new behavior by freezing the original behavior
// at time 77
pt4 = pt1.SubstituteTime(77);

//Tie the new behavior to the x-component of the mouse
pt5 = pt1.SubstituteTime(m.MousePosition.X); 

Also note that time substitutions are cumulative. For example, the following series:

c0 = m.Point2(m.LocalTime, 0);
c1 = c0.SubstituteTime(m.Add(m.LocalTime, 33));
c2 = c1.SubstituteTime(m.Mul(m.LocalTime, 2));

Could also be expressed as:

c0 = m.Point2(m.LocalTime, 0);
c1 = c0.SubstituteTime(m.Add(m.Mul(m.LocalTime, 2), 33));

This Java example shows how to make images of sailboats rock on the water. First a simple number behavior is created:

NumberBvr angle = mul(sin(localTime), toBvr(Math.PI/6));

This behavior begins (at local time zero) with the value zero and then varies with time between +/-p/6 radians (that is, +/-30 degrees). It repeats the behavior every 2p seconds. Assume that the applet has already constructed (or imported) a behavior representing the geometry of a sailboat, sailboat0, that is centered at the origin with its long axis aligned along Z. You can use the behavior angle to rock the boat as shown in the following code:

Transform3Bvr heel1 = rotate(zVector3, angle);
GeometryBvr sailboat1 = sailboat0.transform(heel1);

The boat is initially upright and then heels from one side to the other, passing through upright (sin(0)=0) approximately every p seconds.

Now, create a boat that is rocking more slowly than sailboat1:

Transform3Bvr heel2 = 
(Transform3Bvr)heel1.substituteTime(div(localTime, toBvr(8)));
GeometryBvr sailboat2 = sailboat0.transform(heel2);

The second sailboat, sailboat2, rotates the same amount as sailboat1 but has a period that is 8 times longer (y = sin1/8x).

Note that, even though the period of rocking is different for each boat, both are initially upright. Now add a third boat that rocks at the same period as sailboat2, but is 90 degrees out of phase with it (y = cos 1/8x). The third boat can be defined as:

GeometryBvr sailboat3 = (GeometryBvr)
sailboat2.substituteTime(add(localTime, toBvr(Math.PI/2)));

At the start, this boat is heeled over by p/6 (cos(0)=1) and rocks at the same rate as sailboat2. Notice how using time substitutions contributes to the modularity of the code. The phase change could be achieved by changing the definition of angle, but this would have also changed the phase of the first two boats as well. The code for angle could have been duplicated and the phase changed, but if angle had a more complicated definition or if the source were not available, this approach would be difficult or impossible.

Time substitutions provide the same modularity benefits in the temporal domain as 2-D and 3-D transformations provide in the spatial domain. They enable you to define objects, and then manipulate them, from outside to alter their behavior, without needing to know the internals of the objects.

Naturally, there are certain restrictions on the time substitutions that can be applied to behaviors. For instance, user input in general cannot be time-substituted; and time-substitution cannot replace event transitions that have already occurred.

Uninitialized Behaviors in Java

Sometimes you need to reference a Java behavior before it has been defined. As an example, consider a colored rectangle whose color changes when picked (when the mouse clicks on it or hovers over it). In this case, the color depends on whether or not the image is picked, but the image is dependent upon the color. The uninitialized behavior is provided for these cases and for cases of cyclic dependency. This behavior enables you to create a behavior and use it in the definition of other behaviors, but not actually define its contents until some later point. Uninitialized behaviors are created with the newUninitBvr method of the behavior object being created. All the behavior types have a newUninitBvr method for this purpose.

The following code shows how to use uninitialized behaviors to construct the animation described above. It assumes you have defined the function makeColoredRectangle elsewhere:

ColorBvr col = ColorBvr.newUninitBvr();
ImageBvr im = makeColoredRectangle(col);
PickableImage pim = new PickableImage(im);
DXMEvent ev = pim.getPickEvent();
col.init( until(red, ev, green) );

This example changes the color from red to green the first time the rectangle is passed over by the mouse. Here the behavior is initialized to the color dependent upon the event once the event occurs.

Consider what happens when the last line of the previous code is changed to the following:

col.init ( until(red, ev, until(green, notEvent(ev), col)) );

The rectangle will be red whenever the mouse is not over it, and green when the mouse is over it. This is accomplished by making the col behavior itself cyclic, going back to red and waiting for the pick after the notEvent(ev) occurs.

When using this feature, you should be aware that the system will generate a run-time error if you:

Using Behaviors in Java Applications

A Java application can display behaviors in either an applet or a canvas. There are three general steps to displaying behaviors:

  1. The application overrides (subclasses) the DirectAnimation Model class and implements the createModel method to build up the image, sound, and geometry behaviors to be viewed. The application can optionally implement settings that apply specifically to this model.
  2. The application constructs an instance of a DXMApplet or DXMCanvas class, providing an instance of the Model subclass.
  3. The application uses the applet or canvas as it would use any other applet or canvas. The model (and all of its component behaviors) is automatically sampled and displayed.

For example, the following code sample shows how to view the RedImg model created in the previous section.

public class MyApplet extends DXMApplet {
    public void init() {
       // Always call the superclass's init() first to ensure codeBase is set
       super.init();
       // Now set the model
       setModel(new redImg());
    }
}

Given a model, the setModel method is all you need to construct an applet to view that model.

The use of DXMCanvas is identical to DXMApplet, except that you must position the canvas inside an application frame as you would any other canvas.


Top of Page Top of Page
© 2000 Microsoft and/or its suppliers. All rights reserved. Terms of Use.