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