forked from CelestiaProject/Celestia
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparticlesystem.cpp
More file actions
510 lines (421 loc) · 16.1 KB
/
particlesystem.cpp
File metadata and controls
510 lines (421 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
// particlesystem.cpp
//
// Stateless particle system renderer.
//
// Copyright (C) 2008, Chris Laurel <claurel@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
#include "celutil/util.h"
#include <cmath>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <limits>
#include "modelgeometry.h"
#include "particlesystem.h"
#include "glsupport.h"
#include "vecgl.h"
#include "rendcontext.h"
#include "texmanager.h"
using namespace cmod;
using namespace Eigen;
using namespace std;
/* !!! IMPORTANT !!!
* The particle system code is still under development; the complete
* set of particle system features has not been decided and the cpart
* format is not even close time final. There are most certainly bugs.
* DO NOT enable this code and invest a lot of time in creating your
* own particle system files until development is further along.
*/
/* STATELESS PARTICLE SYSTEMS
*
* THEORY
* In a typical particle system, initial particle states are generated
* and stored in array. At each time step, particles have their states
* updated and are then drawn. A typical sequence is:
* - Compute the forces acting on the particles
* - Update particle positions and velocities
* - Age the particles (updating state such as color and size)
* - Render the particles
*
* This process is well-suited to a simulation where the time steps are
* relatively uniform. But, we cannot rely on a uniform time step in Celestia.
* The user may skip ahead instantly to times in the distance past or future,
* change the time rate to over a billion times normal, or reverse time.
* Numerical integration of particle positions is completely impractical.
*
* Instead, Celestia uses 'stateless' particle systems. From the particle
* system description, the particle positions and appearances can be generated
* for any time. Initial states are generated from a pseudorandom sequence
* and state at the current time is computed analytically from those initial
* values. The fact that motions must be calculated analytically means that
* only very simply force models may be used; still, a large variety of
* effects are still practical.
*
* A particle system is just a list of particle emitters. Each emitter has
* a fixed emission rate (particles per second), start time, and end time.
* There are few properties that apply to all particles produced by an emitter:
* texture, lifetime, start/end color, and start/end size
* Color and size are linearly interpolated between start and end values over
* the lifetime of a particle.
* An emitter has two different 'generators': one produces initial particle
* velocities, the other initial positions.
*
* Emitter generators are fed with values from a linear congruential
* generator. Other pseudorandom number generators can produce sequences with
* better distributions and can have better performance at generating values.
* However, seeding these other generators is very slow, and seeding must
* be every time a particle is to be drawn. The well-known defects in
* pseudorandom sequences produced by an LCG are not visible in a particle
* system (and lack of apparent visual artifacts is the *only* requirement here.)
*/
// Same values as rand48()
static const uint64_t A = ((uint64_t) 0x5deece66ul << 4) | 0xd;
static const uint64_t C = 0xb;
static const uint64_t M = ((uint64_t) 1 << 48) - 1;
/*! Linear congruential random number generator that emulates
* rand48()
*/
class LCGRandomGenerator
{
public:
LCGRandomGenerator() : previous(0) = default;
LCGRandomGenerator(uint64_t seed) :
previous(seed)
{
}
uint64_t randUint64()
{
previous = (A * previous + C) & M;
return previous;
}
/*! Return a random integer between -2^31 and 2^31 - 1
*/
int32_t randInt32()
{
return (int32_t) (randUint64() >> 16);
}
/*! Return a random integer between 0 and 2^32 - 1
*/
uint32_t randUint32()
{
return (uint32_t) (randUint64() >> 16);
}
/*! Generate a random floating point value in [ 0, 1 )
* This function directly manipulates the bits of a floating
* point number, and will not work properly on a system that
* doesn't use IEEE754 floats.
*/
float randFloat()
{
uint32_t randBits = randInt32();
randBits = (randBits & 0x007fffff) | 0x3f800000;
return *reinterpret_cast<float*>(&randBits) - 1.0f;
}
/*! Generate a random floating point value in [ -1, 1 )
* This function directly manipulates the bits of a floating
* point number, and will not work properly on a system that
* doesn't use IEEE754 floats.
*/
float randSfloat()
{
uint32_t randBits = (uint32_t) (randUint64() >> 16);
randBits = (randBits & 0x007fffff) | 0x40000000;
return *reinterpret_cast<float*>(&randBits) - 3.0f;
}
private:
uint64_t previous;
};
/**** Generator implementations ****/
Vector3f
ConstantGenerator::generate(LCGRandomGenerator& /* gen */) const
{
return m_value;
}
Vector3f
BoxGenerator::generate(LCGRandomGenerator& gen) const
{
return Vector3f(gen.randSfloat() * m_semiAxes.x(),
gen.randSfloat() * m_semiAxes.y(),
gen.randSfloat() * m_semiAxes.z()) + m_center;
}
Vector3f
LineGenerator::generate(LCGRandomGenerator& gen) const
{
return m_origin + m_direction * gen.randFloat();
}
Vector3f
EllipsoidSurfaceGenerator::generate(LCGRandomGenerator& gen) const
{
float theta = (float) PI * gen.randSfloat();
float cosPhi = gen.randSfloat();
float sinPhi = std::sqrt(1.0f - cosPhi * cosPhi);
if (cosPhi < 0.0f)
sinPhi = -sinPhi;
float s = std::sin(theta);
float c = std::cos(theta);
return Vector3f(sinPhi * c * m_semiAxes.x(), sinPhi * s * m_semiAxes.y(), cosPhi * m_semiAxes.z()) + m_center;
}
Vector3f
ConeGenerator::generate(LCGRandomGenerator& gen) const
{
float theta = (float) PI * gen.randSfloat();
float cosPhi = 1.0f - m_cosMinAngle - gen.randFloat() * m_cosAngleVariance;
float sinPhi = std::sqrt(1.0f - cosPhi * cosPhi);
if (cosPhi < 0.0f)
sinPhi = -sinPhi;
float s = std::sin(theta);
float c = std::cos(theta);
return Vector3f(sinPhi * c, sinPhi * s, cosPhi) * (m_minLength + gen.randFloat() * m_lengthVariance);
}
Vector3f
GaussianDiscGenerator::generate(LCGRandomGenerator& gen) const
{
float r1 = 0.0f;
float r2 = 0.0f;
float s = 0.0f;
do
{
r1 = gen.randSfloat();
r2 = gen.randSfloat();
s = r1 * r1 + r2 * r2;
} while (s > 1.0f);
// Choose angle uniformly distributed in [ 0, 2*PI ), radius
// with a Gaussian distribution. Use the polar form of the
// Box-Muller transform to produce a normally distributed
// random number.
float r = r1 * std::sqrt(-2.0f * std::log(s) / s) * m_sigma;
float theta = r2 * 2.0f * (float) PI;
return Vector3f(r * std::cos(theta), r * std::sin(theta), 0.0f);
}
ParticleEmitter::ParticleEmitter() :
m_startTime(-numeric_limits<double>::infinity()),
m_endTime(-numeric_limits<double>::infinity()),
m_texture(InvalidResource),
m_rate(1.0f),
m_lifetime(1.0f),
m_startColor(1.0f, 1.0f, 1.0f, 0.0f),
m_startSize(1.0f),
m_endColor(1.0f, 1.0f, 1.0f, 0.0f),
m_endSize(1.0f),
m_positionGenerator(nullptr),
m_velocityGenerator(nullptr),
m_acceleration(Vector3f::Zero()),
m_nonZeroAcceleration(false),
m_minRotationRate(0.0f),
m_rotationRateVariance(0.0f),
m_rotationEnabled(false),
m_blendMode(cmod::Material::PremultipliedAlphaBlend)
{
}
ParticleEmitter::~ParticleEmitter()
{
delete m_positionGenerator;
delete m_velocityGenerator;
}
void
ParticleEmitter::setLifespan(double startTime, double endTime)
{
m_startTime = startTime;
m_endTime = endTime;
}
void
ParticleEmitter::setRotationRateRange(float minRate, float maxRate)
{
m_rotationEnabled = minRate != 0.0f || maxRate != 0.0f;
m_minRotationRate = minRate;
m_rotationRateVariance = maxRate - minRate;
}
void
ParticleEmitter::setAcceleration(const Vector3f& acceleration)
{
m_acceleration = acceleration;
m_nonZeroAcceleration = m_acceleration != Vector3f::Zero();
}
void
ParticleEmitter::setBlendMode(cmod::Material::BlendMode blendMode)
{
m_blendMode = blendMode;
}
static const uint64_t scrambleMask = (uint64_t(0xcccccccc) << 32) | 0xcccccccc;
void
ParticleEmitter::render(double tsec,
RenderContext& rc,
ParticleVertex* particleBuffer,
unsigned int particleBufferCapacity) const
{
double t = tsec;
bool startBounded = m_startTime > -numeric_limits<double>::infinity();
bool endBounded = m_endTime < numeric_limits<double>::infinity();
// Return immediately if we're far enough past the end time that no
// particles remain.
if (endBounded)
{
if (t > m_endTime + m_lifetime)
return;
}
// If a start time is specified, set t to be relative to the start time.
// Return immediately if we haven't reached the start time yet.
if (startBounded)
{
t -= m_startTime;
if (t < 0.0)
return;
}
Matrix3f modelViewMatrix = rc.getCameraOrientation().conjugate().toRotationMatrix();
rc.setMaterial(&m_material);
Vector3f v0 = modelViewMatrix * Vector3f(-1.0f, -1.0f, 0.0f);
Vector3f v1 = modelViewMatrix * Vector3f( 1.0f, -1.0f, 0.0f);
Vector3f v2 = modelViewMatrix * Vector3f( 1.0f, 1.0f, 0.0f);
Vector3f v3 = modelViewMatrix * Vector3f(-1.0f, 1.0f, 0.0f);
Texture* texture = nullptr;
if (m_texture != InvalidResource)
{
texture = GetTextureManager()->find(m_texture);
}
if (texture != nullptr)
texture->bind();
// Use premultiplied alpha
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glDepthMask(GL_FALSE);
double emissionInterval = 1.0 / m_rate;
double dserial = std::fmod(t * m_rate, (double) (1 << 31));
auto serial = (int) (dserial);
double age = (dserial - serial) * emissionInterval;
auto invLifetime = (float) (1.0 / m_lifetime);
double maxAge = m_lifetime;
if (startBounded)
{
maxAge = std::min((double) m_lifetime, t);
}
if (endBounded && tsec > m_endTime)
{
auto skipParticles = (int) ((tsec - m_endTime) * m_rate);
serial -= skipParticles;
age += skipParticles * emissionInterval;
}
Vec4f startColor(m_startColor.red(), m_startColor.green(), m_startColor.blue(), m_startColor.alpha());
Vec4f endColor(m_endColor.red(), m_endColor.green(), m_endColor.blue(), m_endColor.alpha());
unsigned int particleCount = 0;
while (age < maxAge)
{
// When the particle buffer is full, render the particles and flush it
if (particleCount == particleBufferCapacity)
{
glDrawArrays(GL_TRIANGLE_FAN, 0, particleCount * 4);
particleCount = 0;
}
float alpha = (float) age * invLifetime;
float beta = 1.0f - alpha;
float size = alpha * m_endSize + beta * m_startSize;
// Scramble the random number generator seed so that we don't end up with
// artifacts from using regularly incrementing values.
//
// TODO: consider whether the generator could be seeded just once before
// the first particle is drawn. This would entail further restrictions,
// such as no 'branching' (variable number of calls to LCG::generate()) in
// particle state calculation.
LCGRandomGenerator gen(uint64_t(serial) * uint64_t(0x128ef719) ^ scrambleMask);
// Calculate the color of the particle
// TODO: switch to using a lookup table for color and opacity
unsigned char color[4];
color[Color::Red] = (unsigned char) ((alpha * endColor.x + beta * startColor.x) * 255.99f);
color[Color::Green] = (unsigned char) ((alpha * endColor.y + beta * startColor.y) * 255.99f);
color[Color::Blue] = (unsigned char) ((alpha * endColor.z + beta * startColor.z) * 255.99f);
color[Color::Alpha] = (unsigned char) ((alpha * endColor.w + beta * startColor.w) * 255.99f);
Vector3f v = m_velocityGenerator->generate(gen);
Vector3f center = m_positionGenerator->generate(gen) + v * (float) age;
if (m_nonZeroAcceleration)
center += m_acceleration * (float) (age * age);
if (!m_rotationEnabled)
{
particleBuffer[particleCount * 4 + 0].set(center + v0 * size, Vector2f(0.0f, 1.0f), color);
particleBuffer[particleCount * 4 + 1].set(center + v1 * size, Vector2f(1.0f, 1.0f), color);
particleBuffer[particleCount * 4 + 2].set(center + v2 * size, Vector2f(1.0f, 0.0f), color);
particleBuffer[particleCount * 4 + 3].set(center + v3 * size, Vector2f(0.0f, 0.0f), color);
}
else
{
float rotationRate = m_minRotationRate + m_rotationRateVariance * gen.randFloat();
float rotation = rotationRate * (float) age;
float c = std::cos(rotation);
float s = std::sin(rotation);
particleBuffer[particleCount * 4 + 0].set(center + (modelViewMatrix * Vector3f(-c + s, -s - c, 0.0f)) * size, Vector2f(0.0f, 1.0f), color);
particleBuffer[particleCount * 4 + 1].set(center + (modelViewMatrix * Vector3f( c + s, s - c, 0.0f)) * size, Vector2f(1.0f, 1.0f), color);
particleBuffer[particleCount * 4 + 2].set(center + (modelViewMatrix * Vector3f( c - s, s + c, 0.0f)) * size, Vector2f(1.0f, 0.0f), color);
particleBuffer[particleCount * 4 + 3].set(center + (modelViewMatrix * Vector3f(-c - s, -s + c, 0.0f)) * size, Vector2f(0.0f, 0.0f), color);
}
++particleCount;
age += emissionInterval;
serial--;
}
// Render any remaining particles in the buffer
if (particleCount > 0)
{
glDrawArrays(GL_TRIANGLE_FAN, 0, particleCount * 4);
}
}
void
ParticleEmitter::createMaterial()
{
m_material.diffuse = cmod::Material::Color(0.0f, 0.0f, 0.0f);
m_material.emissive = cmod::Material::Color(1.0f, 1.0f, 1.0f);
m_material.blend = m_blendMode;
m_material.opacity = 0.99f;
m_material.maps[0] = new CelestiaTextureResource(m_texture);
}
#define STRUCT_OFFSET(s, memberName) ((uint32_t) (reinterpret_cast<char*>(&(s).memberName) - reinterpret_cast<char*>(&(s))))
ParticleSystem::ParticleSystem() :
m_vertexDesc(nullptr),
m_vertexData(nullptr),
m_particleCapacity(0),
m_particleCount(0)
{
m_particleCapacity = 1000;
m_vertexData = new ParticleVertex[m_particleCapacity * 4];
// Create the vertex description; currently, it is the same for all
// particle systems.
ParticleVertex temp;
Mesh::VertexAttribute attributes[3];
attributes[0] = cmod::Mesh::VertexAttribute(Mesh::Position, Mesh::Float3, STRUCT_OFFSET(temp, position));
attributes[1] = Mesh::VertexAttribute(Mesh::Texture0, Mesh::Float2, STRUCT_OFFSET(temp, texCoord));
attributes[2] = Mesh::VertexAttribute(Mesh::Color0, Mesh::UByte4, STRUCT_OFFSET(temp, color));
m_vertexDesc = new Mesh::VertexDescription(sizeof(ParticleVertex), 3, attributes);
}
ParticleSystem::~ParticleSystem()
{
for (const auto emitter : m_emitterList)
delete emitter;
delete[] m_vertexData;
delete m_vertexDesc;
}
void
ParticleSystem::render(RenderContext& rc, double tsec)
{
rc.setVertexArrays(*m_vertexDesc, m_vertexData);
for (const auto emitter : m_emitterList)
{
emitter->render(tsec, rc, m_vertexData, m_particleCapacity);
}
}
bool
ParticleSystem::pick(const Ray3d& /* r */, double& /* distance */) const
{
// Pick selection for particle systems not supported (because it's
// not typically desirable.)
return false;
}
bool
ParticleSystem::isOpaque() const
{
return false;
}
void
ParticleSystem::addEmitter(ParticleEmitter* emitter)
{
m_emitterList.push_back(emitter);
}