update method
Called once per frame while the component is mounted, enabled, and
loaded. deltaSeconds is the elapsed time since the previous tick.
Implementation
@override
void update(double deltaSeconds) {
_time += deltaSeconds;
final world = node.globalTransform.getTranslation();
if (emitting) {
if (_worldPoints.isEmpty) {
_worldPoints.insert(0, world.clone());
_bornTimes.insert(0, _time);
} else {
// The head follows the node continuously; a new anchor is dropped
// once it has traveled far enough from the previous one.
_worldPoints.first.setFrom(world);
_bornTimes[0] = _time;
final anchored = _worldPoints.length > 1
? _worldPoints[1]
: _worldPoints.first;
if (_worldPoints.length == 1 ||
anchored.distanceTo(world) >= minVertexDistance) {
_worldPoints.insert(0, world.clone());
_bornTimes.insert(0, _time);
}
}
}
// Expire by age and by capacity (head stays, tail goes).
while (_worldPoints.length > _geometry.maxPoints ||
(_bornTimes.isNotEmpty && _time - _bornTimes.last > lifetime)) {
_worldPoints.removeLast();
_bornTimes.removeLast();
if (_worldPoints.isEmpty) break;
}
// Repack into node-local space (the mesh renders under the moving
// node's transform, but the ribbon must stay pinned in the world).
_scratchInverse.setFrom(node.globalTransform);
_scratchInverse.invert();
_geometry.setTrail(
_worldPoints,
_scratchInverse,
(t) => width * (widthOverTrail?.sample(t) ?? (1.0 - t)),
(t, out) {
final gradient = colorOverTrail;
if (gradient != null) {
gradient.sample(t, out);
} else {
out.setValues(1, 1, 1, 1.0 - t);
}
return out;
},
_scratchColor,
);
}