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