This PR introduces a new Anim component to the engine which can be used to anima…te the entity of the component its attached to. A state graph must also be supplied to the component which defines a set of animation states that can be active, when those states should activate and what the blending between new and old states should look like. Animation assets can then be linked to each state in the graph, after which the component will become playable. These animations can be supplied as either GLB assets (for model animations) or animation clips (to animate arbitrary properties of the components entity and its children).
**Anim Component API:**
```javascript
// play the animations in the layer state. If a name is provided then play from that state
pc.AnimComponentLayer#play: function (name);
// pause animations in the layer
pc.AnimComponentLayer#pause: function ();
// reset the layer to its initial state
pc.AnimComponentLayer#reset: function ();
// assigns a given AnimTrack to the state node defined by nodeName. Must be called after loadStateGraph.
pc.AnimComponentLayer#assignAnimation: function (nodeName, animTrack);
// clears all animations from a given state node in the provided layer.
pc.AnimComponentLayer#removeNodeAnimations: function (nodeName);
// name of the layer
pc.AnimComponentLayer#name: string;
// whether the laying is currently playing
pc.AnimComponentLayer#playing: boolean;
// whether all states have an animTrack assigned to them
pc.AnimComponentLayer#playable: boolean;
// the name of the state that is currently playing
pc.AnimComponentLayer#activeState: string;
// the name of the state that was previously playing
pc.AnimComponentLayer#previousState: string;
// the amount of time progressed through the current states animation, normalised to the animations duration
pc.AnimComponentLayer#activeStateProgress: number;
// returns whether the anim component is currently transitioning between states
pc.AnimComponentLayer#transitioning: boolean;
// if the anim component is transitioning, returns the progress of the transition
pc.AnimComponentLayer#transitionProgress: number;
// returns a list of available states in the layer
pc.AnimComponentLayer#states: string[];
// loads in a state graph object which is the resource of an animationstategraph asset
pc.AnimComponent#loadStateGraph: function (stateGraph);
// removes a state graph from the component, clearing all layers and parameters
pc.AnimComponent#removeStateGraph: function ();
// resets all layers and parameters to their initial state
pc.AnimComponent#reset: function ();
// finds an animation layer by name
pc.AnimComponent#findAnimationLayer: function (layerName);
// assigns a given AnimTrack to the state node defined by nodeName. Must be called after loadStateGraph. If layerName is omitted the DEFAULT_LAYER is used
pc.AnimComponent#assignAnimation: function (nodeName, animTrack, layerName);
// clears all animations from a given state node in the provided layer. If layerName is omitted the DEFAULT_LAYER is used
pc.AnimComponent#removeNodeAnimations: function (nodeName, layerName);
// getters and setters for state graph parameters
pc.AnimComponent#getInteger: function (name);
pc.AnimComponent#setInteger: function (name, value);
pc.AnimComponent#getFloat: function (name);
pc.AnimComponent#setFloat: function (name, value);
pc.AnimComponent#getBoolean: function (name);
pc.AnimComponent#setBoolean: function (name, value);
pc.AnimComponent#getTrigger: function (name);
pc.AnimComponent#setTrigger: function (name);
pc.AnimComponent#resetTrigger: function (name);
// resets the animation state graph to its initial state, including all parameters
pc.AnimComponent#reset: function ();
// the speed property multiplies the speed of any animation that is currently playing
pc.AnimComponent#speed: number;
// enables / disables the component
pc.AnimComponent#enabled: boolean;
// allows updates to all animation layers
pc.AnimComponent#playing: boolean;
// if set to true the anim component will begin playing the first animation once all states have been linked
pc.AnimComponent#activate: boolean;
// returns whether all component layers are currently playable
pc.AnimComponent#playable: boolean;
// the state graph asset this component should use to generate it's animation state graph
pc.AnimComponent#stateGraphAsset: number;
```
**State Graph Asset Example:**
```json
{
"states": [
{
"name": "ANIM_STATE_START"
},
{
"name": "Idle",
"speed": 1.0
},
{
"name": "Walk",
"speed": 1.0
},
{
"name": "ANIM_STATE_END"
}
],
"transitions": [
{
"from": "ANIM_STATE_START",
"to": "Idle"
},
{
"from": "Idle",
"to": "Walk",
"time": 0.5,
"conditions": [
{
"parameterName": "speed",
"predicate": "GREATER_THAN",
"value": 0
}
]
},
{
"from": "Walk",
"to": "Idle",
"time": 0.5,
"exitTime": 0.8,
"conditions": [
{
"parameterName": "speed",
"predicate": "LESS_THAN_EQUAL_TO",
"value": 0
}
]
}
],
"parameters": {
"speed": {
"type": "INTEGER",
"value": 0
}
}
}
```
**Character animation example using the state graph asset example above:**
```javascript
// add model and anim components to the character entity
character.addComponent("model", {
type: "asset",
asset: characterModelAsset.resource.model,
castShadows: true
});
character.addComponent("anim", {
speed: 1.0,
activate: false
});
// load the state graph and fully link all states to their animations
character.anim.loadStateGraph(characterStateGraphAsset.resource);
character.anim.assignAnimation('Idle', characterIdleAnimationAsset.resource);
character.anim.assignAnimation('Walk', characterWalkAnimationAsset.resource);
// begin playing Idle animation
character.anim.playing = true;
// create a function to set the charater to run for 5 seconds
var fiveSecondRun = function() {
character.anim.setInteger('speed', 1);
window.setTimeout(function() {
character.anim.setInteger('speed', 0);
}, 5000);
}
```
**State Graph Asset Schema:**
```json
{
"state": {
"type": "object",
"description": "Defines a state which will become a node in the state graph",
"properties": {
"name": {
"type": "String",
"description": "The name of the state. Used to link animations to a specific state and find states used in transitions"
},
"speed": {
"type": "Number",
"description": "The speed at which the animation linked to this state should play at"
}
},
"required": ["name"]
}
}
```
```json
{
"transition": {
"type": "object",
"description": "Defines a state to state transition",
"properties": {
"from": {
"type": "string",
"description": "The state that this transition will exit from"
},
"to": {
"type": "string",
"description": "The state that this transition will transition to"
},
"time": {
"type": "number",
"description": "The duration of the transition in seconds"
},
"priority": {
"type": "number",
"description": "Used to sort all matching transitions in ascending order. The first transition in the list will be selected."
},
"conditions": {
"type": "array[condition]",
"description": "A list of conditions which must pass for this transition to be used"
},
"exitTime": {
"type": "number",
"description": "If provided, this transition will only be active for the exact frame during which the source states progress passes the time specified. Given as a normalised value of the source states duration. Values less than 1 will be checked every animation loop"
},
"transitionOffset": {
"type": "number",
"description": "If provided, the destination state will begin playing its animation at this time. Given in seconds."
},
"interruptionSource": {
"type": "number",
"description": "Defines whether another transition can interrupt this one and which of the current or previous states transitions can do so. One of pc.ANIM_INTERRUPTION_SOURCE_*"
}
},
"required": ["from", "to"]
}
}
```
```json
{
"condition": {
"type": "object",
"description": "A condition used to test whether a transition is active",
"properties": {
"parameterName": {
"type": "string",
"description": "The parameter that should be tested against"
},
"predicate": {
"type": "number",
"description": "Defines the operation used to test the current parameters value against. One of pc.ANIM_TRANSITION_PREDICATE_*"
},
"value": {
"type": ["number", "boolean"],
"description": "Defines the value used to test the current parameters value against."
}
},
"required": ["parameterName", "predicate", "value"]
}
}
```
```json
{
"parameter": {
"type": "object",
"description": "Defines a parameter used to conditionally active transitions",
"properties": {
"type": {
"type": "number",
"description": "Defines the type of the parameter. One of pc.ANIM_PARAMETER_"
},
"value": {
"type": ["number", "boolean"],
"description": "The default value to set the parameter to when the anim controller is created and reset"
}
},
"required": ["type", "value"]
}
}
```
**Animation Clip:**
These are used to create `pc.AnimTrack` objects which can be passed into the `linkAnimationToState` function. The track can be retrieved from the resource of an animation clip `.json` asset that has been loaded using the `animationclip` asset type.
The example below flashes the light component of the signalLight entity red every quarter of a second.
```json
{
"name": "AlertLight",
"duration": 0.25,
"inputs": [
[
0.0,
0.5,
1.0
]
],
"outputs": [
{
"components": 1,
"data": [
0.0,
1.0,
0.0
]
}
],
"curves": [
{
"path": "signalLight/light/color.r",
"inputIndex": 0,
"outputIndex": 0,
"interpolation": 1
}
]
}
```
Each curve in an animation is associated with an input and output array via index. These arrays define the keyframe pairs for the curve (input = time, output = value). In the clip above the single curve is using the only input and output arrays defined.
Curve path strings should be encoded and decoded using an instance of `pc.AnimPropertyLocator()`. Using this class ensures that all names have their forward slash and full stop characters correctly escaped. It provides two functions which can be used as follows:
```javascript
var propertyLocator = new pc.AnimPropertyLocator();
var lightLocation = [['signalLight'], 'light', ['color', 'r']]
// the encode function takes a three value array:
//The first value contains an array of entity names which defines the path to an entity in the scene from the current components entity.
//The second value contains the name of the component within which the animated property exists.
//The third value contains an array of property names which defines the path to the property value in the component.
var lightPath = propertyLocator.encode(lightLocation);
console.log(lightPath); // "signalLight/light/color.r"
var decodedLightLocation = propertyLocator.decode(lightPath);
console.log(decodedLightLocation); // [['signalLight'], 'light', ['color', 'r']]
```
**Engine examples:**
Done: Animate component properties
Done: Character animation