dev-docs/RFCs/v5.1/attribute-transition-rfc.md
Notes:
Adding animation to an visualization, especially an interactive visualization, can take it from great to extraordinary impact. Well-executed animations add a very deep level of polish and interest. However implementing good animations often requires considerable custom work in applications.
The goal of this supplementary RFC is to investigate to what extend deck.gl could provide automatic interpolation of vertex attribute arrays, in addition to primitive values like properties, uniforms and gl parameters.
When attribute updates are triggered by data change or updateTriggers, instead of immediately applying the new values, transition the current values to the new values by user-specified duration and easing curve.
In deck.gl layer classes, certain attributes can declare themselves to be "transition-enabled", which allows for smooth interpolation when their values are updated. This will be desirable for attributes such as sizes, widths, positions and colors. Animation will be an opt-in feature for backward compatibility:
this.state.attributeManager.add({
// Transition disabled
pickingColors: {size: 3, type: GL.UNSIGNED_BYTE, update: this.calculatePickingColors},
// Transition enabled
positions: {size: 3, accessor: 'getPosition', update: this.calculatePositions, transition: true},
colors: {size: 4, type: GL.UNSIGNED_BYTE, accessor: 'getColor', update: this.calculateColors, transition: true}
});
When creating layers, animation can be enabled by supplying an transition prop. Animation parameters are defined per attribute by using attribute names or accessor names as keys, similar to that of updateTriggers:
new Layer({
transition: {
getPositions: 600,
getColors: {
duration: 300,
easing: d3.easeCubicInOut
}
}
});
| Parameter | Type | Default | Description |
|---|---|---|---|
| duration | Number | 0 | Duration of the transition animation, in milliseconds |
| easing | Function | LINEAR (t => t) | Easing function that maps a value from [0, 1] to [0, 1], see http://easings.net/ |
| onStart | Function | null | Callback when the transition is started |
| onEnd | Function | null | Callback when the transition is done |
| onInterrupt | Function | null | Callback when the transition is interrupted |
As a shorthand, if an accessor key maps to a number rather than an object, then the number is assigned to the duration parameter.
Layer class will ensure that the animated attributes are updated every render cycle before the draw call.VertexShader pros:
VertexShader cons:
If interpolation is done on the CPU: both calculating the attribute and uploading it to the GPU will be expensive. Framerate is expected to stagger.
If using TransformFeedback:
Enter (appearance of new geometries) may happen when: A The layer is added or becomes visible B The data array is changed with increased size
Exit (disappearance of rendered geometries) may happen when: C The layer is removed or becomes invisible D The data array is changed with decreased size
Without adding additional complexity, the most straightforward behavior is no enter/exit animation - geometries appear and disappear immediately. The application can create their own enter/exit animations by adding invisible objects (e.g. instead of removing objects from the data array, change their colors to transparent).
To support enter/exit animations, we may consider one of the following:
voidValue to attribute definitions. This could be a value or an transform to be used for enter animation to transition from, or exit animation to transition to.this.state.attributeManager.add({
// Animatable
radius: {size: 1, accessor: 'getRadius', update: this.calculateRadius, animate: true, voidValue: 0},
colors: {size: 4, type: GL.UNSIGNED_BYTE, accessor: 'getColor', update: this.calculateColors, animate: true, voidValue: ([r, g, b, a]) => [r, g, b, 0]}
});
enter and exit to animation parameters. The attribute updater is called with these functions substituting the regular accessor to retrieve the from value for enter animation, or to value for exit animation. This allows the user to control the animations per object, but more expensive at run time:new Layer({
transition: {
getColors: {
duration: 300,
enter: feature => feature.properties.fill.concat(0),
exit: feature => feature.properties.fill.concat(0)
}
}
});
To properly support scenario B and D, we need a way to diff data arrays to determine which objects are added/removed.
In the current layer management logic, C is very difficult to handle, as the layer is immediately removed from the render.
updateTriggers moved away from using attribute names so that the user does not need to know the underlying implementation of each layer. Using accessor names to substitute attribute names may introduce confusing behavior in animation, though:
instancePosition (getSourcePosition and getTargetPosition), will use animation parameters defined by one of the accessor names. No separate control though the semantic suggests so.PathLayer and PolygonLayer do not animate correctly using this method when the vertex count of data changes. I believe the animation feature still has a ton of value even if these layers are excluded at the release, but I expect the support to be highly desired by our users.