Forces: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: affectors (line 37), effect (line 45), burst (line 54), advance (line 71), check (line 95).
1/// What happens to a particle after it is born: gravity, drag, wind,
2/// turbulence and spin, stacked on the same burst.
3///
4/// Quoted by `particle_modifiers.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 ParticleModifiersDemo extends ShowcaseDemo {
13 late final ParticleSystem _particles;
14 late final ParticleEffect _debrisEffect;
15 late final ParticleContributor _contributor;
16 late final List<ParticleAffector> _affectors;
18 static const int _count = 80;
20 // Turbulence and wind are easiest to read while the debris is still
21 // moving, not once it has settled and stopped.
22 static const double _pause = 1.0;
23 double _cooldown = 0.0;
26 void configureView(DemoContext context) {
27 context.orbit
28 ..distance = 4.5
29 ..pitch = 0.25
30 ..yaw = 0.4;
31 }
34 Scene build(DemoContext context) {
35 _particles = ParticleSystem(capacity: _count, seed: 90210);
37 _affectors = <ParticleAffector>[
38 const ParticleGravity(-4.0),
39 const ParticleDrag(0.4),
40 ParticleWind(Vector3(0.6, 0.0, 0.0)),
41 const ParticleTurbulence(strength: 1.5, scale: 0.8),
42 const ParticleSpin(turnsPerSecond: 1.2),
43 ];
45 _debrisEffect = ParticleEffect(
46 count: _count,
47 emitter: const ConeEmitter(speed: Range(1.5, 3.0), halfAngleDegrees: 30),
48 lifetime: const Range(1.5, 2.5),
49 size: const Range(0.06, 0.1),
50 color: Vector4(0.9, 0.6, 0.3, 1.0),
51 affectors: _affectors,
52 );
54 _particles.burst(
55 _debrisEffect,
56 Vector3.zero(),
57 direction: Vector3(0.0, 1.0, 0.0),
58 );
59 _contributor = context.renderer.addContributor(
60 ParticleContributor(_particles),
61 );
63 return Scene()..add(
64 LightNode(name: 'sun', intensity: 2.0)
65 ..setLocalForward(Vector3(-0.3, -0.6, -0.4)),
66 );
67 }
70 void update(DemoContext context, double dt) {
71 _particles.advance(dt);
73 if (_particles.aliveCount == 0) {
74 _cooldown -= dt;
75 if (_cooldown <= 0.0) {
76 _particles.burst(
77 _debrisEffect,
78 Vector3.zero(),
79 direction: Vector3(0.0, 1.0, 0.0),
80 );
81 _cooldown = _pause;
82 }
83 }
84 }
87 void verify(Scene scene, FrameResult frame) {
88 if (_particles.aliveCount != _count) {
89 throw StateError('expected $_count live particles');
90 }
91 if (!_contributor.isActive || frame.drawCalls < 1) {
92 throw StateError('the burst did not reach the frame');
93 }
95 // The system keeps no public handle on one particle, so the claim is
96 // checked the way the affectors are unit tested: run the same list
97 // against a particle of our own and read what changed.
98 final Particle sample = Particle()
99 ..velocity.setValues(0.0, 2.0, 0.0)
100 ..age = 0.4
101 ..lifetime = 2.0
102 ..seed = 0.5;
103 for (final ParticleAffector affector in _affectors) {
104 affector.apply(sample, 1 / 60);
105 }
106 if (sample.velocity.y >= 2.0) {
107 throw StateError('gravity and drag should have slowed the climb');
108 }
109 if (sample.rotation == 0.0) {
110 throw StateError('spin should have turned the particle');
111 }
112 }
113}