1 /*
2 ===========================================================================
3 Copyright (C) 2000 - 2013, Raven Software, Inc.
4 Copyright (C) 2001 - 2013, Activision, Inc.
5 Copyright (C) 2013 - 2015, OpenJK contributors
6
7 This file is part of the OpenJK source code.
8
9 OpenJK is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ===========================================================================
21 */
22
23 ////////////////////////////////////////////////////////////////////////////////////////
24 // RAVEN SOFTWARE - STAR WARS: JK II
25 // (c) 2002 Activision
26 //
27 // World Effects
28 //
29 //
30 ////////////////////////////////////////////////////////////////////////////////////////
31
32 ////////////////////////////////////////////////////////////////////////////////////////
33 // Includes
34 ////////////////////////////////////////////////////////////////////////////////////////
35 #include "tr_local.h"
36 #include "tr_WorldEffects.h"
37
38 #include "Ravl/CVec.h"
39 #include "Ratl/vector_vs.h"
40 #include "Ratl/bits_vs.h"
41
42 #include "glext.h"
43
44 ////////////////////////////////////////////////////////////////////////////////////////
45 // Defines
46 ////////////////////////////////////////////////////////////////////////////////////////
47 #define GLS_ALPHA (GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA)
48 #define MAX_WIND_ZONES 10
49 #define MAX_WEATHER_ZONES 10
50 #define MAX_PUFF_SYSTEMS 2
51 #define MAX_PARTICLE_CLOUDS 5
52
53 #define POINTCACHE_CELL_SIZE 96.0f
54
55
56 ////////////////////////////////////////////////////////////////////////////////////////
57 // Globals
58 ////////////////////////////////////////////////////////////////////////////////////////
59 float mMillisecondsElapsed = 0;
60 float mSecondsElapsed = 0;
61 bool mFrozen = false;
62
63 CVec3 mGlobalWindVelocity;
64 CVec3 mGlobalWindDirection;
65 float mGlobalWindSpeed;
66 int mParticlesRendered;
67
68
69
70 ////////////////////////////////////////////////////////////////////////////////////////
71 // Handy Functions
72 ////////////////////////////////////////////////////////////////////////////////////////
73 // Returns a float min <= x < max (exclusive; will get max - 0.00001; but never max)
WE_flrand(float min,float max)74 inline float WE_flrand(float min, float max) {
75 return ((rand() * (max - min)) / (RAND_MAX)) + min;
76 }
77
78 ////////////////////////////////////////////////////////////////////////////////////////
79 // Externs & Fwd Decl.
80 ////////////////////////////////////////////////////////////////////////////////////////
81 extern void SetViewportAndScissor( void );
82
VectorFloor(vec3_t in)83 inline void VectorFloor(vec3_t in)
84 {
85 in[0] = floorf(in[0]);
86 in[1] = floorf(in[1]);
87 in[2] = floorf(in[2]);
88 }
89
VectorCeil(vec3_t in)90 inline void VectorCeil(vec3_t in)
91 {
92 in[0] = ceilf(in[0]);
93 in[1] = ceilf(in[1]);
94 in[2] = ceilf(in[2]);
95 }
96
FloatRand(void)97 inline float FloatRand(void)
98 {
99 return ((float)rand() / (float)RAND_MAX);
100 }
101
SnapFloatToGrid(float & f,int GridSize)102 inline void SnapFloatToGrid(float& f, int GridSize)
103 {
104 f = (int)(f);
105
106 bool fNeg = (f<0);
107 if (fNeg)
108 {
109 f *= -1; // Temporarly make it positive
110 }
111
112 int Offset = ((int)(f) % (int)(GridSize));
113 int OffsetAbs = abs(Offset);
114 if (OffsetAbs>(GridSize/2))
115 {
116 Offset = (GridSize - OffsetAbs) * -1;
117 }
118
119 f -= Offset;
120
121 if (fNeg)
122 {
123 f *= -1; // Put It Back To Negative
124 }
125
126 f = (int)(f);
127
128 assert(((int)(f)%(int)(GridSize)) == 0);
129 }
130
SnapVectorToGrid(CVec3 & Vec,int GridSize)131 inline void SnapVectorToGrid(CVec3& Vec, int GridSize)
132 {
133 SnapFloatToGrid(Vec[0], GridSize);
134 SnapFloatToGrid(Vec[1], GridSize);
135 SnapFloatToGrid(Vec[2], GridSize);
136 }
137
138
139
140
141
142 ////////////////////////////////////////////////////////////////////////////////////////
143 // Range Structures
144 ////////////////////////////////////////////////////////////////////////////////////////
145 struct SVecRange
146 {
147 CVec3 mMins;
148 CVec3 mMaxs;
149
ClearSVecRange150 inline void Clear()
151 {
152 mMins.Clear();
153 mMaxs.Clear();
154 }
155
PickSVecRange156 inline void Pick(CVec3& V)
157 {
158 V[0] = WE_flrand(mMins[0], mMaxs[0]);
159 V[1] = WE_flrand(mMins[1], mMaxs[1]);
160 V[2] = WE_flrand(mMins[2], mMaxs[2]);
161 }
WrapSVecRange162 inline void Wrap(CVec3& V, SVecRange &spawnRange)
163 {
164 if (V[0]<mMins[0])
165 {
166 const float d = mMins[0]-V[0];
167 V[0] = mMaxs[0]-fmod(d, mMaxs[0]-mMins[0]);
168 }
169 if (V[0]>mMaxs[0])
170 {
171 const float d = V[0]-mMaxs[0];
172 V[0] = mMins[0]+fmod(d, mMaxs[0]-mMins[0]);
173 }
174
175 if (V[1]<mMins[1])
176 {
177 const float d = mMins[1]-V[1];
178 V[1] = mMaxs[1]-fmod(d, mMaxs[1]-mMins[1]);
179 }
180 if (V[1]>mMaxs[1])
181 {
182 const float d = V[1]-mMaxs[1];
183 V[1] = mMins[1]+fmod(d, mMaxs[1]-mMins[1]);
184 }
185
186 if (V[2]<mMins[2])
187 {
188 const float d = mMins[2]-V[2];
189 V[2] = mMaxs[2]-fmod(d, mMaxs[2]-mMins[2]);
190 }
191 if (V[2]>mMaxs[2])
192 {
193 const float d = V[2]-mMaxs[2];
194 V[2] = mMins[2]+fmod(d, mMaxs[2]-mMins[2]);
195 }
196 }
197
InSVecRange198 inline bool In(const CVec3& V)
199 {
200 return (V>mMins && V<mMaxs);
201 }
202 };
203
204 struct SFloatRange
205 {
206 float mMin;
207 float mMax;
208
ClearSFloatRange209 inline void Clear()
210 {
211 mMin = 0;
212 mMin = 0;
213 }
PickSFloatRange214 inline void Pick(float& V)
215 {
216 V = WE_flrand(mMin, mMax);
217 }
InSFloatRange218 inline bool In(const float& V)
219 {
220 return (V>mMin && V<mMax);
221 }
222 };
223
224 struct SIntRange
225 {
226 int mMin;
227 int mMax;
228
ClearSIntRange229 inline void Clear()
230 {
231 mMin = 0;
232 mMin = 0;
233 }
PickSIntRange234 inline void Pick(int& V)
235 {
236 V = Q_irand(mMin, mMax);
237 }
InSIntRange238 inline bool In(const int& V)
239 {
240 return (V>mMin && V<mMax);
241 }
242 };
243
244
245
246
247
248 ////////////////////////////////////////////////////////////////////////////////////////
249 // The Particle Class
250 ////////////////////////////////////////////////////////////////////////////////////////
251 class CWeatherParticle
252 {
253 public:
254 enum
255 {
256 FLAG_RENDER = 0,
257
258 FLAG_FADEIN,
259 FLAG_FADEOUT,
260 FLAG_RESPAWN,
261
262 FLAG_MAX
263 };
264 typedef ratl::bits_vs<FLAG_MAX> TFlags;
265
266 float mAlpha;
267 TFlags mFlags;
268 CVec3 mPosition;
269 CVec3 mVelocity;
270 float mMass; // A higher number will more greatly resist force and result in greater gravity
271 };
272
273
274
275
276
277 ////////////////////////////////////////////////////////////////////////////////////////
278 // The Wind
279 ////////////////////////////////////////////////////////////////////////////////////////
280 class CWindZone
281 {
282 public:
283 bool mGlobal;
284 SVecRange mRBounds;
285 SVecRange mRVelocity;
286 SIntRange mRDuration;
287 SIntRange mRDeadTime;
288 float mMaxDeltaVelocityPerUpdate;
289 float mChanceOfDeadTime;
290
291 CVec3 mCurrentVelocity;
292 CVec3 mTargetVelocity;
293 int mTargetVelocityTimeRemaining;
294
295
296 public:
297 ////////////////////////////////////////////////////////////////////////////////////
298 // Initialize - Will setup default values for all data
299 ////////////////////////////////////////////////////////////////////////////////////
Initialize()300 void Initialize()
301 {
302 mRBounds.Clear();
303 mGlobal = true;
304
305 mRVelocity.mMins = -1500.0f;
306 mRVelocity.mMins[2] = -10.0f;
307 mRVelocity.mMaxs = 1500.0f;
308 mRVelocity.mMaxs[2] = 10.0f;
309
310 mMaxDeltaVelocityPerUpdate = 10.0f;
311
312 mRDuration.mMin = 1000;
313 mRDuration.mMax = 2000;
314
315 mChanceOfDeadTime = 0.3f;
316 mRDeadTime.mMin = 1000;
317 mRDeadTime.mMax = 3000;
318
319 mCurrentVelocity.Clear();
320 mTargetVelocity.Clear();
321 mTargetVelocityTimeRemaining = 0;
322 }
323
324 ////////////////////////////////////////////////////////////////////////////////////
325 // Update - Changes wind when current target velocity expires
326 ////////////////////////////////////////////////////////////////////////////////////
Update()327 void Update()
328 {
329 if (mTargetVelocityTimeRemaining==0)
330 {
331 if (FloatRand()<mChanceOfDeadTime)
332 {
333 mRDeadTime.Pick(mTargetVelocityTimeRemaining);
334 mTargetVelocity.Clear();
335 }
336 else
337 {
338 mRDuration.Pick(mTargetVelocityTimeRemaining);
339 mRVelocity.Pick(mTargetVelocity);
340 }
341 }
342 else if (mTargetVelocityTimeRemaining!=-1)
343 {
344 mTargetVelocityTimeRemaining--;
345
346 CVec3 DeltaVelocity(mTargetVelocity - mCurrentVelocity);
347 float DeltaVelocityLen = VectorNormalize(DeltaVelocity.v);
348 if (DeltaVelocityLen > mMaxDeltaVelocityPerUpdate)
349 {
350 DeltaVelocityLen = mMaxDeltaVelocityPerUpdate;
351 }
352 DeltaVelocity *= (DeltaVelocityLen);
353 mCurrentVelocity += DeltaVelocity;
354 }
355 }
356 };
357 ratl::vector_vs<CWindZone, MAX_WIND_ZONES> mWindZones;
358
R_GetWindVector(vec3_t windVector)359 bool R_GetWindVector(vec3_t windVector)
360 {
361 VectorCopy(mGlobalWindDirection.v, windVector);
362 return true;
363 }
364
R_GetWindSpeed(float & windSpeed)365 bool R_GetWindSpeed(float &windSpeed)
366 {
367 windSpeed = mGlobalWindSpeed;
368 return true;
369 }
370
R_GetWindGusting()371 bool R_GetWindGusting()
372 {
373 return (mGlobalWindSpeed>1000.0f);
374 }
375
376
377
378 ////////////////////////////////////////////////////////////////////////////////////////
379 // Outside Point Cache
380 ////////////////////////////////////////////////////////////////////////////////////////
381 class COutside
382 {
383 public:
384 ////////////////////////////////////////////////////////////////////////////////////
385 //Global Public Outside Variables
386 ////////////////////////////////////////////////////////////////////////////////////
387 bool mOutsideShake;
388 float mOutsidePain;
389
390 private:
391 ////////////////////////////////////////////////////////////////////////////////////
392 // The Outside Cache
393 ////////////////////////////////////////////////////////////////////////////////////
394 bool mCacheInit; // Has It Been Cached?
395
396 struct SWeatherZone
397 {
398 static bool mMarkedOutside;
399 uint32_t* mPointCache;
400 SVecRange mExtents;
401 SVecRange mSize;
402 int mWidth;
403 int mHeight;
404 int mDepth;
405
406 ////////////////////////////////////////////////////////////////////////////////////
407 // Convert To Cell
408 ////////////////////////////////////////////////////////////////////////////////////
ConvertToCellCOutside::SWeatherZone409 inline void ConvertToCell(const CVec3& pos, int& x, int& y, int& z, int& bit)
410 {
411 x = (int)((pos[0] / POINTCACHE_CELL_SIZE) - mSize.mMins[0]);
412 y = (int)((pos[1] / POINTCACHE_CELL_SIZE) - mSize.mMins[1]);
413 z = (int)((pos[2] / POINTCACHE_CELL_SIZE) - mSize.mMins[2]);
414
415 bit = (z & 31);
416 z >>= 5;
417 }
418
419 ////////////////////////////////////////////////////////////////////////////////////
420 // CellOutside - Test to see if a given cell is outside
421 ////////////////////////////////////////////////////////////////////////////////////
CellOutsideCOutside::SWeatherZone422 inline bool CellOutside(int x, int y, int z, int bit)
423 {
424 if ((x < 0 || x >= mWidth) || (y < 0 || y >= mHeight) || (z < 0 || z >= mDepth) || (bit < 0 || bit >= 32))
425 {
426 return !(mMarkedOutside);
427 }
428 return (mMarkedOutside==(!!(mPointCache[((z * mWidth * mHeight) + (y * mWidth) + x)]&(1 << bit))));
429 }
430 };
431 ratl::vector_vs<SWeatherZone, MAX_WEATHER_ZONES> mWeatherZones;
432
433
434 private:
435 ////////////////////////////////////////////////////////////////////////////////////
436 // Iteration Variables
437 ////////////////////////////////////////////////////////////////////////////////////
438 int mWCells;
439 int mHCells;
440
441 int mXCell;
442 int mYCell;
443 int mZBit;
444
445 int mXMax;
446 int mYMax;
447 int mZMax;
448
449
450 private:
451
452
453 ////////////////////////////////////////////////////////////////////////////////////
454 // Contents Outside
455 ////////////////////////////////////////////////////////////////////////////////////
ContentsOutside(int contents)456 inline bool ContentsOutside(int contents)
457 {
458 if (contents&CONTENTS_WATER || contents&CONTENTS_SOLID)
459 {
460 return false;
461 }
462 if (mCacheInit)
463 {
464 if (SWeatherZone::mMarkedOutside)
465 {
466 return (!!(contents&CONTENTS_OUTSIDE));
467 }
468 return (!(contents&CONTENTS_INSIDE));
469 }
470 return !!(contents&CONTENTS_OUTSIDE);
471 }
472
473
474
475
476 public:
477 ////////////////////////////////////////////////////////////////////////////////////
478 // Constructor - Will setup default values for all data
479 ////////////////////////////////////////////////////////////////////////////////////
Reset()480 void Reset()
481 {
482 mOutsideShake = false;
483 mOutsidePain = 0.0;
484 mCacheInit = false;
485 SWeatherZone::mMarkedOutside = false;
486 for (int wz=0; wz<mWeatherZones.size(); wz++)
487 {
488 Z_Free(mWeatherZones[wz].mPointCache);
489 mWeatherZones[wz].mPointCache = 0;
490 }
491 mWeatherZones.clear();
492 }
493
COutside()494 COutside()
495 {
496 Reset();
497 }
~COutside()498 ~COutside()
499 {
500 Reset();
501 }
502
Initialized()503 bool Initialized()
504 {
505 return mCacheInit;
506 }
507
508 ////////////////////////////////////////////////////////////////////////////////////
509 // AddWeatherZone - Will add a zone of mins and maxes
510 ////////////////////////////////////////////////////////////////////////////////////
AddWeatherZone(vec3_t mins,vec3_t maxs)511 void AddWeatherZone(vec3_t mins, vec3_t maxs)
512 {
513 if (!mWeatherZones.full())
514 {
515 SWeatherZone& Wz = mWeatherZones.push_back();
516 Wz.mExtents.mMins = mins;
517 Wz.mExtents.mMaxs = maxs;
518
519 SnapVectorToGrid(Wz.mExtents.mMins, POINTCACHE_CELL_SIZE);
520 SnapVectorToGrid(Wz.mExtents.mMaxs, POINTCACHE_CELL_SIZE);
521
522 Wz.mSize.mMins = Wz.mExtents.mMins;
523 Wz.mSize.mMaxs = Wz.mExtents.mMaxs;
524
525 Wz.mSize.mMins /= POINTCACHE_CELL_SIZE;
526 Wz.mSize.mMaxs /= POINTCACHE_CELL_SIZE;
527 Wz.mWidth = (int)(Wz.mSize.mMaxs[0] - Wz.mSize.mMins[0]);
528 Wz.mHeight = (int)(Wz.mSize.mMaxs[1] - Wz.mSize.mMins[1]);
529 Wz.mDepth = ((int)(Wz.mSize.mMaxs[2] - Wz.mSize.mMins[2]) + 31) >> 5;
530
531 int arraySize = (Wz.mWidth * Wz.mHeight * Wz.mDepth);
532 Wz.mPointCache = (uint32_t *)Z_Malloc(arraySize*sizeof(uint32_t), TAG_POINTCACHE, qtrue);
533 }
534 }
535
536
537
538 ////////////////////////////////////////////////////////////////////////////////////
539 // Cache - Will Scan the World, Creating The Cache
540 ////////////////////////////////////////////////////////////////////////////////////
Cache()541 void Cache()
542 {
543 if (!tr.world || mCacheInit)
544 {
545 return;
546 }
547
548 CVec3 CurPos;
549 CVec3 Size;
550 CVec3 Mins;
551 int x, y, z, q, zbase;
552 bool curPosOutside;
553 uint32_t contents;
554 uint32_t bit;
555
556
557 // Record The Extents Of The World Incase No Other Weather Zones Exist
558 //---------------------------------------------------------------------
559 if (!mWeatherZones.size())
560 {
561 ri.Printf( PRINT_ALL, "WARNING: No Weather Zones Encountered\n");
562 AddWeatherZone(tr.world->bmodels[0].bounds[0], tr.world->bmodels[0].bounds[1]);
563 }
564
565 // Iterate Over All Weather Zones
566 //--------------------------------
567 for (int zone=0; zone<mWeatherZones.size(); zone++)
568 {
569 SWeatherZone wz = mWeatherZones[zone];
570
571 // Make Sure Point Contents Checks Occur At The CENTER Of The Cell
572 //-----------------------------------------------------------------
573 Mins = wz.mExtents.mMins;
574 for (x=0; x<3; x++)
575 {
576 Mins[x] += (POINTCACHE_CELL_SIZE/2);
577 }
578
579
580 // Start Scanning
581 //----------------
582 for(z = 0; z < wz.mDepth; z++)
583 {
584 for(q = 0; q < 32; q++)
585 {
586 bit = (1 << q);
587 zbase = (z << 5);
588
589 for(x = 0; x < wz.mWidth; x++)
590 {
591 for(y = 0; y < wz.mHeight; y++)
592 {
593 CurPos[0] = x * POINTCACHE_CELL_SIZE;
594 CurPos[1] = y * POINTCACHE_CELL_SIZE;
595 CurPos[2] = (zbase + q) * POINTCACHE_CELL_SIZE;
596 CurPos += Mins;
597
598 contents = ri.CM_PointContents(CurPos.v, 0);
599 if (contents&CONTENTS_INSIDE || contents&CONTENTS_OUTSIDE)
600 {
601 curPosOutside = ((contents&CONTENTS_OUTSIDE)!=0);
602 if (!mCacheInit)
603 {
604 mCacheInit = true;
605 SWeatherZone::mMarkedOutside = curPosOutside;
606 }
607 else if (SWeatherZone::mMarkedOutside!=curPosOutside)
608 {
609 assert(0);
610 Com_Error (ERR_DROP, "Weather Effect: Both Indoor and Outdoor brushs encountered in map.\n" );
611 return;
612 }
613
614 // Mark The Point
615 //----------------
616 wz.mPointCache[((z * wz.mWidth * wz.mHeight) + (y * wz.mWidth) + x)] |= bit;
617 }
618 }// for (y)
619 }// for (x)
620 }// for (q)
621 }// for (z)
622 }
623
624
625 // If no indoor or outdoor brushes were found
626 //--------------------------------------------
627 if (!mCacheInit)
628 {
629 mCacheInit = true;
630 SWeatherZone::mMarkedOutside = false; // Assume All Is Outside, Except Solid
631 }
632 }
633
634
635
636
637 public:
638 ////////////////////////////////////////////////////////////////////////////////////
639 // PointOutside - Test to see if a given point is outside
640 ////////////////////////////////////////////////////////////////////////////////////
PointOutside(const CVec3 & pos)641 inline bool PointOutside(const CVec3& pos)
642 {
643 if (!mCacheInit)
644 {
645 return ContentsOutside(ri.CM_PointContents(pos.v, 0));
646 }
647 for (int zone=0; zone<mWeatherZones.size(); zone++)
648 {
649 SWeatherZone wz = mWeatherZones[zone];
650 if (wz.mExtents.In(pos))
651 {
652 int bit, x, y, z;
653 wz.ConvertToCell(pos, x, y, z, bit);
654 return wz.CellOutside(x, y, z, bit);
655 }
656 }
657 return !(SWeatherZone::mMarkedOutside);
658
659 }
660
661
662 ////////////////////////////////////////////////////////////////////////////////////
663 // PointOutside - Test to see if a given bounded plane is outside
664 ////////////////////////////////////////////////////////////////////////////////////
PointOutside(const CVec3 & pos,float width,float height)665 inline bool PointOutside(const CVec3& pos, float width, float height)
666 {
667 for (int zone=0; zone<mWeatherZones.size(); zone++)
668 {
669 SWeatherZone wz = mWeatherZones[zone];
670 if (wz.mExtents.In(pos))
671 {
672 int bit, x, y, z;
673 wz.ConvertToCell(pos, x, y, z, bit);
674 if (width<POINTCACHE_CELL_SIZE || height<POINTCACHE_CELL_SIZE)
675 {
676 return (wz.CellOutside(x, y, z, bit));
677 }
678
679 mWCells = ((int)width / POINTCACHE_CELL_SIZE);
680 mHCells = ((int)height / POINTCACHE_CELL_SIZE);
681
682 mXMax = x + mWCells;
683 mYMax = y + mWCells;
684 mZMax = bit + mHCells;
685
686 for (mXCell=x-mWCells; mXCell<=mXMax; mXCell++)
687 {
688 for (mYCell=y-mWCells; mYCell<=mYMax; mYCell++)
689 {
690 for (mZBit=bit-mHCells; mZBit<=mZMax; mZBit++)
691 {
692 if (!wz.CellOutside(mXCell, mYCell, z, mZBit))
693 {
694 return false;
695 }
696 }
697 }
698 }
699 return true;
700 }
701 }
702 return !(SWeatherZone::mMarkedOutside);
703 }
704 };
705 COutside mOutside;
706 bool COutside::SWeatherZone::mMarkedOutside = false;
707
708
RE_AddWeatherZone(vec3_t mins,vec3_t maxs)709 void RE_AddWeatherZone(vec3_t mins, vec3_t maxs)
710 {
711 mOutside.AddWeatherZone(mins, maxs);
712 }
713
R_IsOutside(vec3_t pos)714 bool R_IsOutside(vec3_t pos)
715 {
716 return mOutside.PointOutside(pos);
717 }
718
R_IsShaking()719 bool R_IsShaking()
720 {
721 return (mOutside.mOutsideShake && mOutside.PointOutside(backEnd.viewParms.ori.origin));
722 }
723
R_IsOutsideCausingPain(vec3_t pos)724 float R_IsOutsideCausingPain(vec3_t pos)
725 {
726 return (mOutside.mOutsidePain && mOutside.PointOutside(pos));
727 }
728
729
730 ////////////////////////////////////////////////////////////////////////////////////////
731 // Particle Cloud
732 ////////////////////////////////////////////////////////////////////////////////////////
733 class CWeatherParticleCloud
734 {
735 private:
736 ////////////////////////////////////////////////////////////////////////////////////
737 // DYNAMIC MEMORY
738 ////////////////////////////////////////////////////////////////////////////////////
739 image_t* mImage;
740 CWeatherParticle* mParticles;
741
742 private:
743 ////////////////////////////////////////////////////////////////////////////////////
744 // RUN TIME VARIANTS
745 ////////////////////////////////////////////////////////////////////////////////////
746 float mSpawnSpeed;
747 CVec3 mSpawnPlaneNorm;
748 CVec3 mSpawnPlaneRight;
749 CVec3 mSpawnPlaneUp;
750 SVecRange mRange;
751
752 CVec3 mCameraPosition;
753 CVec3 mCameraForward;
754 CVec3 mCameraLeft;
755 CVec3 mCameraDown;
756 CVec3 mCameraLeftPlusUp;
757 CVec3 mCameraLeftMinusUp;
758
759
760 int mParticleCountRender;
761 int mGLModeEnum;
762
763 bool mPopulated;
764
765
766 public:
767 ////////////////////////////////////////////////////////////////////////////////////
768 // CONSTANTS
769 ////////////////////////////////////////////////////////////////////////////////////
770 bool mOrientWithVelocity;
771 float mSpawnPlaneSize;
772 float mSpawnPlaneDistance;
773 SVecRange mSpawnRange;
774
775 float mGravity; // How much gravity affects the velocity of a particle
776 CVec4 mColor; // RGBA color
777 int mVertexCount; // 3 for triangle, 4 for quad, other numbers not supported
778
779 float mWidth;
780 float mHeight;
781
782 int mBlendMode; // 0 = ALPHA, 1 = SRC->SRC
783 int mFilterMode; // 0 = LINEAR, 1 = NEAREST
784
785 float mFade; // How much to fade in and out 1.0 = instant, 0.01 = very slow
786
787 SFloatRange mRotation;
788 float mRotationDelta;
789 float mRotationDeltaTarget;
790 float mRotationCurrent;
791 SIntRange mRotationChangeTimer;
792 int mRotationChangeNext;
793
794 SFloatRange mMass; // Determines how slowness to accelerate, higher number = slower
795 float mFrictionInverse; // How much air friction does this particle have 1.0=none, 0.0=nomove
796
797 int mParticleCount;
798
799 bool mWaterParticles;
800
801
802
803
804 public:
805 ////////////////////////////////////////////////////////////////////////////////////
806 // Initialize - Create Image, Particles, And Setup All Values
807 ////////////////////////////////////////////////////////////////////////////////////
Initialize(int count,const char * texturePath,int VertexCount=4)808 void Initialize(int count, const char* texturePath, int VertexCount=4)
809 {
810 Reset();
811 assert(mParticleCount==0 && mParticles==0);
812 assert(mImage==0);
813
814 // Create The Image
815 //------------------
816 mImage = R_FindImageFile(texturePath, qfalse, qfalse, qfalse, GL_CLAMP);
817 if (!mImage)
818 {
819 Com_Error(ERR_DROP, "CWeatherParticleCloud: Could not texture %s", texturePath);
820 }
821
822 GL_Bind(mImage);
823
824
825
826 // Create The Particles
827 //----------------------
828 mParticleCount = count;
829 mParticles = new CWeatherParticle[mParticleCount];
830
831
832
833 CWeatherParticle* part=0;
834 for (int particleNum=0; particleNum<mParticleCount; particleNum++)
835 {
836 part = &(mParticles[particleNum]);
837 part->mPosition.Clear();
838 part->mVelocity.Clear();
839 part->mAlpha = 0.0f;
840 mMass.Pick(part->mMass);
841 }
842
843 mVertexCount = VertexCount;
844
845 mGLModeEnum = (mVertexCount==3)?(GL_TRIANGLES):(GL_QUADS);
846 }
847
848
849 ////////////////////////////////////////////////////////////////////////////////////
850 // Reset - Initializes all data to default values
851 ////////////////////////////////////////////////////////////////////////////////////
Reset()852 void Reset()
853 {
854 if (mImage)
855 {
856 // TODO: Free Image?
857 }
858 mImage = 0;
859 if (mParticleCount)
860 {
861 delete [] mParticles;
862 }
863 mParticleCount = 0;
864 mParticles = 0;
865
866 mPopulated = 0;
867
868
869
870 // These Are The Default Startup Values For Constant Data
871 //========================================================
872 mOrientWithVelocity = false;
873 mWaterParticles = false;
874
875 mSpawnPlaneDistance = 500;
876 mSpawnPlaneSize = 500;
877 mSpawnRange.mMins = -(mSpawnPlaneDistance*1.25f);
878 mSpawnRange.mMaxs = (mSpawnPlaneDistance*1.25f);
879
880 mGravity = 300.0f; // Units Per Second
881
882 mColor = 1.0f;
883
884 mVertexCount = 4;
885 mWidth = 1.0f;
886 mHeight = 1.0f;
887
888 mBlendMode = 0;
889 mFilterMode = 0;
890
891 mFade = 10.0f;
892
893 mRotation.Clear();
894 mRotationDelta = 0.0f;
895 mRotationDeltaTarget= 0.0f;
896 mRotationCurrent = 0.0f;
897 mRotationChangeNext = -1;
898 mRotation.mMin = -0.7f;
899 mRotation.mMax = 0.7f;
900 mRotationChangeTimer.mMin = 500;
901 mRotationChangeTimer.mMax = 2000;
902
903 mMass.mMin = 5.0f;
904 mMass.mMax = 10.0f;
905
906 mFrictionInverse = 0.7f; // No Friction?
907 }
908
909 ////////////////////////////////////////////////////////////////////////////////////
910 // Constructor - Will setup default values for all data
911 ////////////////////////////////////////////////////////////////////////////////////
CWeatherParticleCloud()912 CWeatherParticleCloud()
913 {
914 mImage = 0;
915 mParticleCount = 0;
916 Reset();
917 }
918
919 ////////////////////////////////////////////////////////////////////////////////////
920 // Initialize - Will setup default values for all data
921 ////////////////////////////////////////////////////////////////////////////////////
~CWeatherParticleCloud()922 ~CWeatherParticleCloud()
923 {
924 Reset();
925 }
926
927
928 ////////////////////////////////////////////////////////////////////////////////////
929 // UseSpawnPlane - Check To See If We Should Spawn On A Plane, Or Just Wrap The Box
930 ////////////////////////////////////////////////////////////////////////////////////
UseSpawnPlane()931 inline bool UseSpawnPlane()
932 {
933 return (mGravity!=0.0f);
934 }
935
936
937 ////////////////////////////////////////////////////////////////////////////////////
938 // Update - Applies All Physics Forces To All Contained Particles
939 ////////////////////////////////////////////////////////////////////////////////////
Update()940 void Update()
941 {
942 CWeatherParticle* part=0;
943 CVec3 partForce;
944 CVec3 partMoved;
945 CVec3 partToCamera;
946 bool partRendering;
947 bool partOutside;
948 bool partInRange;
949 bool partInView;
950 int particleNum;
951 float particleFade = (mFade * mSecondsElapsed);
952
953 /* TODO: Non Global Wind Zones
954 CWindZone* wind=0;
955 int windNum;
956 int windCount = mWindZones.size();
957 */
958
959 // Compute Camera
960 //----------------
961 {
962 mCameraPosition = backEnd.viewParms.ori.origin;
963 mCameraForward = backEnd.viewParms.ori.axis[0];
964 mCameraLeft = backEnd.viewParms.ori.axis[1];
965 mCameraDown = backEnd.viewParms.ori.axis[2];
966
967 if (mRotationChangeNext!=-1)
968 {
969 if (mRotationChangeNext==0)
970 {
971 mRotation.Pick(mRotationDeltaTarget);
972 mRotationChangeTimer.Pick(mRotationChangeNext);
973 if (mRotationChangeNext<=0)
974 {
975 mRotationChangeNext = 1;
976 }
977 }
978 mRotationChangeNext--;
979
980 float RotationDeltaDifference = (mRotationDeltaTarget - mRotationDelta);
981 if (fabsf(RotationDeltaDifference)>0.01)
982 {
983 mRotationDelta += RotationDeltaDifference; // Blend To New Delta
984 }
985 mRotationCurrent += (mRotationDelta * mSecondsElapsed);
986 float s = sinf(mRotationCurrent);
987 float c = cosf(mRotationCurrent);
988
989 CVec3 TempCamLeft(mCameraLeft);
990
991 mCameraLeft *= (c * mWidth);
992 mCameraLeft.ScaleAdd(mCameraDown, (s * mWidth * -1.0f));
993
994 mCameraDown *= (c * mHeight);
995 mCameraDown.ScaleAdd(TempCamLeft, (s * mHeight));
996 }
997 else
998 {
999 mCameraLeft *= mWidth;
1000 mCameraDown *= mHeight;
1001 }
1002 }
1003
1004
1005 // Compute Global Force
1006 //----------------------
1007 CVec3 force;
1008 {
1009 force.Clear();
1010
1011 // Apply Gravity
1012 //---------------
1013 force[2] = -1.0f * mGravity;
1014
1015 // Apply Wind Velocity
1016 //---------------------
1017 force += mGlobalWindVelocity;
1018 }
1019
1020
1021 // Update Range
1022 //--------------
1023 {
1024 mRange.mMins = mCameraPosition + mSpawnRange.mMins;
1025 mRange.mMaxs = mCameraPosition + mSpawnRange.mMaxs;
1026
1027 // If Using A Spawn Plane, Increase The Range Box A Bit To Account For Rotation On The Spawn Plane
1028 //-------------------------------------------------------------------------------------------------
1029 if (UseSpawnPlane())
1030 {
1031 for (int dim=0; dim<3; dim++)
1032 {
1033 if (force[dim]>0.01)
1034 {
1035 mRange.mMins[dim] -= (mSpawnPlaneDistance/2.0f);
1036 }
1037 else if (force[dim]<-0.01)
1038 {
1039 mRange.mMaxs[dim] += (mSpawnPlaneDistance/2.0f);
1040 }
1041 }
1042 mSpawnPlaneNorm = force;
1043 mSpawnSpeed = VectorNormalize(mSpawnPlaneNorm.v);
1044 MakeNormalVectors(mSpawnPlaneNorm.v, mSpawnPlaneRight.v, mSpawnPlaneUp.v);
1045 if (mOrientWithVelocity)
1046 {
1047 mCameraDown = mSpawnPlaneNorm;
1048 mCameraDown *= (mHeight * -1);
1049 }
1050 }
1051
1052 // Optimization For Quad Position Calculation
1053 //--------------------------------------------
1054 if (mVertexCount==4)
1055 {
1056 mCameraLeftPlusUp = (mCameraLeft - mCameraDown);
1057 mCameraLeftMinusUp = (mCameraLeft + mCameraDown);
1058 }
1059 else
1060 {
1061 mCameraLeftPlusUp = (mCameraDown + mCameraLeft); // should really be called mCamera Left + Down
1062 }
1063 }
1064
1065 // Stop All Additional Processing
1066 //--------------------------------
1067 if (mFrozen)
1068 {
1069 return;
1070 }
1071
1072
1073
1074 // Now Update All Particles
1075 //--------------------------
1076 mParticleCountRender = 0;
1077 for (particleNum=0; particleNum<mParticleCount; particleNum++)
1078 {
1079 part = &mParticles[particleNum];
1080
1081 if (!mPopulated)
1082 {
1083 mRange.Pick(part->mPosition); // First Time Spawn Location
1084 }
1085
1086 // Grab The Force And Apply Non Global Wind
1087 //------------------------------------------
1088 partForce = force;
1089 partForce /= part->mMass;
1090
1091
1092 // Apply The Force
1093 //-----------------
1094 part->mVelocity += partForce;
1095 part->mVelocity *= mFrictionInverse;
1096
1097 part->mPosition.ScaleAdd(part->mVelocity, mSecondsElapsed);
1098
1099 partToCamera = (part->mPosition - mCameraPosition);
1100 partRendering = part->mFlags.get_bit(CWeatherParticle::FLAG_RENDER);
1101 partOutside = mOutside.PointOutside(part->mPosition, mWidth, mHeight);
1102 partInRange = mRange.In(part->mPosition);
1103 partInView = (partOutside && partInRange && (partToCamera.Dot(mCameraForward)>0.0f));
1104
1105 // Process Respawn
1106 //-----------------
1107 if (!partInRange && !partRendering)
1108 {
1109 part->mVelocity.Clear();
1110
1111 // Reselect A Position On The Spawn Plane
1112 //----------------------------------------
1113 if (UseSpawnPlane())
1114 {
1115 part->mPosition = mCameraPosition;
1116 part->mPosition -= (mSpawnPlaneNorm* mSpawnPlaneDistance);
1117 part->mPosition += (mSpawnPlaneRight*WE_flrand(-mSpawnPlaneSize, mSpawnPlaneSize));
1118 part->mPosition += (mSpawnPlaneUp* WE_flrand(-mSpawnPlaneSize, mSpawnPlaneSize));
1119 }
1120
1121 // Otherwise, Just Wrap Around To The Other End Of The Range
1122 //-----------------------------------------------------------
1123 else
1124 {
1125 mRange.Wrap(part->mPosition, mSpawnRange);
1126 }
1127 partInRange = true;
1128 }
1129
1130 // Process Fade
1131 //--------------
1132 {
1133 // Start A Fade Out
1134 //------------------
1135 if (partRendering && !partInView)
1136 {
1137 part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEIN);
1138 part->mFlags.set_bit(CWeatherParticle::FLAG_FADEOUT);
1139 }
1140
1141 // Switch From Fade Out To Fade In
1142 //---------------------------------
1143 else if (partRendering && partInView && part->mFlags.get_bit(CWeatherParticle::FLAG_FADEOUT))
1144 {
1145 part->mFlags.set_bit(CWeatherParticle::FLAG_FADEIN);
1146 part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEOUT);
1147 }
1148
1149 // Start A Fade In
1150 //-----------------
1151 else if (!partRendering && partInView)
1152 {
1153 partRendering = true;
1154 part->mAlpha = 0.0f;
1155 part->mFlags.set_bit(CWeatherParticle::FLAG_RENDER);
1156 part->mFlags.set_bit(CWeatherParticle::FLAG_FADEIN);
1157 part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEOUT);
1158 }
1159
1160 // Update Fade
1161 //-------------
1162 if (partRendering)
1163 {
1164
1165 // Update Fade Out
1166 //-----------------
1167 if (part->mFlags.get_bit(CWeatherParticle::FLAG_FADEOUT))
1168 {
1169 part->mAlpha -= particleFade;
1170 if (part->mAlpha<=0.0f)
1171 {
1172 part->mAlpha = 0.0f;
1173 part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEOUT);
1174 part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEIN);
1175 part->mFlags.clear_bit(CWeatherParticle::FLAG_RENDER);
1176 partRendering = false;
1177 }
1178 }
1179
1180 // Update Fade In
1181 //----------------
1182 else if (part->mFlags.get_bit(CWeatherParticle::FLAG_FADEIN))
1183 {
1184 part->mAlpha += particleFade;
1185 if (part->mAlpha>=mColor[3])
1186 {
1187 part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEIN);
1188 part->mAlpha = mColor[3];
1189 }
1190 }
1191 }
1192 }
1193
1194 // Keep Track Of The Number Of Particles To Render
1195 //-------------------------------------------------
1196 if (part->mFlags.get_bit(CWeatherParticle::FLAG_RENDER))
1197 {
1198 mParticleCountRender ++;
1199 }
1200
1201
1202
1203
1204
1205 }
1206 mPopulated = true;
1207 }
1208
1209 ////////////////////////////////////////////////////////////////////////////////////
1210 // Render -
1211 ////////////////////////////////////////////////////////////////////////////////////
Render()1212 void Render()
1213 {
1214 CWeatherParticle* part=0;
1215 int particleNum;
1216
1217
1218 // Set The GL State And Image Binding
1219 //------------------------------------
1220 GL_State((mBlendMode==0)?(GLS_ALPHA):(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE));
1221 GL_Bind(mImage);
1222
1223
1224 // Enable And Disable Things
1225 //---------------------------
1226 qglEnable(GL_TEXTURE_2D);
1227 //qglDisable(GL_CULL_FACE);
1228 //naughty, you are making the assumption that culling is on when you get here. -rww
1229 GL_Cull(CT_TWO_SIDED);
1230
1231 qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (mFilterMode==0)?(GL_LINEAR):(GL_NEAREST));
1232 qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (mFilterMode==0)?(GL_LINEAR):(GL_NEAREST));
1233
1234
1235 // Setup Matrix Mode And Translation
1236 //-----------------------------------
1237 qglMatrixMode(GL_MODELVIEW);
1238 qglPushMatrix();
1239
1240
1241 // Begin
1242 //-------
1243 qglBegin(mGLModeEnum);
1244 for (particleNum=0; particleNum<mParticleCount; particleNum++)
1245 {
1246 part = &(mParticles[particleNum]);
1247 if (!part->mFlags.get_bit(CWeatherParticle::FLAG_RENDER))
1248 {
1249 continue;
1250 }
1251
1252 // Blend Mode Zero -> Apply Alpha Just To Alpha Channel
1253 //------------------------------------------------------
1254 if (mBlendMode==0)
1255 {
1256 qglColor4f(mColor[0], mColor[1], mColor[2], part->mAlpha);
1257 }
1258
1259 // Otherwise Apply Alpha To All Channels
1260 //---------------------------------------
1261 else
1262 {
1263 qglColor4f(mColor[0]*part->mAlpha, mColor[1]*part->mAlpha, mColor[2]*part->mAlpha, mColor[3]*part->mAlpha);
1264 }
1265
1266 // Render A Triangle
1267 //-------------------
1268 if (mVertexCount==3)
1269 {
1270 qglTexCoord2f(1.0, 0.0);
1271 qglVertex3f(part->mPosition[0],
1272 part->mPosition[1],
1273 part->mPosition[2]);
1274
1275 qglTexCoord2f(0.0, 1.0);
1276 qglVertex3f(part->mPosition[0] + mCameraLeft[0],
1277 part->mPosition[1] + mCameraLeft[1],
1278 part->mPosition[2] + mCameraLeft[2]);
1279
1280 qglTexCoord2f(0.0, 0.0);
1281 qglVertex3f(part->mPosition[0] + mCameraLeftPlusUp[0],
1282 part->mPosition[1] + mCameraLeftPlusUp[1],
1283 part->mPosition[2] + mCameraLeftPlusUp[2]);
1284 }
1285
1286 // Render A Quad
1287 //---------------
1288 else
1289 {
1290 // Left bottom.
1291 qglTexCoord2f( 0.0, 0.0 );
1292 qglVertex3f(part->mPosition[0] - mCameraLeftMinusUp[0],
1293 part->mPosition[1] - mCameraLeftMinusUp[1],
1294 part->mPosition[2] - mCameraLeftMinusUp[2] );
1295
1296 // Right bottom.
1297 qglTexCoord2f( 1.0, 0.0 );
1298 qglVertex3f(part->mPosition[0] - mCameraLeftPlusUp[0],
1299 part->mPosition[1] - mCameraLeftPlusUp[1],
1300 part->mPosition[2] - mCameraLeftPlusUp[2] );
1301
1302 // Right top.
1303 qglTexCoord2f( 1.0, 1.0 );
1304 qglVertex3f(part->mPosition[0] + mCameraLeftMinusUp[0],
1305 part->mPosition[1] + mCameraLeftMinusUp[1],
1306 part->mPosition[2] + mCameraLeftMinusUp[2] );
1307
1308 // Left top.
1309 qglTexCoord2f( 0.0, 1.0 );
1310 qglVertex3f(part->mPosition[0] + mCameraLeftPlusUp[0],
1311 part->mPosition[1] + mCameraLeftPlusUp[1],
1312 part->mPosition[2] + mCameraLeftPlusUp[2] );
1313 }
1314 }
1315 qglEnd();
1316
1317 //qglEnable(GL_CULL_FACE);
1318 //you don't need to do this when you are properly setting cull state.
1319 qglPopMatrix();
1320
1321 mParticlesRendered += mParticleCountRender;
1322 }
1323 };
1324 ratl::vector_vs<CWeatherParticleCloud, MAX_PARTICLE_CLOUDS> mParticleClouds;
1325
1326
1327
1328 ////////////////////////////////////////////////////////////////////////////////////////
1329 // Init World Effects - Will Iterate Over All Particle Clouds, Clear Them Out, And Erase
1330 ////////////////////////////////////////////////////////////////////////////////////////
R_InitWorldEffects(void)1331 void R_InitWorldEffects(void)
1332 {
1333 srand(ri.Milliseconds());
1334
1335 for (int i=0; i<mParticleClouds.size(); i++)
1336 {
1337 mParticleClouds[i].Reset();
1338 }
1339 mParticleClouds.clear();
1340 mWindZones.clear();
1341 mOutside.Reset();
1342 }
1343
1344 ////////////////////////////////////////////////////////////////////////////////////////
1345 // Init World Effects - Will Iterate Over All Particle Clouds, Clear Them Out, And Erase
1346 ////////////////////////////////////////////////////////////////////////////////////////
R_ShutdownWorldEffects(void)1347 void R_ShutdownWorldEffects(void)
1348 {
1349 R_InitWorldEffects();
1350 }
1351
1352 ////////////////////////////////////////////////////////////////////////////////////////
1353 // RB_RenderWorldEffects - If any particle clouds exist, this will update and render them
1354 ////////////////////////////////////////////////////////////////////////////////////////
RB_RenderWorldEffects(void)1355 void RB_RenderWorldEffects(void)
1356 {
1357 if (!tr.world ||
1358 (tr.refdef.rdflags & RDF_NOWORLDMODEL) ||
1359 (backEnd.refdef.rdflags & RDF_SKYBOXPORTAL) ||
1360 !mParticleClouds.size())
1361 { // no world rendering or no world or no particle clouds
1362 return;
1363 }
1364
1365 SetViewportAndScissor();
1366 qglMatrixMode(GL_MODELVIEW);
1367 qglLoadMatrixf(backEnd.viewParms.world.modelMatrix);
1368
1369
1370 // Calculate Elapsed Time For Scale Purposes
1371 //-------------------------------------------
1372 mMillisecondsElapsed = backEnd.refdef.frametime;
1373 if (mMillisecondsElapsed<1)
1374 {
1375 mMillisecondsElapsed = 1.0f;
1376 }
1377 if (mMillisecondsElapsed>1000.0f)
1378 {
1379 mMillisecondsElapsed = 1000.0f;
1380 }
1381 mSecondsElapsed = (mMillisecondsElapsed / 1000.0f);
1382
1383
1384 // Make Sure We Are Always Outside Cached
1385 //----------------------------------------
1386 if (!mOutside.Initialized())
1387 {
1388 mOutside.Cache();
1389 }
1390 else
1391 {
1392 // Update All Wind Zones
1393 //-----------------------
1394 if (!mFrozen)
1395 {
1396 mGlobalWindVelocity.Clear();
1397 for (int wz=0; wz<mWindZones.size(); wz++)
1398 {
1399 mWindZones[wz].Update();
1400 if (mWindZones[wz].mGlobal)
1401 {
1402 mGlobalWindVelocity += mWindZones[wz].mCurrentVelocity;
1403 }
1404 }
1405 mGlobalWindDirection = mGlobalWindVelocity;
1406 mGlobalWindSpeed = VectorNormalize(mGlobalWindDirection.v);
1407 }
1408
1409 // Update All Particle Clouds
1410 //----------------------------
1411 mParticlesRendered = 0;
1412 for (int i=0; i<mParticleClouds.size(); i++)
1413 {
1414 mParticleClouds[i].Update();
1415 mParticleClouds[i].Render();
1416 }
1417 if (false)
1418 {
1419 ri.Printf( PRINT_ALL, "Weather: %d Particles Rendered\n", mParticlesRendered);
1420 }
1421 }
1422 }
1423
1424
R_WorldEffect_f(void)1425 void R_WorldEffect_f(void)
1426 {
1427 char temp[2048] = {0};
1428 ri.Cmd_ArgsBuffer( temp, sizeof( temp ) );
1429 RE_WorldEffectCommand( temp );
1430 }
1431
1432 /*
1433 ===============
1434 WE_ParseVector
1435 ===============
1436 */
WE_ParseVector(const char ** text,int count,float * v)1437 qboolean WE_ParseVector( const char **text, int count, float *v ) {
1438 char *token;
1439 int i;
1440
1441 // FIXME: spaces are currently required after parens, should change parseext...
1442 token = COM_ParseExt( text, qfalse );
1443 if ( strcmp( token, "(" ) ) {
1444 ri.Printf (PRINT_WARNING, "WARNING: missing parenthesis in weather effect\n" );
1445 return qfalse;
1446 }
1447
1448 for ( i = 0 ; i < count ; i++ ) {
1449 token = COM_ParseExt( text, qfalse );
1450 if ( !token[0] ) {
1451 ri.Printf (PRINT_WARNING, "WARNING: missing vector element in weather effect\n" );
1452 return qfalse;
1453 }
1454 v[i] = atof( token );
1455 }
1456
1457 token = COM_ParseExt( text, qfalse );
1458 if ( strcmp( token, ")" ) ) {
1459 ri.Printf (PRINT_WARNING, "WARNING: missing parenthesis in weather effect\n" );
1460 return qfalse;
1461 }
1462
1463 return qtrue;
1464 }
1465
RE_WorldEffectCommand(const char * command)1466 void RE_WorldEffectCommand(const char *command)
1467 {
1468 if ( !command )
1469 {
1470 return;
1471 }
1472
1473 COM_BeginParseSession ("RE_WorldEffectCommand");
1474
1475 const char *token;//, *origCommand;
1476
1477 token = COM_ParseExt(&command, qfalse);
1478
1479 if ( !token )
1480 {
1481 return;
1482 }
1483
1484
1485 //Die - clean up the whole weather system -rww
1486 if (Q_stricmp(token, "die") == 0)
1487 {
1488 R_ShutdownWorldEffects();
1489 return;
1490 }
1491
1492 // Clear - Removes All Particle Clouds And Wind Zones
1493 //----------------------------------------------------
1494 else if (Q_stricmp(token, "clear") == 0)
1495 {
1496 for (int p=0; p<mParticleClouds.size(); p++)
1497 {
1498 mParticleClouds[p].Reset();
1499 }
1500 mParticleClouds.clear();
1501 mWindZones.clear();
1502 }
1503
1504 // Freeze / UnFreeze - Stops All Particle Motion Updates
1505 //--------------------------------------------------------
1506 else if (Q_stricmp(token, "freeze") == 0)
1507 {
1508 mFrozen = !mFrozen;
1509 }
1510
1511 // Add a zone
1512 //---------------
1513 else if (Q_stricmp(token, "zone") == 0)
1514 {
1515 vec3_t mins;
1516 vec3_t maxs;
1517 if (WE_ParseVector(&command, 3, mins) && WE_ParseVector(&command, 3, maxs))
1518 {
1519 mOutside.AddWeatherZone(mins, maxs);
1520 }
1521 }
1522
1523 // Basic Wind
1524 //------------
1525 else if (Q_stricmp(token, "wind") == 0)
1526 {
1527 if (mWindZones.full())
1528 {
1529 return;
1530 }
1531 CWindZone& nWind = mWindZones.push_back();
1532 nWind.Initialize();
1533 }
1534
1535 // Constant Wind
1536 //---------------
1537 else if (Q_stricmp(token, "constantwind") == 0)
1538 {
1539 if (mWindZones.full())
1540 {
1541 return;
1542 }
1543 CWindZone& nWind = mWindZones.push_back();
1544 nWind.Initialize();
1545 if (!WE_ParseVector(&command, 3, nWind.mCurrentVelocity.v))
1546 {
1547 nWind.mCurrentVelocity.Clear();
1548 nWind.mCurrentVelocity[1] = 800.0f;
1549 }
1550 nWind.mTargetVelocityTimeRemaining = -1;
1551 }
1552
1553 // Gusting Wind
1554 //--------------
1555 else if (Q_stricmp(token, "gustingwind") == 0)
1556 {
1557 if (mWindZones.full())
1558 {
1559 return;
1560 }
1561 CWindZone& nWind = mWindZones.push_back();
1562 nWind.Initialize();
1563 nWind.mRVelocity.mMins = -3000.0f;
1564 nWind.mRVelocity.mMins[2] = -100.0f;
1565 nWind.mRVelocity.mMaxs = 3000.0f;
1566 nWind.mRVelocity.mMaxs[2] = 100.0f;
1567
1568 nWind.mMaxDeltaVelocityPerUpdate = 10.0f;
1569
1570 nWind.mRDuration.mMin = 1000;
1571 nWind.mRDuration.mMax = 3000;
1572
1573 nWind.mChanceOfDeadTime = 0.5f;
1574 nWind.mRDeadTime.mMin = 2000;
1575 nWind.mRDeadTime.mMax = 4000;
1576 }
1577
1578
1579
1580 // Create A Rain Storm
1581 //---------------------
1582 else if (Q_stricmp(token, "lightrain") == 0)
1583 {
1584 if (mParticleClouds.full())
1585 {
1586 return;
1587 }
1588 CWeatherParticleCloud& nCloud = mParticleClouds.push_back();
1589 nCloud.Initialize(500, "gfx/world/rain.jpg", 3);
1590 nCloud.mHeight = 80.0f;
1591 nCloud.mWidth = 1.2f;
1592 nCloud.mGravity = 2000.0f;
1593 nCloud.mFilterMode = 1;
1594 nCloud.mBlendMode = 1;
1595 nCloud.mFade = 100.0f;
1596 nCloud.mColor = 0.5f;
1597 nCloud.mOrientWithVelocity = true;
1598 nCloud.mWaterParticles = true;
1599 }
1600
1601 // Create A Rain Storm
1602 //---------------------
1603 else if (Q_stricmp(token, "rain") == 0)
1604 {
1605 if (mParticleClouds.full())
1606 {
1607 return;
1608 }
1609 CWeatherParticleCloud& nCloud = mParticleClouds.push_back();
1610 nCloud.Initialize(1000, "gfx/world/rain.jpg", 3);
1611 nCloud.mHeight = 80.0f;
1612 nCloud.mWidth = 1.2f;
1613 nCloud.mGravity = 2000.0f;
1614 nCloud.mFilterMode = 1;
1615 nCloud.mBlendMode = 1;
1616 nCloud.mFade = 100.0f;
1617 nCloud.mColor = 0.5f;
1618 nCloud.mOrientWithVelocity = true;
1619 nCloud.mWaterParticles = true;
1620 }
1621
1622 // Create A Rain Storm
1623 //---------------------
1624 else if (Q_stricmp(token, "acidrain") == 0)
1625 {
1626 if (mParticleClouds.full())
1627 {
1628 return;
1629 }
1630 CWeatherParticleCloud& nCloud = mParticleClouds.push_back();
1631 nCloud.Initialize(1000, "gfx/world/rain.jpg", 3);
1632 nCloud.mHeight = 80.0f;
1633 nCloud.mWidth = 2.0f;
1634 nCloud.mGravity = 2000.0f;
1635 nCloud.mFilterMode = 1;
1636 nCloud.mBlendMode = 1;
1637 nCloud.mFade = 100.0f;
1638
1639 nCloud.mColor[0] = 0.34f;
1640 nCloud.mColor[1] = 0.70f;
1641 nCloud.mColor[2] = 0.34f;
1642 nCloud.mColor[3] = 0.70f;
1643
1644 nCloud.mOrientWithVelocity = true;
1645 nCloud.mWaterParticles = true;
1646
1647 mOutside.mOutsidePain = 0.1f;
1648 }
1649
1650 // Create A Rain Storm
1651 //---------------------
1652 else if (Q_stricmp(token, "heavyrain") == 0)
1653 {
1654 if (mParticleClouds.full())
1655 {
1656 return;
1657 }
1658 CWeatherParticleCloud& nCloud = mParticleClouds.push_back();
1659 nCloud.Initialize(1000, "gfx/world/rain.jpg", 3);
1660 nCloud.mHeight = 80.0f;
1661 nCloud.mWidth = 1.2f;
1662 nCloud.mGravity = 2800.0f;
1663 nCloud.mFilterMode = 1;
1664 nCloud.mBlendMode = 1;
1665 nCloud.mFade = 15.0f;
1666 nCloud.mColor = 0.5f;
1667 nCloud.mOrientWithVelocity = true;
1668 nCloud.mWaterParticles = true;
1669 }
1670
1671 // Create A Snow Storm
1672 //---------------------
1673 else if (Q_stricmp(token, "snow") == 0)
1674 {
1675 if (mParticleClouds.full())
1676 {
1677 return;
1678 }
1679 CWeatherParticleCloud& nCloud = mParticleClouds.push_back();
1680 nCloud.Initialize(1000, "gfx/effects/snowflake1.bmp");
1681 nCloud.mBlendMode = 1;
1682 nCloud.mRotationChangeNext = 0;
1683 nCloud.mColor = 0.75f;
1684 nCloud.mWaterParticles = true;
1685 }
1686
1687 // Create A Some stuff
1688 //---------------------
1689 else if (Q_stricmp(token, "spacedust") == 0)
1690 {
1691 int count;
1692 if (mParticleClouds.full())
1693 {
1694 return;
1695 }
1696 token = COM_ParseExt(&command, qfalse);
1697 count = atoi(token);
1698
1699 CWeatherParticleCloud& nCloud = mParticleClouds.push_back();
1700 nCloud.Initialize(count, "gfx/effects/snowpuff1.tga");
1701 nCloud.mHeight = 1.2f;
1702 nCloud.mWidth = 1.2f;
1703 nCloud.mGravity = 0.0f;
1704 nCloud.mBlendMode = 1;
1705 nCloud.mRotationChangeNext = 0;
1706 nCloud.mColor = 0.75f;
1707 nCloud.mWaterParticles = true;
1708 nCloud.mMass.mMax = 30.0f;
1709 nCloud.mMass.mMin = 10.0f;
1710 nCloud.mSpawnRange.mMins[0] = -1500.0f;
1711 nCloud.mSpawnRange.mMins[1] = -1500.0f;
1712 nCloud.mSpawnRange.mMins[2] = -1500.0f;
1713 nCloud.mSpawnRange.mMaxs[0] = 1500.0f;
1714 nCloud.mSpawnRange.mMaxs[1] = 1500.0f;
1715 nCloud.mSpawnRange.mMaxs[2] = 1500.0f;
1716 }
1717
1718 // Create A Sand Storm
1719 //---------------------
1720 else if (Q_stricmp(token, "sand") == 0)
1721 {
1722 if (mParticleClouds.full())
1723 {
1724 return;
1725 }
1726 CWeatherParticleCloud& nCloud = mParticleClouds.push_back();
1727 nCloud.Initialize(400, "gfx/effects/alpha_smoke2b.tga");
1728
1729 nCloud.mGravity = 0;
1730 nCloud.mWidth = 70;
1731 nCloud.mHeight = 70;
1732 nCloud.mColor[0] = 0.9f;
1733 nCloud.mColor[1] = 0.6f;
1734 nCloud.mColor[2] = 0.0f;
1735 nCloud.mColor[3] = 0.5f;
1736 nCloud.mFade = 5.0f;
1737 nCloud.mMass.mMax = 30.0f;
1738 nCloud.mMass.mMin = 10.0f;
1739 nCloud.mSpawnRange.mMins[2] = -150;
1740 nCloud.mSpawnRange.mMaxs[2] = 150;
1741
1742 nCloud.mRotationChangeNext = 0;
1743 }
1744
1745 // Create Blowing Clouds Of Fog
1746 //------------------------------
1747 else if (Q_stricmp(token, "fog") == 0)
1748 {
1749 if (mParticleClouds.full())
1750 {
1751 return;
1752 }
1753 CWeatherParticleCloud& nCloud = mParticleClouds.push_back();
1754 nCloud.Initialize(60, "gfx/effects/alpha_smoke2b.tga");
1755 nCloud.mBlendMode = 1;
1756 nCloud.mGravity = 0;
1757 nCloud.mWidth = 70;
1758 nCloud.mHeight = 70;
1759 nCloud.mColor = 0.2f;
1760 nCloud.mFade = 5.0f;
1761 nCloud.mMass.mMax = 30.0f;
1762 nCloud.mMass.mMin = 10.0f;
1763 nCloud.mSpawnRange.mMins[2] = -150;
1764 nCloud.mSpawnRange.mMaxs[2] = 150;
1765
1766 nCloud.mRotationChangeNext = 0;
1767 }
1768
1769 // Create Heavy Rain Particle Cloud
1770 //-----------------------------------
1771 else if (Q_stricmp(token, "heavyrainfog") == 0)
1772 {
1773 if (mParticleClouds.full())
1774 {
1775 return;
1776 }
1777 CWeatherParticleCloud& nCloud = mParticleClouds.push_back();
1778 nCloud.Initialize(70, "gfx/effects/alpha_smoke2b.tga");
1779 nCloud.mBlendMode = 1;
1780 nCloud.mGravity = 0;
1781 nCloud.mWidth = 100;
1782 nCloud.mHeight = 100;
1783 nCloud.mColor = 0.3f;
1784 nCloud.mFade = 1.0f;
1785 nCloud.mMass.mMax = 10.0f;
1786 nCloud.mMass.mMin = 5.0f;
1787
1788 nCloud.mSpawnRange.mMins = -(nCloud.mSpawnPlaneDistance*1.25f);
1789 nCloud.mSpawnRange.mMaxs = (nCloud.mSpawnPlaneDistance*1.25f);
1790 nCloud.mSpawnRange.mMins[2] = -150;
1791 nCloud.mSpawnRange.mMaxs[2] = 150;
1792
1793 nCloud.mRotationChangeNext = 0;
1794 }
1795
1796 // Create Blowing Clouds Of Fog
1797 //------------------------------
1798 else if (Q_stricmp(token, "light_fog") == 0)
1799 {
1800 if (mParticleClouds.full())
1801 {
1802 return;
1803 }
1804 CWeatherParticleCloud& nCloud = mParticleClouds.push_back();
1805 nCloud.Initialize(40, "gfx/effects/alpha_smoke2b.tga");
1806 nCloud.mBlendMode = 1;
1807 nCloud.mGravity = 0;
1808 nCloud.mWidth = 100;
1809 nCloud.mHeight = 100;
1810 nCloud.mColor[0] = 0.19f;
1811 nCloud.mColor[1] = 0.6f;
1812 nCloud.mColor[2] = 0.7f;
1813 nCloud.mColor[3] = 0.12f;
1814 nCloud.mFade = 0.10f;
1815 nCloud.mMass.mMax = 30.0f;
1816 nCloud.mMass.mMin = 10.0f;
1817 nCloud.mSpawnRange.mMins[2] = -150;
1818 nCloud.mSpawnRange.mMaxs[2] = 150;
1819
1820 nCloud.mRotationChangeNext = 0;
1821 }
1822
1823 else if (Q_stricmp(token, "outsideshake") == 0)
1824 {
1825 mOutside.mOutsideShake = !mOutside.mOutsideShake;
1826 }
1827 else if (Q_stricmp(token, "outsidepain") == 0)
1828 {
1829 mOutside.mOutsidePain = !mOutside.mOutsidePain;
1830 }
1831 else
1832 {
1833 ri.Printf( PRINT_ALL, "Weather Effect: Please enter a valid command.\n" );
1834 ri.Printf( PRINT_ALL, " die\n" );
1835 ri.Printf( PRINT_ALL, " clear\n" );
1836 ri.Printf( PRINT_ALL, " freeze\n" );
1837 ri.Printf( PRINT_ALL, " zone (mins) (maxs)\n" );
1838 ri.Printf( PRINT_ALL, " wind\n" );
1839 ri.Printf( PRINT_ALL, " constantwind (velocity)\n" );
1840 ri.Printf( PRINT_ALL, " gustingwind\n" );
1841 //ri.Printf( PRINT_ALL, " windzone (mins) (maxs) (velocity)\n" );
1842 ri.Printf( PRINT_ALL, " lightrain\n" );
1843 ri.Printf( PRINT_ALL, " rain\n" );
1844 ri.Printf( PRINT_ALL, " acidrain\n" );
1845 ri.Printf( PRINT_ALL, " heavyrain\n" );
1846 ri.Printf( PRINT_ALL, " snow\n" );
1847 ri.Printf( PRINT_ALL, " spacedust\n" );
1848 ri.Printf( PRINT_ALL, " sand\n" );
1849 ri.Printf( PRINT_ALL, " fog\n" );
1850 ri.Printf( PRINT_ALL, " heavyrainfog\n" );
1851 ri.Printf( PRINT_ALL, " light_fog\n" );
1852 ri.Printf( PRINT_ALL, " outsideshake\n" );
1853 ri.Printf( PRINT_ALL, " outsidepain\n" );
1854 }
1855 }
1856
1857
1858
R_GetChanceOfSaberFizz()1859 float R_GetChanceOfSaberFizz()
1860 {
1861 float chance = 0.0f;
1862 int numWater = 0;
1863 for (int i=0; i<mParticleClouds.size(); i++)
1864 {
1865 if (mParticleClouds[i].mWaterParticles)
1866 {
1867 chance += (mParticleClouds[i].mGravity/20000.0f);
1868 numWater ++;
1869 }
1870 }
1871 if (numWater)
1872 {
1873 return (chance / numWater);
1874 }
1875 return 0.0f;
1876 }
1877
R_IsRaining()1878 bool R_IsRaining()
1879 {
1880 return !mParticleClouds.empty();
1881 }
1882
R_IsPuffing()1883 bool R_IsPuffing()
1884 { //Eh? Don't want surfacesprites to know this?
1885 return false;
1886 }
1887