1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 2013-2016, The OpenClonk Team and contributors
5 *
6 * Distributed under the terms of the ISC license; see accompanying file
7 * "COPYING" for details.
8 *
9 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
10 * See accompanying file "TRADEMARK" for details.
11 *
12 * To redistribute this file separately, substitute the full license texts
13 * for the above references.
14 */
15
16 #include "C4Include.h"
17 #include "C4ForbidLibraryCompilation.h"
18 #include "landscape/C4Particles.h"
19
20 // headers for particle loading
21 #include "c4group/C4Components.h"
22
23 #ifndef USE_CONSOLE
24 // headers for particle execution
25 #include "game/C4Application.h"
26 #include "graphics/C4DrawGL.h"
27 #include "landscape/C4Material.h"
28 #include "landscape/C4Landscape.h"
29 #include "landscape/C4Weather.h"
30 #include "object/C4MeshAnimation.h"
31 #include "object/C4Object.h"
32 #include "script/C4Aul.h"
33 #include "script/C4Value.h"
34 #include "script/C4ValueArray.h"
35 #include <random>
36 #endif
37
38
CompileFunc(StdCompiler * pComp)39 void C4ParticleDefCore::CompileFunc(StdCompiler * pComp)
40 {
41 pComp->Value(mkNamingAdapt(toC4CStrBuf(Name), "Name", ""));
42 pComp->Value(mkNamingAdapt(GfxFace, "Face"));
43 }
44
C4ParticleDefCore()45 C4ParticleDefCore::C4ParticleDefCore()
46 {
47 GfxFace.Default();
48 }
49
Compile(char * particle_source,const char * name)50 bool C4ParticleDefCore::Compile(char *particle_source, const char *name)
51 {
52 return CompileFromBuf_LogWarn<StdCompilerINIRead>(mkNamingAdapt(*this, "Particle"),
53 StdStrBuf(particle_source), name);
54 }
55
C4ParticleDef()56 C4ParticleDef::C4ParticleDef() : C4ParticleDefCore()
57 {
58 // zero fields
59 Gfx.Default();
60 // link into list
61 if (!Particles.definitions.first)
62 {
63 previous = nullptr;
64 Particles.definitions.first = this;
65 }
66 else
67 {
68 previous = Particles.definitions.last;
69 previous->next = this;
70 }
71 Particles.definitions.last = this;
72 next = 0;
73 }
74
~C4ParticleDef()75 C4ParticleDef::~C4ParticleDef()
76 {
77 // clear
78 Clear();
79 // unlink from list
80 if (previous) previous->next = next; else Particles.definitions.first = next;
81 if (next) next->previous = previous; else Particles.definitions.last = previous;
82 }
83
Clear()84 void C4ParticleDef::Clear()
85 {
86 Name.Clear();
87 }
88
Load(C4Group & group)89 bool C4ParticleDef::Load(C4Group &group)
90 {
91 // store file
92 Filename.Copy(group.GetFullName());
93 // load
94 char *particle_source;
95 if (group.LoadEntry(C4CFN_ParticleCore,&particle_source,nullptr,1))
96 {
97 if (!Compile(particle_source, Filename.getData()))
98 {
99 DebugLogF("invalid particle def at '%s'", group.GetFullName().getData());
100 delete [] particle_source; return false;
101 }
102 delete [] particle_source;
103 // load graphics
104 if (!Gfx.Load(group, C4CFN_DefGraphics, C4FCT_Full, C4FCT_Full, false, C4SF_MipMap))
105 {
106 DebugLogF("particle %s has no valid graphics defined", Name.getData());
107 return false;
108 }
109 // set facet, if assigned - otherwise, assume full surface
110 if (GfxFace.Wdt) Gfx.Set(Gfx.Surface, GfxFace.x, GfxFace.y, GfxFace.Wdt, GfxFace.Hgt);
111 // set phase num
112 int32_t Q; Gfx.GetPhaseNum(PhasesX, Q);
113 Length = PhasesX * Q;
114 if (!Length)
115 {
116 DebugLogF("invalid facet for particle '%s'", Name.getData());
117 return false;
118 }
119 // calc aspect
120 Aspect=(float) Gfx.Hgt/Gfx.Wdt;
121
122 // particle overloading
123 C4ParticleDef *def_overload;
124 if ((def_overload = Particles.definitions.GetDef(Name.getData(), this)))
125 {
126 if (Config.Graphics.VerboseObjectLoading >= 1)
127 { char ostr[250]; sprintf(ostr,LoadResStr("IDS_PRC_DEFOVERLOAD"),def_overload->Name.getData(),"<particle>"); Log(ostr); }
128 delete def_overload;
129 }
130 // success
131 return true;
132 }
133 return false;
134 }
135
Reload()136 bool C4ParticleDef::Reload()
137 {
138 // no file?
139 if (!Filename[0]) return false;
140 // open group
141 C4Group group;
142 if (!group.Open(Filename.getData())) return false;
143 // reset class
144 Clear();
145 // load
146 return Load(group);
147 }
148
149 #ifndef USE_CONSOLE
150 const int C4Particle::DrawingData::vertexCountPerParticle(4);
151
SetPosition(float x,float y,float size,float rotation,float stretch)152 void C4Particle::DrawingData::SetPosition(float x, float y, float size, float rotation, float stretch)
153 {
154 if (size != originalSize || stretch != currentStretch)
155 {
156 currentStretch = stretch;
157 originalSize = std::max(size, 0.0001f); // a size of zero results in undefined behavior
158 sizeX = originalSize / aspect;
159 sizeY = originalSize * currentStretch;
160 }
161
162 if (rotation == 0.f)
163 {
164 vertices[0].x = x - sizeX + offsetX;
165 vertices[0].y = y + sizeY + offsetY;
166 vertices[1].x = x - sizeX + offsetX;
167 vertices[1].y = y - sizeY + offsetY;
168 vertices[2].x = x + sizeX + offsetX;
169 vertices[2].y = y + sizeY + offsetY;
170 vertices[3].x = x + sizeX + offsetX;
171 vertices[3].y = y - sizeY + offsetY;
172 }
173 else
174 {
175 float sine = sinf(rotation);
176 float cosine = cosf(rotation);
177
178 vertices[0].x = x + ((-sizeX) * cosine - (+sizeY) * sine) + offsetX;
179 vertices[0].y = y + ((-sizeX) * sine + (+sizeY) * cosine) + offsetY;
180 vertices[1].x = x + ((-sizeX) * cosine - (-sizeY) * sine) + offsetX;
181 vertices[1].y = y + ((-sizeX) * sine + (-sizeY) * cosine) + offsetY;
182 vertices[2].x = x + ((+sizeX) * cosine - (+sizeY) * sine) + offsetX;
183 vertices[2].y = y + ((+sizeX) * sine + (+sizeY) * cosine) + offsetY;
184 vertices[3].x = x + ((+sizeX) * cosine - (-sizeY) * sine) + offsetX;
185 vertices[3].y = y + ((+sizeX) * sine + (-sizeY) * cosine) + offsetY;
186 }
187 }
188
SetPhase(int phase,C4ParticleDef * sourceDef)189 void C4Particle::DrawingData::SetPhase(int phase, C4ParticleDef *sourceDef)
190 {
191 this->phase = phase;
192 phase = phase % sourceDef->Length;
193 int offsetY = phase / sourceDef->PhasesX;
194 int offsetX = phase % sourceDef->PhasesX;
195 float wdt = 1.0f / (float)sourceDef->PhasesX;
196 int numOfLines = sourceDef->Length / sourceDef->PhasesX;
197 float hgt = 1.0f / (float)numOfLines;
198
199 float x = wdt * (float)offsetX;
200 float y = hgt * (float)offsetY;
201 float xr = x + wdt;
202 float yr = y + hgt;
203
204 vertices[0].u = x; vertices[0].v = yr;
205 vertices[1].u = x; vertices[1].v = y;
206 vertices[2].u = xr; vertices[2].v = yr;
207 vertices[3].u = xr; vertices[3].v = y;
208 }
209
operator =(const C4ParticleValueProvider & other)210 C4ParticleValueProvider & C4ParticleValueProvider::operator= (const C4ParticleValueProvider &other)
211 {
212 startValue = other.startValue;
213 endValue = other.endValue;
214 currentValue = other.currentValue;
215 rerollInterval = other.rerollInterval;
216 smoothing = other.smoothing;
217 valueFunction = other.valueFunction;
218 isConstant = other.isConstant;
219 keyFrameCount = other.keyFrameCount;
220 rng = other.rng;
221
222 if (keyFrameCount > 0)
223 {
224 keyFrames.reserve(2 * keyFrameCount);
225 keyFrames.assign(other.keyFrames.begin(), other.keyFrames.end());
226 }
227
228 typeOfValueToChange = other.typeOfValueToChange;
229 switch (typeOfValueToChange)
230 {
231 case VAL_TYPE_FLOAT:
232 floatValueToChange = other.floatValueToChange;
233 break;
234 case VAL_TYPE_INT:
235 intValueToChange = other.intValueToChange;
236 break;
237 case VAL_TYPE_KEYFRAMES:
238 keyFrameIndex = other.keyFrameIndex;
239 break;
240 default:
241 assert (false && "Trying to copy C4ParticleValueProvider with invalid value type");
242 break;
243 }
244
245 // copy the other's children, too
246 for (std::vector<C4ParticleValueProvider*>::const_iterator iter = other.childrenValueProviders.begin(); iter != other.childrenValueProviders.end(); ++iter)
247 {
248 childrenValueProviders.push_back(new C4ParticleValueProvider(**iter)); // custom copy constructor usage
249 }
250 return (*this);
251 }
252
SetParameterValue(int type,const C4Value & value,float C4ParticleValueProvider::* floatVal,int C4ParticleValueProvider::* intVal,size_t keyFrameIndex)253 void C4ParticleValueProvider::SetParameterValue(int type, const C4Value &value, float C4ParticleValueProvider::*floatVal, int C4ParticleValueProvider::*intVal, size_t keyFrameIndex)
254 {
255 // just an atomic data type
256 if (value.GetType() == C4V_Int)
257 {
258 if (type == VAL_TYPE_FLOAT)
259 this->*floatVal = (float)value.getInt();
260 else if (type == VAL_TYPE_INT)
261 this->*intVal = value.getInt();
262 else if (type == VAL_TYPE_KEYFRAMES)
263 this->keyFrames[keyFrameIndex] = (float)value.getInt();
264 }
265 else if (value.GetType() == C4V_Array)
266 {
267 // might be another value provider!
268 C4ParticleValueProvider *child = new C4ParticleValueProvider();
269 childrenValueProviders.push_back(child);
270
271 child->Set(*value.getArray());
272 child->typeOfValueToChange = type;
273
274 if (type == VAL_TYPE_FLOAT)
275 {
276 child->floatValueToChange = floatVal;
277 }
278 else if (type == VAL_TYPE_INT)
279 {
280 child->intValueToChange = intVal;
281 }
282 else if (type == VAL_TYPE_KEYFRAMES)
283 {
284 child->keyFrameIndex = keyFrameIndex;
285 }
286
287 }
288 else // invalid
289 {
290 if (type == VAL_TYPE_FLOAT)
291 this->*floatVal = 0.f;
292 else if (type == VAL_TYPE_INT)
293 this->*intVal = 0;
294 else if (type == VAL_TYPE_KEYFRAMES)
295 this->keyFrames[keyFrameIndex] = 0.f;
296 }
297 }
298
UpdatePointerValue(C4Particle * particle,C4ParticleValueProvider * parent)299 void C4ParticleValueProvider::UpdatePointerValue(C4Particle *particle, C4ParticleValueProvider *parent)
300 {
301 switch (typeOfValueToChange)
302 {
303 case VAL_TYPE_FLOAT:
304 parent->*floatValueToChange = GetValue(particle);
305 break;
306 case VAL_TYPE_INT:
307 parent->*intValueToChange = (int) GetValue(particle);
308 break;
309 case VAL_TYPE_KEYFRAMES:
310 parent->keyFrames[keyFrameIndex] = GetValue(particle);
311 break;
312 default:
313 assert (false);
314 }
315 }
316
UpdateChildren(C4Particle * particle)317 void C4ParticleValueProvider::UpdateChildren(C4Particle *particle)
318 {
319 for (std::vector<C4ParticleValueProvider*>::iterator iter = childrenValueProviders.begin(); iter != childrenValueProviders.end(); ++iter)
320 {
321 (*iter)->UpdatePointerValue(particle, this);
322 }
323 }
324
FloatifyParameterValue(float C4ParticleValueProvider::* value,float denominator,size_t keyFrameIndex)325 void C4ParticleValueProvider::FloatifyParameterValue(float C4ParticleValueProvider::*value, float denominator, size_t keyFrameIndex)
326 {
327 if (value == 0)
328 this->keyFrames[keyFrameIndex] /= denominator;
329 else
330 this->*value /= denominator;
331
332 for (std::vector<C4ParticleValueProvider*>::iterator iter = childrenValueProviders.begin(); iter != childrenValueProviders.end(); ++iter)
333 {
334 C4ParticleValueProvider *child = *iter;
335 if (value == 0)
336 {
337 if (child->typeOfValueToChange == VAL_TYPE_KEYFRAMES && child->keyFrameIndex == keyFrameIndex)
338 child->Floatify(denominator);
339 }
340 else
341 {
342 if (child->floatValueToChange == value)
343 child->Floatify(denominator);
344 }
345 }
346
347 }
348
Floatify(float denominator)349 void C4ParticleValueProvider::Floatify(float denominator)
350 {
351 assert (denominator != 0.f && "Trying to floatify C4ParticleValueProvider with denominator of 0");
352
353 if (valueFunction == &C4ParticleValueProvider::Direction)
354 {
355 FloatifyParameterValue(&C4ParticleValueProvider::startValue, 1000.f);
356 return;
357 }
358
359 FloatifyParameterValue(&C4ParticleValueProvider::startValue, denominator);
360 FloatifyParameterValue(&C4ParticleValueProvider::endValue, denominator);
361 FloatifyParameterValue(&C4ParticleValueProvider::currentValue, denominator);
362
363 // special treatment for keyframes
364 if (valueFunction == &C4ParticleValueProvider::KeyFrames)
365 {
366 for (size_t i = 0; i < keyFrameCount; ++i)
367 {
368 FloatifyParameterValue(0, 1000.f, 2 * i); // even numbers are the time values
369 FloatifyParameterValue(0, denominator, 2 * i + 1); // odd numbers are the actual values
370 }
371 }
372 else if (valueFunction == &C4ParticleValueProvider::Speed || valueFunction == &C4ParticleValueProvider::Wind || valueFunction == &C4ParticleValueProvider::Gravity)
373 {
374 FloatifyParameterValue(&C4ParticleValueProvider::speedFactor, 1000.0f);
375 }
376 else if (valueFunction == &C4ParticleValueProvider::Step)
377 {
378 FloatifyParameterValue(&C4ParticleValueProvider::maxValue, denominator);
379 }
380 else if (valueFunction == &C4ParticleValueProvider::Sin || valueFunction == &C4ParticleValueProvider::Cos)
381 {
382 FloatifyParameterValue(&C4ParticleValueProvider::parameterValue, 1.0f);
383 FloatifyParameterValue(&C4ParticleValueProvider::maxValue, denominator);
384 }
385 }
386
GetValue(C4Particle * forParticle)387 float C4ParticleValueProvider::GetValue(C4Particle *forParticle)
388 {
389 UpdateChildren(forParticle);
390 return (this->*valueFunction)(forParticle);
391 }
392
Linear(C4Particle * forParticle)393 float C4ParticleValueProvider::Linear(C4Particle *forParticle)
394 {
395 return startValue + (endValue - startValue) * forParticle->GetRelativeAge();
396 }
397
Const(C4Particle * forParticle)398 float C4ParticleValueProvider::Const(C4Particle *forParticle)
399 {
400 return startValue;
401 }
402
Random(C4Particle * forParticle)403 float C4ParticleValueProvider::Random(C4Particle *forParticle)
404 {
405 // We need to roll again if..
406 const bool needToReevaluate =
407 // .. we haven't rolled yet
408 alreadyRolled == 0
409 // .. we are still in the intialization stage (and thus, this value provider could be an intialization term for multiple particles)
410 || (forParticle->lifetime == forParticle->startingLifetime)
411 // .. or the reroll interval is set and expired.
412 || (rerollInterval != 0 && ((int)forParticle->GetAge() % rerollInterval == 0));
413
414 if (needToReevaluate)
415 {
416 alreadyRolled = 1;
417 // Even for seeded PV_Random, each particle should behave differently. Thus, we use a different
418 // stream for each one. Since this is by no means synchronisation relevant and since the
419 // particles lie on the heap we just use the address here.
420 const std::uintptr_t ourAddress = reinterpret_cast<std::uintptr_t>(forParticle);
421 rng.set_stream(ourAddress);
422 // We need to advance the RNG a bit to make streams with the same seed diverge.
423 rng.advance(5);
424 std::uniform_real_distribution<float> distribution(std::min(startValue, endValue), std::max(startValue, endValue));
425 currentValue = distribution(rng);
426 }
427 return currentValue;
428 }
429
Direction(C4Particle * forParticle)430 float C4ParticleValueProvider::Direction(C4Particle *forParticle)
431 {
432 float distX = forParticle->currentSpeedX;
433 float distY = forParticle->currentSpeedY;
434
435 if (distX == 0.f) return distY > 0.f ? M_PI : 0.f;
436 if (distY == 0.f) return distX < 0.f ? 3.0f * M_PI_2 : M_PI_2;
437
438 return startValue * (atan2(distY, distX) + (float)M_PI_2);
439 }
440
Step(C4Particle * forParticle)441 float C4ParticleValueProvider::Step(C4Particle *forParticle)
442 {
443 float value = currentValue + startValue * forParticle->GetAge() / delay;
444 if (maxValue != 0.0f && value > maxValue) value = maxValue;
445 return value;
446 }
447
KeyFrames(C4Particle * forParticle)448 float C4ParticleValueProvider::KeyFrames(C4Particle *forParticle)
449 {
450 float age = forParticle->GetRelativeAge();
451 // todo, implement smoothing
452 //if (smoothing == 0) // linear
453 {
454 for (size_t i = 0; i < keyFrameCount; ++i)
455 {
456 if (age > keyFrames[i * 2]) continue;
457 assert(i >= 1);
458
459 float x1 = keyFrames[(i - 1) * 2];
460 float x2 = keyFrames[i * 2];
461 float y1 = keyFrames[(i - 1) * 2 + 1];
462 float y2 = keyFrames[i * 2 + 1];
463 float position = (age - x1) / (x2 - x1);
464 float totalRange = (y2 - y1);
465
466 float value = position * totalRange + y1;
467 return value;
468 }
469 }
470
471 return startValue;
472 }
473
Sin(C4Particle * forParticle)474 float C4ParticleValueProvider::Sin(C4Particle *forParticle)
475 {
476 return sin(parameterValue * M_PI / 180.0f) * maxValue + startValue;
477 }
478
Cos(C4Particle * forParticle)479 float C4ParticleValueProvider::Cos(C4Particle *forParticle)
480 {
481 return cos(parameterValue * M_PI / 180.0f) * maxValue + startValue;
482 }
483
Speed(C4Particle * forParticle)484 float C4ParticleValueProvider::Speed(C4Particle *forParticle)
485 {
486 float distX = forParticle->currentSpeedX;
487 float distY = forParticle->currentSpeedY;
488 float speed = sqrtf((distX * distX) + (distY * distY));
489
490 return startValue + speedFactor * speed;
491 }
492
Wind(C4Particle * forParticle)493 float C4ParticleValueProvider::Wind(C4Particle *forParticle)
494 {
495 return startValue + (0.01f * speedFactor * ::Weather.GetWind((int)forParticle->positionX, (int)forParticle->positionY));
496 }
497
Gravity(C4Particle * forParticle)498 float C4ParticleValueProvider::Gravity(C4Particle *forParticle)
499 {
500 return startValue + (speedFactor * fixtof(::Landscape.GetGravity()));
501 }
502
SetType(C4ParticleValueProviderID what)503 void C4ParticleValueProvider::SetType(C4ParticleValueProviderID what)
504 {
505 switch (what)
506 {
507 case C4PV_Const:
508 valueFunction = &C4ParticleValueProvider::Const;
509 break;
510 case C4PV_Linear:
511 valueFunction = &C4ParticleValueProvider::Linear;
512 break;
513 case C4PV_Random:
514 valueFunction = &C4ParticleValueProvider::Random;
515 break;
516 case C4PV_Direction:
517 valueFunction = &C4ParticleValueProvider::Direction;
518 break;
519 case C4PV_Step:
520 valueFunction = &C4ParticleValueProvider::Step;
521 break;
522 case C4PV_KeyFrames:
523 valueFunction = &C4ParticleValueProvider::KeyFrames;
524 break;
525 case C4PV_Sin:
526 valueFunction = &C4ParticleValueProvider::Sin;
527 break;
528 case C4PV_Cos:
529 valueFunction = &C4ParticleValueProvider::Cos;
530 break;
531 case C4PV_Speed:
532 valueFunction = &C4ParticleValueProvider::Speed;
533 break;
534 case C4PV_Wind:
535 valueFunction = &C4ParticleValueProvider::Wind;
536 break;
537 case C4PV_Gravity:
538 valueFunction = &C4ParticleValueProvider::Gravity;
539 break;
540 default:
541 assert(false && "Invalid C4ParticleValueProvider ID passed");
542 };
543
544 if (what != C4PV_Const)
545 {
546 isConstant = false;
547 }
548 }
549
Set(const C4ValueArray & fromArray)550 void C4ParticleValueProvider::Set(const C4ValueArray &fromArray)
551 {
552 startValue = endValue = 1.0f;
553 valueFunction = &C4ParticleValueProvider::Const;
554
555 size_t arraySize = (size_t) fromArray.GetSize();
556 if (arraySize < 2) return;
557
558 int type = fromArray[0].getInt();
559
560 switch (type)
561 {
562 case C4PV_Const:
563 if (arraySize >= 2)
564 {
565 SetType(C4PV_Const);
566 SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::startValue);
567 }
568 break;
569
570 case C4PV_Linear:
571 if (arraySize >= 3)
572 {
573 SetType(C4PV_Linear);
574 SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::startValue);
575 SetParameterValue(VAL_TYPE_FLOAT, fromArray[2], &C4ParticleValueProvider::endValue);
576 }
577 break;
578 case C4PV_Random:
579 if (arraySize >= 3)
580 {
581 SetType(C4PV_Random);
582 SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::startValue);
583 SetParameterValue(VAL_TYPE_FLOAT, fromArray[2], &C4ParticleValueProvider::endValue);
584 if (arraySize >= 4 && fromArray[3].GetType() != C4V_Type::C4V_Nil)
585 SetParameterValue(VAL_TYPE_INT, fromArray[3], 0, &C4ParticleValueProvider::rerollInterval);
586 if (arraySize >= 5 && fromArray[4].GetType() != C4V_Type::C4V_Nil)
587 {
588 // We don't need the seed later on, but SetParameterValue won't accept local
589 // variables. Use an unrelated member instead which is reset below.
590 SetParameterValue(VAL_TYPE_INT, fromArray[4], 0, &C4ParticleValueProvider::alreadyRolled);
591 rng.seed(alreadyRolled);
592 }
593 else
594 {
595 rng.seed(UnsyncedRandom());
596 }
597 alreadyRolled = 0;
598 }
599 break;
600 case C4PV_Direction:
601 if (arraySize >= 2)
602 {
603 SetType(C4PV_Direction);
604 SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::startValue);
605 }
606 break;
607 case C4PV_Step:
608 if (arraySize >= 4)
609 {
610 SetType(C4PV_Step);
611 SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::startValue);
612 SetParameterValue(VAL_TYPE_FLOAT, fromArray[2], &C4ParticleValueProvider::currentValue);
613 SetParameterValue(VAL_TYPE_FLOAT, fromArray[3], &C4ParticleValueProvider::delay);
614 SetParameterValue(VAL_TYPE_FLOAT, fromArray[4], &C4ParticleValueProvider::maxValue);
615 if (delay == 0.f) delay = 1.f;
616 }
617 break;
618 case C4PV_KeyFrames:
619 if (arraySize >= 5)
620 {
621 SetType(C4PV_KeyFrames);
622 SetParameterValue(VAL_TYPE_INT, fromArray[1], 0, &C4ParticleValueProvider::smoothing);
623 keyFrames.resize(arraySize + 4 - 1); // 2*2 additional information floats at the beginning and ending, offset the first array item, though
624
625 keyFrameCount = 0;
626 const size_t startingOffset = 2;
627 size_t i = startingOffset;
628 for (; i < arraySize; ++i)
629 {
630 SetParameterValue(VAL_TYPE_KEYFRAMES, fromArray[(int32_t)i], 0, 0, 2 + i - startingOffset);
631 }
632 keyFrameCount = (i - startingOffset) / 2 + 2;
633
634 startValue = keyFrames[2 + 1];
635 endValue = keyFrames[2 * keyFrameCount - 1];
636
637 // add two points for easier interpolation at beginning and ending
638 keyFrames[0] = -500.f;
639 keyFrames[1] = keyFrames[2 + 1];
640 keyFrames[2 * keyFrameCount - 2] = 1500.f;
641 keyFrames[2 * keyFrameCount - 1] = keyFrames[keyFrameCount - 1 - 2];
642
643 }
644 break;
645 case C4PV_Sin: // fallthrough
646 case C4PV_Cos:
647 if (arraySize >= 3)
648 {
649 SetType(static_cast<C4ParticleValueProviderID> (type)); // Sin(parameterValue) * maxValue + startValue
650 SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::parameterValue);
651 SetParameterValue(VAL_TYPE_FLOAT, fromArray[2], &C4ParticleValueProvider::maxValue);
652 SetParameterValue(VAL_TYPE_FLOAT, fromArray[3], &C4ParticleValueProvider::startValue);
653 }
654 break;
655 case C4PV_Speed:
656 if (arraySize >= 3)
657 {
658 SetType(C4PV_Speed);
659 SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::speedFactor);
660 SetParameterValue(VAL_TYPE_FLOAT, fromArray[2], &C4ParticleValueProvider::startValue);
661 }
662 break;
663 case C4PV_Wind:
664 if (arraySize >= 3)
665 {
666 SetType(C4PV_Wind);
667 SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::speedFactor);
668 SetParameterValue(VAL_TYPE_FLOAT, fromArray[2], &C4ParticleValueProvider::startValue);
669 }
670 break;
671 case C4PV_Gravity:
672 if (arraySize >= 3)
673 {
674 SetType(C4PV_Gravity);
675 SetParameterValue(VAL_TYPE_FLOAT, fromArray[1], &C4ParticleValueProvider::speedFactor);
676 SetParameterValue(VAL_TYPE_FLOAT, fromArray[2], &C4ParticleValueProvider::startValue);
677 }
678 break;
679 default:
680 throw C4AulExecError("invalid particle value provider supplied");
681 break;
682 }
683 }
684
Set(const C4Value & value)685 void C4ParticleValueProvider::Set(const C4Value &value)
686 {
687 C4ValueArray *valueArray= value.getArray();
688
689 if (valueArray != 0)
690 Set(*valueArray);
691 else
692 Set((float)value.getInt());
693 }
694
Set(float to)695 void C4ParticleValueProvider::Set(float to)
696 {
697 SetType(C4PV_Const);
698 startValue = endValue = to;
699 }
700
C4ParticleProperties()701 C4ParticleProperties::C4ParticleProperties()
702 {
703 blitMode = 0;
704 attachment = C4ATTACH_None;
705 hasConstantColor = false;
706 hasCollisionVertex = false;
707 collisionCallback = 0;
708 bouncyness = 0.f;
709
710 // all values in pre-floatified range (f.e. 0..255 instead of 0..1)
711 collisionDensity.Set(static_cast<float>(C4M_Solid));
712 collisionVertex.Set(0.f);
713 size.Set(8.f);
714 stretch.Set(1000.f);
715 forceX.Set(0.f);
716 forceY.Set(0.f);
717 speedDampingX.Set(1000.f);
718 speedDampingY.Set(1000.f);
719 colorR.Set(255.f);
720 colorG.Set(255.f);
721 colorB.Set(255.f);
722 colorAlpha.Set(255.f);
723 rotation.Set(0.f);
724 phase.Set(0.f);
725 }
726
Floatify()727 void C4ParticleProperties::Floatify()
728 {
729 bouncyness /= 1000.f;
730
731 collisionDensity.Floatify(1.f);
732 collisionVertex.Floatify(1000.f);
733 size.Floatify(2.f);
734 stretch.Floatify(1000.f);
735 forceX.Floatify(100.f);
736 forceY.Floatify(100.f);
737 speedDampingX.Floatify(1000.f);
738 speedDampingY.Floatify(1000.f);
739 colorR.Floatify(255.f);
740 colorG.Floatify(255.f);
741 colorB.Floatify(255.f);
742 colorAlpha.Floatify(255.f);
743 rotation.Floatify(180.0f / (float)M_PI);
744 phase.Floatify(1.f);
745
746 hasConstantColor = colorR.IsConstant() && colorG.IsConstant() && colorB.IsConstant() && colorAlpha.IsConstant();
747 }
748
Set(C4PropList * dataSource)749 void C4ParticleProperties::Set(C4PropList *dataSource)
750 {
751 if (!dataSource) return;
752
753 C4PropList::Iterator iter = dataSource->begin(), end = dataSource->end();
754
755 for (;iter != end; ++iter)
756 {
757 const C4Property * p = *iter;
758 C4String *key = p->Key;
759 assert(key && "PropList returns non-string as key");
760 const C4Value &property = p->Value;
761
762 if(&Strings.P[P_R] == key)
763 {
764 colorR.Set(property);
765 }
766 else if(&Strings.P[P_G] == key)
767 {
768 colorG.Set(property);
769 }
770 else if(&Strings.P[P_B] == key)
771 {
772 colorB.Set(property);
773 }
774 else if(&Strings.P[P_Alpha] == key)
775 {
776 colorAlpha.Set(property);
777 }
778 else if(&Strings.P[P_ForceX] == key)
779 {
780 forceX.Set(property);
781 }
782 else if(&Strings.P[P_ForceY] == key)
783 {
784 forceY.Set(property);
785 }
786 else if(&Strings.P[P_DampingX] == key)
787 {
788 speedDampingX.Set(property);
789 }
790 else if(&Strings.P[P_DampingY] == key)
791 {
792 speedDampingY.Set(property);
793 }
794 else if(&Strings.P[P_Size] == key)
795 {
796 size.Set(property);
797 }
798 else if(&Strings.P[P_Stretch] == key)
799 {
800 stretch.Set(property);
801 }
802 else if(&Strings.P[P_Rotation] == key)
803 {
804 rotation.Set(property);
805 }
806 else if(&Strings.P[P_BlitMode] == key)
807 {
808 // needs to be constant
809 blitMode = (uint32_t) property.getInt();
810 }
811 else if(&Strings.P[P_Attach] == key)
812 {
813 // needs to be constant
814 attachment = (uint32_t) property.getInt();
815 }
816 else if(&Strings.P[P_Phase] == key)
817 {
818 phase.Set(property);
819 }
820 else if(&Strings.P[P_CollisionVertex] == key)
821 {
822 collisionVertex.Set(property);
823 if (property.GetType() != C4V_Nil)
824 hasCollisionVertex = true;
825 }
826 else if (&Strings.P[P_CollisionDensity] == key)
827 {
828 collisionDensity.Set(property);
829 }
830 else if(&Strings.P[P_OnCollision] == key)
831 {
832 SetCollisionFunc(property);
833 }
834 }
835
836 }
837
SetCollisionFunc(const C4Value & source)838 void C4ParticleProperties::SetCollisionFunc(const C4Value &source)
839 {
840 C4ValueArray *valueArray;
841 if (!(valueArray = source.getArray())) return;
842
843 int arraySize = valueArray->GetSize();
844 if (arraySize < 1) return;
845
846 int type = (*valueArray)[0].getInt();
847
848 switch (type)
849 {
850 case C4PC_Die:
851 collisionCallback = &C4ParticleProperties::CollisionDie;
852 break;
853 case C4PC_Bounce:
854 collisionCallback = &C4ParticleProperties::CollisionBounce;
855 bouncyness = 1.f;
856 if (arraySize >= 2)
857 bouncyness = ((float)(*valueArray)[1].getInt());
858 break;
859 case C4PC_Stop:
860 collisionCallback = &C4ParticleProperties::CollisionStop;
861 break;
862 default:
863 assert(false);
864 break;
865 }
866 }
867
CollisionBounce(C4Particle * forParticle)868 bool C4ParticleProperties::CollisionBounce(C4Particle *forParticle)
869 {
870 forParticle->currentSpeedX = -forParticle->currentSpeedX * bouncyness;
871 forParticle->currentSpeedY = -forParticle->currentSpeedY * bouncyness;
872 return true;
873 }
874
CollisionStop(C4Particle * forParticle)875 bool C4ParticleProperties::CollisionStop(C4Particle *forParticle)
876 {
877 forParticle->currentSpeedX = 0.f;
878 forParticle->currentSpeedY = 0.f;
879 return true;
880 }
881
Init()882 void C4Particle::Init()
883 {
884 currentSpeedX = currentSpeedY = 0.f;
885 positionX = positionY = 0.f;
886 lifetime = startingLifetime = 5.f * 38.f;
887 }
888
Exec(C4Object * obj,float timeDelta,C4ParticleDef * sourceDef)889 bool C4Particle::Exec(C4Object *obj, float timeDelta, C4ParticleDef *sourceDef)
890 {
891 // die of old age? :<
892 lifetime -= timeDelta;
893 // check only if we had a maximum lifetime to begin with (for permanent particles)
894 if (startingLifetime > 0.f)
895 {
896 if (lifetime <= 0.f) return false;
897 }
898
899 // movement
900 float currentForceX = properties.forceX.GetValue(this);
901 float currentForceY = properties.forceY.GetValue(this);
902
903 currentSpeedX += currentForceX;
904 currentSpeedY += currentForceY;
905
906 if (currentSpeedX != 0.f || currentSpeedY != 0.f)
907 {
908 float currentDampingX = properties.speedDampingX.GetValue(this);
909 float currentDampingY = properties.speedDampingY.GetValue(this);
910 float size = properties.size.GetValue(this);
911
912 currentSpeedX *= currentDampingX;
913 currentSpeedY *= currentDampingY;
914
915 // move & collision check
916 // note: accessing Landscape.GetDensity here is not protected by locks
917 // it is assumed that the particle system is cleaned up before, f.e., the landscape memory is freed
918 bool collided = false;
919 if (properties.hasCollisionVertex)
920 {
921 float collisionPoint = properties.collisionVertex.GetValue(this);
922 float size_x = (currentSpeedX > 0.f ? size : -size) * 0.5f * collisionPoint;
923 float size_y = (currentSpeedY > 0.f ? size : -size) * 0.5f * collisionPoint;
924 float density = static_cast<float>(GBackDensity(positionX + size_x + timeDelta * currentSpeedX, positionY + size_y + timeDelta * currentSpeedY));
925
926 if (density + 0.5f >= properties.collisionDensity.GetValue(this)) // Small offset against floating point insanities.
927 {
928 // exec collision func
929 if (properties.collisionCallback != 0 && !(properties.*properties.collisionCallback)(this)) return false;
930 collided = true;
931 }
932 }
933
934 if (!collided)
935 {
936 positionX += timeDelta * currentSpeedX;
937 positionY += timeDelta * currentSpeedY;
938 }
939 drawingData.SetPosition(positionX, positionY, size, properties.rotation.GetValue(this), properties.stretch.GetValue(this));
940
941 }
942 else if(!properties.size.IsConstant() || !properties.rotation.IsConstant() || !properties.stretch.IsConstant())
943 {
944 drawingData.SetPosition(positionX, positionY, properties.size.GetValue(this), properties.rotation.GetValue(this), properties.stretch.GetValue(this));
945 }
946
947 // adjust color
948 if (!properties.hasConstantColor)
949 {
950 drawingData.SetColor(properties.colorR.GetValue(this), properties.colorG.GetValue(this), properties.colorB.GetValue(this), properties.colorAlpha.GetValue(this));
951 }
952
953 int currentPhase = (int)(properties.phase.GetValue(this) + 0.5f);
954 if (currentPhase != drawingData.phase)
955 drawingData.SetPhase(currentPhase, sourceDef);
956
957 return true;
958 }
959
Clear()960 void C4ParticleChunk::Clear()
961 {
962 for (size_t i = 0; i < particleCount; ++i)
963 {
964 delete particles[i];
965 }
966 particleCount = 0;
967 particles.clear();
968 vertexCoordinates.clear();
969
970 ClearBufferObjects();
971 }
972
DeleteAndReplaceParticle(size_t indexToReplace,size_t indexFrom)973 void C4ParticleChunk::DeleteAndReplaceParticle(size_t indexToReplace, size_t indexFrom)
974 {
975 C4Particle *oldParticle = particles[indexToReplace];
976
977 // try to replace the soon-to-be empty slot in the array
978 if (indexFrom != indexToReplace) // false when "replacing" the last one
979 {
980 std::copy(&vertexCoordinates[indexFrom * C4Particle::DrawingData::vertexCountPerParticle], &vertexCoordinates[indexFrom * C4Particle::DrawingData::vertexCountPerParticle] + C4Particle::DrawingData::vertexCountPerParticle, &vertexCoordinates[indexToReplace * C4Particle::DrawingData::vertexCountPerParticle]);
981 particles[indexToReplace] = particles[indexFrom];
982 particles[indexToReplace]->drawingData.SetPointer(&vertexCoordinates[indexToReplace * C4Particle::DrawingData::vertexCountPerParticle]);
983 }
984
985 delete oldParticle;
986 }
987
Exec(C4Object * obj,float timeDelta)988 bool C4ParticleChunk::Exec(C4Object *obj, float timeDelta)
989 {
990 for (size_t i = 0; i < particleCount; ++i)
991 {
992 if (!particles[i]->Exec(obj, timeDelta, sourceDefinition))
993 {
994 DeleteAndReplaceParticle(i, particleCount - 1);
995 --particleCount;
996 }
997 }
998 return particleCount > 0;
999 }
1000
Draw(C4TargetFacet cgo,C4Object * obj,C4ShaderCall & call,int texUnit,const StdProjectionMatrix & modelview)1001 void C4ParticleChunk::Draw(C4TargetFacet cgo, C4Object *obj, C4ShaderCall& call, int texUnit, const StdProjectionMatrix& modelview)
1002 {
1003 if (particleCount == 0) return;
1004 const int stride = sizeof(C4Particle::DrawingData::Vertex);
1005 assert(sourceDefinition && "No source definition assigned to particle chunk.");
1006 C4TexRef *textureRef = sourceDefinition->Gfx.GetFace().texture.get();
1007 assert(textureRef != 0 && "Particle definition had no texture assigned.");
1008
1009 // use a relative offset?
1010 // (note the normal matrix is unaffected by this)
1011 if ((attachment & C4ATTACH_MoveRelative) && (obj != 0))
1012 {
1013 StdProjectionMatrix new_modelview(modelview);
1014 Translate(new_modelview, fixtof(obj->GetFixedX()), fixtof(obj->GetFixedY()), 0.0f);
1015 call.SetUniformMatrix4x4(C4SSU_ModelViewMatrix, new_modelview);
1016 }
1017 else
1018 {
1019 call.SetUniformMatrix4x4(C4SSU_ModelViewMatrix, modelview);
1020 }
1021
1022 // enable additive blending for particles with that blit mode
1023 glBlendFunc(GL_SRC_ALPHA, (blitMode & C4GFXBLIT_ADDITIVE) ? GL_ONE : GL_ONE_MINUS_SRC_ALPHA);
1024
1025 glActiveTexture(texUnit);
1026 glBindTexture(GL_TEXTURE_2D, textureRef->texName);
1027
1028 // generate the buffer as necessary
1029 if (drawingDataVertexBufferObject == 0)
1030 {
1031 // clear up old data
1032 ClearBufferObjects();
1033 // generate new buffer objects
1034 glGenBuffers(1, &drawingDataVertexBufferObject);
1035 assert (drawingDataVertexBufferObject != 0 && "Could not generate OpenGL buffer object.");
1036 // Immediately bind the buffer.
1037 // glVertexAttribPointer requires a valid GL_ARRAY_BUFFER to be bound and we need the buffer to be created for glObjectLabel.
1038 glBindBuffer(GL_ARRAY_BUFFER, drawingDataVertexBufferObject);
1039
1040 pGL->ObjectLabel(GL_BUFFER, drawingDataVertexBufferObject, -1, "<particles>/VBO");
1041
1042 // generate new VAO ID
1043 drawingDataVertexArraysObject = pGL->GenVAOID();
1044 assert (drawingDataVertexArraysObject != 0 && "Could not generate a VAO ID.");
1045 }
1046
1047
1048 // Push the new vertex data
1049 glBindBuffer(GL_ARRAY_BUFFER, drawingDataVertexBufferObject);
1050 glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(C4Particle::DrawingData::Vertex) * particleCount, &vertexCoordinates[0], GL_DYNAMIC_DRAW);
1051 glBindBuffer(GL_ARRAY_BUFFER, 0);
1052
1053 // set up the vertex array structure
1054 GLuint vao;
1055 const bool has_vao = pGL->GetVAO(drawingDataVertexArraysObject, vao);
1056 glBindVertexArray(vao);
1057
1058 assert ((drawingDataVertexBufferObject != 0) && "No buffer object has been created yet.");
1059 assert ((drawingDataVertexArraysObject != 0) && "No vertex arrays object has been created yet.");
1060
1061 if (!has_vao)
1062 {
1063 glBindBuffer(GL_ARRAY_BUFFER, drawingDataVertexBufferObject);
1064 pGL->ObjectLabel(GL_VERTEX_ARRAY, vao, -1, "<particles>/VAO");
1065
1066 glEnableVertexAttribArray(call.GetAttribute(C4SSA_Position));
1067 glEnableVertexAttribArray(call.GetAttribute(C4SSA_Color));
1068 glEnableVertexAttribArray(call.GetAttribute(C4SSA_TexCoord));
1069 glVertexAttribPointer(call.GetAttribute(C4SSA_Position), 2, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<GLvoid*>(offsetof(C4Particle::DrawingData::Vertex, x)));
1070 glVertexAttribPointer(call.GetAttribute(C4SSA_TexCoord), 2, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<GLvoid*>(offsetof(C4Particle::DrawingData::Vertex, u)));
1071 glVertexAttribPointer(call.GetAttribute(C4SSA_Color), 4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<GLvoid*>(offsetof(C4Particle::DrawingData::Vertex, r)));
1072 }
1073
1074 // We need to always bind the ibo, because it might change its size.
1075 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ::Particles.GetIBO(particleCount));
1076
1077 glDrawElements(GL_TRIANGLE_STRIP, static_cast<GLsizei> (5 * particleCount), GL_UNSIGNED_INT, 0);
1078
1079 // reset buffer data
1080 glBindVertexArray(0);
1081 }
1082
IsOfType(C4ParticleDef * def,uint32_t _blitMode,uint32_t _attachment) const1083 bool C4ParticleChunk::IsOfType(C4ParticleDef *def, uint32_t _blitMode, uint32_t _attachment) const
1084 {
1085 return def == sourceDefinition && blitMode == _blitMode && attachment == _attachment;
1086 }
1087
ClearBufferObjects()1088 void C4ParticleChunk::ClearBufferObjects()
1089 {
1090 if (drawingDataVertexBufferObject != 0) // the value 0 as a buffer index is reserved and will never be returned by glGenBuffers
1091 glDeleteBuffers(1, &drawingDataVertexBufferObject);
1092 if (drawingDataVertexArraysObject != 0)
1093 pGL->FreeVAOID(drawingDataVertexArraysObject);
1094
1095 drawingDataVertexArraysObject = 0;
1096 drawingDataVertexBufferObject = 0;
1097 }
1098
ReserveSpace(uint32_t forAmount)1099 void C4ParticleChunk::ReserveSpace(uint32_t forAmount)
1100 {
1101 uint32_t newSize = static_cast<uint32_t>(particleCount) + forAmount + 1;
1102
1103 if (particles.capacity() < newSize)
1104 particles.reserve(std::max<uint32_t>(newSize, particles.capacity() * 2));
1105
1106 // resizing the points vector is relatively costly, hopefully we only do it rarely
1107 while (vertexCoordinates.capacity() <= newSize * C4Particle::DrawingData::vertexCountPerParticle)
1108 {
1109 vertexCoordinates.reserve(std::max<uint32_t>(C4Particle::DrawingData::vertexCountPerParticle * newSize, vertexCoordinates.capacity() * 2));
1110
1111 // update all existing particles' pointers..
1112 for (size_t i = 0; i < particleCount; ++i)
1113 particles[i]->drawingData.SetPointer(&vertexCoordinates[i * C4Particle::DrawingData::vertexCountPerParticle]);
1114 }
1115 }
1116
AddNewParticle()1117 C4Particle *C4ParticleChunk::AddNewParticle()
1118 {
1119 size_t currentIndex = particleCount++;
1120
1121 if (currentIndex < particles.size())
1122 {
1123 particles[currentIndex] = new C4Particle();
1124 }
1125 else
1126 {
1127 particles.push_back(new C4Particle());
1128 vertexCoordinates.resize(vertexCoordinates.size() + C4Particle::DrawingData::vertexCountPerParticle);
1129 }
1130
1131 C4Particle *newParticle = particles[currentIndex];
1132 newParticle->drawingData.SetPointer(&vertexCoordinates[currentIndex * C4Particle::DrawingData::vertexCountPerParticle], true);
1133 return newParticle;
1134 }
1135
Exec(float timeDelta)1136 void C4ParticleList::Exec(float timeDelta)
1137 {
1138 if (particleChunks.empty()) return;
1139
1140 accessMutex.Enter();
1141
1142 for (std::list<C4ParticleChunk*>::iterator iter = particleChunks.begin(); iter != particleChunks.end();++iter)
1143 {
1144 C4ParticleChunk *chunk = *iter;
1145 chunk->Exec(targetObject, timeDelta);
1146 }
1147
1148 accessMutex.Leave();
1149 }
1150
Draw(C4TargetFacet cgo,C4Object * obj)1151 void C4ParticleList::Draw(C4TargetFacet cgo, C4Object *obj)
1152 {
1153 if (particleChunks.empty()) return;
1154
1155 pDraw->DeactivateBlitModulation();
1156 pDraw->ResetBlitMode();
1157
1158 glPrimitiveRestartIndex(0xffffffff);
1159 glEnable(GL_PRIMITIVE_RESTART);
1160
1161 // enable shader
1162 C4ShaderCall call(pGL->GetSpriteShader(true, false, false));
1163 // apply zoom and upload shader uniforms
1164 StdProjectionMatrix modelview = StdProjectionMatrix::Identity();
1165 pGL->SetupMultiBlt(call, nullptr, 0, 0, 0, 0, &modelview);
1166 // go to correct output position (note the normal matrix is unaffected
1167 // by this)
1168 Translate(modelview, cgo.X-cgo.TargetX, cgo.Y-cgo.TargetY, 0.0f);
1169 // allocate texture unit for particle texture, and remember allocated
1170 // texture unit. Will be used for each particle chunk to bind
1171 // their texture to this unit.
1172 const GLint texUnit = call.AllocTexUnit(C4SSU_BaseTex);
1173
1174 accessMutex.Enter();
1175
1176 for (std::list<C4ParticleChunk*>::iterator iter = particleChunks.begin(); iter != particleChunks.end(); )
1177 {
1178 if ((*iter)->IsEmpty())
1179 {
1180 delete *iter;
1181 iter = particleChunks.erase(iter);
1182 lastAccessedChunk = 0;
1183 }
1184 else
1185 {
1186 (*iter)->Draw(cgo, obj, call, texUnit, modelview);
1187 ++iter;
1188 }
1189 }
1190
1191 accessMutex.Leave();
1192
1193 glDisable(GL_PRIMITIVE_RESTART);
1194 }
1195
Clear()1196 void C4ParticleList::Clear()
1197 {
1198 accessMutex.Enter();
1199
1200 for (std::list<C4ParticleChunk*>::iterator iter = particleChunks.begin(); iter != particleChunks.end(); ++iter)
1201 delete *iter;
1202 particleChunks.clear();
1203
1204 if (targetObject)
1205 {
1206 if (this == targetObject->FrontParticles) targetObject->FrontParticles = nullptr;
1207 else if (this == targetObject->BackParticles) targetObject->BackParticles = nullptr;
1208 }
1209 else
1210 if(this == ::Particles.globalParticles) ::Particles.globalParticles = nullptr;
1211
1212 accessMutex.Leave();
1213 }
1214
GetFittingParticleChunk(C4ParticleDef * def,uint32_t blitMode,uint32_t attachment,bool alreadyLocked)1215 C4ParticleChunk *C4ParticleList::GetFittingParticleChunk(C4ParticleDef *def, uint32_t blitMode, uint32_t attachment, bool alreadyLocked)
1216 {
1217 if (!alreadyLocked)
1218 accessMutex.Enter();
1219
1220 // if not cached, find correct chunk in list
1221 C4ParticleChunk *chunk = 0;
1222 if (lastAccessedChunk && lastAccessedChunk->IsOfType(def, blitMode, attachment))
1223 chunk = lastAccessedChunk;
1224 else
1225 {
1226 for (std::list<C4ParticleChunk*>::iterator iter = particleChunks.begin(); iter != particleChunks.end(); ++iter)
1227 {
1228 C4ParticleChunk *current = *iter;
1229 if (!current->IsOfType(def, blitMode, attachment)) continue;
1230 chunk = current;
1231 break;
1232 }
1233 }
1234
1235 // add new chunk?
1236 if (!chunk)
1237 {
1238 particleChunks.push_back(new C4ParticleChunk());
1239 chunk = particleChunks.back();
1240 chunk->sourceDefinition = def;
1241 chunk->blitMode = blitMode;
1242 chunk->attachment = attachment;
1243 }
1244
1245 assert(chunk && "No suitable particle chunk could be found or created.");
1246 lastAccessedChunk = chunk;
1247
1248 if (!alreadyLocked)
1249 accessMutex.Leave();
1250
1251 return chunk;
1252 }
1253
Execute()1254 void C4ParticleSystem::CalculationThread::Execute()
1255 {
1256 Particles.ExecuteCalculation();
1257 }
1258
C4ParticleSystem()1259 C4ParticleSystem::C4ParticleSystem() : frameCounterAdvancedEvent(false)
1260 {
1261 currentSimulationTime = 0;
1262 globalParticles = 0;
1263 ibo = 0;
1264 ibo_size = 0;
1265 }
1266
~C4ParticleSystem()1267 C4ParticleSystem::~C4ParticleSystem()
1268 {
1269 Clear();
1270
1271 calculationThread.SignalStop();
1272 CalculateNextStep();
1273 }
1274
ExecuteCalculation()1275 void C4ParticleSystem::ExecuteCalculation()
1276 {
1277 frameCounterAdvancedEvent.WaitFor(INFINITE);
1278 frameCounterAdvancedEvent.Reset();
1279
1280 int gameTime = Game.FrameCounter;
1281 if (currentSimulationTime < gameTime)
1282 {
1283 float timeDelta = 1.f;
1284 if (currentSimulationTime != 0)
1285 timeDelta = (float)(gameTime - currentSimulationTime);
1286 currentSimulationTime = gameTime;
1287
1288 particleListAccessMutex.Enter();
1289
1290 for (std::list<C4ParticleList>::iterator iter = particleLists.begin(); iter != particleLists.end(); ++iter)
1291 {
1292 iter->Exec(timeDelta);
1293 }
1294
1295 particleListAccessMutex.Leave();
1296 }
1297 }
1298 #endif
1299
GetNewParticleList(C4Object * forObject)1300 C4ParticleList *C4ParticleSystem::GetNewParticleList(C4Object *forObject)
1301 {
1302 #ifdef USE_CONSOLE
1303 return 0;
1304 #else
1305 C4ParticleList *newList = 0;
1306
1307 particleListAccessMutex.Enter();
1308 particleLists.emplace_back(forObject);
1309 newList = &particleLists.back();
1310 particleListAccessMutex.Leave();
1311
1312 return newList;
1313 #endif
1314 }
1315
ReleaseParticleList(C4ParticleList * first,C4ParticleList * second)1316 void C4ParticleSystem::ReleaseParticleList(C4ParticleList *first, C4ParticleList *second)
1317 {
1318 #ifndef USE_CONSOLE
1319 particleListAccessMutex.Enter();
1320
1321 for(std::list<C4ParticleList>::iterator iter = particleLists.begin(); iter != particleLists.end();)
1322 {
1323 C4ParticleList *list = &(*iter);
1324 if (list == first || list == second)
1325 {
1326 iter = particleLists.erase(iter);
1327 }
1328 else
1329 {
1330 ++iter;
1331 }
1332 }
1333
1334 particleListAccessMutex.Leave();
1335 #endif
1336 }
1337
1338 #ifndef USE_CONSOLE
Create(C4ParticleDef * of_def,C4ParticleValueProvider & x,C4ParticleValueProvider & y,C4ParticleValueProvider & speedX,C4ParticleValueProvider & speedY,C4ParticleValueProvider & lifetime,C4PropList * properties,int amount,C4Object * object)1339 void C4ParticleSystem::Create(C4ParticleDef *of_def, C4ParticleValueProvider &x, C4ParticleValueProvider &y, C4ParticleValueProvider &speedX, C4ParticleValueProvider &speedY, C4ParticleValueProvider &lifetime, C4PropList *properties, int amount, C4Object *object)
1340 {
1341 // todo: check amount etc
1342
1343 C4ParticleList * pxList(0);
1344
1345
1346 // initialize the particle properties
1347 // this is done here, because it would also be the right place to implement caching
1348 C4ParticleProperties particleProperties;
1349 particleProperties.Set(properties);
1350
1351 speedX.Floatify(10.f);
1352 speedY.Floatify(10.f);
1353
1354 // position offset that will be added to the particle
1355 float xoff(0.f), yoff(0.f);
1356
1357 // offset only for the drawing position - this is needed so that particles relative to an object work correctly
1358 float drawingOffsetX(0.f), drawingOffsetY(0.f);
1359
1360 if (object != 0)
1361 {
1362 // for all types of particles add object's offset (mainly for collision etc.)
1363 xoff = object->GetX();
1364 yoff = object->GetY();
1365
1366 if (particleProperties.attachment & C4ATTACH_MoveRelative)
1367 {
1368 drawingOffsetX = -xoff;
1369 drawingOffsetY = -yoff;
1370
1371 // move relative implies that the particle needs to be in the object's particle list (back OR front)
1372 // just select the front particles here - will be overwritten below if necessary
1373 if (!(particleProperties.attachment & C4ATTACH_Front) && !(particleProperties.attachment & C4ATTACH_Back))
1374 particleProperties.attachment |= C4ATTACH_Front;
1375 }
1376
1377 // figure out particle list to use
1378 if (particleProperties.attachment & C4ATTACH_Front)
1379 {
1380 if (!object->FrontParticles) object->FrontParticles = GetNewParticleList(object);
1381 pxList = object->FrontParticles;
1382 }
1383 else if (particleProperties.attachment & C4ATTACH_Back)
1384 {
1385 if (!object->BackParticles) object->BackParticles = GetNewParticleList(object);
1386 pxList = object->BackParticles;
1387 }
1388 }
1389
1390 // no assigned list implies that we are going to use the global particles
1391 if (!pxList)
1392 {
1393 if (!globalParticles) globalParticles = GetNewParticleList();
1394 pxList = globalParticles;
1395 }
1396
1397 // It is necessary to lock the particle list, because we will have it create a particle first that we are going to modify.
1398 // Inbetween creation of the particle and modification, the particle list's calculations should not be executed
1399 // (this could f.e. lead to the particle being removed before it was fully instantiated).
1400 pxList->Lock();
1401
1402 // retrieve the fitting chunk for the particle (note that we tell the particle list, we already locked it)
1403 C4ParticleChunk *chunk = pxList->GetFittingParticleChunk(of_def, particleProperties.blitMode, particleProperties.attachment, true);
1404
1405 // set up chunk to be able to contain enough particles
1406 chunk->ReserveSpace(static_cast<uint32_t>(amount));
1407
1408 while (amount--)
1409 {
1410 // create a particle in the fitting chunk (note that we tell the particle list, we already locked it)
1411 C4Particle *particle = chunk->AddNewParticle();
1412
1413 // initialize some more properties
1414 particle->properties = particleProperties;
1415 // this will adjust the initial values of the (possibly cached) particle properties
1416 particle->properties.Floatify();
1417
1418 // setup some more non-property attributes of the particle
1419 // The particle having lifetime == startingLifetime will force all random values to alway-reevaluate.
1420 // Thus we need to guarantee that even before setting the lifetime (to allow a PV_Random for the lifetime).
1421 particle->lifetime = particle->startingLifetime = 0.0f;
1422 const float lifetime_value = lifetime.GetValue(particle);
1423 // Negative values are not allowed (would crash later); using a value of 0 is most likely visible to the scripter.
1424 if (lifetime_value >= 0.0f)
1425 particle->lifetime = particle->startingLifetime = lifetime_value;
1426
1427 particle->currentSpeedX = speedX.GetValue(particle);
1428 particle->currentSpeedY = speedY.GetValue(particle);
1429 particle->drawingData.aspect = of_def->Aspect;
1430 particle->drawingData.SetOffset(drawingOffsetX, drawingOffsetY);
1431 particle->SetPosition(x.GetValue(particle) + xoff, y.GetValue(particle) + yoff);
1432 particle->drawingData.SetColor(particle->properties.colorR.GetValue(particle), particle->properties.colorG.GetValue(particle), particle->properties.colorB.GetValue(particle), particle->properties.colorAlpha.GetValue(particle));
1433 particle->drawingData.SetPhase((int)(particle->properties.phase.GetValue(particle) + 0.5f), of_def);
1434 }
1435
1436 pxList->Unlock();
1437 }
1438
GetIBO(size_t forParticleAmount)1439 GLuint C4ParticleSystem::GetIBO(size_t forParticleAmount)
1440 {
1441 PreparePrimitiveRestartIndices(forParticleAmount);
1442 return ibo;
1443 }
1444
PreparePrimitiveRestartIndices(uint32_t forAmount)1445 void C4ParticleSystem::PreparePrimitiveRestartIndices(uint32_t forAmount)
1446 {
1447 if (ibo == 0) glGenBuffers(1, &ibo);
1448 // Each particle has 4 vertices and one PRI.
1449 const size_t neededEntryAmount = 5 * forAmount;
1450 const size_t neededIboSize = neededEntryAmount * sizeof(GLuint);
1451 // Nothing to do?
1452 if (ibo_size >= neededIboSize) return;
1453
1454 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
1455
1456 // prepare array with indices, separated by special primitive restart index
1457 const uint32_t PRI = 0xffffffff;
1458
1459 std::vector<GLuint> ibo_data;
1460 ibo_data.reserve(neededEntryAmount);
1461
1462 unsigned int index = 0;
1463 for (unsigned int i = 0; i < neededEntryAmount; ++i)
1464 {
1465 if ((i+1) % 5 == 0)
1466 ibo_data.push_back(PRI);
1467 else
1468 ibo_data.push_back(index++);
1469 }
1470
1471 ibo_size = neededEntryAmount * sizeof(GLuint);
1472 glBufferData(GL_ELEMENT_ARRAY_BUFFER, ibo_size, &ibo_data[0], GL_STATIC_DRAW);
1473 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
1474 }
1475 #endif
1476
Clear()1477 void C4ParticleSystem::Clear()
1478 {
1479 #ifndef USE_CONSOLE
1480 if (ibo != 0) glDeleteBuffers(1, &ibo);
1481 ibo = 0; ibo_size = 0;
1482
1483 currentSimulationTime = 0;
1484 ClearAllParticles();
1485 #endif
1486 // clear definitions even in console mode
1487 definitions.Clear();
1488 }
1489
ClearAllParticles()1490 void C4ParticleSystem::ClearAllParticles()
1491 {
1492 #ifndef USE_CONSOLE
1493 particleListAccessMutex.Enter();
1494 particleLists.clear();
1495 particleListAccessMutex.Leave();
1496 #endif
1497 }
1498
GetDef(const char * name,C4ParticleDef * exclude)1499 C4ParticleDef *C4ParticleSystemDefinitionList::GetDef(const char *name, C4ParticleDef *exclude)
1500 {
1501 #ifndef USE_CONSOLE
1502 // seek list
1503 for (C4ParticleDef *def = first; def != 0; def=def->next)
1504 if (def != exclude && def->Name == name)
1505 return def;
1506 #endif
1507 // nothing found
1508 return 0;
1509 }
1510
Clear()1511 void C4ParticleSystemDefinitionList::Clear()
1512 {
1513 // the particle definitions update the list in their destructor
1514 while (first)
1515 delete first;
1516 }
1517 C4ParticleSystem Particles;
1518