Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

cg_ents.c (27281B)


      1 /*
      2 ===========================================================================
      3 Copyright (C) 1999-2005 Id Software, Inc.
      4 
      5 This file is part of Quake III Arena source code.
      6 
      7 Quake III Arena source code is free software; you can redistribute it
      8 and/or modify it under the terms of the GNU General Public License as
      9 published by the Free Software Foundation; either version 2 of the License,
     10 or (at your option) any later version.
     11 
     12 Quake III Arena source code is distributed in the hope that it will be
     13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 GNU General Public License for more details.
     16 
     17 You should have received a copy of the GNU General Public License
     18 along with Foobar; if not, write to the Free Software
     19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     20 ===========================================================================
     21 */
     22 //
     23 // cg_ents.c -- present snapshot entities, happens every single frame
     24 
     25 #include "cg_local.h"
     26 
     27 
     28 /*
     29 ======================
     30 CG_PositionEntityOnTag
     31 
     32 Modifies the entities position and axis by the given
     33 tag location
     34 ======================
     35 */
     36 void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, 
     37 							qhandle_t parentModel, char *tagName ) {
     38 	int				i;
     39 	orientation_t	lerped;
     40 	
     41 	// lerp the tag
     42 	trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
     43 		1.0 - parent->backlerp, tagName );
     44 
     45 	// FIXME: allow origin offsets along tag?
     46 	VectorCopy( parent->origin, entity->origin );
     47 	for ( i = 0 ; i < 3 ; i++ ) {
     48 		VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
     49 	}
     50 
     51 	// had to cast away the const to avoid compiler problems...
     52 	MatrixMultiply( lerped.axis, ((refEntity_t *)parent)->axis, entity->axis );
     53 	entity->backlerp = parent->backlerp;
     54 }
     55 
     56 
     57 /*
     58 ======================
     59 CG_PositionRotatedEntityOnTag
     60 
     61 Modifies the entities position and axis by the given
     62 tag location
     63 ======================
     64 */
     65 void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, 
     66 							qhandle_t parentModel, char *tagName ) {
     67 	int				i;
     68 	orientation_t	lerped;
     69 	vec3_t			tempAxis[3];
     70 
     71 //AxisClear( entity->axis );
     72 	// lerp the tag
     73 	trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
     74 		1.0 - parent->backlerp, tagName );
     75 
     76 	// FIXME: allow origin offsets along tag?
     77 	VectorCopy( parent->origin, entity->origin );
     78 	for ( i = 0 ; i < 3 ; i++ ) {
     79 		VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
     80 	}
     81 
     82 	// had to cast away the const to avoid compiler problems...
     83 	MatrixMultiply( entity->axis, lerped.axis, tempAxis );
     84 	MatrixMultiply( tempAxis, ((refEntity_t *)parent)->axis, entity->axis );
     85 }
     86 
     87 
     88 
     89 /*
     90 ==========================================================================
     91 
     92 FUNCTIONS CALLED EACH FRAME
     93 
     94 ==========================================================================
     95 */
     96 
     97 /*
     98 ======================
     99 CG_SetEntitySoundPosition
    100 
    101 Also called by event processing code
    102 ======================
    103 */
    104 void CG_SetEntitySoundPosition( centity_t *cent ) {
    105 	if ( cent->currentState.solid == SOLID_BMODEL ) {
    106 		vec3_t	origin;
    107 		float	*v;
    108 
    109 		v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ];
    110 		VectorAdd( cent->lerpOrigin, v, origin );
    111 		trap_S_UpdateEntityPosition( cent->currentState.number, origin );
    112 	} else {
    113 		trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin );
    114 	}
    115 }
    116 
    117 /*
    118 ==================
    119 CG_EntityEffects
    120 
    121 Add continuous entity effects, like local entity emission and lighting
    122 ==================
    123 */
    124 static void CG_EntityEffects( centity_t *cent ) {
    125 
    126 	// update sound origins
    127 	CG_SetEntitySoundPosition( cent );
    128 
    129 	// add loop sound
    130 	if ( cent->currentState.loopSound ) {
    131 		if (cent->currentState.eType != ET_SPEAKER) {
    132 			trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, 
    133 				cgs.gameSounds[ cent->currentState.loopSound ] );
    134 		} else {
    135 			trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, 
    136 				cgs.gameSounds[ cent->currentState.loopSound ] );
    137 		}
    138 	}
    139 
    140 
    141 	// constant light glow
    142 	if ( cent->currentState.constantLight ) {
    143 		int		cl;
    144 		int		i, r, g, b;
    145 
    146 		cl = cent->currentState.constantLight;
    147 		r = cl & 255;
    148 		g = ( cl >> 8 ) & 255;
    149 		b = ( cl >> 16 ) & 255;
    150 		i = ( ( cl >> 24 ) & 255 ) * 4;
    151 		trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b );
    152 	}
    153 
    154 }
    155 
    156 
    157 /*
    158 ==================
    159 CG_General
    160 ==================
    161 */
    162 static void CG_General( centity_t *cent ) {
    163 	refEntity_t			ent;
    164 	entityState_t		*s1;
    165 
    166 	s1 = &cent->currentState;
    167 
    168 	// if set to invisible, skip
    169 	if (!s1->modelindex) {
    170 		return;
    171 	}
    172 
    173 	memset (&ent, 0, sizeof(ent));
    174 
    175 	// set frame
    176 
    177 	ent.frame = s1->frame;
    178 	ent.oldframe = ent.frame;
    179 	ent.backlerp = 0;
    180 
    181 	VectorCopy( cent->lerpOrigin, ent.origin);
    182 	VectorCopy( cent->lerpOrigin, ent.oldorigin);
    183 
    184 	ent.hModel = cgs.gameModels[s1->modelindex];
    185 
    186 	// player model
    187 	if (s1->number == cg.snap->ps.clientNum) {
    188 		ent.renderfx |= RF_THIRD_PERSON;	// only draw from mirrors
    189 	}
    190 
    191 	// convert angles to axis
    192 	AnglesToAxis( cent->lerpAngles, ent.axis );
    193 
    194 	// add to refresh list
    195 	trap_R_AddRefEntityToScene (&ent);
    196 }
    197 
    198 /*
    199 ==================
    200 CG_Speaker
    201 
    202 Speaker entities can automatically play sounds
    203 ==================
    204 */
    205 static void CG_Speaker( centity_t *cent ) {
    206 	if ( ! cent->currentState.clientNum ) {	// FIXME: use something other than clientNum...
    207 		return;		// not auto triggering
    208 	}
    209 
    210 	if ( cg.time < cent->miscTime ) {
    211 		return;
    212 	}
    213 
    214 	trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm] );
    215 
    216 	//	ent->s.frame = ent->wait * 10;
    217 	//	ent->s.clientNum = ent->random * 10;
    218 	cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom();
    219 }
    220 
    221 /*
    222 ==================
    223 CG_Item
    224 ==================
    225 */
    226 static void CG_Item( centity_t *cent ) {
    227 	refEntity_t		ent;
    228 	entityState_t	*es;
    229 	gitem_t			*item;
    230 	int				msec;
    231 	float			frac;
    232 	float			scale;
    233 	weaponInfo_t	*wi;
    234 
    235 	es = &cent->currentState;
    236 	if ( es->modelindex >= bg_numItems ) {
    237 		CG_Error( "Bad item index %i on entity", es->modelindex );
    238 	}
    239 
    240 	// if set to invisible, skip
    241 	if ( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) {
    242 		return;
    243 	}
    244 
    245 	item = &bg_itemlist[ es->modelindex ];
    246 	if ( cg_simpleItems.integer && item->giType != IT_TEAM ) {
    247 		memset( &ent, 0, sizeof( ent ) );
    248 		ent.reType = RT_SPRITE;
    249 		VectorCopy( cent->lerpOrigin, ent.origin );
    250 		ent.radius = 14;
    251 		ent.customShader = cg_items[es->modelindex].icon;
    252 		ent.shaderRGBA[0] = 255;
    253 		ent.shaderRGBA[1] = 255;
    254 		ent.shaderRGBA[2] = 255;
    255 		ent.shaderRGBA[3] = 255;
    256 		trap_R_AddRefEntityToScene(&ent);
    257 		return;
    258 	}
    259 
    260 	// items bob up and down continuously
    261 	scale = 0.005 + cent->currentState.number * 0.00001;
    262 	cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) *  scale ) * 4;
    263 
    264 	memset (&ent, 0, sizeof(ent));
    265 
    266 	// autorotate at one of two speeds
    267 	if ( item->giType == IT_HEALTH ) {
    268 		VectorCopy( cg.autoAnglesFast, cent->lerpAngles );
    269 		AxisCopy( cg.autoAxisFast, ent.axis );
    270 	} else {
    271 		VectorCopy( cg.autoAngles, cent->lerpAngles );
    272 		AxisCopy( cg.autoAxis, ent.axis );
    273 	}
    274 
    275 	wi = NULL;
    276 	// the weapons have their origin where they attatch to player
    277 	// models, so we need to offset them or they will rotate
    278 	// eccentricly
    279 	if ( item->giType == IT_WEAPON ) {
    280 		wi = &cg_weapons[item->giTag];
    281 		cent->lerpOrigin[0] -= 
    282 			wi->weaponMidpoint[0] * ent.axis[0][0] +
    283 			wi->weaponMidpoint[1] * ent.axis[1][0] +
    284 			wi->weaponMidpoint[2] * ent.axis[2][0];
    285 		cent->lerpOrigin[1] -= 
    286 			wi->weaponMidpoint[0] * ent.axis[0][1] +
    287 			wi->weaponMidpoint[1] * ent.axis[1][1] +
    288 			wi->weaponMidpoint[2] * ent.axis[2][1];
    289 		cent->lerpOrigin[2] -= 
    290 			wi->weaponMidpoint[0] * ent.axis[0][2] +
    291 			wi->weaponMidpoint[1] * ent.axis[1][2] +
    292 			wi->weaponMidpoint[2] * ent.axis[2][2];
    293 
    294 		cent->lerpOrigin[2] += 8;	// an extra height boost
    295 	}
    296 
    297 	ent.hModel = cg_items[es->modelindex].models[0];
    298 
    299 	VectorCopy( cent->lerpOrigin, ent.origin);
    300 	VectorCopy( cent->lerpOrigin, ent.oldorigin);
    301 
    302 	ent.nonNormalizedAxes = qfalse;
    303 
    304 	// if just respawned, slowly scale up
    305 	msec = cg.time - cent->miscTime;
    306 	if ( msec >= 0 && msec < ITEM_SCALEUP_TIME ) {
    307 		frac = (float)msec / ITEM_SCALEUP_TIME;
    308 		VectorScale( ent.axis[0], frac, ent.axis[0] );
    309 		VectorScale( ent.axis[1], frac, ent.axis[1] );
    310 		VectorScale( ent.axis[2], frac, ent.axis[2] );
    311 		ent.nonNormalizedAxes = qtrue;
    312 	} else {
    313 		frac = 1.0;
    314 	}
    315 
    316 	// items without glow textures need to keep a minimum light value
    317 	// so they are always visible
    318 	if ( ( item->giType == IT_WEAPON ) ||
    319 		 ( item->giType == IT_ARMOR ) ) {
    320 		ent.renderfx |= RF_MINLIGHT;
    321 	}
    322 
    323 	// increase the size of the weapons when they are presented as items
    324 	if ( item->giType == IT_WEAPON ) {
    325 		VectorScale( ent.axis[0], 1.5, ent.axis[0] );
    326 		VectorScale( ent.axis[1], 1.5, ent.axis[1] );
    327 		VectorScale( ent.axis[2], 1.5, ent.axis[2] );
    328 		ent.nonNormalizedAxes = qtrue;
    329 #ifdef MISSIONPACK
    330 		trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.weaponHoverSound );
    331 #endif
    332 	}
    333 
    334 #ifdef MISSIONPACK
    335 	if ( item->giType == IT_HOLDABLE && item->giTag == HI_KAMIKAZE ) {
    336 		VectorScale( ent.axis[0], 2, ent.axis[0] );
    337 		VectorScale( ent.axis[1], 2, ent.axis[1] );
    338 		VectorScale( ent.axis[2], 2, ent.axis[2] );
    339 		ent.nonNormalizedAxes = qtrue;
    340 	}
    341 #endif
    342 
    343 	// add to refresh list
    344 	trap_R_AddRefEntityToScene(&ent);
    345 
    346 #ifdef MISSIONPACK
    347 	if ( item->giType == IT_WEAPON && wi->barrelModel ) {
    348 		refEntity_t	barrel;
    349 
    350 		memset( &barrel, 0, sizeof( barrel ) );
    351 
    352 		barrel.hModel = wi->barrelModel;
    353 
    354 		VectorCopy( ent.lightingOrigin, barrel.lightingOrigin );
    355 		barrel.shadowPlane = ent.shadowPlane;
    356 		barrel.renderfx = ent.renderfx;
    357 
    358 		CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" );
    359 
    360 		AxisCopy( ent.axis, barrel.axis );
    361 		barrel.nonNormalizedAxes = ent.nonNormalizedAxes;
    362 
    363 		trap_R_AddRefEntityToScene( &barrel );
    364 	}
    365 #endif
    366 
    367 	// accompanying rings / spheres for powerups
    368 	if ( !cg_simpleItems.integer ) 
    369 	{
    370 		vec3_t spinAngles;
    371 
    372 		VectorClear( spinAngles );
    373 
    374 		if ( item->giType == IT_HEALTH || item->giType == IT_POWERUP )
    375 		{
    376 			if ( ( ent.hModel = cg_items[es->modelindex].models[1] ) != 0 )
    377 			{
    378 				if ( item->giType == IT_POWERUP )
    379 				{
    380 					ent.origin[2] += 12;
    381 					spinAngles[1] = ( cg.time & 1023 ) * 360 / -1024.0f;
    382 				}
    383 				AnglesToAxis( spinAngles, ent.axis );
    384 				
    385 				// scale up if respawning
    386 				if ( frac != 1.0 ) {
    387 					VectorScale( ent.axis[0], frac, ent.axis[0] );
    388 					VectorScale( ent.axis[1], frac, ent.axis[1] );
    389 					VectorScale( ent.axis[2], frac, ent.axis[2] );
    390 					ent.nonNormalizedAxes = qtrue;
    391 				}
    392 				trap_R_AddRefEntityToScene( &ent );
    393 			}
    394 		}
    395 	}
    396 }
    397 
    398 //============================================================================
    399 
    400 /*
    401 ===============
    402 CG_Missile
    403 ===============
    404 */
    405 static void CG_Missile( centity_t *cent ) {
    406 	refEntity_t			ent;
    407 	entityState_t		*s1;
    408 	const weaponInfo_t		*weapon;
    409 //	int	col;
    410 
    411 	s1 = &cent->currentState;
    412 	if ( s1->weapon > WP_NUM_WEAPONS ) {
    413 		s1->weapon = 0;
    414 	}
    415 	weapon = &cg_weapons[s1->weapon];
    416 
    417 	// calculate the axis
    418 	VectorCopy( s1->angles, cent->lerpAngles);
    419 
    420 	// add trails
    421 	if ( weapon->missileTrailFunc ) 
    422 	{
    423 		weapon->missileTrailFunc( cent, weapon );
    424 	}
    425 /*
    426 	if ( cent->currentState.modelindex == TEAM_RED ) {
    427 		col = 1;
    428 	}
    429 	else if ( cent->currentState.modelindex == TEAM_BLUE ) {
    430 		col = 2;
    431 	}
    432 	else {
    433 		col = 0;
    434 	}
    435 
    436 	// add dynamic light
    437 	if ( weapon->missileDlight ) {
    438 		trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, 
    439 			weapon->missileDlightColor[col][0], weapon->missileDlightColor[col][1], weapon->missileDlightColor[col][2] );
    440 	}
    441 */
    442 	// add dynamic light
    443 	if ( weapon->missileDlight ) {
    444 		trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, 
    445 			weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] );
    446 	}
    447 
    448 	// add missile sound
    449 	if ( weapon->missileSound ) {
    450 		vec3_t	velocity;
    451 
    452 		BG_EvaluateTrajectoryDelta( &cent->currentState.pos, cg.time, velocity );
    453 
    454 		trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound );
    455 	}
    456 
    457 	// create the render entity
    458 	memset (&ent, 0, sizeof(ent));
    459 	VectorCopy( cent->lerpOrigin, ent.origin);
    460 	VectorCopy( cent->lerpOrigin, ent.oldorigin);
    461 
    462 	if ( cent->currentState.weapon == WP_PLASMAGUN ) {
    463 		ent.reType = RT_SPRITE;
    464 		ent.radius = 16;
    465 		ent.rotation = 0;
    466 		ent.customShader = cgs.media.plasmaBallShader;
    467 		trap_R_AddRefEntityToScene( &ent );
    468 		return;
    469 	}
    470 
    471 	// flicker between two skins
    472 	ent.skinNum = cg.clientFrame & 1;
    473 	ent.hModel = weapon->missileModel;
    474 	ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW;
    475 
    476 #ifdef MISSIONPACK
    477 	if ( cent->currentState.weapon == WP_PROX_LAUNCHER ) {
    478 		if (s1->generic1 == TEAM_BLUE) {
    479 			ent.hModel = cgs.media.blueProxMine;
    480 		}
    481 	}
    482 #endif
    483 
    484 	// convert direction of travel into axis
    485 	if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) {
    486 		ent.axis[0][2] = 1;
    487 	}
    488 
    489 	// spin as it moves
    490 	if ( s1->pos.trType != TR_STATIONARY ) {
    491 		RotateAroundDirection( ent.axis, cg.time / 4 );
    492 	} else {
    493 #ifdef MISSIONPACK
    494 		if ( s1->weapon == WP_PROX_LAUNCHER ) {
    495 			AnglesToAxis( cent->lerpAngles, ent.axis );
    496 		}
    497 		else
    498 #endif
    499 		{
    500 			RotateAroundDirection( ent.axis, s1->time );
    501 		}
    502 	}
    503 
    504 	// add to refresh list, possibly with quad glow
    505 	CG_AddRefEntityWithPowerups( &ent, s1, TEAM_FREE );
    506 }
    507 
    508 /*
    509 ===============
    510 CG_Grapple
    511 
    512 This is called when the grapple is sitting up against the wall
    513 ===============
    514 */
    515 static void CG_Grapple( centity_t *cent ) {
    516 	refEntity_t			ent;
    517 	entityState_t		*s1;
    518 	const weaponInfo_t		*weapon;
    519 
    520 	s1 = &cent->currentState;
    521 	if ( s1->weapon > WP_NUM_WEAPONS ) {
    522 		s1->weapon = 0;
    523 	}
    524 	weapon = &cg_weapons[s1->weapon];
    525 
    526 	// calculate the axis
    527 	VectorCopy( s1->angles, cent->lerpAngles);
    528 
    529 #if 0 // FIXME add grapple pull sound here..?
    530 	// add missile sound
    531 	if ( weapon->missileSound ) {
    532 		trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->missileSound );
    533 	}
    534 #endif
    535 
    536 	// Will draw cable if needed
    537 	CG_GrappleTrail ( cent, weapon );
    538 
    539 	// create the render entity
    540 	memset (&ent, 0, sizeof(ent));
    541 	VectorCopy( cent->lerpOrigin, ent.origin);
    542 	VectorCopy( cent->lerpOrigin, ent.oldorigin);
    543 
    544 	// flicker between two skins
    545 	ent.skinNum = cg.clientFrame & 1;
    546 	ent.hModel = weapon->missileModel;
    547 	ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW;
    548 
    549 	// convert direction of travel into axis
    550 	if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) {
    551 		ent.axis[0][2] = 1;
    552 	}
    553 
    554 	trap_R_AddRefEntityToScene( &ent );
    555 }
    556 
    557 /*
    558 ===============
    559 CG_Mover
    560 ===============
    561 */
    562 static void CG_Mover( centity_t *cent ) {
    563 	refEntity_t			ent;
    564 	entityState_t		*s1;
    565 
    566 	s1 = &cent->currentState;
    567 
    568 	// create the render entity
    569 	memset (&ent, 0, sizeof(ent));
    570 	VectorCopy( cent->lerpOrigin, ent.origin);
    571 	VectorCopy( cent->lerpOrigin, ent.oldorigin);
    572 	AnglesToAxis( cent->lerpAngles, ent.axis );
    573 
    574 	ent.renderfx = RF_NOSHADOW;
    575 
    576 	// flicker between two skins (FIXME?)
    577 	ent.skinNum = ( cg.time >> 6 ) & 1;
    578 
    579 	// get the model, either as a bmodel or a modelindex
    580 	if ( s1->solid == SOLID_BMODEL ) {
    581 		ent.hModel = cgs.inlineDrawModel[s1->modelindex];
    582 	} else {
    583 		ent.hModel = cgs.gameModels[s1->modelindex];
    584 	}
    585 
    586 	// add to refresh list
    587 	trap_R_AddRefEntityToScene(&ent);
    588 
    589 	// add the secondary model
    590 	if ( s1->modelindex2 ) {
    591 		ent.skinNum = 0;
    592 		ent.hModel = cgs.gameModels[s1->modelindex2];
    593 		trap_R_AddRefEntityToScene(&ent);
    594 	}
    595 
    596 }
    597 
    598 /*
    599 ===============
    600 CG_Beam
    601 
    602 Also called as an event
    603 ===============
    604 */
    605 void CG_Beam( centity_t *cent ) {
    606 	refEntity_t			ent;
    607 	entityState_t		*s1;
    608 
    609 	s1 = &cent->currentState;
    610 
    611 	// create the render entity
    612 	memset (&ent, 0, sizeof(ent));
    613 	VectorCopy( s1->pos.trBase, ent.origin );
    614 	VectorCopy( s1->origin2, ent.oldorigin );
    615 	AxisClear( ent.axis );
    616 	ent.reType = RT_BEAM;
    617 
    618 	ent.renderfx = RF_NOSHADOW;
    619 
    620 	// add to refresh list
    621 	trap_R_AddRefEntityToScene(&ent);
    622 }
    623 
    624 
    625 /*
    626 ===============
    627 CG_Portal
    628 ===============
    629 */
    630 static void CG_Portal( centity_t *cent ) {
    631 	refEntity_t			ent;
    632 	entityState_t		*s1;
    633 
    634 	s1 = &cent->currentState;
    635 
    636 	// create the render entity
    637 	memset (&ent, 0, sizeof(ent));
    638 	VectorCopy( cent->lerpOrigin, ent.origin );
    639 	VectorCopy( s1->origin2, ent.oldorigin );
    640 	ByteToDir( s1->eventParm, ent.axis[0] );
    641 	PerpendicularVector( ent.axis[1], ent.axis[0] );
    642 
    643 	// negating this tends to get the directions like they want
    644 	// we really should have a camera roll value
    645 	VectorSubtract( vec3_origin, ent.axis[1], ent.axis[1] );
    646 
    647 	CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] );
    648 	ent.reType = RT_PORTALSURFACE;
    649 	ent.oldframe = s1->powerups;
    650 	ent.frame = s1->frame;		// rotation speed
    651 	ent.skinNum = s1->clientNum/256.0 * 360;	// roll offset
    652 
    653 	// add to refresh list
    654 	trap_R_AddRefEntityToScene(&ent);
    655 }
    656 
    657 
    658 /*
    659 =========================
    660 CG_AdjustPositionForMover
    661 
    662 Also called by client movement prediction code
    663 =========================
    664 */
    665 void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ) {
    666 	centity_t	*cent;
    667 	vec3_t	oldOrigin, origin, deltaOrigin;
    668 	vec3_t	oldAngles, angles, deltaAngles;
    669 
    670 	if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) {
    671 		VectorCopy( in, out );
    672 		return;
    673 	}
    674 
    675 	cent = &cg_entities[ moverNum ];
    676 	if ( cent->currentState.eType != ET_MOVER ) {
    677 		VectorCopy( in, out );
    678 		return;
    679 	}
    680 
    681 	BG_EvaluateTrajectory( &cent->currentState.pos, fromTime, oldOrigin );
    682 	BG_EvaluateTrajectory( &cent->currentState.apos, fromTime, oldAngles );
    683 
    684 	BG_EvaluateTrajectory( &cent->currentState.pos, toTime, origin );
    685 	BG_EvaluateTrajectory( &cent->currentState.apos, toTime, angles );
    686 
    687 	VectorSubtract( origin, oldOrigin, deltaOrigin );
    688 	VectorSubtract( angles, oldAngles, deltaAngles );
    689 
    690 	VectorAdd( in, deltaOrigin, out );
    691 
    692 	// FIXME: origin change when on a rotating object
    693 }
    694 
    695 
    696 /*
    697 =============================
    698 CG_InterpolateEntityPosition
    699 =============================
    700 */
    701 static void CG_InterpolateEntityPosition( centity_t *cent ) {
    702 	vec3_t		current, next;
    703 	float		f;
    704 
    705 	// it would be an internal error to find an entity that interpolates without
    706 	// a snapshot ahead of the current one
    707 	if ( cg.nextSnap == NULL ) {
    708 		CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" );
    709 	}
    710 
    711 	f = cg.frameInterpolation;
    712 
    713 	// this will linearize a sine or parabolic curve, but it is important
    714 	// to not extrapolate player positions if more recent data is available
    715 	BG_EvaluateTrajectory( &cent->currentState.pos, cg.snap->serverTime, current );
    716 	BG_EvaluateTrajectory( &cent->nextState.pos, cg.nextSnap->serverTime, next );
    717 
    718 	cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] );
    719 	cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] );
    720 	cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] );
    721 
    722 	BG_EvaluateTrajectory( &cent->currentState.apos, cg.snap->serverTime, current );
    723 	BG_EvaluateTrajectory( &cent->nextState.apos, cg.nextSnap->serverTime, next );
    724 
    725 	cent->lerpAngles[0] = LerpAngle( current[0], next[0], f );
    726 	cent->lerpAngles[1] = LerpAngle( current[1], next[1], f );
    727 	cent->lerpAngles[2] = LerpAngle( current[2], next[2], f );
    728 
    729 }
    730 
    731 /*
    732 ===============
    733 CG_CalcEntityLerpPositions
    734 
    735 ===============
    736 */
    737 static void CG_CalcEntityLerpPositions( centity_t *cent ) {
    738 
    739 	// if this player does not want to see extrapolated players
    740 	if ( !cg_smoothClients.integer ) {
    741 		// make sure the clients use TR_INTERPOLATE
    742 		if ( cent->currentState.number < MAX_CLIENTS ) {
    743 			cent->currentState.pos.trType = TR_INTERPOLATE;
    744 			cent->nextState.pos.trType = TR_INTERPOLATE;
    745 		}
    746 	}
    747 
    748 	if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) {
    749 		CG_InterpolateEntityPosition( cent );
    750 		return;
    751 	}
    752 
    753 	// first see if we can interpolate between two snaps for
    754 	// linear extrapolated clients
    755 	if ( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP &&
    756 											cent->currentState.number < MAX_CLIENTS) {
    757 		CG_InterpolateEntityPosition( cent );
    758 		return;
    759 	}
    760 
    761 	// just use the current frame and evaluate as best we can
    762 	BG_EvaluateTrajectory( &cent->currentState.pos, cg.time, cent->lerpOrigin );
    763 	BG_EvaluateTrajectory( &cent->currentState.apos, cg.time, cent->lerpAngles );
    764 
    765 	// adjust for riding a mover if it wasn't rolled into the predicted
    766 	// player state
    767 	if ( cent != &cg.predictedPlayerEntity ) {
    768 		CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, 
    769 		cg.snap->serverTime, cg.time, cent->lerpOrigin );
    770 	}
    771 }
    772 
    773 /*
    774 ===============
    775 CG_TeamBase
    776 ===============
    777 */
    778 static void CG_TeamBase( centity_t *cent ) {
    779 	refEntity_t model;
    780 #ifdef MISSIONPACK
    781 	vec3_t angles;
    782 	int t, h;
    783 	float c;
    784 
    785 	if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF ) {
    786 #else
    787 	if ( cgs.gametype == GT_CTF) {
    788 #endif
    789 		// show the flag base
    790 		memset(&model, 0, sizeof(model));
    791 		model.reType = RT_MODEL;
    792 		VectorCopy( cent->lerpOrigin, model.lightingOrigin );
    793 		VectorCopy( cent->lerpOrigin, model.origin );
    794 		AnglesToAxis( cent->currentState.angles, model.axis );
    795 		if ( cent->currentState.modelindex == TEAM_RED ) {
    796 			model.hModel = cgs.media.redFlagBaseModel;
    797 		}
    798 		else if ( cent->currentState.modelindex == TEAM_BLUE ) {
    799 			model.hModel = cgs.media.blueFlagBaseModel;
    800 		}
    801 		else {
    802 			model.hModel = cgs.media.neutralFlagBaseModel;
    803 		}
    804 		trap_R_AddRefEntityToScene( &model );
    805 	}
    806 #ifdef MISSIONPACK
    807 	else if ( cgs.gametype == GT_OBELISK ) {
    808 		// show the obelisk
    809 		memset(&model, 0, sizeof(model));
    810 		model.reType = RT_MODEL;
    811 		VectorCopy( cent->lerpOrigin, model.lightingOrigin );
    812 		VectorCopy( cent->lerpOrigin, model.origin );
    813 		AnglesToAxis( cent->currentState.angles, model.axis );
    814 
    815 		model.hModel = cgs.media.overloadBaseModel;
    816 		trap_R_AddRefEntityToScene( &model );
    817 		// if hit
    818 		if ( cent->currentState.frame == 1) {
    819 			// show hit model
    820 			// modelindex2 is the health value of the obelisk
    821 			c = cent->currentState.modelindex2;
    822 			model.shaderRGBA[0] = 0xff;
    823 			model.shaderRGBA[1] = c;
    824 			model.shaderRGBA[2] = c;
    825 			model.shaderRGBA[3] = 0xff;
    826 			//
    827 			model.hModel = cgs.media.overloadEnergyModel;
    828 			trap_R_AddRefEntityToScene( &model );
    829 		}
    830 		// if respawning
    831 		if ( cent->currentState.frame == 2) {
    832 			if ( !cent->miscTime ) {
    833 				cent->miscTime = cg.time;
    834 			}
    835 			t = cg.time - cent->miscTime;
    836 			h = (cg_obeliskRespawnDelay.integer - 5) * 1000;
    837 			//
    838 			if (t > h) {
    839 				c = (float) (t - h) / h;
    840 				if (c > 1)
    841 					c = 1;
    842 			}
    843 			else {
    844 				c = 0;
    845 			}
    846 			// show the lights
    847 			AnglesToAxis( cent->currentState.angles, model.axis );
    848 			//
    849 			model.shaderRGBA[0] = c * 0xff;
    850 			model.shaderRGBA[1] = c * 0xff;
    851 			model.shaderRGBA[2] = c * 0xff;
    852 			model.shaderRGBA[3] = c * 0xff;
    853 
    854 			model.hModel = cgs.media.overloadLightsModel;
    855 			trap_R_AddRefEntityToScene( &model );
    856 			// show the target
    857 			if (t > h) {
    858 				if ( !cent->muzzleFlashTime ) {
    859 					trap_S_StartSound (cent->lerpOrigin, ENTITYNUM_NONE, CHAN_BODY,  cgs.media.obeliskRespawnSound);
    860 					cent->muzzleFlashTime = 1;
    861 				}
    862 				VectorCopy(cent->currentState.angles, angles);
    863 				angles[YAW] += (float) 16 * acos(1-c) * 180 / M_PI;
    864 				AnglesToAxis( angles, model.axis );
    865 
    866 				VectorScale( model.axis[0], c, model.axis[0]);
    867 				VectorScale( model.axis[1], c, model.axis[1]);
    868 				VectorScale( model.axis[2], c, model.axis[2]);
    869 
    870 				model.shaderRGBA[0] = 0xff;
    871 				model.shaderRGBA[1] = 0xff;
    872 				model.shaderRGBA[2] = 0xff;
    873 				model.shaderRGBA[3] = 0xff;
    874 				//
    875 				model.origin[2] += 56;
    876 				model.hModel = cgs.media.overloadTargetModel;
    877 				trap_R_AddRefEntityToScene( &model );
    878 			}
    879 			else {
    880 				//FIXME: show animated smoke
    881 			}
    882 		}
    883 		else {
    884 			cent->miscTime = 0;
    885 			cent->muzzleFlashTime = 0;
    886 			// modelindex2 is the health value of the obelisk
    887 			c = cent->currentState.modelindex2;
    888 			model.shaderRGBA[0] = 0xff;
    889 			model.shaderRGBA[1] = c;
    890 			model.shaderRGBA[2] = c;
    891 			model.shaderRGBA[3] = 0xff;
    892 			// show the lights
    893 			model.hModel = cgs.media.overloadLightsModel;
    894 			trap_R_AddRefEntityToScene( &model );
    895 			// show the target
    896 			model.origin[2] += 56;
    897 			model.hModel = cgs.media.overloadTargetModel;
    898 			trap_R_AddRefEntityToScene( &model );
    899 		}
    900 	}
    901 	else if ( cgs.gametype == GT_HARVESTER ) {
    902 		// show harvester model
    903 		memset(&model, 0, sizeof(model));
    904 		model.reType = RT_MODEL;
    905 		VectorCopy( cent->lerpOrigin, model.lightingOrigin );
    906 		VectorCopy( cent->lerpOrigin, model.origin );
    907 		AnglesToAxis( cent->currentState.angles, model.axis );
    908 
    909 		if ( cent->currentState.modelindex == TEAM_RED ) {
    910 			model.hModel = cgs.media.harvesterModel;
    911 			model.customSkin = cgs.media.harvesterRedSkin;
    912 		}
    913 		else if ( cent->currentState.modelindex == TEAM_BLUE ) {
    914 			model.hModel = cgs.media.harvesterModel;
    915 			model.customSkin = cgs.media.harvesterBlueSkin;
    916 		}
    917 		else {
    918 			model.hModel = cgs.media.harvesterNeutralModel;
    919 			model.customSkin = 0;
    920 		}
    921 		trap_R_AddRefEntityToScene( &model );
    922 	}
    923 #endif
    924 }
    925 
    926 /*
    927 ===============
    928 CG_AddCEntity
    929 
    930 ===============
    931 */
    932 static void CG_AddCEntity( centity_t *cent ) {
    933 	// event-only entities will have been dealt with already
    934 	if ( cent->currentState.eType >= ET_EVENTS ) {
    935 		return;
    936 	}
    937 
    938 	// calculate the current origin
    939 	CG_CalcEntityLerpPositions( cent );
    940 
    941 	// add automatic effects
    942 	CG_EntityEffects( cent );
    943 
    944 	switch ( cent->currentState.eType ) {
    945 	default:
    946 		CG_Error( "Bad entity type: %i\n", cent->currentState.eType );
    947 		break;
    948 	case ET_INVISIBLE:
    949 	case ET_PUSH_TRIGGER:
    950 	case ET_TELEPORT_TRIGGER:
    951 		break;
    952 	case ET_GENERAL:
    953 		CG_General( cent );
    954 		break;
    955 	case ET_PLAYER:
    956 		CG_Player( cent );
    957 		break;
    958 	case ET_ITEM:
    959 		CG_Item( cent );
    960 		break;
    961 	case ET_MISSILE:
    962 		CG_Missile( cent );
    963 		break;
    964 	case ET_MOVER:
    965 		CG_Mover( cent );
    966 		break;
    967 	case ET_BEAM:
    968 		CG_Beam( cent );
    969 		break;
    970 	case ET_PORTAL:
    971 		CG_Portal( cent );
    972 		break;
    973 	case ET_SPEAKER:
    974 		CG_Speaker( cent );
    975 		break;
    976 	case ET_GRAPPLE:
    977 		CG_Grapple( cent );
    978 		break;
    979 	case ET_TEAM:
    980 		CG_TeamBase( cent );
    981 		break;
    982 	}
    983 }
    984 
    985 /*
    986 ===============
    987 CG_AddPacketEntities
    988 
    989 ===============
    990 */
    991 void CG_AddPacketEntities( void ) {
    992 	int					num;
    993 	centity_t			*cent;
    994 	playerState_t		*ps;
    995 
    996 	// set cg.frameInterpolation
    997 	if ( cg.nextSnap ) {
    998 		int		delta;
    999 
   1000 		delta = (cg.nextSnap->serverTime - cg.snap->serverTime);
   1001 		if ( delta == 0 ) {
   1002 			cg.frameInterpolation = 0;
   1003 		} else {
   1004 			cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta;
   1005 		}
   1006 	} else {
   1007 		cg.frameInterpolation = 0;	// actually, it should never be used, because 
   1008 									// no entities should be marked as interpolating
   1009 	}
   1010 
   1011 	// the auto-rotating items will all have the same axis
   1012 	cg.autoAngles[0] = 0;
   1013 	cg.autoAngles[1] = ( cg.time & 2047 ) * 360 / 2048.0;
   1014 	cg.autoAngles[2] = 0;
   1015 
   1016 	cg.autoAnglesFast[0] = 0;
   1017 	cg.autoAnglesFast[1] = ( cg.time & 1023 ) * 360 / 1024.0f;
   1018 	cg.autoAnglesFast[2] = 0;
   1019 
   1020 	AnglesToAxis( cg.autoAngles, cg.autoAxis );
   1021 	AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast );
   1022 
   1023 	// generate and add the entity from the playerstate
   1024 	ps = &cg.predictedPlayerState;
   1025 	BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse );
   1026 	CG_AddCEntity( &cg.predictedPlayerEntity );
   1027 
   1028 	// lerp the non-predicted value for lightning gun origins
   1029 	CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] );
   1030 
   1031 	// add each entity sent over by the server
   1032 	for ( num = 0 ; num < cg.snap->numEntities ; num++ ) {
   1033 		cent = &cg_entities[ cg.snap->entities[ num ].number ];
   1034 		CG_AddCEntity( cent );
   1035 	}
   1036 }
   1037