1 /** @file generator.cpp  World map (particle) generator.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2006-2015 Daniel Swanson <danij@dengine.net>
5  * @authors Copyright © 2006-2007 Jamie Jones <jamie_jones_au@yahoo.com.au>
6  *
7  * @par License
8  * GPL: http://www.gnu.org/licenses/gpl.html
9  *
10  * <small>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 the
12  * Free Software Foundation; either version 2 of the License, or (at your
13  * option) any later version. This program is distributed in the hope that it
14  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
15  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16  * Public License for more details. You should have received a copy of the GNU
17  * General Public License along with this program; if not, write to the Free
18  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19  * 02110-1301 USA</small>
20  */
21 
22 #include "de_platform.h"
23 #include "world/generator.h"
24 
25 #include "world/clientserverworld.h" // validCount
26 #include "world/thinkers.h"
27 #include "client/cl_mobj.h"
28 #include "BspLeaf"
29 #include "ConvexSubspace"
30 #include "Surface"
31 
32 #include "render/rend_model.h"
33 #include "render/rend_particle.h"
34 
35 #include "client/clientsubsector.h"
36 #include "network/net_main.h"
37 
38 #include "api_sound.h"
39 
40 #include "Face"
41 #include "dd_def.h"
42 #include "clientapp.h"
43 
44 #include <doomsday/console/var.h>
45 #include <de/String>
46 #include <de/Time>
47 #include <de/fixedpoint.h>
48 #include <de/memoryzone.h>
49 #include <de/timer.h>
50 #include <de/vector1.h>
51 #include <cmath>
52 
53 using namespace de;
54 
55 #define DOT2F(a,b)          ( FIX2FLT(a[0]) * FIX2FLT(b[0]) + FIX2FLT(a[1]) * FIX2FLT(b[1]) )
56 #define VECMUL(a,scalar)    ( a[0] = FixedMul(a[0], scalar), a[1] = FixedMul(a[1], scalar) )
57 #define VECADD(a,b)         ( a[0] += b[0], a[1] += b[1] )
58 #define VECMULADD(a,scal,b) ( a[0] += FixedMul(scal, b[0]), a[1] += FixedMul(scal, b[1]) )
59 #define VECSUB(a,b)         ( a[0] -= b[0], a[1] -= b[1] )
60 #define VECCPY(a,b)         ( a[0] = b[0], a[1] = b[1] )
61 
62 static float particleSpawnRate = 1; // Unmodified (cvar).
63 
64 /**
65  * The offset is spherical and random.
66  * Low and High should be positive.
67  */
uncertainPosition(fixed_t * pos,fixed_t low,fixed_t high)68 static void uncertainPosition(fixed_t *pos, fixed_t low, fixed_t high)
69 {
70     if(!low)
71     {
72         // The simple, cubic algorithm.
73         for(int i = 0; i < 3; ++i)
74         {
75             pos[i] += (high * (RNG_RandByte() - RNG_RandByte())) * reciprocal255;
76         }
77     }
78     else
79     {
80         // The more complicated, spherical algorithm.
81         fixed_t off = ((high - low) * (RNG_RandByte() - RNG_RandByte())) * reciprocal255;
82         off += off < 0 ? -low : low;
83 
84         fixed_t theta = RNG_RandByte() << (24 - ANGLETOFINESHIFT);
85         fixed_t phi = acos(2 * (RNG_RandByte() * reciprocal255) - 1) / PI * (ANGLE_180 >> ANGLETOFINESHIFT);
86 
87         fixed_t vec[3];
88         vec[0] = FixedMul(fineCosine[theta], finesine[phi]);
89         vec[1] = FixedMul(finesine[theta], finesine[phi]);
90         vec[2] = FixedMul(fineCosine[phi], FLT2FIX(0.8333f));
91 
92         for(int i = 0; i < 3; ++i)
93         {
94             pos[i] += FixedMul(vec[i], off);
95         }
96     }
97 }
98 
99 namespace world {
100 
map() const101 Map &Generator::map() const
102 {
103     return Thinker_Map(thinker);
104 }
105 
id() const106 Generator::Id Generator::id() const
107 {
108     return _id;
109 }
110 
setId(Id newId)111 void Generator::setId(Id newId)
112 {
113     DENG2_ASSERT(newId >= 1 && newId <= Map::MAX_GENERATORS); // 1-based
114     _id = newId;
115 }
116 
age() const117 dint Generator::age() const
118 {
119     return _age;
120 }
121 
origin() const122 Vector3d Generator::origin() const
123 {
124     if(source)
125     {
126         Vector3d origin(source->origin);
127         origin.z += -source->floorClip + FIX2FLT(originAtSpawn[2]);
128         return origin;
129     }
130 
131     return Vector3d(FIX2FLT(originAtSpawn[0]), FIX2FLT(originAtSpawn[1]), FIX2FLT(originAtSpawn[2]));
132 }
133 
clearParticles()134 void Generator::clearParticles()
135 {
136     Z_Free(_pinfo);
137     _pinfo = nullptr;
138 }
139 
configureFromDef(ded_ptcgen_t const * newDef)140 void Generator::configureFromDef(ded_ptcgen_t const *newDef)
141 {
142     DENG2_ASSERT(newDef);
143 
144     if(count <= 0)
145         count = 1;
146 
147     // Make sure no generator is type-triggered by default.
148     type   = type2 = -1;
149 
150     def    = newDef;
151     _flags = Flags(def->flags);
152     _pinfo = (ParticleInfo *) Z_Calloc(sizeof(ParticleInfo) * count, PU_MAP, 0);
153     stages = (ParticleStage *) Z_Calloc(sizeof(ParticleStage) * def->stages.size(), PU_MAP, 0);
154 
155     for(dint i = 0; i < def->stages.size(); ++i)
156     {
157         ded_ptcstage_t const *sdef = &def->stages[i];
158         ParticleStage *s = &stages[i];
159 
160         s->bounce     = FLT2FIX(sdef->bounce);
161         s->resistance = FLT2FIX(1 - sdef->resistance);
162         s->radius     = FLT2FIX(sdef->radius);
163         s->gravity    = FLT2FIX(sdef->gravity);
164         s->type       = sdef->type;
165         s->flags      = ParticleStage::Flags(sdef->flags);
166     }
167 
168     // Init some data.
169     for(dint i = 0; i < 3; ++i)
170     {
171         originAtSpawn[i] = FLT2FIX(def->center[i]);
172         vector[i] = FLT2FIX(def->vector[i]);
173     }
174 
175     // Apply a random component to the spawn vector.
176     if(def->initVectorVariance > 0)
177     {
178         uncertainPosition(vector, 0, FLT2FIX(def->initVectorVariance));
179     }
180 
181     // Mark unused.
182     for(dint i = 0; i < count; ++i)
183     {
184         ParticleInfo* pinfo = &_pinfo[i];
185         pinfo->stage = -1;
186     }
187 }
188 
presimulate(dint tics)189 void Generator::presimulate(dint tics)
190 {
191     for(; tics > 0; tics--)
192     {
193         runTick();
194     }
195 
196     // Reset age so presim doesn't affect it.
197     _age = 0;
198 }
199 
isStatic() const200 bool Generator::isStatic() const
201 {
202     return _flags.testFlag(Static);
203 }
204 
isUntriggered() const205 bool Generator::isUntriggered() const
206 {
207     return _untriggered;
208 }
209 
setUntriggered(bool yes)210 void Generator::setUntriggered(bool yes)
211 {
212     _untriggered = yes;
213 }
214 
blendmode() const215 blendmode_t Generator::blendmode() const
216 {
217     /// @todo Translate these old flags once, during definition parsing -ds
218     if(_flags.testFlag(BlendAdditive))        return BM_ADD;
219     if(_flags.testFlag(BlendSubtract))        return BM_SUBTRACT;
220     if(_flags.testFlag(BlendReverseSubtract)) return BM_REVERSE_SUBTRACT;
221     if(_flags.testFlag(BlendMultiply))        return BM_MUL;
222     if(_flags.testFlag(BlendInverseMultiply)) return BM_INVERSE_MUL;
223     return BM_NORMAL;
224 }
225 
activeParticleCount() const226 dint Generator::activeParticleCount() const
227 {
228     dint numActive = 0;
229     for(dint i = 0; i < count; ++i)
230     {
231         if(_pinfo[i].stage >= 0)
232         {
233             numActive += 1;
234         }
235     }
236     return numActive;
237 }
238 
particleInfo() const239 ParticleInfo const *Generator::particleInfo() const
240 {
241     return _pinfo;
242 }
243 
setParticleAngles(ParticleInfo * pinfo,dint flags)244 static void setParticleAngles(ParticleInfo *pinfo, dint flags)
245 {
246     DENG2_ASSERT(pinfo);
247 
248     if(flags & Generator::ParticleStage::ZeroYaw)
249         pinfo->yaw = 0;
250     if(flags & Generator::ParticleStage::ZeroPitch)
251         pinfo->pitch = 0;
252     if(flags & Generator::ParticleStage::RandomYaw)
253         pinfo->yaw = RNG_RandFloat() * 65536;
254     if(flags & Generator::ParticleStage::RandomPitch)
255         pinfo->pitch = RNG_RandFloat() * 65536;
256 }
257 
particleSound(fixed_t pos[3],ded_embsound_t * sound)258 static void particleSound(fixed_t pos[3], ded_embsound_t *sound)
259 {
260     DENG2_ASSERT(pos && sound);
261 
262     // Is there any sound to play?
263     if(!sound->id || sound->volume <= 0) return;
264 
265     ddouble orig[3];
266     for(dint i = 0; i < 3; ++i)
267     {
268         orig[i] = FIX2FLT(pos[i]);
269     }
270 
271     S_LocalSoundAtVolumeFrom(sound->id, nullptr, orig, sound->volume);
272 }
273 
newParticle()274 dint Generator::newParticle()
275 {
276 #ifdef __CLIENT__
277     // Check for model-only generators.
278     dfloat inter = -1;
279     FrameModelDef *mf = 0, *nextmf = 0;
280     if(source)
281     {
282         mf = Mobj_ModelDef(*source, &nextmf, &inter);
283         if(((!mf || !useModels) && def->flags & ModelOnly) ||
284            (mf && useModels && mf->flags & MFF_NO_PARTICLES))
285             return -1;
286     }
287 
288     // Keep the spawn cursor in the valid range.
289     if(++_spawnCP >= count)
290     {
291         _spawnCP -= count;
292     }
293 
294     dint const newParticleIdx = _spawnCP;
295 
296     // Set the particle's data.
297     ParticleInfo *pinfo = &_pinfo[_spawnCP];
298     pinfo->stage = 0;
299     if(RNG_RandFloat() < def->altStartVariance)
300     {
301         pinfo->stage = def->altStart;
302     }
303 
304     pinfo->tics = def->stages[pinfo->stage].tics *
305         (1 - def->stages[pinfo->stage].variance * RNG_RandFloat());
306 
307     // Launch vector.
308     pinfo->mov[0] = vector[0];
309     pinfo->mov[1] = vector[1];
310     pinfo->mov[2] = vector[2];
311 
312     // Apply some random variance.
313     pinfo->mov[0] += FLT2FIX(def->vectorVariance * (RNG_RandFloat() - RNG_RandFloat()));
314     pinfo->mov[1] += FLT2FIX(def->vectorVariance * (RNG_RandFloat() - RNG_RandFloat()));
315     pinfo->mov[2] += FLT2FIX(def->vectorVariance * (RNG_RandFloat() - RNG_RandFloat()));
316 
317     // Apply some aspect ratio scaling to the momentum vector.
318     // This counters the 200/240 difference nearly completely.
319     pinfo->mov[0] = FixedMul(pinfo->mov[0], FLT2FIX(1.1f));
320     pinfo->mov[1] = FixedMul(pinfo->mov[1], FLT2FIX(0.95f));
321     pinfo->mov[2] = FixedMul(pinfo->mov[2], FLT2FIX(1.1f));
322 
323     // Set proper speed.
324     fixed_t uncertain = FLT2FIX(def->speed * (1 - def->speedVariance * RNG_RandFloat()));
325 
326     fixed_t len = FLT2FIX(M_ApproxDistancef(
327         M_ApproxDistancef(FIX2FLT(pinfo->mov[0]), FIX2FLT(pinfo->mov[1])), FIX2FLT(pinfo->mov[2])));
328     if(!len) len = FRACUNIT;
329     len = FixedDiv(uncertain, len);
330 
331     pinfo->mov[0] = FixedMul(pinfo->mov[0], len);
332     pinfo->mov[1] = FixedMul(pinfo->mov[1], len);
333     pinfo->mov[2] = FixedMul(pinfo->mov[2], len);
334 
335     // The source is a mobj?
336     if(source)
337     {
338         if(_flags & RelativeVector)
339         {
340             // Rotate the vector using the source angle.
341             dfloat temp[3];
342 
343             temp[0] = FIX2FLT(pinfo->mov[0]);
344             temp[1] = FIX2FLT(pinfo->mov[1]);
345             temp[2] = 0;
346 
347             // Player visangles have some problems, let's not use them.
348             M_RotateVector(temp, source->angle / (float) ANG180 * -180 + 90, 0);
349 
350             pinfo->mov[0] = FLT2FIX(temp[0]);
351             pinfo->mov[1] = FLT2FIX(temp[1]);
352         }
353 
354         if(_flags & RelativeVelocity)
355         {
356             pinfo->mov[0] += FLT2FIX(source->mom[MX]);
357             pinfo->mov[1] += FLT2FIX(source->mom[MY]);
358             pinfo->mov[2] += FLT2FIX(source->mom[MZ]);
359         }
360 
361         // Origin.
362         pinfo->origin[0] = FLT2FIX(source->origin[0]);
363         pinfo->origin[1] = FLT2FIX(source->origin[1]);
364         pinfo->origin[2] = FLT2FIX(source->origin[2] - source->floorClip);
365 
366         uncertainPosition(pinfo->origin, FLT2FIX(def->spawnRadiusMin), FLT2FIX(def->spawnRadius));
367 
368         // Offset to the real center.
369         pinfo->origin[2] += originAtSpawn[2];
370 
371         // Include bobbing in the spawn height.
372         pinfo->origin[2] -= FLT2FIX(Mobj_BobOffset(*source));
373 
374         // Calculate XY center with mobj angle.
375         angle_t const angle = Mobj_AngleSmoothed(source) + (fixed_t) (FIX2FLT(originAtSpawn[1]) / 180.0f * ANG180);
376         duint const an      = angle >> ANGLETOFINESHIFT;
377         duint const an2     = (angle + ANG90) >> ANGLETOFINESHIFT;
378 
379         pinfo->origin[0] += FixedMul(fineCosine[an], originAtSpawn[0]);
380         pinfo->origin[1] += FixedMul(finesine[an], originAtSpawn[0]);
381 
382         // There might be an offset from the model of the mobj.
383         if(mf && (mf->testSubFlag(0, MFF_PARTICLE_SUB1) || def->subModel >= 0))
384         {
385             dfloat off[3] = { 0, 0, 0 };
386             dint subidx;
387 
388             // Select the right submodel to use as the origin.
389             if(def->subModel >= 0)
390                 subidx = def->subModel;
391             else
392                 subidx = 1; // Default to submodel #1.
393 
394             // Interpolate the offset.
395             if(inter > 0 && nextmf)
396             {
397                 off[0] = nextmf->particleOffset(subidx)[0] - mf->particleOffset(subidx)[0];
398                 off[1] = nextmf->particleOffset(subidx)[1] - mf->particleOffset(subidx)[1];
399                 off[2] = nextmf->particleOffset(subidx)[2] - mf->particleOffset(subidx)[2];
400 
401                 off[0] *= inter;
402                 off[1] *= inter;
403                 off[2] *= inter;
404             }
405 
406             off[0] += mf->particleOffset(subidx)[0];
407             off[1] += mf->particleOffset(subidx)[1];
408             off[2] += mf->particleOffset(subidx)[2];
409 
410             // Apply it to the particle coords.
411             pinfo->origin[0] += FixedMul(fineCosine[an],  FLT2FIX(off[0]));
412             pinfo->origin[0] += FixedMul(fineCosine[an2], FLT2FIX(off[2]));
413             pinfo->origin[1] += FixedMul(finesine[an],    FLT2FIX(off[0]));
414             pinfo->origin[1] += FixedMul(finesine[an2],   FLT2FIX(off[2]));
415             pinfo->origin[2] += FLT2FIX(off[1]);
416         }
417     }
418     else if(plane)
419     {
420         /// @todo fixme: ignorant of mapped sector planes.
421         fixed_t radius = stages[pinfo->stage].radius;
422         Sector const *sector = &plane->sector();
423 
424         // Choose a random spot inside the sector, on the spawn plane.
425         if(_flags & SpawnSpace)
426         {
427             pinfo->origin[2] =
428                 FLT2FIX(sector->floor().height()) + radius +
429                 FixedMul(RNG_RandByte() << 8,
430                          FLT2FIX(sector->ceiling().height() -
431                                  sector->floor().height()) - 2 * radius);
432         }
433         else if((_flags & SpawnFloor) ||
434                 (!(_flags & (SpawnFloor | SpawnCeiling)) &&
435                  plane->isSectorFloor()))
436         {
437             // Spawn on the floor.
438             pinfo->origin[2] = FLT2FIX(plane->height()) + radius;
439         }
440         else
441         {
442             // Spawn on the ceiling.
443             pinfo->origin[2] = FLT2FIX(plane->height()) - radius;
444         }
445 
446         /**
447          * Choosing the XY spot is a bit more difficult.
448          * But we must be fast and only sufficiently accurate.
449          *
450          * @todo Nothing prevents spawning on the wrong side (or inside) of one-sided
451          * walls (large diagonal subspaces!).
452          */
453         ConvexSubspace *subspace = 0;
454         for(dint i = 0; i < 5; ++i) // Try a couple of times (max).
455         {
456             dfloat x = sector->bounds().minX +
457                 RNG_RandFloat() * (sector->bounds().maxX - sector->bounds().minX);
458             dfloat y = sector->bounds().minY +
459                 RNG_RandFloat() * (sector->bounds().maxY - sector->bounds().minY);
460 
461             subspace = map().bspLeafAt(Vector2d(x, y)).subspacePtr();
462             if(subspace && sector == &subspace->sector())
463                 break;
464 
465             subspace = 0;
466         }
467 
468         if(!subspace)
469         {
470             pinfo->stage = -1;
471             return -1;
472         }
473 
474         AABoxd const &subBounds = subspace->poly().bounds();
475 
476         // Try a couple of times to get a good random spot.
477         dint tries;
478         for(tries = 0; tries < 10; ++tries) // Max this many tries before giving up.
479         {
480             dfloat x = subBounds.minX +
481                 RNG_RandFloat() * (subBounds.maxX - subBounds.minX);
482             dfloat y = subBounds.minY +
483                 RNG_RandFloat() * (subBounds.maxY - subBounds.minY);
484 
485             pinfo->origin[0] = FLT2FIX(x);
486             pinfo->origin[1] = FLT2FIX(y);
487 
488             if(subspace == map().bspLeafAt(Vector2d(x, y)).subspacePtr())
489                 break; // This is a good place.
490         }
491 
492         if(tries == 10) // No good place found?
493         {
494             pinfo->stage = -1; // Damn.
495             return -1;
496         }
497     }
498     else if(isUntriggered())
499     {
500         // The center position is the spawn origin.
501         pinfo->origin[0] = originAtSpawn[0];
502         pinfo->origin[1] = originAtSpawn[1];
503         pinfo->origin[2] = originAtSpawn[2];
504         uncertainPosition(pinfo->origin, FLT2FIX(def->spawnRadiusMin),
505                           FLT2FIX(def->spawnRadius));
506     }
507 
508     // Initial angles for the particle.
509     setParticleAngles(pinfo, def->stages[pinfo->stage].flags);
510 
511     // The other place where this gets updated is after moving over
512     // a two-sided line.
513     /*if(plane)
514     {
515         pinfo->sector = &plane->sector();
516     }
517     else*/
518     {
519         Vector2d ptOrigin(FIX2FLT(pinfo->origin[0]), FIX2FLT(pinfo->origin[1]));
520         pinfo->bspLeaf = &map().bspLeafAt(ptOrigin);
521 
522         // A BSP leaf with no geometry is not a suitable place for a particle.
523         if(!pinfo->bspLeaf->hasSubspace())
524         {
525             pinfo->stage = -1;
526             return -1;
527         }
528     }
529 
530     // Play a stage sound?
531     particleSound(pinfo->origin, &def->stages[pinfo->stage].sound);
532 
533     return newParticleIdx;
534 #else  // !__CLIENT__
535     return -1;
536 #endif
537 }
538 
539 #ifdef __CLIENT__
540 
541 /**
542  * Callback for the client mobj iterator, called from P_PtcGenThinker.
543  */
newGeneratorParticlesWorker(mobj_t * cmo,void * context)544 static dint newGeneratorParticlesWorker(mobj_t *cmo, void *context)
545 {
546     Generator *gen = (Generator *) context;
547     ClientMobjThinkerData::RemoteSync *info = ClMobj_GetInfo(cmo);
548 
549     // If the clmobj is not valid at the moment, don't do anything.
550     if(info->flags & (CLMF_UNPREDICTABLE | CLMF_HIDDEN))
551     {
552         return false;
553     }
554 
555     if(cmo->type != gen->type && cmo->type != gen->type2)
556     {
557         // Type mismatch.
558         return false;
559     }
560 
561     gen->source = cmo;
562     gen->newParticle();
563     return false;
564 }
565 
566 #endif
567 
568 /**
569  * Particle touches something solid. Returns false iff the particle dies.
570  */
touchParticle(ParticleInfo * pinfo,Generator::ParticleStage * stage,ded_ptcstage_t * stageDef,bool touchWall)571 static dint touchParticle(ParticleInfo *pinfo, Generator::ParticleStage *stage,
572     ded_ptcstage_t *stageDef, bool touchWall)
573 {
574     // Play a hit sound.
575     particleSound(pinfo->origin, &stageDef->hitSound);
576 
577     if(stage->flags.testFlag(Generator::ParticleStage::DieTouch))
578     {
579         // Particle dies from touch.
580         pinfo->stage = -1;
581         return false;
582     }
583 
584     if(stage->flags.testFlag(Generator::ParticleStage::StageTouch) ||
585        (touchWall && stage->flags.testFlag(Generator::ParticleStage::StageWallTouch)) ||
586        (!touchWall && stage->flags.testFlag(Generator::ParticleStage::StageFlatTouch)))
587     {
588         // Particle advances to the next stage.
589         pinfo->tics = 0;
590     }
591 
592     // Particle survives the touch.
593     return true;
594 }
595 
particleZ(ParticleInfo const & pinfo) const596 dfloat Generator::particleZ(ParticleInfo const &pinfo) const
597 {
598     auto const &subsec = pinfo.bspLeaf->subspace().subsector().as<world::ClientSubsector>();
599     if(pinfo.origin[2] == DDMAXINT)
600     {
601         return subsec.visCeiling().heightSmoothed() - 2;
602     }
603     if(pinfo.origin[2] == DDMININT)
604     {
605         return (subsec.visFloor().heightSmoothed() + 2);
606     }
607     return FIX2FLT(pinfo.origin[2]);
608 }
609 
particleOrigin(ParticleInfo const & pt) const610 Vector3f Generator::particleOrigin(ParticleInfo const &pt) const
611 {
612     return Vector3f(FIX2FLT(pt.origin[0]), FIX2FLT(pt.origin[1]), particleZ(pt));
613 }
614 
particleMomentum(ParticleInfo const & pt) const615 Vector3f Generator::particleMomentum(ParticleInfo const &pt) const
616 {
617     return Vector3f(FIX2FLT(pt.mov[0]), FIX2FLT(pt.mov[1]), FIX2FLT(pt.mov[2]));
618 }
619 
spinParticle(ParticleInfo & pinfo)620 void Generator::spinParticle(ParticleInfo &pinfo)
621 {
622     static dint const yawSigns[4]   = { 1,  1, -1, -1 };
623     static dint const pitchSigns[4] = { 1, -1,  1, -1 };
624 
625     ded_ptcstage_t const *stDef = &def->stages[pinfo.stage];
626     duint const spinIndex        = uint(&pinfo - &_pinfo[id() / 8]) % 4;
627 
628     DENG2_ASSERT(spinIndex < 4);
629 
630     dint const yawSign   =   yawSigns[spinIndex];
631     dint const pitchSign = pitchSigns[spinIndex];
632 
633     if(stDef->spin[0] != 0)
634     {
635         pinfo.yaw   += 65536 * yawSign   * stDef->spin[0] / (360 * TICSPERSEC);
636     }
637     if(stDef->spin[1] != 0)
638     {
639         pinfo.pitch += 65536 * pitchSign * stDef->spin[1] / (360 * TICSPERSEC);
640     }
641 
642     pinfo.yaw   *= 1 - stDef->spinResistance[0];
643     pinfo.pitch *= 1 - stDef->spinResistance[1];
644 }
645 
moveParticle(dint index)646 void Generator::moveParticle(dint index)
647 {
648     DENG2_ASSERT(index >= 0 && index < count);
649 
650     ParticleInfo *pinfo   = &_pinfo[index];
651     ParticleStage *st     = &stages[pinfo->stage];
652     ded_ptcstage_t *stDef = &def->stages[pinfo->stage];
653 
654     // Particle rotates according to spin speed.
655     spinParticle(*pinfo);
656 
657     // Changes to momentum.
658     /// @todo Do not assume generator is from the CURRENT map.
659     pinfo->mov[2] -= FixedMul(FLT2FIX(map().gravity()), st->gravity);
660 
661     // Vector force.
662     if(stDef->vectorForce[0] != 0 || stDef->vectorForce[1] != 0 ||
663        stDef->vectorForce[2] != 0)
664     {
665         for(dint i = 0; i < 3; ++i)
666         {
667             pinfo->mov[i] += FLT2FIX(stDef->vectorForce[i]);
668         }
669     }
670 
671     // Sphere force pull and turn.
672     // Only applicable to sourced or untriggered generators. For other
673     // types it's difficult to define the center coordinates.
674     if(st->flags.testFlag(ParticleStage::SphereForce) &&
675        (source || isUntriggered()))
676     {
677         dfloat delta[3];
678 
679         if(source)
680         {
681             delta[0] = FIX2FLT(pinfo->origin[0]) - source->origin[0];
682             delta[1] = FIX2FLT(pinfo->origin[1]) - source->origin[1];
683             delta[2] = particleZ(*pinfo) - (source->origin[2] + FIX2FLT(originAtSpawn[2]));
684         }
685         else
686         {
687             for(dint i = 0; i < 3; ++i)
688             {
689                 delta[i] = FIX2FLT(pinfo->origin[i] - originAtSpawn[i]);
690             }
691         }
692 
693         // Apply the offset (to source coords).
694         for(dint i = 0; i < 3; ++i)
695         {
696             delta[i] -= def->forceOrigin[i];
697         }
698 
699         // Counter the aspect ratio of old times.
700         delta[2] *= 1.2f;
701 
702         dfloat dist = M_ApproxDistancef(M_ApproxDistancef(delta[0], delta[1]), delta[2]);
703         if(dist != 0)
704         {
705             // Radial force pushes the particles on the surface of a sphere.
706             if(def->force)
707             {
708                 // Normalize delta vector, multiply with (dist - forceRadius),
709                 // multiply with radial force strength.
710                 for(dint i = 0; i < 3; ++i)
711                 {
712                     pinfo->mov[i] -= FLT2FIX(
713                         ((delta[i] / dist) * (dist - def->forceRadius)) * def->force);
714                 }
715             }
716 
717             // Rotate!
718             if(def->forceAxis[0] || def->forceAxis[1] || def->forceAxis[2])
719             {
720                 dfloat cross[3];
721                 V3f_CrossProduct(cross, def->forceAxis, delta);
722 
723                 for(dint i = 0; i < 3; ++i)
724                 {
725                     pinfo->mov[i] += FLT2FIX(cross[i]) >> 8;
726                 }
727             }
728         }
729     }
730 
731     if(st->resistance != FRACUNIT)
732     {
733         for(dint i = 0; i < 3; ++i)
734         {
735             pinfo->mov[i] = FixedMul(pinfo->mov[i], st->resistance);
736         }
737     }
738 
739     // The particle is 'soft': half of radius is ignored.
740     // The exception is plane flat particles, which are rendered flat
741     // against planes. They are almost entirely soft when it comes to plane
742     // collisions.
743     fixed_t hardRadius = st->radius / 2;
744     if((st->type == PTC_POINT || (st->type >= PTC_TEXTURE && st->type < PTC_TEXTURE + MAX_PTC_TEXTURES)) &&
745        st->flags.testFlag(ParticleStage::PlaneFlat))
746     {
747         hardRadius = FRACUNIT;
748     }
749 
750     // Check the new Z position only if not stuck to a plane.
751     fixed_t z = pinfo->origin[2] + pinfo->mov[2];
752     bool zBounce = false, hitFloor = false;
753     if(pinfo->origin[2] != DDMININT && pinfo->origin[2] != DDMAXINT && pinfo->bspLeaf)
754     {
755         auto &subsec = pinfo->bspLeaf->subspace().subsector().as<world::ClientSubsector>();
756         if(z > FLT2FIX(subsec.visCeiling().heightSmoothed()) - hardRadius)
757         {
758             // The Z is through the roof!
759             if(subsec.visCeiling().surface().hasSkyMaskedMaterial())
760             {
761                 // Special case: particle gets lost in the sky.
762                 pinfo->stage = -1;
763                 return;
764             }
765 
766             if(!touchParticle(pinfo, st, stDef, false))
767                 return;
768 
769             z = FLT2FIX(subsec.visCeiling().heightSmoothed()) - hardRadius;
770             zBounce = true;
771             hitFloor = false;
772         }
773 
774         // Also check the floor.
775         if(z < FLT2FIX(subsec.visFloor().heightSmoothed()) + hardRadius)
776         {
777             if(subsec.visFloor().surface().hasSkyMaskedMaterial())
778             {
779                 pinfo->stage = -1;
780                 return;
781             }
782 
783             if(!touchParticle(pinfo, st, stDef, false))
784                 return;
785 
786             z = FLT2FIX(subsec.visFloor().heightSmoothed()) + hardRadius;
787             zBounce = true;
788             hitFloor = true;
789         }
790 
791         if(zBounce)
792         {
793             pinfo->mov[2] = FixedMul(-pinfo->mov[2], st->bounce);
794             if(!pinfo->mov[2])
795             {
796                 // The particle has stopped moving. This means its Z-movement
797                 // has ceased because of the collision with a plane. Plane-flat
798                 // particles will stick to the plane.
799                 if((st->type == PTC_POINT || (st->type >= PTC_TEXTURE && st->type < PTC_TEXTURE + MAX_PTC_TEXTURES)) &&
800                    st->flags.testFlag(ParticleStage::PlaneFlat))
801                 {
802                     z = hitFloor ? DDMININT : DDMAXINT;
803                 }
804             }
805         }
806 
807         // Move to the new Z coordinate.
808         pinfo->origin[2] = z;
809     }
810 
811     // Now check the XY direction.
812     // - Check if the movement crosses any solid lines.
813     // - If it does, quit when first one contacted and apply appropriate
814     //   bounce (result depends on the angle of the contacted wall).
815     fixed_t x = pinfo->origin[0] + pinfo->mov[0];
816     fixed_t y = pinfo->origin[1] + pinfo->mov[1];
817 
818     struct checklineworker_params_t
819     {
820         AABoxd box;
821         fixed_t tmpz, tmprad, tmpx1, tmpx2, tmpy1, tmpy2;
822         bool tmcross;
823         Line *ptcHitLine;
824     };
825     checklineworker_params_t clParm; zap(clParm);
826     clParm.tmcross = false; // Has crossed potential sector boundary?
827 
828     // XY movement can be skipped if the particle is not moving on the
829     // XY plane.
830     if(!pinfo->mov[0] && !pinfo->mov[1])
831     {
832         // If the particle is contacting a line, there is a chance that the
833         // particle should be killed (if it's moving slowly at max).
834         if(pinfo->contact)
835         {
836             Sector *front = pinfo->contact->front().sectorPtr();
837             Sector *back  = pinfo->contact->back().sectorPtr();
838 
839             if(front && back && abs(pinfo->mov[2]) < FRACUNIT / 2)
840             {
841                 coord_t const pz = particleZ(*pinfo);
842 
843                 coord_t fz;
844                 if(front->floor().height() > back->floor().height())
845                 {
846                     fz = front->floor().height();
847                 }
848                 else
849                 {
850                     fz = back->floor().height();
851                 }
852 
853                 coord_t cz;
854                 if(front->ceiling().height() < back->ceiling().height())
855                 {
856                     cz = front->ceiling().height();
857                 }
858                 else
859                 {
860                     cz = back->ceiling().height();
861                 }
862 
863                 // If the particle is in the opening of a 2-sided line, it's
864                 // quite likely that it shouldn't be here...
865                 if(pz > fz && pz < cz)
866                 {
867                     // Kill the particle.
868                     pinfo->stage = -1;
869                     return;
870                 }
871             }
872         }
873 
874         // Still not moving on the XY plane...
875         goto quit_iteration;
876     }
877 
878     // We're moving in XY, so if we don't hit anything there can't be any line contact.
879     pinfo->contact = 0;
880 
881     // Bounding box of the movement line.
882     clParm.tmpz = z;
883     clParm.tmprad = hardRadius;
884     clParm.tmpx1 = pinfo->origin[0];
885     clParm.tmpx2 = x;
886     clParm.tmpy1 = pinfo->origin[1];
887     clParm.tmpy2 = y;
888 
889     vec2d_t point;
890     V2d_Set(point, FIX2FLT(MIN_OF(x, pinfo->origin[0]) - st->radius),
891                    FIX2FLT(MIN_OF(y, pinfo->origin[1]) - st->radius));
892     V2d_InitBox(clParm.box.arvec2, point);
893     V2d_Set(point, FIX2FLT(MAX_OF(x, pinfo->origin[0]) + st->radius),
894                    FIX2FLT(MAX_OF(y, pinfo->origin[1]) + st->radius));
895     V2d_AddToBox(clParm.box.arvec2, point);
896 
897     // Iterate the lines in the contacted blocks.
898 
899     validCount++;
900     DENG2_ASSERT(!clParm.ptcHitLine);
901     map().forAllLinesInBox(clParm.box, [&clParm] (Line &line)
902     {
903         // Does the bounding box miss the line completely?
904         if(clParm.box.maxX <= line.bounds().minX || clParm.box.minX >= line.bounds().maxX ||
905            clParm.box.maxY <= line.bounds().minY || clParm.box.minY >= line.bounds().maxY)
906         {
907             return LoopContinue;
908         }
909 
910         // Movement must cross the line.
911         if((line.pointOnSide(Vector2d(FIX2FLT(clParm.tmpx1), FIX2FLT(clParm.tmpy1))) < 0) ==
912            (line.pointOnSide(Vector2d(FIX2FLT(clParm.tmpx2), FIX2FLT(clParm.tmpy2))) < 0))
913         {
914             return LoopContinue;
915         }
916 
917         /*
918          * We are possibly hitting something here.
919          */
920 
921         // Bounce if we hit a solid wall.
922         /// @todo fixme: What about "one-way" window lines?
923         clParm.ptcHitLine = &line;
924         if(!line.back().hasSector())
925         {
926             return LoopAbort; // Boing!
927         }
928 
929         Sector *front = line.front().sectorPtr();
930         Sector *back  = line.back().sectorPtr();
931 
932         // Determine the opening we have here.
933         /// @todo Use R_OpenRange()
934         fixed_t ceil;
935         if(front->ceiling().height() < back->ceiling().height())
936         {
937             ceil = FLT2FIX(front->ceiling().height());
938         }
939         else
940         {
941             ceil = FLT2FIX(back->ceiling().height());
942         }
943 
944         fixed_t floor;
945         if(front->floor().height() > back->floor().height())
946         {
947             floor = FLT2FIX(front->floor().height());
948         }
949         else
950         {
951             floor = FLT2FIX(back->floor().height());
952         }
953 
954         // There is a backsector. We possibly might hit something.
955         if(clParm.tmpz - clParm.tmprad < floor || clParm.tmpz + clParm.tmprad > ceil)
956         {
957             return LoopAbort; // Boing!
958         }
959 
960         // False alarm, continue checking.
961         clParm.ptcHitLine = nullptr;
962         // There is a possibility that the new position is in a new sector.
963         clParm.tmcross    = true; // Afterwards, update the sector pointer.
964         return LoopContinue;
965     });
966 
967     if(clParm.ptcHitLine)
968     {
969         fixed_t normal[2], dotp;
970 
971         // Must survive the touch.
972         if(!touchParticle(pinfo, st, stDef, true))
973             return;
974 
975         // There was a hit! Calculate bounce vector.
976         // - Project movement vector on the normal of hitline.
977         // - Calculate the difference to the point on the normal.
978         // - Add the difference to movement vector, negate movement.
979         // - Multiply with bounce.
980 
981         // Calculate the normal.
982         normal[0] = -FLT2FIX(clParm.ptcHitLine->direction().x);
983         normal[1] = -FLT2FIX(clParm.ptcHitLine->direction().y);
984 
985         if(!normal[0] && !normal[1])
986             goto quit_iteration;
987 
988         // Calculate as floating point so we don't overflow.
989         dotp = FRACUNIT * (DOT2F(pinfo->mov, normal) / DOT2F(normal, normal));
990         VECMUL(normal, dotp);
991         VECSUB(normal, pinfo->mov);
992         VECMULADD(pinfo->mov, 2 * FRACUNIT, normal);
993         VECMUL(pinfo->mov, st->bounce);
994 
995         // Continue from the old position.
996         x = pinfo->origin[0];
997         y = pinfo->origin[1];
998         clParm.tmcross = false; // Sector can't change if XY doesn't.
999 
1000         // This line is the latest contacted line.
1001         pinfo->contact = clParm.ptcHitLine;
1002         goto quit_iteration;
1003     }
1004 
1005   quit_iteration:
1006     // The move is now OK.
1007     pinfo->origin[0] = x;
1008     pinfo->origin[1] = y;
1009 
1010     // Should we update the sector pointer?
1011     if(clParm.tmcross)
1012     {
1013         pinfo->bspLeaf = &map().bspLeafAt(Vector2d(FIX2FLT(x), FIX2FLT(y)));
1014 
1015         // A BSP leaf with no geometry is not a suitable place for a particle.
1016         if(!pinfo->bspLeaf->hasSubspace())
1017         {
1018             // Kill the particle.
1019             pinfo->stage = -1;
1020         }
1021     }
1022 }
1023 
runTick()1024 void Generator::runTick()
1025 {
1026     // Source has been destroyed?
1027     if(!isUntriggered() && !map().thinkers().isUsedMobjId(srcid))
1028     {
1029         // Blasted... Spawning new particles becomes impossible.
1030         source = nullptr;
1031     }
1032 
1033     // Time to die?
1034     DENG2_ASSERT(def);
1035     if(++_age > def->maxAge && def->maxAge >= 0)
1036     {
1037         Generator_Delete(this);
1038         return;
1039     }
1040 
1041     // Spawn new particles?
1042     dfloat newParts = 0;
1043     if((_age <= def->spawnAge || def->spawnAge < 0) &&
1044        (source || plane || type >= 0 || type == DED_PTCGEN_ANY_MOBJ_TYPE ||
1045         isUntriggered()))
1046     {
1047         newParts = def->spawnRate * spawnRateMultiplier;
1048 
1049         newParts *= particleSpawnRate *
1050             (1 - def->spawnRateVariance * RNG_RandFloat());
1051 
1052         newParts = de::min(newParts, float(def->particles)); // don't spawn too many
1053 
1054         _spawnCount += newParts;
1055         while(_spawnCount >= 1)
1056         {
1057             // Spawn a new particle.
1058             if(type == DED_PTCGEN_ANY_MOBJ_TYPE || type >= 0)  // Type-triggered?
1059             {
1060 #ifdef __CLIENT__
1061                 // Client's should also check the client mobjs.
1062                 if(isClient)
1063                 {
1064                     map().clMobjIterator(newGeneratorParticlesWorker, this);
1065                 }
1066 #endif
1067 
1068                 // Spawn new particles using all applicable sources.
1069                 map().thinkers().forAll(reinterpret_cast<thinkfunc_t>(gx.MobjThinker), 0x1 /*public*/, [this] (thinker_t *th)
1070                 {
1071                     // Type match?
1072                     auto &mob = *reinterpret_cast<mobj_t *>(th);
1073                     if(type == DED_PTCGEN_ANY_MOBJ_TYPE || mob.type == type || mob.type == type2)
1074                     {
1075                         // Someone might think this is a slight hack...
1076                         source = &mob;
1077                         newParticle();
1078                     }
1079                     return LoopContinue;
1080                 });
1081 
1082                 // The generator has no real source.
1083                 source = nullptr;
1084             }
1085             else
1086             {
1087                 newParticle();
1088             }
1089 
1090             _spawnCount--;
1091         }
1092     }
1093 
1094     // Move particles.
1095     ParticleInfo *pinfo = _pinfo;
1096     for(dint i = 0; i < count; ++i, pinfo++)
1097     {
1098         if(pinfo->stage < 0) continue; // Not in use.
1099 
1100         if(pinfo->tics-- <= 0)
1101         {
1102             // Advance to next stage.
1103             if(++pinfo->stage == def->stages.size() ||
1104                stages[pinfo->stage].type == PTC_NONE)
1105             {
1106                 // Kill the particle.
1107                 pinfo->stage = -1;
1108                 continue;
1109             }
1110 
1111             pinfo->tics = def->stages[pinfo->stage].tics * (1 - def->stages[pinfo->stage].variance * RNG_RandFloat());
1112 
1113             // Change in particle angles?
1114             setParticleAngles(pinfo, def->stages[pinfo->stage].flags);
1115 
1116             // Play a sound?
1117             particleSound(pinfo->origin, &def->stages[pinfo->stage].sound);
1118         }
1119 
1120         // Try to move.
1121         moveParticle(i);
1122     }
1123 }
1124 
consoleRegister()1125 void Generator::consoleRegister() //static
1126 {
1127     C_VAR_FLOAT("rend-particle-rate", &particleSpawnRate, 0, 0, 5);
1128 }
1129 
Generator_Delete(Generator * gen)1130 void Generator_Delete(Generator *gen)
1131 {
1132     if(!gen) return;
1133     gen->map().unlink(*gen);
1134     gen->map().thinkers().remove(gen->thinker);
1135     gen->clearParticles();
1136     Z_Free(gen->stages); gen->stages = nullptr;
1137     // The generator itself is free'd when it's next turn for thinking comes.
1138 }
1139 
Generator_Thinker(Generator * gen)1140 void Generator_Thinker(Generator *gen)
1141 {
1142     DENG2_ASSERT(gen != 0);
1143     gen->runTick();
1144 }
1145 
1146 }  // namespace world
1147