Curve

From Virtual World Web Wiki
Jump to: navigation, search

Curves are a powerful feature of the DOM that allow you to animate the properties of selected nodes in a scene. Generally, you define a curve by creating key-frames at which the values of named properties are set. You then create a **Curve Player** that moves along the curve over time projecting the animating properties onto other nodes in the DOM. There's a lot going on in those last two sentences, so let's break it down a bit.

Defining A Curve

A curve can be created anywhere in a DOM document (a scene, on a participant, wherever). A curve starts with a DOMCurve node. You can create these in four ways: In server JavaScript you can call CreateCurve on any DOMObject (see DOMObjectExtended and DOMCurveExtended), this will create a new curve with the title specified. In server JavaScript you can call CreateCurveFromJson and pass a JSON Curve Definition object (stringified). You can create them manually from server component code and add them to a DOM tree you have access to (new DOMCurve()). Finally, you can create them manually in Curiosity.

If you're using JSON to define the curve, you won't need to create key-frames and values as described below here.

A curve then needs to have some key-frames. Key-frames represent a point in time at which values for properties can be specified. Generally you'll want at least two key-frames. A key-frame is added by creating a DOMCurveKeyframe node under a DOMCurve. Once created, you'll need to set they key-frames Time property. The first key-frame will usually start at "0" seconds. The key-frames will be sorted by their time indices. If you're working in server JavaScript you can call CreateKeyframe on a DOMCurveExtended object to create new key-frame objects (see example below).

Once you have some keyframes, you'll need to describe the properties that are being animated and their values at each key-frame. You do this by placing DOMCurveValue nodes under each key-frame. These nodes do two things at once: they define the name of the property being animated via their Title property, and they also contain a single value for that property in their own properties collection. For example, if you wanted to animate a property called "Color" you would create a DOMCurveValue node with the title "Color", which would also have a property called "Color", of type Color32, with a value of whatever color you want at that key-frame. From server JavaScript you can create DOMCurveValue nodes by calling one of the convenient Create*Value methods on a DOMCurveKeyframeExtended. One of the more important things you can do on a DOMCurveValue node is specify an easing function. See the documentation for DOMCurveValue for and DOMCurveEasing for more information.

Setting Up A Curve Player

A curve player is what does the work of animating things in the DOM using your curve as the input data. A player is represented by a DOMCurvePlayer node (DOMCurvePlayerExtended in JavaScript). A player has a Time property that represents it's progress along the curve key-frames in seconds. Technically you can set this property directly, but generally, it's for you to query from JavaScript or look at in Curiosity to see the players progress.

To create a player from JavaScript call the CreatePlayer method on your curve (DOMCurveExtended). You can optionally give the player a title. There are several useful properties on the player. Setting Loop to true ensures that the player will loop around to the first key-frame after it passes the last one. Setting Reverse to true causes the player to run backwards in time (looping when the first key-frame is hit if Loop is true).

Finally, you'll need to tell the player where to project the properties it's animating. You do this by specifying the Selector property on the player node. Briefly, a selector works much like CSS selectors in JQuery or HTML. They help you "select" a set of nodes in the DOM to operate on. In this case, the set of nodes you want the player to affect. Selectors take the form [#NodeID][NodeType][.ClassName][[property='value']]. All of the parts are optional, but there must be at least one part for the selector to be valid. For example: "DOMLight" would select all DOMLights in the scene, ".AnimateMe" would select all nodes with AnimateMe as one of their class values (works like html element class attribute), "DOMLight.AnimateMe" would ensure that only DOMLight nodes with AnimateMe would be affected. For more information see Selectors.

To start a player running, simply call the Run method on it.

If you want the player to run only until the next key-frame and then stop, call the Step method on it. You can pass Step an optional number of key-frames to cross before stopping.

You can call Stop on a player at any time to stop it from running and projecting property values.

How Playing A Curve Works

Once you put a curve in the scene, set up a player, set the player's selector, and called "Run" on the player (or set its Operation to Run in Curiosity) the following takes place:

  • The player initializes a list of properties it will be animating. This list starts out empty.
  • The player starts tracking time from whatever its time was when it started, going forward (or backward if Reverse is true on the player)
  • When the player crosses a key-frame, it reads the DOMCurveValue nodes there.
    • Each node describes a property that will be animated going forward. If it's not in the above list, it's added.
    • The player searches along the keyframes looking for the "next" DOMCurveValue node of the same name, taking into account looping if needed. This value node pair will be used as the player passes between keyframes to interpolate values for this property. The future value node is stored along with this node in the animating properties list.
    • Once a property has been seen once in a DOMCurveValue node on a key-frame it will be always be animated by the player, even if it's value just remains constant since there are no future DOMCurveValues for that property.
    • There does not need to be a DOMCurveValue for each property being animated at every key-frame. You only need a value node if you want to specify a target value for a property at that key-frame.
  • Using the list of properties being animated, each frame, the player calculates current values for each property.
    • A method is called on the current (most recent) DOMCurveValue node passing the next DOMCurveValue and the current player time offset. This method derives the current value for the property using a simple interpolation function specified by the Easing property on the current DOMCurveValue. This method knows how to interpolate most basic primitive types (integers, floating-point, etc) as well as DateTime, Color32, and Point3D. If the property being animated is any other type (like a boolean, or ResourceValue) the value will simply "step" or "snap" from one value to another as keyframes are crossed.
    • The calculated value of each property is set on the DOMCurvePlayer object itself. If you have no Selector set on the player processing stops here.
  • The Selector is evaluated to get a list of all the nodes that should be affected by the player. This is highly optimized by a special SelectorView that helps avoid scene-wide scans for matching nodes on every frame (see DOMSelectorViewMode).
  • Each current property value is projected (set) onto each matching node.

Scripting A Curve

Below is an example that uses manual curve node creation to set up a scene with a few lights, two of which animate and two of which don't. The curve changes the color of the animated lights from red to blue and back again over two seconds. It also projects the color value onto a dummy DOMObject node just to demonstrate how the property projection works.

// Create a curve
var curve = Instance.Scene.CreateCurve("AnimateClubLights");
 
// Create some keyframes
var key1 = curve.CreateKeyframe(0); // start keyframe
var key2 = curve.CreateKeyframe(1); // keyframe at one second
var key3 = curve.CreateKeyframe(2); // keyframe at two seconds
 
// Put some values at the keyframes
var val1 = key1.CreateColorValue("Color","#FF0000");
var val2 = key2.CreateColorValue("Color","#0000FF");
var val3 = key3.CreateColorValue("Color","#FF0000");
 
// Change the easing function on the values
val1.Easing = "Smooth";
val2.Easing = "Smooth";
val3.Easing = "Smooth";
 
// Create some lights around origin. Two of these will have their color animated, two will not. They all start out white.
var light1 = Instance.Scene.CreateLight("#FFFFFF", 1, "Hard", 0.5, {x:-1,y:1,z:1}, {x:0,y:0,z:0});
var light2 = Instance.Scene.CreateLight("#FFFFFF", 1, "Hard", 0.5, {x:-1,y:1,z:-1}, {x:0,y:0,z:0});
var light3 = Instance.Scene.CreateLight("#FFFFFF", 1, "Hard", 0.5, {x:1,y:1,z:1}, {x:0,y:0,z:0});
var light4 = Instance.Scene.CreateLight("#FFFFFF", 1, "Hard", 0.5, {x:1,y:1,z:-1}, {x:0,y:0,z:0});
 
light2.AddClass("AnimateMe");
light3.AddClass("AnimateMe");
 
// Just to demonstrate, this node will receive the animating color value, but because it is not a light, the "Color" property will end up in the generic properties collection rather than the formal Color property
var justForFun = Instance.Scene.CreateEmptyNode(true);
justForFun.Title = "AnimateMeToo";
justForFun.AddClass("AnimateMe");
 
// Create a player to tie everything together and animate the lights
var player = curve.CreatePlayer("ClubLightPlayer");
player.Selector = ".AnimateMe";  // If you made this "DOMLight.AnimateMe", then it would only affect the lights, and not the "AnimateMeToo" object
player.Loop = true;
player.Run();

Here's another example that does the same thing, but uses JSON curve definition instead

// Create a curve from JSON
var curve = Instance.Scene.CreateCurveFromJson(JSON.stringify({
    "Title" : "AnimateClubLights",
    "Keyframes" : [
        {
            "Title" : "Keyframe One",
            "Time" : 0.0,
            "Values" : [{"Name":"Color", "ColorValue":[255,0,0,255], "Easing":4}] // Red
        },
        {
            "Title" : "Keyframe Two",
            "Time" : 1.0,
            "Values" : [{"Name":"Color", "ColorValue":[0,0,255,255], "Easing":4}] // Blue
        },
        {
            "Title" : "Keyframe Three",
            "Time" : 2.0,
            "Values" : [{"Name":"Color", "ColorValue":[255,0,0,255], "Easing":4}] // Red
        },
    ]
}));
 
// Create some lights around origin. Two of these will have their color animated, two will not. They all start out white.
var light1 = Instance.Scene.CreateLight("#FFFFFF", 1, "Hard", 0.5, {x:-1,y:1,z:1}, {x:0,y:0,z:0});
var light2 = Instance.Scene.CreateLight("#FFFFFF", 1, "Hard", 0.5, {x:-1,y:1,z:-1}, {x:0,y:0,z:0});
var light3 = Instance.Scene.CreateLight("#FFFFFF", 1, "Hard", 0.5, {x:1,y:1,z:1}, {x:0,y:0,z:0});
var light4 = Instance.Scene.CreateLight("#FFFFFF", 1, "Hard", 0.5, {x:1,y:1,z:-1}, {x:0,y:0,z:0});
 
light2.AddClass("AnimateMe");
light3.AddClass("AnimateMe");
 
// Just to demonstrate, this node will receive the animating color value, but because it is not a light, the "Color" property will end up in the generic properties collection rather than the formal Color property
var justForFun = Instance.Scene.CreateEmptyNode(true);
justForFun.Title = "AnimateMeToo";
justForFun.AddClass("AnimateMe");
 
// Create a player to tie everything together and animate the lights
var player = curve.CreatePlayer("ClubLightPlayer");
player.Selector = ".AnimateMe";  // If you made this "DOMLight.AnimateMe", then it would only affect the lights, and not the "AnimateMeToo" object
player.Loop = true;
player.Run();

Things To Know