Over-life curves: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: curve (line 39), gradient (line 48), effect (line 54), check (line 98).
1/// Size and colour described as curves over a particle's life, instead of a
2/// pair of affectors that can only ever go from one number to another.
3///
4/// Quoted by `particle_curves.md` and shown whole in the Source tab.
5library;
7import 'package:flutter3d/flutter3d.dart';
8import 'package:flutter3d_particles/flutter3d_particles.dart';
9import 'package:flutter3d_showcase/src/demo/demo.dart';
10import 'package:vector_math/vector_math.dart';
12final class ParticleCurvesDemo extends ShowcaseDemo {
13 late final ParticleSystem _particles;
14 late final ParticleEffect _smokeEffect;
15 late final ParticleContributor _contributor;
16 late final ParticleCurve _size;
17 late final ParticleGradient _color;
19 static const int _count = 60;
21 // The curve and the gradient are the whole point of this page, and both
22 // finish fading to nothing by the end of a particle's life — a burst that
23 // never repeated would leave nothing on screen to read them from.
24 static const double _pause = 1.0;
25 double _cooldown = 0.0;
28 void configureView(DemoContext context) {
29 context.orbit
30 ..distance = 4.0
31 ..pitch = 0.2
32 ..yaw = 0.5;
33 }
36 Scene build(DemoContext context) {
37 _particles = ParticleSystem(capacity: _count, seed: 4242);
39 // Puffs up to one and a half times its birth size by the middle of its
40 // life, then shrinks away, easing smoothly through the peak rather than
41 // arriving at it with a corner.
42 _size = ParticleCurve(<CurveKey>[
43 CurveKey(0.0, 0.4, ease: KeyEase.smooth),
44 CurveKey(0.5, 1.5, ease: KeyEase.smooth),
45 CurveKey(1.0, 0.0),
46 ]);
48 _color = ParticleGradient(<GradientKey>[
49 GradientKey(0.0, Vector4(1.0, 0.95, 0.6, 1.0)),
50 GradientKey(0.4, Vector4(1.0, 0.4, 0.1, 1.0), ease: KeyEase.smooth),
51 GradientKey(1.0, Vector4(0.2, 0.05, 0.05, 0.0)),
52 ]);
54 _smokeEffect = ParticleEffect(
55 count: _count,
56 emitter: const DriftEmitter(speed: Range(0.4, 0.9)),
57 lifetime: const Range(1.2, 2.0),
58 size: const Range(0.08, 0.12),
59 color: Vector4(1.0, 1.0, 1.0, 1.0),
60 affectors: <ParticleAffector>[
61 ParticleSizeCurve(_size),
62 ParticleColorGradient(_color),
63 ],
64 );
66 _particles.burst(_smokeEffect, Vector3.zero());
67 _contributor = context.renderer.addContributor(
68 ParticleContributor(_particles),
69 );
71 return Scene()..add(
72 LightNode(name: 'sun', intensity: 2.0)
73 ..setLocalForward(Vector3(-0.3, -0.6, -0.4)),
74 );
75 }
78 void update(DemoContext context, double dt) {
79 _particles.advance(dt);
81 if (_particles.aliveCount == 0) {
82 _cooldown -= dt;
83 if (_cooldown <= 0.0) {
84 _particles.burst(_smokeEffect, Vector3.zero());
85 _cooldown = _pause;
86 }
87 }
88 }
91 void verify(Scene scene, FrameResult frame) {
92 if (_particles.aliveCount != _count ||
93 !_contributor.isActive ||
94 frame.drawCalls < 1) {
95 throw StateError('the smoke did not reach the frame');
96 }
98 // The curve holds its end values outside its keys and rises then falls
99 // in between, which is what "puffs up, then shrinks" means as numbers.
100 if (_size.sample(0.0) >= _size.sample(0.5) ||
101 _size.sample(0.5) <= _size.sample(1.0)) {
102 throw StateError('the size curve should rise then fall');
103 }
104 // The gradient fades to nothing: alpha at the last key is zero.
105 final Vector4 end = Vector4.zero();
106 _color.sampleInto(end, 1.0);
107 if (end.w != 0.0) {
108 throw StateError('the gradient should fade to nothing');
109 }
110 }
111}