1 |
|
|
/* |
2 |
|
|
* The ManaPlus Client |
3 |
|
|
* Copyright (C) 2006-2009 The Mana World Development Team |
4 |
|
|
* Copyright (C) 2009-2010 The Mana Developers |
5 |
|
|
* Copyright (C) 2011-2019 The ManaPlus Developers |
6 |
|
|
* Copyright (C) 2019-2021 Andrei Karas |
7 |
|
|
* |
8 |
|
|
* This file is part of The ManaPlus Client. |
9 |
|
|
* |
10 |
|
|
* This program is free software; you can redistribute it and/or modify |
11 |
|
|
* it under the terms of the GNU General Public License as published by |
12 |
|
|
* the Free Software Foundation; either version 2 of the License, or |
13 |
|
|
* any later version. |
14 |
|
|
* |
15 |
|
|
* This program is distributed in the hope that it will be useful, |
16 |
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 |
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 |
|
|
* GNU General Public License for more details. |
19 |
|
|
* |
20 |
|
|
* You should have received a copy of the GNU General Public License |
21 |
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
22 |
|
|
*/ |
23 |
|
|
|
24 |
|
|
#include "particle/particleemitter.h" |
25 |
|
|
|
26 |
|
|
#include "logger.h" |
27 |
|
|
|
28 |
|
|
#include "const/resources/map/map.h" |
29 |
|
|
|
30 |
|
|
#include "particle/animationparticle.h" |
31 |
|
|
#include "particle/rotationalparticle.h" |
32 |
|
|
|
33 |
|
|
#include "utils/foreach.h" |
34 |
|
|
|
35 |
|
|
#include "resources/imageset.h" |
36 |
|
|
|
37 |
|
|
#include "resources/dye/dye.h" |
38 |
|
|
|
39 |
|
|
#include "resources/image/image.h" |
40 |
|
|
|
41 |
|
|
#include "resources/loaders/imageloader.h" |
42 |
|
|
#include "resources/loaders/imagesetloader.h" |
43 |
|
|
#include "resources/loaders/subimageloader.h" |
44 |
|
|
#include "resources/loaders/subimagesetloader.h" |
45 |
|
|
|
46 |
|
|
#include "debug.h" |
47 |
|
|
|
48 |
|
|
static const float SIN45 = 0.707106781F; |
49 |
|
|
static const float DEG_RAD_FACTOR = 0.017453293F; |
50 |
|
|
|
51 |
|
|
typedef STD_VECTOR<ImageSet*>::const_iterator ImageSetVectorCIter; |
52 |
|
|
typedef std::list<ParticleEmitter>::const_iterator ParticleEmitterListCIter; |
53 |
|
|
|
54 |
|
|
ParticleEmitter::ParticleEmitter(XmlNodeConstPtrConst emitterNode, |
55 |
|
|
Particle *const target, |
56 |
|
|
Map *const map, const int rotation, |
57 |
|
|
const std::string& dyePalettes) : |
58 |
|
|
mParticleTarget(target), |
59 |
|
|
mMap(map), |
60 |
|
|
mOutput(), |
61 |
|
|
mOutputPause(), |
62 |
|
|
mParticleImage(nullptr), |
63 |
|
|
mParticleAnimation("particle animation"), |
64 |
|
|
mParticleRotation("particle rotation"), |
65 |
|
|
mParticleAlpha(), |
66 |
|
|
mDeathEffect(), |
67 |
|
|
mParticleChildEmitters(), |
68 |
|
|
mTempSets(), |
69 |
|
|
mOutputPauseLeft(0), |
70 |
|
|
mDeathEffectConditions(0), |
71 |
|
|
mParticleFollow(false) |
72 |
|
|
{ |
73 |
|
|
// Initializing default values |
74 |
|
|
mParticlePosX.set(0.0F); |
75 |
|
|
mParticlePosY.set(0.0F); |
76 |
|
|
mParticlePosZ.set(0.0F); |
77 |
|
|
mParticleAngleHorizontal.set(0.0F); |
78 |
|
|
mParticleAngleVertical.set(0.0F); |
79 |
|
|
mParticlePower.set(0.0F); |
80 |
|
|
mParticleGravity.set(0.0F); |
81 |
|
|
mParticleRandomness.set(0); |
82 |
|
|
mParticleBounce.set(0.0F); |
83 |
|
|
mParticleAcceleration.set(0.0F); |
84 |
|
|
mParticleDieDistance.set(-1.0F); |
85 |
|
|
mParticleMomentum.set(1.0F); |
86 |
|
|
mParticleLifetime.set(-1); |
87 |
|
|
mParticleFadeOut.set(0); |
88 |
|
|
mParticleFadeIn.set(0); |
89 |
|
|
mOutput.set(1); |
90 |
|
|
mOutputPause.set(0); |
91 |
|
|
mParticleAlpha.set(1.0F); |
92 |
|
|
|
93 |
|
|
if (emitterNode == nullptr) |
94 |
|
|
return; |
95 |
|
|
for_each_xml_child_node(propertyNode, emitterNode) |
96 |
|
|
{ |
97 |
|
|
if (xmlNameEqual(propertyNode, "property")) |
98 |
|
|
{ |
99 |
|
|
const std::string name = XML::getProperty( |
100 |
|
|
propertyNode, "name", ""); |
101 |
|
|
|
102 |
|
|
if (name == "position-x") |
103 |
|
|
{ |
104 |
|
|
mParticlePosX = readParticleEmitterProp(propertyNode, 0.0F); |
105 |
|
|
} |
106 |
|
|
else if (name == "position-y") |
107 |
|
|
{ |
108 |
|
|
mParticlePosY = readParticleEmitterProp(propertyNode, 0.0F); |
109 |
|
|
mParticlePosY.minVal *= SIN45; |
110 |
|
|
mParticlePosY.maxVal *= SIN45; |
111 |
|
|
mParticlePosY.changeAmplitude *= SIN45; |
112 |
|
|
} |
113 |
|
|
else if (name == "position-z") |
114 |
|
|
{ |
115 |
|
|
mParticlePosZ = readParticleEmitterProp(propertyNode, 0.0F); |
116 |
|
|
mParticlePosZ.minVal *= SIN45; |
117 |
|
|
mParticlePosZ.maxVal *= SIN45; |
118 |
|
|
mParticlePosZ.changeAmplitude *= SIN45; |
119 |
|
|
} |
120 |
|
|
else if (name == "image") |
121 |
|
|
{ |
122 |
|
|
std::string image = XML::getProperty( |
123 |
|
|
propertyNode, "value", ""); |
124 |
|
|
// Don't leak when multiple images are defined |
125 |
|
|
if (!image.empty() && (mParticleImage == nullptr)) |
126 |
|
|
{ |
127 |
|
|
if (!dyePalettes.empty()) |
128 |
|
|
Dye::instantiate(image, dyePalettes); |
129 |
|
|
mParticleImage = Loader::getImage(image); |
130 |
|
|
} |
131 |
|
|
} |
132 |
|
|
else if (name == "subimage") |
133 |
|
|
{ |
134 |
|
|
std::string image = XML::getProperty( |
135 |
|
|
propertyNode, "value", ""); |
136 |
|
|
// Don't leak when multiple images are defined |
137 |
|
|
if (!image.empty() && (mParticleImage == nullptr)) |
138 |
|
|
{ |
139 |
|
|
if (!dyePalettes.empty()) |
140 |
|
|
Dye::instantiate(image, dyePalettes); |
141 |
|
|
Image *img = Loader::getImage(image); |
142 |
|
|
if (img != nullptr) |
143 |
|
|
{ |
144 |
|
|
mParticleImage = Loader::getSubImage(img, |
145 |
|
|
XML::getProperty(propertyNode, "x", 0), |
146 |
|
|
XML::getProperty(propertyNode, "y", 0), |
147 |
|
|
XML::getProperty(propertyNode, "width", 0), |
148 |
|
|
XML::getProperty(propertyNode, "height", 0)); |
149 |
|
|
img->decRef(); |
150 |
|
|
} |
151 |
|
|
else |
152 |
|
|
{ |
153 |
|
|
mParticleImage = nullptr; |
154 |
|
|
} |
155 |
|
|
} |
156 |
|
|
} |
157 |
|
|
else if (name == "horizontal-angle") |
158 |
|
|
{ |
159 |
|
|
mParticleAngleHorizontal = |
160 |
|
|
readParticleEmitterProp(propertyNode, 0.0F); |
161 |
|
|
mParticleAngleHorizontal.minVal |
162 |
|
|
+= static_cast<float>(rotation); |
163 |
|
|
mParticleAngleHorizontal.minVal *= DEG_RAD_FACTOR; |
164 |
|
|
mParticleAngleHorizontal.maxVal |
165 |
|
|
+= static_cast<float>(rotation); |
166 |
|
|
mParticleAngleHorizontal.maxVal *= DEG_RAD_FACTOR; |
167 |
|
|
mParticleAngleHorizontal.changeAmplitude *= DEG_RAD_FACTOR; |
168 |
|
|
} |
169 |
|
|
else if (name == "vertical-angle") |
170 |
|
|
{ |
171 |
|
|
mParticleAngleVertical = |
172 |
|
|
readParticleEmitterProp(propertyNode, 0.0F); |
173 |
|
|
mParticleAngleVertical.minVal *= DEG_RAD_FACTOR; |
174 |
|
|
mParticleAngleVertical.maxVal *= DEG_RAD_FACTOR; |
175 |
|
|
mParticleAngleVertical.changeAmplitude *= DEG_RAD_FACTOR; |
176 |
|
|
} |
177 |
|
|
else if (name == "power") |
178 |
|
|
{ |
179 |
|
|
mParticlePower = readParticleEmitterProp(propertyNode, 0.0F); |
180 |
|
|
} |
181 |
|
|
else if (name == "gravity") |
182 |
|
|
{ |
183 |
|
|
mParticleGravity = readParticleEmitterProp(propertyNode, 0.0F); |
184 |
|
|
} |
185 |
|
|
else if (name == "randomnes" |
186 |
|
|
|| name == "randomness") // legacy bug |
187 |
|
|
{ |
188 |
|
|
mParticleRandomness = readParticleEmitterProp(propertyNode, 0); |
189 |
|
|
} |
190 |
|
|
else if (name == "bounce") |
191 |
|
|
{ |
192 |
|
|
mParticleBounce = readParticleEmitterProp(propertyNode, 0.0F); |
193 |
|
|
} |
194 |
|
|
else if (name == "lifetime") |
195 |
|
|
{ |
196 |
|
|
mParticleLifetime = readParticleEmitterProp(propertyNode, 0); |
197 |
|
|
mParticleLifetime.minVal += 1; |
198 |
|
|
} |
199 |
|
|
else if (name == "output") |
200 |
|
|
{ |
201 |
|
|
mOutput = readParticleEmitterProp(propertyNode, 0); |
202 |
|
|
mOutput.maxVal += 1; |
203 |
|
|
} |
204 |
|
|
else if (name == "output-pause") |
205 |
|
|
{ |
206 |
|
|
mOutputPause = readParticleEmitterProp(propertyNode, 0); |
207 |
|
|
mOutputPauseLeft = mOutputPause.value(0); |
208 |
|
|
} |
209 |
|
|
else if (name == "acceleration") |
210 |
|
|
{ |
211 |
|
|
mParticleAcceleration = readParticleEmitterProp( |
212 |
|
|
propertyNode, 0.0F); |
213 |
|
|
} |
214 |
|
|
else if (name == "die-distance") |
215 |
|
|
{ |
216 |
|
|
mParticleDieDistance = readParticleEmitterProp( |
217 |
|
|
propertyNode, 0.0F); |
218 |
|
|
} |
219 |
|
|
else if (name == "momentum") |
220 |
|
|
{ |
221 |
|
|
mParticleMomentum = readParticleEmitterProp( |
222 |
|
|
propertyNode, 1.0F); |
223 |
|
|
} |
224 |
|
|
else if (name == "fade-out") |
225 |
|
|
{ |
226 |
|
|
mParticleFadeOut = readParticleEmitterProp(propertyNode, 0); |
227 |
|
|
} |
228 |
|
|
else if (name == "fade-in") |
229 |
|
|
{ |
230 |
|
|
mParticleFadeIn = readParticleEmitterProp(propertyNode, 0); |
231 |
|
|
} |
232 |
|
|
else if (name == "alpha") |
233 |
|
|
{ |
234 |
|
|
mParticleAlpha = readParticleEmitterProp(propertyNode, 1.0F); |
235 |
|
|
} |
236 |
|
|
else if (name == "follow-parent") |
237 |
|
|
{ |
238 |
|
|
const std::string value = XML::getProperty(propertyNode, |
239 |
|
|
"value", "0"); |
240 |
|
|
if (value == "1" || value == "true") |
241 |
|
|
mParticleFollow = true; |
242 |
|
|
} |
243 |
|
|
else |
244 |
|
|
{ |
245 |
|
|
logger->log("Particle Engine: Warning, " |
246 |
|
|
"unknown emitter property \"%s\"", |
247 |
|
|
name.c_str()); |
248 |
|
|
} |
249 |
|
|
} |
250 |
|
|
else if (xmlNameEqual(propertyNode, "emitter")) |
251 |
|
|
{ |
252 |
|
|
ParticleEmitter newEmitter(propertyNode, mParticleTarget, map, |
253 |
|
|
rotation, dyePalettes); |
254 |
|
|
mParticleChildEmitters.push_back(newEmitter); |
255 |
|
|
} |
256 |
|
|
else if (xmlNameEqual(propertyNode, "rotation") |
257 |
|
|
|| xmlNameEqual(propertyNode, "animation")) |
258 |
|
|
{ |
259 |
|
|
ImageSet *const imageset = getImageSet(propertyNode); |
260 |
|
|
if (imageset == nullptr) |
261 |
|
|
{ |
262 |
|
|
logger->log1("Error: no valid imageset"); |
263 |
|
|
continue; |
264 |
|
|
} |
265 |
|
|
mTempSets.push_back(imageset); |
266 |
|
|
|
267 |
|
|
Animation &animation = (xmlNameEqual(propertyNode, "rotation")) != |
268 |
|
|
0 ? mParticleRotation : mParticleAnimation; |
269 |
|
|
|
270 |
|
|
// Get animation frames |
271 |
|
|
for_each_xml_child_node(frameNode, propertyNode) |
272 |
|
|
{ |
273 |
|
|
const int delay = XML::getIntProperty( |
274 |
|
|
frameNode, "delay", 0, 0, 100000); |
275 |
|
|
const int offsetX = XML::getProperty(frameNode, "offsetX", 0) |
276 |
|
|
- imageset->getWidth() / 2 + mapTileSize / 2; |
277 |
|
|
const int offsetY = XML::getProperty(frameNode, "offsetY", 0) |
278 |
|
|
- imageset->getHeight() + mapTileSize; |
279 |
|
|
const int rand = XML::getIntProperty( |
280 |
|
|
frameNode, "rand", 100, 0, 100); |
281 |
|
|
|
282 |
|
|
if (xmlNameEqual(frameNode, "frame")) |
283 |
|
|
{ |
284 |
|
|
const int index = XML::getProperty(frameNode, "index", -1); |
285 |
|
|
|
286 |
|
|
if (index < 0) |
287 |
|
|
{ |
288 |
|
|
logger->log1("No valid value for 'index'"); |
289 |
|
|
continue; |
290 |
|
|
} |
291 |
|
|
|
292 |
|
|
Image *const img = imageset->get(index); |
293 |
|
|
|
294 |
|
|
if (img == nullptr) |
295 |
|
|
{ |
296 |
|
|
logger->log("No image at index %d", index); |
297 |
|
|
continue; |
298 |
|
|
} |
299 |
|
|
|
300 |
|
|
animation.addFrame(img, delay, |
301 |
|
|
offsetX, offsetY, rand); |
302 |
|
|
} |
303 |
|
|
else if (xmlNameEqual(frameNode, "sequence")) |
304 |
|
|
{ |
305 |
|
|
int start = XML::getProperty(frameNode, "start", -1); |
306 |
|
|
const int end = XML::getProperty(frameNode, "end", -1); |
307 |
|
|
|
308 |
|
|
if (start < 0 || end < 0) |
309 |
|
|
{ |
310 |
|
|
logger->log1("No valid value for 'start' or 'end'"); |
311 |
|
|
continue; |
312 |
|
|
} |
313 |
|
|
|
314 |
|
|
while (end >= start) |
315 |
|
|
{ |
316 |
|
|
Image *const img = imageset->get(start); |
317 |
|
|
if (img == nullptr) |
318 |
|
|
{ |
319 |
|
|
logger->log("No image at index %d", start); |
320 |
|
|
continue; |
321 |
|
|
} |
322 |
|
|
|
323 |
|
|
animation.addFrame(img, delay, |
324 |
|
|
offsetX, offsetY, rand); |
325 |
|
|
start ++; |
326 |
|
|
} |
327 |
|
|
} |
328 |
|
|
else if (xmlNameEqual(frameNode, "end")) |
329 |
|
|
{ |
330 |
|
|
animation.addTerminator(rand); |
331 |
|
|
} |
332 |
|
|
} // for frameNode |
333 |
|
|
} |
334 |
|
|
else if (xmlNameEqual(propertyNode, "deatheffect")) |
335 |
|
|
{ |
336 |
|
|
if (!XmlHaveChildContent(propertyNode)) |
337 |
|
|
continue; |
338 |
|
|
mDeathEffect = XmlChildContent(propertyNode); |
339 |
|
|
mDeathEffectConditions = 0x00; |
340 |
|
|
if (XML::getBoolProperty(propertyNode, "on-floor", true)) |
341 |
|
|
{ |
342 |
|
|
mDeathEffectConditions += CAST_S8( |
343 |
|
|
AliveStatus::DEAD_FLOOR); |
344 |
|
|
} |
345 |
|
|
if (XML::getBoolProperty(propertyNode, "on-sky", true)) |
346 |
|
|
{ |
347 |
|
|
mDeathEffectConditions += CAST_S8( |
348 |
|
|
AliveStatus::DEAD_SKY); |
349 |
|
|
} |
350 |
|
|
if (XML::getBoolProperty(propertyNode, "on-other", false)) |
351 |
|
|
{ |
352 |
|
|
mDeathEffectConditions += CAST_S8( |
353 |
|
|
AliveStatus::DEAD_OTHER); |
354 |
|
|
} |
355 |
|
|
if (XML::getBoolProperty(propertyNode, "on-impact", true)) |
356 |
|
|
{ |
357 |
|
|
mDeathEffectConditions += CAST_S8( |
358 |
|
|
AliveStatus::DEAD_IMPACT); |
359 |
|
|
} |
360 |
|
|
if (XML::getBoolProperty(propertyNode, "on-timeout", true)) |
361 |
|
|
{ |
362 |
|
|
mDeathEffectConditions += CAST_S8( |
363 |
|
|
AliveStatus::DEAD_TIMEOUT); |
364 |
|
|
} |
365 |
|
|
} |
366 |
|
|
} |
367 |
|
|
} |
368 |
|
|
|
369 |
|
|
ParticleEmitter::ParticleEmitter(const ParticleEmitter &o) |
370 |
|
|
{ |
371 |
|
|
*this = o; |
372 |
|
|
} |
373 |
|
|
|
374 |
|
|
ImageSet *ParticleEmitter::getImageSet(XmlNodePtrConst node) |
375 |
|
|
{ |
376 |
|
|
ImageSet *imageset = nullptr; |
377 |
|
|
const int subX = XML::getProperty(node, "subX", -1); |
378 |
|
|
if (subX != -1) |
379 |
|
|
{ |
380 |
|
|
Image *const img = Loader::getImage(XML::getProperty( |
381 |
|
|
node, "imageset", "")); |
382 |
|
|
if (img == nullptr) |
383 |
|
|
return nullptr; |
384 |
|
|
|
385 |
|
|
Image *const img2 = Loader::getSubImage(img, subX, |
386 |
|
|
XML::getProperty(node, "subY", 0), |
387 |
|
|
XML::getProperty(node, "subWidth", 0), |
388 |
|
|
XML::getProperty(node, "subHeight", 0)); |
389 |
|
|
if (img2 == nullptr) |
390 |
|
|
{ |
391 |
|
|
img->decRef(); |
392 |
|
|
return nullptr; |
393 |
|
|
} |
394 |
|
|
|
395 |
|
|
imageset = Loader::getSubImageSet(img2, |
396 |
|
|
XML::getProperty(node, "width", 0), |
397 |
|
|
XML::getProperty(node, "height", 0)); |
398 |
|
|
img2->decRef(); |
399 |
|
|
img->decRef(); |
400 |
|
|
} |
401 |
|
|
else |
402 |
|
|
{ |
403 |
|
|
imageset = Loader::getImageSet( |
404 |
|
|
XML::getProperty(node, "imageset", ""), |
405 |
|
|
XML::getProperty(node, "width", 0), |
406 |
|
|
XML::getProperty(node, "height", 0)); |
407 |
|
|
} |
408 |
|
|
return imageset; |
409 |
|
|
} |
410 |
|
|
|
411 |
|
|
ParticleEmitter & ParticleEmitter::operator=(const ParticleEmitter &o) |
412 |
|
|
{ |
413 |
|
|
mParticlePosX = o.mParticlePosX; |
414 |
|
|
mParticlePosY = o.mParticlePosY; |
415 |
|
|
mParticlePosZ = o.mParticlePosZ; |
416 |
|
|
mParticleAngleHorizontal = o.mParticleAngleHorizontal; |
417 |
|
|
mParticleAngleVertical = o.mParticleAngleVertical; |
418 |
|
|
mParticlePower = o.mParticlePower; |
419 |
|
|
mParticleGravity = o.mParticleGravity; |
420 |
|
|
mParticleRandomness = o.mParticleRandomness; |
421 |
|
|
mParticleBounce = o.mParticleBounce; |
422 |
|
|
mParticleFollow = o.mParticleFollow; |
423 |
|
|
mParticleTarget = o.mParticleTarget; |
424 |
|
|
mParticleAcceleration = o.mParticleAcceleration; |
425 |
|
|
mParticleDieDistance = o.mParticleDieDistance; |
426 |
|
|
mParticleMomentum = o.mParticleMomentum; |
427 |
|
|
mParticleLifetime = o.mParticleLifetime; |
428 |
|
|
mParticleFadeOut = o.mParticleFadeOut; |
429 |
|
|
mParticleFadeIn = o.mParticleFadeIn; |
430 |
|
|
mParticleAlpha = o.mParticleAlpha; |
431 |
|
|
mMap = o.mMap; |
432 |
|
|
mOutput = o.mOutput; |
433 |
|
|
mOutputPause = o.mOutputPause; |
434 |
|
|
mParticleImage = o.mParticleImage; |
435 |
|
|
mParticleAnimation = o.mParticleAnimation; |
436 |
|
|
mParticleRotation = o.mParticleRotation; |
437 |
|
|
mParticleChildEmitters = o.mParticleChildEmitters; |
438 |
|
|
mDeathEffectConditions = o.mDeathEffectConditions; |
439 |
|
|
mDeathEffect = o.mDeathEffect; |
440 |
|
|
mTempSets = o.mTempSets; |
441 |
|
|
|
442 |
|
|
FOR_EACH (ImageSetVectorCIter, i, mTempSets) |
443 |
|
|
{ |
444 |
|
|
if (*i != nullptr) |
445 |
|
|
(*i)->incRef(); |
446 |
|
|
} |
447 |
|
|
|
448 |
|
|
mOutputPauseLeft = 0; |
449 |
|
|
|
450 |
|
|
if (mParticleImage != nullptr) |
451 |
|
|
mParticleImage->incRef(); |
452 |
|
|
|
453 |
|
|
return *this; |
454 |
|
|
} |
455 |
|
|
|
456 |
|
|
ParticleEmitter::~ParticleEmitter() |
457 |
|
|
{ |
458 |
|
|
FOR_EACH (ImageSetVectorCIter, i, mTempSets) |
459 |
|
|
{ |
460 |
|
|
if (*i != nullptr) |
461 |
|
|
(*i)->decRef(); |
462 |
|
|
} |
463 |
|
|
mTempSets.clear(); |
464 |
|
|
|
465 |
|
|
if (mParticleImage != nullptr) |
466 |
|
|
{ |
467 |
|
|
mParticleImage->decRef(); |
468 |
|
|
mParticleImage = nullptr; |
469 |
|
|
} |
470 |
|
|
} |
471 |
|
|
|
472 |
|
|
template <typename T> ParticleEmitterProp<T> |
473 |
|
|
ParticleEmitter::readParticleEmitterProp(XmlNodePtrConst propertyNode, T def) |
474 |
|
|
{ |
475 |
|
|
ParticleEmitterProp<T> retval; |
476 |
|
|
|
477 |
|
|
def = static_cast<T>(XML::getDoubleProperty(propertyNode, "value", |
478 |
|
|
static_cast<double>(def))); |
479 |
|
|
retval.set(static_cast<T>(XML::getDoubleProperty(propertyNode, "min", |
480 |
|
|
static_cast<double>(def))), static_cast<T>(XML::getDoubleProperty( |
481 |
|
|
propertyNode, "max", static_cast<double>(def)))); |
482 |
|
|
|
483 |
|
|
const std::string change = XML::getProperty( |
484 |
|
|
propertyNode, "change-func", "none"); |
485 |
|
|
T amplitude = static_cast<T>(XML::getDoubleProperty(propertyNode, |
486 |
|
|
"change-amplitude", 0.0)); |
487 |
|
|
|
488 |
|
|
const int period = XML::getProperty(propertyNode, "change-period", 0); |
489 |
|
|
const int phase = XML::getProperty(propertyNode, "change-phase", 0); |
490 |
|
|
if (change == "saw" || change == "sawtooth") |
491 |
|
|
{ |
492 |
|
|
retval.setFunction(ParticleChangeFunc::FUNC_SAW, |
493 |
|
|
amplitude, period, phase); |
494 |
|
|
} |
495 |
|
|
else if (change == "sine" || change == "sinewave") |
496 |
|
|
{ |
497 |
|
|
retval.setFunction(ParticleChangeFunc::FUNC_SINE, |
498 |
|
|
amplitude, period, phase); |
499 |
|
|
} |
500 |
|
|
else if (change == "triangle") |
501 |
|
|
{ |
502 |
|
|
retval.setFunction(ParticleChangeFunc::FUNC_TRIANGLE, |
503 |
|
|
amplitude, period, phase); |
504 |
|
|
} |
505 |
|
|
else if (change == "square") |
506 |
|
|
{ |
507 |
|
|
retval.setFunction(ParticleChangeFunc::FUNC_SQUARE, |
508 |
|
|
amplitude, period, phase); |
509 |
|
|
} |
510 |
|
|
|
511 |
|
|
return retval; |
512 |
|
|
} |
513 |
|
|
|
514 |
|
|
void ParticleEmitter::createParticles(const int tick, |
515 |
|
|
STD_VECTOR<Particle*> &newParticles) |
516 |
|
|
{ |
517 |
|
|
if (mOutputPauseLeft > 0) |
518 |
|
|
{ |
519 |
|
|
mOutputPauseLeft --; |
520 |
|
|
return; |
521 |
|
|
} |
522 |
|
|
mOutputPauseLeft = mOutputPause.value(tick); |
523 |
|
|
|
524 |
|
|
for (int i = mOutput.value(tick); i > 0; i--) |
525 |
|
|
{ |
526 |
|
|
// Limit maximum particles |
527 |
|
|
if (ParticleEngine::particleCount > ParticleEngine::maxCount) |
528 |
|
|
break; |
529 |
|
|
|
530 |
|
|
Particle *newParticle = nullptr; |
531 |
|
|
if (mParticleImage != nullptr) |
532 |
|
|
{ |
533 |
|
|
const std::string &name = mParticleImage->mIdPath; |
534 |
|
|
if (ImageParticle::imageParticleCountByName.find(name) == |
535 |
|
|
ImageParticle::imageParticleCountByName.end()) |
536 |
|
|
{ |
537 |
|
|
ImageParticle::imageParticleCountByName[name] = 0; |
538 |
|
|
} |
539 |
|
|
|
540 |
|
|
if (ImageParticle::imageParticleCountByName[name] > 200) |
541 |
|
|
break; |
542 |
|
|
|
543 |
|
|
newParticle = new ImageParticle(mParticleImage); |
544 |
|
|
newParticle->setMap(mMap); |
545 |
|
|
} |
546 |
|
|
else if (!mParticleRotation.mFrames.empty()) |
547 |
|
|
{ |
548 |
|
|
Animation *const newAnimation = new Animation(mParticleRotation); |
549 |
|
|
newParticle = new RotationalParticle(newAnimation); |
550 |
|
|
newParticle->setMap(mMap); |
551 |
|
|
} |
552 |
|
|
else if (!mParticleAnimation.mFrames.empty()) |
553 |
|
|
{ |
554 |
|
|
Animation *const newAnimation = new Animation(mParticleAnimation); |
555 |
|
|
newParticle = new AnimationParticle(newAnimation); |
556 |
|
|
newParticle->setMap(mMap); |
557 |
|
|
} |
558 |
|
|
else |
559 |
|
|
{ |
560 |
|
|
newParticle = new Particle; |
561 |
|
|
newParticle->setMap(mMap); |
562 |
|
|
} |
563 |
|
|
|
564 |
|
|
const Vector position(mParticlePosX.value(tick), |
565 |
|
|
mParticlePosY.value(tick), |
566 |
|
|
mParticlePosZ.value(tick)); |
567 |
|
|
newParticle->moveTo(position); |
568 |
|
|
|
569 |
|
|
const float angleH = mParticleAngleHorizontal.value(tick); |
570 |
|
|
const float cosAngleH = static_cast<float>(cos(angleH)); |
571 |
|
|
const float sinAngleH = static_cast<float>(sin(angleH)); |
572 |
|
|
const float angleV = mParticleAngleVertical.value(tick); |
573 |
|
|
const float cosAngleV = static_cast<float>(cos(angleV)); |
574 |
|
|
const float sinAngleV = static_cast<float>(sin(angleV)); |
575 |
|
|
const float power = mParticlePower.value(tick); |
576 |
|
|
newParticle->setVelocity(cosAngleH * cosAngleV * power, |
577 |
|
|
sinAngleH * cosAngleV * power, |
578 |
|
|
sinAngleV * power); |
579 |
|
|
|
580 |
|
|
newParticle->setRandomness(mParticleRandomness.value(tick)); |
581 |
|
|
newParticle->setGravity(mParticleGravity.value(tick)); |
582 |
|
|
newParticle->setBounce(mParticleBounce.value(tick)); |
583 |
|
|
newParticle->setFollow(mParticleFollow); |
584 |
|
|
|
585 |
|
|
newParticle->setDestination(mParticleTarget, |
586 |
|
|
mParticleAcceleration.value(tick), |
587 |
|
|
mParticleMomentum.value(tick)); |
588 |
|
|
|
589 |
|
|
newParticle->setDieDistance(mParticleDieDistance.value(tick)); |
590 |
|
|
|
591 |
|
|
newParticle->setLifetime(mParticleLifetime.value(tick)); |
592 |
|
|
newParticle->setFadeOut(mParticleFadeOut.value(tick)); |
593 |
|
|
newParticle->setFadeIn(mParticleFadeIn.value(tick)); |
594 |
|
|
newParticle->setAlpha(mParticleAlpha.value(tick)); |
595 |
|
|
|
596 |
|
|
FOR_EACH (ParticleEmitterListCIter, it, mParticleChildEmitters) |
597 |
|
|
newParticle->addEmitter(new ParticleEmitter(*it)); |
598 |
|
|
|
599 |
|
|
if (!mDeathEffect.empty()) |
600 |
|
|
newParticle->setDeathEffect(mDeathEffect, mDeathEffectConditions); |
601 |
|
|
|
602 |
|
|
newParticles.push_back(newParticle); |
603 |
|
|
} |
604 |
|
|
} |
605 |
|
|
|
606 |
|
|
void ParticleEmitter::adjustSize(const int w, const int h) |
607 |
|
|
{ |
608 |
|
|
if (w == 0 || h == 0) |
609 |
|
|
return; // new dimensions are illegal |
610 |
|
|
|
611 |
|
|
// calculate the old rectangle |
612 |
|
|
const int oldArea = CAST_S32( |
613 |
|
|
mParticlePosX.maxVal - mParticlePosX.minVal) * CAST_S32( |
614 |
|
|
mParticlePosX.maxVal - mParticlePosY.minVal); |
615 |
|
|
if (oldArea == 0) |
616 |
|
|
{ |
617 |
|
|
// when the effect has no dimension it is |
618 |
|
|
// not designed to be resizeable |
619 |
|
|
return; |
620 |
|
|
} |
621 |
|
|
|
622 |
|
|
// set the new dimensions |
623 |
|
|
mParticlePosX.set(0, static_cast<float>(w)); |
624 |
|
|
mParticlePosY.set(0, static_cast<float>(h)); |
625 |
|
|
const int newArea = w * h; |
626 |
|
|
// adjust the output so that the particle density stays the same |
627 |
|
|
const float outputFactor = static_cast<float>(newArea) |
628 |
|
|
/ static_cast<float>(oldArea); |
629 |
|
|
mOutput.minVal = CAST_S32(static_cast<float>( |
630 |
|
|
mOutput.minVal) * outputFactor); |
631 |
|
|
mOutput.maxVal = CAST_S32(static_cast<float>( |
632 |
|
|
mOutput.maxVal) * outputFactor); |
633 |
|
2 |
} |