Creating Your Own Animation Algorithms
An animation algorithm is a formula that describes the motion of a variable. Each animation algorithm is processed by a MotiveProcessor
.
A MotiveProcessor
holds the data for every Motivator
that uses its algorithm. Once per frame, MotiveProcessor::AdvanceFrame
is called and all Motivators
are updated, in bulk. This bulk storage and processing allows us to optimize with SIMD and multiple threads, which is important for scalability.
You can add your own custom animation algorithms by deriving classes from one of the MotiveProcessor
subclasses. To create a class that animates a one-dimensional variable, derive from MotiveProcessor1f
. For a 4x4 matrix variable, derive from MatrixProcessor4f
.
Example
Each MotiveProcessor
subclass has its own interface that must be instantiated by your derivation. Additionally, there are macros MOTIVE_INTERFACE() and MOTIVE_INSTANCE() that you must add to your code.
For example, here is a simple one-dimensional algorithm that moves linearly from the current value to the target value, in the target time.
Put your initialization code in a header file:
class LinearInit : public MotivatorInit {
public:
LinearInit() : MotivatorInit(kType) {}
MOTIVE_INTERFACE();
};
The remaining code can be in a .cpp file. There is no need to expose it, since it gets registered with the MotiveEngine
, and all calls to it come through MotiveEngine
.
class LinearMotiveProcessor : public MotiveProcessorNf {
public:
virtual ~LinearMotiveProcessor() {}
virtual void AdvanceFrame(MotiveTime delta_time) {
for (size_t i = 0; i < data_.size(); ++i) {
LinearData& d = data_[i];
float& value = values_[i];
if (d.target_time > 0.0f) {
const float percent_complete = delta_time / d.target_time;
value = mathfu::Lerp(value, d.target_value, percent_complete);
} else {
value = d.target_value;
}
d.target_time -= delta_time;
}
}
virtual MotivatorType Type() const { return LinearInit::kType; }
virtual int Priority() const { return 0; }
virtual const float* Values(MotiveIndex index) const {
return &values_[index];
}
virtual void Velocities(MotiveIndex index, MotiveDimension dimensions,
float* out) const {
const LinearData* d = &Data(index);
const float* values = &values_[index];
for (MotiveDimension i = 0; i < dimensions; ++i, ++d, ++values) {
out[i] = d->target_time <= 0.0f ? 0.0f : (d->target_value - *values) /
d->target_time;
}
}
virtual void TargetValues(MotiveIndex index, MotiveDimension dimensions,
float* out) const {
const LinearData* d = &Data(index);
for (MotiveDimension i = 0; i < dimensions; ++i, ++d) {
out[i] = d->target_value;
}
}
virtual void TargetVelocities(MotiveIndex , MotiveDimension dimensions,
float* out) const {
for (MotiveDimension i = 0; i < dimensions; ++i) {
out[i] = 0.0f;
}
}
virtual void Differences(MotiveIndex index, MotiveDimension dimensions,
float* out) const {
const LinearData* d = &Data(index);
const float* values = &values_[index];
for (MotiveDimension i = 0; i < dimensions; ++i, ++d, ++values) {
out[i] = d->target_value - *values;
}
}
virtual MotiveTime TargetTime(MotiveIndex index,
MotiveDimension dimensions) const {
MotiveTime greatest = std::numeric_limits<MotiveTime>::min();
for (MotiveDimension i = 0; i < dimensions; ++i) {
greatest = std::max(greatest,
static_cast<MotiveTime>(Data(index).target_time));
}
return greatest;
}
virtual void SetTargets(MotiveIndex index, MotiveDimension dimensions,
const MotiveTarget1f* ts) {
for (int i = 0; i < dimensions; ++i) {
LinearData& d = Data(index + i);
const MotiveTarget1f& t = ts[i];
const MotiveNode1f& node0 = t.Node(0);
const bool override_current = node0.time == 0;
if (override_current) {
values_[index + i] = node0.value;
}
const MotiveNode1f* target_node =
override_current ? (t.num_nodes() > 1 ? &t.Node(1) : nullptr)
: &node0;
if (target_node != nullptr) {
d.target_value = target_node->value;
d.target_time = static_cast<float>(target_node->time);
}
}
}
protected:
struct LinearData {
float target_value;
float target_time;
LinearData() { Reset(); }
void Reset() {
target_value = 0.0f;
target_time = 0.0f;
}
};
virtual void InitializeIndices(const MotivatorInit& init, MotiveIndex index,
MotiveDimension dimensions,
MotiveEngine* ) {
(void)init;
assert(init.type() == LinearInit::kType);
for (MotiveIndex i = index; i < index + dimensions; ++i) {
Data(i).Reset();
values_[i] = 0;
}
}
virtual void RemoveIndices(MotiveIndex index, MotiveDimension dimensions) {
for (MotiveIndex i = index; i < index + dimensions; ++i) {
Data(i).Reset();
values_[i] = 0;
}
}
virtual void MoveIndices(MotiveIndex old_index, MotiveIndex new_index,
MotiveDimension dimensions) {
MotiveIndex old_i = old_index;
MotiveIndex new_i = new_index;
for (MotiveDimension i = 0; i < dimensions; ++i, ++new_i, ++old_i) {
data_[new_i] = data_[old_i];
values_[new_i] = values_[old_i];
}
}
virtual void SetNumIndices(MotiveIndex num_indices) {
data_.resize(num_indices);
values_.resize(num_indices);
}
const LinearData& Data(MotiveIndex index) const {
assert(ValidIndex(index));
return data_[index];
}
LinearData& Data(MotiveIndex index) {
assert(ValidIndex(index));
return data_[index];
}
std::vector<LinearData> data_;
std::vector<float> values_;
};
MOTIVE_INSTANCE(LinearInit, LinearMotiveProcessor);
The MOTIVE_INSTANCE() macro sets up the LinearMotiveProcessor to be registered with the MotiveEngine
, but it does not actually register it. The registration must happen in the main program.
The Register()
call is under LinearInit
since it is generally the only thing exposed in a header file.
Now that your processor is registered, you can instantiate a motivator like so,
const MotiveTarget1f target = motive::CurrentToTarget1f(10, 0, -5, 0, 100);
Motivator1f linear_motivator(LinearInit(), &engine, target);
Your processor gets updated with all the other processors, during MotiveEngine::AdvanceFrame()
.
std::vector<vec2> points;
points.reserve(target.EndTime() + 1);
for (MotiveTime t = 0; t <= target.EndTime(); ++t) {
points.push_back(vec2(static_cast<float>(t), linear_motivator.Value()));
engine.AdvanceFrame(1);
}
printf("\n%s",
motive::Graph2DPoints(&points[0], static_cast<int>(points.size()))
.c_str());
The final output of our example is below. Notice that the variable animates linearly from 10 down to -5.
y = 10.000000
|
**
| ***
| **
| ***
| ***
| **
| ***
| ***
| ***
| **
| ***
| ***
| ***
| **
| ****
| ***
| **
| ***
| ***
|--------------------------------------------------***---------------------------
| **
| ***
| ***
| ***
| **
| ***
| ***
| **
| ***
| **
y = -5.000000
If you'd like to experiment more with this program, it is compiled for you from src/samples/linear_processor/linear_processor.cpp
.