/** * @brief Generate rain particle * @details Attempt to 'spot' a raindrop somewhere below a sky texture. */ static qboolean CG_RainParticleGenerate(cg_atmosphericParticle_t *particle, vec3_t currvec, float currweight) { float angle = random() * 2 * M_PI, distance = 20 + MAX_ATMOSPHERIC_DISTANCE * random(); float groundHeight, skyHeight; particle->pos[0] = cg.refdef_current->vieworg[0] + sin(angle) * distance; particle->pos[1] = cg.refdef_current->vieworg[1] + cos(angle) * distance; // choose a spawn point randomly between sky and ground skyHeight = BG_GetSkyHeightAtPoint(particle->pos); if (skyHeight >= MAX_ATMOSPHERIC_HEIGHT) { return qfalse; } groundHeight = BG_GetSkyGroundHeightAtPoint(particle->pos); if (groundHeight + particle->height + ATMOSPHERIC_PARTICLE_OFFSET >= skyHeight) { return qfalse; } particle->pos[2] = groundHeight + random() * (skyHeight - groundHeight); // make sure it doesn't fall from too far cause it then will go over our heads ('lower the ceiling') if (cg_atmFx.baseHeightOffset > 0) { if (particle->pos[2] - cg.refdef_current->vieworg[2] > cg_atmFx.baseHeightOffset) { particle->pos[2] = cg.refdef_current->vieworg[2] + cg_atmFx.baseHeightOffset; if (particle->pos[2] < groundHeight) { return qfalse; } } } // rain goes in bursts - allow max raindrops every 10 seconds if (cg_atmFx.oldDropsActive > (0.50 * cg_atmFx.numDrops + 0.001 * cg_atmFx.numDrops * (10000 - (cg.time % 10000)))) { return qfalse; } CG_SetParticleActive(particle, ACT_FALLING); particle->colour[0] = 0.6 + 0.2 * random() * 0xFF; particle->colour[1] = 0.6 + 0.2 * random() * 0xFF; particle->colour[2] = 0.6 + 0.2 * random() * 0xFF; VectorCopy(currvec, particle->delta); particle->delta[2] += crandom() * 100; VectorCopy(particle->delta, particle->deltaNormalized); VectorNormalizeFast(particle->deltaNormalized); particle->height = ATMOSPHERIC_RAIN_HEIGHT + crandom() * 100; particle->weight = currweight; particle->effectshader = &cg_atmFx.effectshaders[0]; //particle->effectshader = &cg_atmFx.effectshaders[ (int) (random() * ( cg_atmFx.numEffectShaders - 1 )) ]; return qtrue; }
/* * CG_BladeImpact */ void CG_BladeImpact( vec3_t pos, vec3_t dir ) { lentity_t *le; vec3_t angles; vec3_t end; trace_t trace; //find what are we hitting VectorNormalizeFast( dir ); VectorMA( pos, -1.0, dir, end ); CG_Trace( &trace, pos, vec3_origin, vec3_origin, end, cg.view.POVent, MASK_SHOT ); if( trace.fraction == 1.0 ) return; VecToAngles( dir, angles ); if( trace.surfFlags & SURF_FLESH || ( trace.ent > 0 && cg_entities[trace.ent].current.type == ET_PLAYER ) || ( trace.ent > 0 && cg_entities[trace.ent].current.type == ET_CORPSE ) ) { le = CG_AllocModel( LE_ALPHA_FADE, pos, angles, 3, //3 frames for weak 1, 1, 1, 1, //full white no inducted alpha 0, 0, 0, 0, //dlight CG_MediaModel( cgs.media.modBladeWallHit ), NULL ); le->ent.rotation = rand() % 360; le->ent.scale = 1.0f; trap_S_StartFixedSound( CG_MediaSfx( cgs.media.sfxBladeFleshHit[(int)( random()*3 )] ), pos, CHAN_AUTO, cg_volume_effects->value, ATTN_NORM ); } else if( trace.surfFlags & SURF_DUST ) { // throw particles on dust CG_ParticleEffect( trace.endpos, trace.plane.normal, 0.30f, 0.30f, 0.25f, 30 ); //fixme? would need a dust sound trap_S_StartFixedSound( CG_MediaSfx( cgs.media.sfxBladeWallHit[(int)( random()*2 )] ), pos, CHAN_AUTO, cg_volume_effects->value, ATTN_NORM ); } else { le = CG_AllocModel( LE_ALPHA_FADE, pos, angles, 3, //3 frames for weak 1, 1, 1, 1, //full white no inducted alpha 0, 0, 0, 0, //dlight CG_MediaModel( cgs.media.modBladeWallHit ), NULL ); le->ent.rotation = rand() % 360; le->ent.scale = 1.0f; CG_ParticleEffect( trace.endpos, trace.plane.normal, 0.30f, 0.30f, 0.25f, 15 ); trap_S_StartFixedSound( CG_MediaSfx( cgs.media.sfxBladeWallHit[(int)( random()*2 )] ), pos, CHAN_AUTO, cg_volume_effects->value, ATTN_NORM ); if( !( trace.surfFlags & SURF_NOMARKS ) ) CG_SpawnDecal( pos, dir, random()*360, 8, 1, 1, 1, 1, 10, 1, qfalse, CG_MediaShader( cgs.media.shaderBulletMark ) ); } }
qboolean AI_infront2D( vec3_t lookDir, vec3_t origin, vec3_t point, float accuracy ) { vec3_t vec; float dot; vec3_t origin2D, point2D, lookDir2D; VectorSet( origin2D, origin[0], origin[1], 0 ); VectorSet( point2D, point[0], point[1], 0 ); VectorSet( lookDir2D, lookDir[0], lookDir[1], 0 ); VectorNormalizeFast( lookDir2D ); VectorSubtract( point2D, origin2D, vec ); VectorNormalizeFast( vec ); dot = DotProduct( vec, lookDir2D ); clamp( accuracy, -1, 1 ); return ( dot > accuracy ); }
static int vector_NormalizeFast(lua_State * L) { vec_t *a; a = lua_getvector(L, 1); VectorNormalizeFast(a); return 1; }
/** * @brief Generates a single texture coordinate for the specified stage and vertex. */ static void R_StageTexCoord (const materialStage_t *stage, const vec3_t v, const vec2_t in, vec2_t out) { if (stage->flags & STAGE_ENVMAP) { /* generate texcoords */ vec3_t tmp; VectorSubtract(v, refdef.viewOrigin, tmp); VectorNormalizeFast(tmp); Vector2Copy(tmp, out); } else { /* or use the ones we were given */ Vector2Copy(in, out); } }
static qboolean CG_SnowParticleGenerate( cg_atmosphericParticle_t *particle, vec3_t currvec, float currweight ) { // Attempt to 'spot' a snowflake somewhere below a sky texture. float angle, distance; float groundHeight, skyHeight; // int msec = trap_Milliseconds(); // n_generatetime++; angle = random() * 2 * M_PI; distance = 20 + MAX_ATMOSPHERIC_DISTANCE * random(); particle->pos[0] = cg.refdef.vieworg[0] + sin( angle ) * distance; particle->pos[1] = cg.refdef.vieworg[1] + cos( angle ) * distance; // ydnar: choose a spawn point randomly between sky and ground skyHeight = BG_GetSkyHeightAtPoint( particle->pos ); if ( skyHeight == MAX_ATMOSPHERIC_HEIGHT ) { return qfalse; } groundHeight = BG_GetSkyGroundHeightAtPoint( particle->pos ); if ( groundHeight >= skyHeight ) { return qfalse; } particle->pos[2] = groundHeight + random() * ( skyHeight - groundHeight ); // make sure it doesn't fall from too far cause it then will go over our heads ('lower the ceiling') if ( cg_atmFx.baseHeightOffset > 0 ) { if ( particle->pos[2] - cg.refdef.vieworg[2] > cg_atmFx.baseHeightOffset ) { particle->pos[2] = cg.refdef.vieworg[2] + cg_atmFx.baseHeightOffset; if ( particle->pos[2] < groundHeight ) { return qfalse; } } } CG_SetParticleActive( particle, ACT_FALLING ); VectorCopy( currvec, particle->delta ); particle->delta[2] += crandom() * 25; VectorCopy( particle->delta, particle->deltaNormalized ); VectorNormalizeFast( particle->deltaNormalized ); particle->height = ATMOSPHERIC_SNOW_HEIGHT + random() * 2; particle->weight = particle->height * 0.5f; if (cg_atmFx.numEffectShaders > 1) { particle->effectshader = &cg_atmFx.effectshaders[ rand()%cg_atmFx.numEffectShaders ]; } else { particle->effectshader = &cg_atmFx.effectshaders[0]; } // generatetime += trap_Milliseconds() - msec; return( qtrue ); }
void PerpendicularVector(vec3_t dst, const vec3_t src) { if (!src[0]) { VectorSet(dst, 1, 0, 0); } else if (!src[1]) { VectorSet(dst, 0, 1, 0); } else if (!src[2]) { VectorSet(dst, 0, 0, 1); } else { VectorSet(dst, -src[1], src[0], 0); VectorNormalizeFast(dst); } }
/** * @brief Generate a snowflake * @details Attempt to 'spot' a snowflake somewhere below a sky texture. */ static qboolean CG_SnowParticleGenerate(cg_atmosphericParticle_t *particle, vec3_t currvec, float currweight) { float angle = random() * 2 * M_PI, distance = 20 + MAX_ATMOSPHERIC_DISTANCE * random(); float groundHeight, skyHeight; particle->pos[0] = cg.refdef_current->vieworg[0] + sin(angle) * distance; particle->pos[1] = cg.refdef_current->vieworg[1] + cos(angle) * distance; // choose a spawn point randomly between sky and ground skyHeight = BG_GetSkyHeightAtPoint(particle->pos); if (skyHeight >= MAX_ATMOSPHERIC_HEIGHT) { return qfalse; } groundHeight = BG_GetSkyGroundHeightAtPoint(particle->pos); if (groundHeight + particle->height + ATMOSPHERIC_PARTICLE_OFFSET >= skyHeight) { return qfalse; } particle->pos[2] = groundHeight + random() * (skyHeight - groundHeight); // make sure it doesn't fall from too far cause it then will go over our heads ('lower the ceiling') if (cg_atmFx.baseHeightOffset > 0) { if (particle->pos[2] - cg.refdef_current->vieworg[2] > cg_atmFx.baseHeightOffset) { particle->pos[2] = cg.refdef_current->vieworg[2] + cg_atmFx.baseHeightOffset; if (particle->pos[2] < groundHeight) { return qfalse; } } } CG_SetParticleActive(particle, ACT_FALLING); VectorCopy(currvec, particle->delta); particle->delta[2] += crandom() * 25; VectorCopy(particle->delta, particle->deltaNormalized); VectorNormalizeFast(particle->deltaNormalized); particle->height = ATMOSPHERIC_SNOW_HEIGHT + random() * 2; particle->weight = particle->height * 0.5f; particle->effectshader = &cg_atmFx.effectshaders[0]; //particle->effectshader = &cg_atmFx.effectshaders[ (int) (random() * ( cg_atmFx.numEffectShaders - 1 )) ]; return qtrue; }
static void VelocityForDamage( int damage, vec3_t v ) { v[0] = 10.0 * crandom(); v[1] = 10.0 * crandom(); v[2] = 20.0 + 10.0 * random(); VectorNormalizeFast( v ); if( damage < 50 ) VectorScale( v, 0.7, v ); else VectorScale( v, 1.2, v ); }
/* * CG_Event_Jump */ void CG_Event_Jump( entity_state_t *state, int parm ) { #define MOVEDIREPSILON 0.25f centity_t *cent; int xyspeedcheck; cent = &cg_entities[state->number]; xyspeedcheck = SQRTFAST( cent->animVelocity[0]*cent->animVelocity[0] + cent->animVelocity[1]*cent->animVelocity[1] ); if( xyspeedcheck < 100 ) { // the player is jumping on the same place, not running CG_PModel_AddAnimation( state->number, LEGS_JUMP_NEUTRAL, 0, 0, EVENT_CHANNEL ); CG_SexedSound( state->number, CHAN_BODY, va( S_PLAYER_JUMP_1_to_2, ( rand()&1 )+1 ), cg_volume_players->value ); } else { vec3_t movedir, viewaxis[3]; movedir[0] = cent->animVelocity[0]; movedir[1] = cent->animVelocity[1]; movedir[2] = 0; VectorNormalizeFast( movedir ); AngleVectors( tv( 0, cent->current.angles[YAW], 0 ), viewaxis[FORWARD], viewaxis[RIGHT], viewaxis[UP] ); // see what's his relative movement direction if( DotProduct( movedir, viewaxis[FORWARD] ) > MOVEDIREPSILON ) { cent->jumpedLeft = !cent->jumpedLeft; if( !cent->jumpedLeft ) { CG_PModel_AddAnimation( state->number, LEGS_JUMP_LEG2, 0, 0, EVENT_CHANNEL ); CG_SexedSound( state->number, CHAN_BODY, va( S_PLAYER_JUMP_1_to_2, ( rand()&1 )+1 ), cg_volume_players->value ); } else { CG_PModel_AddAnimation( state->number, LEGS_JUMP_LEG1, 0, 0, EVENT_CHANNEL ); CG_SexedSound( state->number, CHAN_BODY, va( S_PLAYER_JUMP_1_to_2, ( rand()&1 )+1 ), cg_volume_players->value ); } } else { CG_PModel_AddAnimation( state->number, LEGS_JUMP_NEUTRAL, 0, 0, EVENT_CHANNEL ); CG_SexedSound( state->number, CHAN_BODY, va( S_PLAYER_JUMP_1_to_2, ( rand()&1 )+1 ), cg_volume_players->value ); } } #undef MOVEDIREPSILON //racesow - lm: filter out other players if( ISVIEWERENTITY( state->number )) CG_AddJumpspeed(); //!racesow }
qboolean AI_infront( edict_t *self, edict_t *other ) { vec3_t vec; float dot; vec3_t forward; AngleVectors( self->s.angles, forward, NULL, NULL ); VectorSubtract( other->s.origin, self->s.origin, vec ); VectorNormalizeFast( vec ); dot = DotProduct( vec, forward ); if( dot > 0.3 ) return qtrue; return qfalse; }
//----------------------------------------------------------------------------- // compute the decal basis based on surface normal, and preferred saxis //----------------------------------------------------------------------------- void R_DecalComputeBasis( msurface_t *surf, vec3_t pSAxis, vec3_t textureSpaceBasis[3] ) { vec3_t surfaceNormal; // setup normal if( surf->flags & SURF_PLANEBACK ) VectorNegate( surf->plane->normal, surfaceNormal ); else VectorCopy( surf->plane->normal, surfaceNormal ); VectorCopy( surfaceNormal, textureSpaceBasis[2] ); if( pSAxis ) { // T = S cross N CrossProduct( pSAxis, textureSpaceBasis[2], textureSpaceBasis[1] ); // Name sure they aren't parallel or antiparallel // In that case, fall back to the normal algorithm. if( DotProduct( textureSpaceBasis[1], textureSpaceBasis[1] ) > 1e-6 ) { // S = N cross T CrossProduct( textureSpaceBasis[2], textureSpaceBasis[1], textureSpaceBasis[0] ); VectorNormalizeFast( textureSpaceBasis[0] ); VectorNormalizeFast( textureSpaceBasis[1] ); return; } // Fall through to the standard algorithm for parallel or antiparallel } // original Half-Life algorithm: get textureBasis from linked surface VectorCopy( surf->texinfo->vecs[0], textureSpaceBasis[0] ); VectorCopy( surf->texinfo->vecs[1], textureSpaceBasis[1] ); VectorNormalizeFast( textureSpaceBasis[0] ); VectorNormalizeFast( textureSpaceBasis[1] ); }
/* ==================== StudioGetAttachment ==================== */ void Mod_StudioGetAttachment( const edict_t *e, int iAttachment, float *origin, float *angles ) { mstudioattachment_t *pAtt; vec3_t angles2; model_t *mod; mod = Mod_Handle( e->v.modelindex ); mod_studiohdr = (studiohdr_t *)Mod_Extradata( mod ); if( !mod_studiohdr ) return; if( mod_studiohdr->numattachments <= 0 ) return; ASSERT( pBlendAPI != NULL ); if( mod_studiohdr->numattachments > MAXSTUDIOATTACHMENTS ) { mod_studiohdr->numattachments = MAXSTUDIOATTACHMENTS; // reduce it MsgDev( D_WARN, "SV_StudioGetAttahment: too many attachments on %s\n", mod_studiohdr->name ); } iAttachment = bound( 0, iAttachment, mod_studiohdr->numattachments ); // calculate attachment origin and angles pAtt = (mstudioattachment_t *)((byte *)mod_studiohdr + mod_studiohdr->attachmentindex); VectorCopy( e->v.angles, angles2 ); if( !( host.features & ENGINE_COMPENSATE_QUAKE_BUG )) angles2[PITCH] = -angles2[PITCH]; pBlendAPI->SV_StudioSetupBones( mod, e->v.frame, e->v.sequence, angles2, e->v.origin, e->v.controller, e->v.blending, pAtt[iAttachment].bone, e ); // compute pos and angles if( origin != NULL ) Matrix3x4_VectorTransform( studio_bones[pAtt[iAttachment].bone], pAtt[iAttachment].org, origin ); if( sv_allow_studio_attachment_angles->integer && origin != NULL && angles != NULL ) { vec3_t forward, bonepos; Matrix3x4_OriginFromMatrix( studio_bones[pAtt[iAttachment].bone], bonepos ); VectorSubtract( origin, bonepos, forward ); // make forward VectorNormalizeFast( forward ); VectorAngles( forward, angles ); } }
/** * @brief Checks whether a point is visible from a given position * @param[in] origin Origin to test from * @param[in] dir Direction to test into * @param[in] point This is the point we want to check the visibility for */ qboolean FrustumVis (const vec3_t origin, int dir, const vec3_t point) { /* view frustum check */ vec3_t delta; byte dv; delta[0] = point[0] - origin[0]; delta[1] = point[1] - origin[1]; delta[2] = 0; VectorNormalizeFast(delta); dv = dir & (DIRECTIONS - 1); /* test 120 frustum (cos 60 = 0.5) */ if ((delta[0] * dvecsn[dv][0] + delta[1] * dvecsn[dv][1]) < 0.5) return qfalse; else return qtrue; }
/* * CG_Event_WeaponBeam */ static void CG_Event_WeaponBeam( vec3_t origin, vec3_t dir, int ownerNum, int weapon, int firemode ) { gs_weapon_definition_t *weapondef; int range; vec3_t end; trace_t trace; switch( weapon ) { case WEAP_ELECTROBOLT: weapondef = GS_GetWeaponDef( WEAP_ELECTROBOLT ); range = ELECTROBOLT_RANGE; break; case WEAP_INSTAGUN: weapondef = GS_GetWeaponDef( WEAP_INSTAGUN ); range = weapondef->firedef.timeout; break; default: return; } VectorNormalizeFast( dir ); VectorMA( origin, range, dir, end ); // retrace to spawn wall impact CG_Trace( &trace, origin, vec3_origin, vec3_origin, end, cg.view.POVent, MASK_SOLID ); if( trace.ent != -1 && !(trace.surfFlags & (SURF_SKY|SURF_NOMARKS|SURF_NOIMPACT)) ) { if( weapondef->weapon_id == WEAP_ELECTROBOLT ) CG_BoltExplosionMode( trace.endpos, trace.plane.normal, FIRE_MODE_STRONG ); else if( weapondef->weapon_id == WEAP_INSTAGUN ) CG_InstaExplosionMode( trace.endpos, trace.plane.normal, FIRE_MODE_STRONG ); } // when it's predicted we have to delay the drawing until the view weapon is calculated cg_entities[ownerNum].localEffects[LOCALEFFECT_EV_WEAPONBEAM] = weapon; VectorCopy( origin, cg_entities[ownerNum].laserOrigin ); VectorCopy( trace.endpos, cg_entities[ownerNum].laserPoint ); }
/* * G_ClientAddDamageIndicatorImpact */ void G_ClientAddDamageIndicatorImpact( gclient_t *client, int damage, const vec3_t basedir ) { edict_t *ent; vec3_t dir; float frac; if( damage < 1 ) return; if( !client || client - game.clients < 0 || client - game.clients >= gs.maxclients ) return; ent = &game.edicts[ ( client - game.clients ) + 1 ]; if( !basedir ) { VectorCopy( vec3_origin, dir ); } else { VectorNormalize2( basedir, dir ); //#define ACCENT_SCALE 2.0f #ifdef ACCENT_SCALE // accent the vertical or horizontal aspect of the direction if( VectorLengthFast( tv( dir[0], dir[1], 0 ) ) > dir[2] ) { dir[0] *= ACCENT_SCALE; dir[1] *= ACCENT_SCALE; } else dir[2] *= ACCENT_SCALE; VectorNormalizeFast( dir ); #endif #undef ACCENT_SCALE } frac = damage / ( damage + client->resp.snap.damageTaken ); VectorLerp( client->resp.snap.damageTakenDir, frac, dir, client->resp.snap.damageTakenDir ); client->resp.snap.damageTaken += damage; }
void RB_CalcPushVertexes( deformStage_t *ds ) { int i; float *xyz; //float *table; float scale; vec3_t offset; vec3_t delta; //table = TableForFunc( ds->deformationWave.func ); scale = ds->bulgeHeight; xyz = ( float * ) tess.xyz; for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { VectorSubtract( xyz, ds->moveVector, delta ); VectorNormalizeFast( delta ); VectorScale( delta,scale,offset ); VectorAdd( xyz, offset, xyz ); } }
// Wiggle the normals for wavy environment mapping void RB_CalcDeformNormals( deformStage_t *ds ) { int i; float scale; vector3 *xyz = &tess.xyz[0], *normal = &tess.normal[0]; for ( i = 0; i < tess.numVertexes; i++, xyz++, normal++ ) { scale = 0.98f; scale = R_NoiseGet4f( 000 + xyz->x * scale, xyz->y * scale, xyz->z * scale, tess.shaderTime * ds->deformationWave.frequency ); normal->x += ds->deformationWave.amplitude * scale; scale = 0.98f; scale = R_NoiseGet4f( 100 + xyz->x * scale, xyz->y * scale, xyz->z * scale, tess.shaderTime * ds->deformationWave.frequency ); normal->y += ds->deformationWave.amplitude * scale; scale = 0.98f; scale = R_NoiseGet4f( 200 + xyz->z * scale, xyz->y * scale, xyz->z * scale, tess.shaderTime * ds->deformationWave.frequency ); normal->z += ds->deformationWave.amplitude * scale; VectorNormalizeFast( normal ); } }
void WolfRevivePushEnt( gentity_t *self, gentity_t *other ) { vec3_t dir, push; VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, dir ); dir[2] = 0; VectorNormalizeFast( dir ); VectorScale( dir, WR_PUSHAMOUNT, push ); if ( self->client ) { VectorAdd( self->s.pos.trDelta, push, self->s.pos.trDelta ); VectorAdd( self->client->ps.velocity, push, self->client->ps.velocity ); } VectorScale( dir, -WR_PUSHAMOUNT, push ); push[2] = WR_PUSHAMOUNT / 2; VectorAdd( other->s.pos.trDelta, push, other->s.pos.trDelta ); //VectorAdd( other->client->ps.velocity, push, other->client->ps.velocity ); if ( other->client ) { VectorAdd( other->client->ps.velocity, push, other->client->ps.velocity ); } }
/** * @brief Finds a vector perpendicular to the source vector * @param[in] src The source vector * @param[out] dst A vector perpendicular to @c src * @note @c dst is a perpendicular vector to @c src such that it is the closest * to one of the three axis: {1,0,0}, {0,1,0} and {0,0,1} (chosen in that order * in case of equality) * @pre non-NULL pointers and @c src is normalized * @sa ProjectPointOnPlane */ void PerpendicularVector (vec3_t dst, const vec3_t src) { int pos; int i; float minelem = 1.0F; vec3_t tempvec; /* find the smallest magnitude axially aligned vector */ for (pos = 0, i = 0; i < 3; i++) { const float a = fabs(src[i]); if (a < minelem) { pos = i; minelem = a; } } tempvec[0] = tempvec[1] = tempvec[2] = 0.0F; tempvec[pos] = 1.0F; /* project the point onto the plane defined by src */ ProjectPointOnPlane(dst, tempvec, src); /* normalize the result */ VectorNormalizeFast(dst); }
/* ================== RB_AddFlare This is called at surface tesselation time ================== */ void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ) { int i; flare_t *f, *oldest; vec3_t local; float d = 1; vec4_t eye, clip, normalized, window; backEnd.pc.c_flareAdds++; if(normal && (normal[0] || normal[1] || normal[2])) { VectorSubtract( backEnd.viewParms.or.origin, point, local ); VectorNormalizeFast(local); d = DotProduct(local, normal); // If the viewer is behind the flare don't add it. if(d < 0) return; } // if the point is off the screen, don't bother adding it // calculate screen coordinates and depth R_TransformModelToClip( point, backEnd.or.modelMatrix, backEnd.viewParms.projectionMatrix, eye, clip ); // check to see if the point is completely off screen for ( i = 0 ; i < 3 ; i++ ) { if ( clip[i] >= clip[3] || clip[i] <= -clip[3] ) { return; } } R_TransformClipToWindow( clip, &backEnd.viewParms, normalized, window ); if ( window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth || window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight ) { return; // shouldn't happen, since we check the clip[] above, except for FP rounding } // see if a flare with a matching surface, scene, and view exists oldest = r_flareStructs; for ( f = r_activeFlares ; f ; f = f->next ) { if ( f->surface == surface && f->frameSceneNum == backEnd.viewParms.frameSceneNum && f->inPortal == backEnd.viewParms.isPortal ) { break; } } // allocate a new one if (!f ) { if ( !r_inactiveFlares ) { // the list is completely full return; } f = r_inactiveFlares; r_inactiveFlares = r_inactiveFlares->next; f->next = r_activeFlares; r_activeFlares = f; f->surface = surface; f->frameSceneNum = backEnd.viewParms.frameSceneNum; f->inPortal = backEnd.viewParms.isPortal; f->addedFrame = -1; } if ( f->addedFrame != backEnd.viewParms.frameCount - 1 ) { f->visible = qfalse; f->fadeTime = backEnd.refdef.time - 2000; } f->addedFrame = backEnd.viewParms.frameCount; f->fogNum = fogNum; VectorCopy(point, f->origin); VectorCopy( color, f->color ); // fade the intensity of the flare down as the // light surface turns away from the viewer VectorScale( f->color, d, f->color ); // save info needed to test f->windowX = backEnd.viewParms.viewportX + window[0]; f->windowY = backEnd.viewParms.viewportY + window[1]; f->eyeZ = eye[2]; }
/* ================== RB_AddFlare This is called at surface tesselation time ================== */ void RB_AddFlare(void *surface, int fogNum, vec3_t point, vec3_t color, float scale, vec3_t normal, int id, qboolean cgvisible) // added scale. added id. added visible { int i; flare_t *f; vec3_t local; vec4_t eye, clip, normalized, window; backEnd.pc.c_flareAdds++; // if the point is off the screen, don't bother adding it // calculate screen coordinates and depth R_TransformModelToClip(point, backEnd.orientation.modelMatrix, backEnd.viewParms.projectionMatrix, eye, clip); //Ren_Print("src: %f %f %f \n", point[0], point[1], point[2]); //Ren_Print("eye: %f %f %f %f\n", eye[0], eye[1], eye[2], eye[3]); // check to see if the point is completely off screen for (i = 0 ; i < 3 ; i++) { if (clip[i] >= clip[3] || clip[i] <= -clip[3]) { return; } } R_TransformClipToWindow(clip, &backEnd.viewParms, normalized, window); //Ren_Print("window: %f %f %f \n", window[0], window[1], window[2]); if (window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth || window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight) { return; // shouldn't happen, since we check the clip[] above, except for FP rounding } // see if a flare with a matching surface, scene, and view exists for (f = r_activeFlares ; f ; f = f->next) { // added back in more checks for different scenes if (f->id == id && f->frameSceneNum == backEnd.viewParms.frameSceneNum && f->inPortal == backEnd.viewParms.isPortal) { break; } } // allocate a new one if (!f) { if (!r_inactiveFlares) { // the list is completely full return; } f = r_inactiveFlares; r_inactiveFlares = r_inactiveFlares->next; f->next = r_activeFlares; r_activeFlares = f; f->surface = surface; f->frameSceneNum = backEnd.viewParms.frameSceneNum; f->inPortal = backEnd.viewParms.isPortal; f->addedFrame = -1; f->id = id; } f->cgvisible = cgvisible; if (f->addedFrame != backEnd.viewParms.frameCount - 1) { f->visible = qfalse; f->fadeTime = backEnd.refdef.time - 2000; } f->addedFrame = backEnd.viewParms.frameCount; f->fogNum = fogNum; VectorCopy(color, f->color); f->scale = scale; // fade the intensity of the flare down as the // light surface turns away from the viewer if (normal) { float d; VectorSubtract(backEnd.viewParms.orientation.origin, point, local); VectorNormalizeFast(local); d = DotProduct(local, normal); VectorScale(f->color, d, f->color); } // save info needed to test f->windowX = backEnd.viewParms.viewportX + window[0]; f->windowY = backEnd.viewParms.viewportY + window[1]; f->eyeZ = eye[2]; }
void HealthComponent::HandleDamage(float amount, gentity_t* source, Util::optional<Vec3> location, Util::optional<Vec3> direction, int flags, meansOfDeath_t meansOfDeath) { if (health <= 0.0f) return; if (amount <= 0.0f) return; gclient_t *client = entity.oldEnt->client; // Check for immunity. if (entity.oldEnt->flags & FL_GODMODE) return; if (client) { if (client->noclip) return; if (client->sess.spectatorState != SPECTATOR_NOT) return; } // Set source to world if missing. if (!source) source = &g_entities[ENTITYNUM_WORLD]; // Don't handle ET_MOVER w/o die or pain function. // TODO: Handle mover special casing in a dedicated component. if (entity.oldEnt->s.eType == entityType_t::ET_MOVER && !(entity.oldEnt->die || entity.oldEnt->pain)) { // Special case for ET_MOVER with act function in initial position. if ((entity.oldEnt->moverState == MOVER_POS1 || entity.oldEnt->moverState == ROTATOR_POS1) && entity.oldEnt->act) { entity.oldEnt->act(entity.oldEnt, source, source); } return; } // Check for protection. if (!(flags & DAMAGE_NO_PROTECTION)) { // Check for protection from friendly damage. if (entity.oldEnt != source && G_OnSameTeam(entity.oldEnt, source)) { // Check if friendly fire has been disabled. if (!g_friendlyFire.integer) return; // Never do friendly damage on movement attacks. switch (meansOfDeath) { case MOD_LEVEL3_POUNCE: case MOD_LEVEL4_TRAMPLE: return; default: break; } // If dretchpunt is enabled and this is a dretch, do dretchpunt instead of damage. // TODO: Add a message for pushing. if (g_dretchPunt.integer && client && client->ps.stats[STAT_CLASS] == PCL_ALIEN_LEVEL0) { vec3_t dir, push; VectorSubtract(entity.oldEnt->r.currentOrigin, source->r.currentOrigin, dir); VectorNormalizeFast(dir); VectorScale(dir, (amount * 10.0f), push); push[ 2 ] = 64.0f; VectorAdd( client->ps.velocity, push, client->ps.velocity ); return; } } // Check for protection from friendly buildable damage. Never protect from building actions. // TODO: Use DAMAGE_NO_PROTECTION flag instead of listing means of death here. if (entity.oldEnt->s.eType == entityType_t::ET_BUILDABLE && source->client && meansOfDeath != MOD_DECONSTRUCT && meansOfDeath != MOD_SUICIDE && meansOfDeath != MOD_REPLACE) { if (G_OnSameTeam(entity.oldEnt, source) && !g_friendlyBuildableFire.integer) { return; } } } float take = amount; // Apply damage modifiers. if (!(flags & DAMAGE_PURE)) { entity.ApplyDamageModifier(take, location, direction, flags, meansOfDeath); } // Update combat timers. // TODO: Add a message to update combat timers. if (client && source->client && entity.oldEnt != source) { client->lastCombatTime = entity.oldEnt->client->lastCombatTime = level.time; } if (client) { // Save damage w/o armor modifier. client->damage_received += (int)(amount + 0.5f); // Save damage direction. if (direction) { VectorCopy(direction.value().Data(), client->damage_from); client->damage_fromWorld = false; } else { VectorCopy(entity.oldEnt->r.currentOrigin, client->damage_from); client->damage_fromWorld = true; } // Drain jetpack fuel. // TODO: Have another component handle jetpack fuel drain. client->ps.stats[STAT_FUEL] = std::max(0, client->ps.stats[STAT_FUEL] - (int)(amount + 0.5f) * JETPACK_FUEL_PER_DMG); // If boosted poison every attack. // TODO: Add a poison message and a PoisonableComponent. if (source->client && (source->client->ps.stats[STAT_STATE] & SS_BOOSTED) && client->pers.team == TEAM_HUMANS && client->poisonImmunityTime < level.time) { switch (meansOfDeath) { case MOD_POISON: case MOD_LEVEL2_ZAP: break; default: client->ps.stats[STAT_STATE] |= SS_POISONED; client->lastPoisonTime = level.time; client->lastPoisonClient = source; break; } } } healthLogger.Notice("Taking damage: %3.1f (%3.1f → %3.1f)", take, health, health - take); // Do the damage. health -= take; // Update team overlay info. if (client) client->pers.infoChangeTime = level.time; // TODO: Move lastDamageTime to HealthComponent. entity.oldEnt->lastDamageTime = level.time; // HACK: gentity_t.nextRegenTime only affects alien clients. // TODO: Catch damage message in a new RegenerationComponent. entity.oldEnt->nextRegenTime = level.time + ALIEN_CLIENT_REGEN_WAIT; // Handle non-self damage. if (entity.oldEnt != source) { float loss = take; if (health < 0.0f) loss += health; // TODO: Use ClientComponent. if (source->client) { // Add to the attacker's account on the target. // TODO: Move damage account array to HealthComponent. entity.oldEnt->credits[source->client->ps.clientNum].value += loss; entity.oldEnt->credits[source->client->ps.clientNum].time = level.time; entity.oldEnt->credits[source->client->ps.clientNum].team = (team_t)source->client->pers.team; } } // Handle death. // TODO: Send a Die/Pain message and handle details where appropriate. if (health <= 0) { healthLogger.Notice("Dying with %.1f health.", health); // Disable knockback. if (client) entity.oldEnt->flags |= FL_NO_KNOCKBACK; // Call legacy die function. if (entity.oldEnt->die) entity.oldEnt->die(entity.oldEnt, source, source, meansOfDeath); // Send die message. entity.Die(source, meansOfDeath); // Trigger ON_DIE event. if(!client) G_EventFireEntity(entity.oldEnt, source, ON_DIE); } else if (entity.oldEnt->pain) { entity.oldEnt->pain(entity.oldEnt, source, (int)std::ceil(take)); } if (entity.oldEnt != source && source->client) { bool lethal = (health <= 0); CombatFeedback::HitNotify(source, entity.oldEnt, location, take, meansOfDeath, lethal); } }
/* ================= R_MarkFragments ================= */ int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ) { int numsurfaces, numPlanes; int i, j, k, m, n; surfaceType_t *surfaces[64]; vec3_t mins, maxs; int returnedFragments; int returnedPoints; vec3_t normals[MAX_VERTS_ON_POLY+2]; float dists[MAX_VERTS_ON_POLY+2]; vec3_t clipPoints[2][MAX_VERTS_ON_POLY]; int numClipPoints; float *v; srfSurfaceFace_t *surf; srfGridMesh_t *cv; drawVert_t *dv; vec3_t normal; vec3_t projectionDir; vec3_t v1, v2; int *indexes; //increment view count for double check prevention tr.viewCount++; // VectorNormalize2( projection, projectionDir ); // find all the brushes that are to be considered ClearBounds( mins, maxs ); for ( i = 0 ; i < numPoints ; i++ ) { vec3_t temp; AddPointToBounds( points[i], mins, maxs ); VectorAdd( points[i], projection, temp ); AddPointToBounds( temp, mins, maxs ); // make sure we get all the leafs (also the one(s) in front of the hit surface) VectorMA( points[i], -20, projectionDir, temp ); AddPointToBounds( temp, mins, maxs ); } if (numPoints > MAX_VERTS_ON_POLY) numPoints = MAX_VERTS_ON_POLY; // create the bounding planes for the to be projected polygon for ( i = 0 ; i < numPoints ; i++ ) { VectorSubtract(points[(i+1)%numPoints], points[i], v1); VectorAdd(points[i], projection, v2); VectorSubtract(points[i], v2, v2); CrossProduct(v1, v2, normals[i]); VectorNormalizeFast(normals[i]); dists[i] = DotProduct(normals[i], points[i]); } // add near and far clipping planes for projection VectorCopy(projectionDir, normals[numPoints]); dists[numPoints] = DotProduct(normals[numPoints], points[0]) - 32; VectorCopy(projectionDir, normals[numPoints+1]); VectorInverse(normals[numPoints+1]); dists[numPoints+1] = DotProduct(normals[numPoints+1], points[0]) - 20; numPlanes = numPoints + 2; numsurfaces = 0; R_BoxSurfaces_r(tr.world->nodes, mins, maxs, surfaces, 64, &numsurfaces, projectionDir); //assert(numsurfaces <= 64); //assert(numsurfaces != 64); returnedPoints = 0; returnedFragments = 0; for ( i = 0 ; i < numsurfaces ; i++ ) { if (*surfaces[i] == SF_GRID) { cv = (srfGridMesh_t *) surfaces[i]; for ( m = 0 ; m < cv->height - 1 ; m++ ) { for ( n = 0 ; n < cv->width - 1 ; n++ ) { // We triangulate the grid and chop all triangles within // the bounding planes of the to be projected polygon. // LOD is not taken into account, not such a big deal though. // // It's probably much nicer to chop the grid itself and deal // with this grid as a normal SF_GRID surface so LOD will // be applied. However the LOD of that chopped grid must // be synced with the LOD of the original curve. // One way to do this; the chopped grid shares vertices with // the original curve. When LOD is applied to the original // curve the unused vertices are flagged. Now the chopped curve // should skip the flagged vertices. This still leaves the // problems with the vertices at the chopped grid edges. // // To avoid issues when LOD applied to "hollow curves" (like // the ones around many jump pads) we now just add a 2 unit // offset to the triangle vertices. // The offset is added in the vertex normal vector direction // so all triangles will still fit together. // The 2 unit offset should avoid pretty much all LOD problems. numClipPoints = 3; dv = cv->verts + m * cv->width + n; VectorCopy(dv[0].xyz, clipPoints[0][0]); VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[0].normal, clipPoints[0][0]); VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]); VectorCopy(dv[1].xyz, clipPoints[0][2]); VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[1].normal, clipPoints[0][2]); // check the normal of this triangle VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); CrossProduct(v1, v2, normal); VectorNormalizeFast(normal); if (DotProduct(normal, projectionDir) < -0.1) { // add the fragments of this triangle R_AddMarkFragments(numClipPoints, clipPoints, numPlanes, normals, dists, maxPoints, pointBuffer, maxFragments, fragmentBuffer, &returnedPoints, &returnedFragments, mins, maxs); if ( returnedFragments == maxFragments ) { return returnedFragments; // not enough space for more fragments } } VectorCopy(dv[1].xyz, clipPoints[0][0]); VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[1].normal, clipPoints[0][0]); VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]); VectorCopy(dv[cv->width+1].xyz, clipPoints[0][2]); VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[cv->width+1].normal, clipPoints[0][2]); // check the normal of this triangle VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); CrossProduct(v1, v2, normal); VectorNormalizeFast(normal); if (DotProduct(normal, projectionDir) < -0.05) { // add the fragments of this triangle R_AddMarkFragments(numClipPoints, clipPoints, numPlanes, normals, dists, maxPoints, pointBuffer, maxFragments, fragmentBuffer, &returnedPoints, &returnedFragments, mins, maxs); if ( returnedFragments == maxFragments ) { return returnedFragments; // not enough space for more fragments } } } } } else if (*surfaces[i] == SF_FACE) { surf = ( srfSurfaceFace_t * ) surfaces[i]; // check the normal of this face if (DotProduct(surf->plane.normal, projectionDir) > -0.5) { continue; } /* VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); CrossProduct(v1, v2, normal); VectorNormalize(normal); if (DotProduct(normal, projectionDir) > -0.5) continue; */ indexes = (int *)( (byte *)surf + surf->ofsIndices ); for ( k = 0 ; k < surf->numIndices ; k += 3 ) { for ( j = 0 ; j < 3 ; j++ ) { v = surf->points[0] + VERTEXSIZE * indexes[k+j];; VectorMA( v, MARKER_OFFSET, surf->plane.normal, clipPoints[0][j] ); } // add the fragments of this face R_AddMarkFragments( 3 , clipPoints, numPlanes, normals, dists, maxPoints, pointBuffer, maxFragments, fragmentBuffer, &returnedPoints, &returnedFragments, mins, maxs); if ( returnedFragments == maxFragments ) { return returnedFragments; // not enough space for more fragments } } continue; } else { // ignore all other world surfaces // might be cool to also project polygons on a triangle soup // however this will probably create huge amounts of extra polys // even more than the projection onto curves continue; } } return returnedFragments; }
/* * R_LightForOrigin */ void R_LightForOrigin( const vec3_t origin, vec3_t dir, vec4_t ambient, vec4_t diffuse, float radius ) { int i, j; int k, s; int vi[3], elem[4]; float dot, t[8], scale; vec3_t vf, vf2, tdir; vec3_t ambientLocal, diffuseLocal; vec_t *gridSize, *gridMins; int *gridBounds; static mgridlight_t lightarray[8]; lightstyle_t *lightStyles = rsc.lightStyles; VectorSet( ambientLocal, 0, 0, 0 ); VectorSet( diffuseLocal, 0, 0, 0 ); if( !rsh.worldModel /* || (rn.refdef.rdflags & RDF_NOWORLDMODEL)*/ || !rsh.worldBrushModel->lightgrid || !rsh.worldBrushModel->numlightgridelems ) { VectorSet( dir, 0.1f, 0.2f, 0.7f ); goto dynamic; } gridSize = rsh.worldBrushModel->gridSize; gridMins = rsh.worldBrushModel->gridMins; gridBounds = rsh.worldBrushModel->gridBounds; for( i = 0; i < 3; i++ ) { vf[i] = ( origin[i] - gridMins[i] ) / gridSize[i]; vi[i] = (int)vf[i]; vf[i] = vf[i] - floor( vf[i] ); vf2[i] = 1.0f - vf[i]; } elem[0] = vi[2] * gridBounds[3] + vi[1] * gridBounds[0] + vi[0]; elem[1] = elem[0] + gridBounds[0]; elem[2] = elem[0] + gridBounds[3]; elem[3] = elem[2] + gridBounds[0]; for( i = 0; i < 4; i++ ) { lightarray[i*2+0] = *rsh.worldBrushModel->lightarray[bound( 0, elem[i]+0, (int)rsh.worldBrushModel->numlightarrayelems-1)]; lightarray[i*2+1] = *rsh.worldBrushModel->lightarray[bound( 1, elem[i]+1, (int)rsh.worldBrushModel->numlightarrayelems-1)]; } t[0] = vf2[0] * vf2[1] * vf2[2]; t[1] = vf[0] * vf2[1] * vf2[2]; t[2] = vf2[0] * vf[1] * vf2[2]; t[3] = vf[0] * vf[1] * vf2[2]; t[4] = vf2[0] * vf2[1] * vf[2]; t[5] = vf[0] * vf2[1] * vf[2]; t[6] = vf2[0] * vf[1] * vf[2]; t[7] = vf[0] * vf[1] * vf[2]; VectorClear( dir ); for( i = 0; i < 4; i++ ) { R_LatLongToNorm( lightarray[i*2].direction, tdir ); VectorScale( tdir, t[i*2], tdir ); for( k = 0; k < MAX_LIGHTMAPS && ( s = lightarray[i*2].styles[k] ) != 255; k++ ) { dir[0] += lightStyles[s].rgb[0] * tdir[0]; dir[1] += lightStyles[s].rgb[1] * tdir[1]; dir[2] += lightStyles[s].rgb[2] * tdir[2]; } R_LatLongToNorm( lightarray[i*2+1].direction, tdir ); VectorScale( tdir, t[i*2+1], tdir ); for( k = 0; k < MAX_LIGHTMAPS && ( s = lightarray[i*2+1].styles[k] ) != 255; k++ ) { dir[0] += lightStyles[s].rgb[0] * tdir[0]; dir[1] += lightStyles[s].rgb[1] * tdir[1]; dir[2] += lightStyles[s].rgb[2] * tdir[2]; } } for( j = 0; j < 3; j++ ) { if( ambient ) { for( i = 0; i < 4; i++ ) { for( k = 0; k < MAX_LIGHTMAPS; k++ ) { if( ( s = lightarray[i*2].styles[k] ) != 255 ) ambientLocal[j] += t[i*2] * lightarray[i*2].ambient[k][j] * lightStyles[s].rgb[j]; if( ( s = lightarray[i*2+1].styles[k] ) != 255 ) ambientLocal[j] += t[i*2+1] * lightarray[i*2+1].ambient[k][j] * lightStyles[s].rgb[j]; } } } if( diffuse || radius ) { for( i = 0; i < 4; i++ ) { for( k = 0; k < MAX_LIGHTMAPS; k++ ) { if( ( s = lightarray[i*2].styles[k] ) != 255 ) diffuseLocal[j] += t[i*2] * lightarray[i*2].diffuse[k][j] * lightStyles[s].rgb[j]; if( ( s = lightarray[i*2+1].styles[k] ) != 255 ) diffuseLocal[j] += t[i*2+1] * lightarray[i*2+1].diffuse[k][j] * lightStyles[s].rgb[j]; } } } } // convert to grayscale if( r_lighting_grayscale->integer ) { vec_t grey; if( ambient ) { grey = ColorGrayscale( ambientLocal ); ambientLocal[0] = ambientLocal[1] = ambientLocal[2] = bound( 0, grey, 255 ); } if( diffuse || radius ) { grey = ColorGrayscale( diffuseLocal ); diffuseLocal[0] = diffuseLocal[1] = diffuseLocal[2] = bound( 0, grey, 255 ); } } dynamic: // add dynamic lights if( radius && r_dynamiclight->integer ) { unsigned int lnum; dlight_t *dl; float dist, dist2, add; vec3_t direction; qboolean anyDlights = qfalse; for( lnum = 0; lnum < rsc.numDlights; lnum++ ) { dl = rsc.dlights + lnum; if( Distance( dl->origin, origin ) > dl->intensity + radius ) continue; VectorSubtract( dl->origin, origin, direction ); dist = VectorLength( direction ); if( !dist || dist > dl->intensity + radius ) continue; if( !anyDlights ) { VectorNormalizeFast( dir ); anyDlights = qtrue; } add = 1.0 - (dist / (dl->intensity + radius)); dist2 = add * 0.5 / dist; for( i = 0; i < 3; i++ ) { dot = dl->color[i] * add; diffuseLocal[i] += dot; ambientLocal[i] += dot * 0.05; dir[i] += direction[i] * dist2; } } } VectorNormalizeFast( dir ); scale = mapConfig.mapLightColorScale / 255.0f; if( ambient ) { float scale2 = bound( 0.0f, r_lighting_ambientscale->value, 1.0f ) * scale; for( i = 0; i < 3; i++ ) ambient[i] = ambientLocal[i] * scale2; ambient[3] = 1.0f; } if( diffuse ) { float scale2 = bound( 0.0f, r_lighting_directedscale->value, 1.0f ) * scale; for( i = 0; i < 3; i++ ) diffuse[i] = diffuseLocal[i] * scale2; diffuse[3] = 1.0f; } }
/* ================= R_MarkFragments ================= */ int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ) { int numsurfaces, numPlanes; int i, j, k, m, n; surfaceType_t *surfaces[64]; int surfacesBmodel[64]; int lastBmodel; vec3_t mins, maxs; int returnedFragments; int returnedPoints; vec3_t normals[MAX_VERTS_ON_POLY+2], localNormals[MAX_VERTS_ON_POLY+2]; float dists[MAX_VERTS_ON_POLY+2], localDists[MAX_VERTS_ON_POLY+2]; vec3_t clipPoints[2][MAX_VERTS_ON_POLY]; int numClipPoints; float *v; srfBspSurface_t *cv; glIndex_t *tri; srfVert_t *dv; vec3_t normal; vec3_t projectionDir, localProjectionDir; vec3_t v1, v2; if (numPoints <= 0) { return 0; } //increment view count for double check prevention tr.viewCount++; // VectorNormalize2( projection, projectionDir ); // find all the brushes that are to be considered ClearBounds( mins, maxs ); for ( i = 0 ; i < numPoints ; i++ ) { vec3_t temp; AddPointToBounds( points[i], mins, maxs ); VectorAdd( points[i], projection, temp ); AddPointToBounds( temp, mins, maxs ); // make sure we get all the leafs (also the one(s) in front of the hit surface) VectorMA( points[i], -20, projectionDir, temp ); AddPointToBounds( temp, mins, maxs ); } if (numPoints > MAX_VERTS_ON_POLY) numPoints = MAX_VERTS_ON_POLY; // create the bounding planes for the to be projected polygon for ( i = 0 ; i < numPoints ; i++ ) { VectorSubtract(points[(i+1)%numPoints], points[i], v1); VectorAdd(points[i], projection, v2); VectorSubtract(points[i], v2, v2); CrossProduct(v1, v2, normals[i]); VectorNormalizeFast(normals[i]); dists[i] = DotProduct(normals[i], points[i]); } // add near and far clipping planes for projection VectorCopy(projectionDir, normals[numPoints]); dists[numPoints] = DotProduct(normals[numPoints], points[0]) - 32; VectorCopy(projectionDir, normals[numPoints+1]); VectorInverse(normals[numPoints+1]); dists[numPoints+1] = DotProduct(normals[numPoints+1], points[0]) - 20; numPlanes = numPoints + 2; numsurfaces = 0; R_BoxSurfaces_r(tr.world->nodes, mins, maxs, surfaces, surfacesBmodel, 64, &numsurfaces, projectionDir); //assert(numsurfaces <= 64); //assert(numsurfaces != 64); // add bmodel surfaces for ( j = 1; j < tr.world->numBModels; j++ ) { vec3_t localProjection, bmodelOrigin, bmodelAxis[3]; R_TransformMarkProjection( j, projection, localProjection, 0, NULL, NULL, NULL, NULL ); R_GetBmodelInfo( j, NULL, bmodelOrigin, bmodelAxis ); VectorNormalize2( localProjection, localProjectionDir ); // find all the brushes that are to be considered ClearBounds( mins, maxs ); for ( i = 0 ; i < numPoints ; i++ ) { vec3_t temp; vec3_t delta; vec3_t localPoint; // convert point to bmodel local space VectorSubtract( points[i], bmodelOrigin, delta ); localPoint[0] = DotProduct( delta, bmodelAxis[0] ); localPoint[1] = DotProduct( delta, bmodelAxis[1] ); localPoint[2] = DotProduct( delta, bmodelAxis[2] ); AddPointToBounds( localPoint, mins, maxs ); VectorAdd( localPoint, localProjection, temp ); AddPointToBounds( temp, mins, maxs ); // make sure we get all the leafs (also the one(s) in front of the hit surface) VectorMA( localPoint, -20, localProjectionDir, temp ); AddPointToBounds( temp, mins, maxs ); } R_BmodelSurfaces( j, mins, maxs, surfaces, surfacesBmodel, 64, &numsurfaces, localProjectionDir); } returnedPoints = 0; returnedFragments = 0; lastBmodel = -1; for ( i = 0 ; i < numsurfaces ; i++ ) { if (i == 0 || surfacesBmodel[i] != lastBmodel) { R_TransformMarkProjection( surfacesBmodel[i], projectionDir, localProjectionDir, numPlanes, normals, dists, localNormals, localDists ); lastBmodel = surfacesBmodel[i]; // don't use projectionDir, normals, or dists beyond this point !!! // mins and maxs are not setup, so they are not valid !!! } if (*surfaces[i] == SF_GRID) { cv = (srfBspSurface_t *) surfaces[i]; for ( m = 0 ; m < cv->height - 1 ; m++ ) { for ( n = 0 ; n < cv->width - 1 ; n++ ) { // We triangulate the grid and chop all triangles within // the bounding planes of the to be projected polygon. // LOD is not taken into account, not such a big deal though. // // It's probably much nicer to chop the grid itself and deal // with this grid as a normal SF_GRID surface so LOD will // be applied. However the LOD of that chopped grid must // be synced with the LOD of the original curve. // One way to do this; the chopped grid shares vertices with // the original curve. When LOD is applied to the original // curve the unused vertices are flagged. Now the chopped curve // should skip the flagged vertices. This still leaves the // problems with the vertices at the chopped grid edges. // // To avoid issues when LOD applied to "hollow curves" (like // the ones around many jump pads) we now just add a 2 unit // offset to the triangle vertices. // The offset is added in the vertex normal vector direction // so all triangles will still fit together. // The 2 unit offset should avoid pretty much all LOD problems. numClipPoints = 3; dv = cv->verts + m * cv->width + n; VectorCopy(dv[0].xyz, clipPoints[0][0]); VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[0].normal, clipPoints[0][0]); VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]); VectorCopy(dv[1].xyz, clipPoints[0][2]); VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[1].normal, clipPoints[0][2]); // check the normal of this triangle VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); CrossProduct(v1, v2, normal); VectorNormalizeFast(normal); if (DotProduct(normal, localProjectionDir) < -0.1) { // add the fragments of this triangle R_AddMarkFragments(numClipPoints, clipPoints, numPlanes, localNormals, localDists, maxPoints, pointBuffer, maxFragments, fragmentBuffer, &returnedPoints, &returnedFragments, mins, maxs, lastBmodel, localProjectionDir); if ( returnedFragments == maxFragments ) { return returnedFragments; // not enough space for more fragments } } VectorCopy(dv[1].xyz, clipPoints[0][0]); VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[1].normal, clipPoints[0][0]); VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]); VectorCopy(dv[cv->width+1].xyz, clipPoints[0][2]); VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[cv->width+1].normal, clipPoints[0][2]); // check the normal of this triangle VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); CrossProduct(v1, v2, normal); VectorNormalizeFast(normal); if (DotProduct(normal, localProjectionDir) < -0.05) { // add the fragments of this triangle R_AddMarkFragments(numClipPoints, clipPoints, numPlanes, localNormals, localDists, maxPoints, pointBuffer, maxFragments, fragmentBuffer, &returnedPoints, &returnedFragments, mins, maxs, lastBmodel, localProjectionDir); if ( returnedFragments == maxFragments ) { return returnedFragments; // not enough space for more fragments } } } } } else if (*surfaces[i] == SF_FACE) { srfBspSurface_t *surf = ( srfBspSurface_t * ) surfaces[i]; // check the normal of this face if (DotProduct(surf->cullPlane.normal, localProjectionDir) > -0.5) { continue; } for(k = 0, tri = surf->indexes; k < surf->numIndexes; k += 3, tri += 3) { for(j = 0; j < 3; j++) { v = surf->verts[tri[j]].xyz; VectorMA(v, MARKER_OFFSET, surf->cullPlane.normal, clipPoints[0][j]); } // add the fragments of this face R_AddMarkFragments( 3 , clipPoints, numPlanes, localNormals, localDists, maxPoints, pointBuffer, maxFragments, fragmentBuffer, &returnedPoints, &returnedFragments, mins, maxs, lastBmodel, localProjectionDir); if ( returnedFragments == maxFragments ) { return returnedFragments; // not enough space for more fragments } } } else if(*surfaces[i] == SF_TRIANGLES && r_marksOnTriangleMeshes->integer) { srfBspSurface_t *surf = (srfBspSurface_t *) surfaces[i]; for(k = 0, tri = surf->indexes; k < surf->numIndexes; k += 3, tri += 3) { for(j = 0; j < 3; j++) { v = surf->verts[tri[j]].xyz; VectorMA(v, MARKER_OFFSET, surf->verts[tri[j]].normal, clipPoints[0][j]); } // add the fragments of this face R_AddMarkFragments(3, clipPoints, numPlanes, localNormals, localDists, maxPoints, pointBuffer, maxFragments, fragmentBuffer, &returnedPoints, &returnedFragments, mins, maxs, lastBmodel, localProjectionDir); if(returnedFragments == maxFragments) { return returnedFragments; // not enough space for more fragments } } } } return returnedFragments; }
void CCam::mortarCam(camInfo_t *ci) { if (eth32.cg.snap->ps.ammo == 0) return; // Set mortar trajectory from current view vec3_t angles, forward; VectorCopy(eth32.cg.refdef->viewaxis[ROLL], forward); VectorCopy(eth32.cg.snap->ps.viewangles, angles); angles[PITCH] -= 60.f; AngleVectors(angles, forward, NULL, NULL); forward[0] *= 3000 * 1.1f; forward[1] *= 3000 * 1.1f; forward[2] *= 1500 * 1.1f; trajectory_t mortarTrajectory; mortarTrajectory.trType = TR_GRAVITY; mortarTrajectory.trTime = eth32.cg.time; VectorCopy(eth32.cg.muzzle, mortarTrajectory.trBase); VectorCopy(forward, mortarTrajectory.trDelta); // Calculate mortar impact int timeOffset = 0; trace_t mortarTrace; vec3_t mortarImpact; VectorCopy(mortarTrajectory.trBase, mortarImpact); #define TIME_STEP 20 while (timeOffset < 10000) { vec3_t nextPos; timeOffset += TIME_STEP; BG_EvaluateTrajectory(&mortarTrajectory, eth32.cg.time + timeOffset, nextPos, qfalse, 0); orig_CG_Trace(&mortarTrace, mortarImpact, 0, 0, nextPos, eth32.cg.snap->ps.clientNum, MASK_MISSILESHOT); if ((mortarTrace.fraction != 1) // Stop if we hit sky && !((mortarTrace.surfaceFlags & SURF_NODRAW) || (mortarTrace.surfaceFlags & SURF_NOIMPACT)) && (mortarTrace.contents != 0)) { break; } VectorCopy(nextPos, mortarImpact); } memcpy(&camRefDef, ð32.cg.refdef, sizeof(refdef_t)); // kobject: add some angles vec3_t dpos; vec3_t camOrg; dpos[0] = eth32.cg.refdef->vieworg[0]-mortarImpact[0]; dpos[1] = eth32.cg.refdef->vieworg[1]-mortarImpact[1]; dpos[2] = 0.0f; VectorNormalizeFast( dpos ); VectorCopy( mortarImpact, camOrg ); VectorMA( camOrg, ci->distance * sinf(ci->angle * M_PI/180.0), zAxis, camOrg ); VectorMA( camOrg, ci->distance * cosf(ci->angle * M_PI/180.0), dpos, camOrg ); int w = ci->x2 - ci->x1; int h = ci->y2 - ci->y1; camRefDef.fov_x = (w>h) ? ci->fov : ci->fov * w / h; camRefDef.fov_y = (h>w) ? ci->fov : ci->fov * h / w; VectorCopy(camOrg, camRefDef.vieworg); vec3_t camAngle; VectorCopy(eth32.cg.refdefViewAngles, camAngle); camAngle[PITCH] = ci->angle; AnglesToAxis(camAngle, camRefDef.viewaxis); drawCam(ci->x1, ci->y1, w, h, &camRefDef, qtrue); // Draw impact time sprintf(this->str, "^7Impact Time: ^b%.1f ^7seconds", (float)timeOffset / 1000.0f); Draw.Text(ci->x1 + (w / 2) - (TEXTWIDTH(this->str) / 2), ci->y1 + h - 22 , 0.24f, str, GUI_FONTCOLOR1, qfalse, qtrue, ð32.cg.media.fontArial, true); }
void R_DrawSprite (void) { int i; msprite_t *psprite; vec3_t tvec; float dot, angle, sr, cr; VectorCopy (currententity->origin, r_entorigin); VectorSubtract (r_origin, r_entorigin, modelorg); psprite = currententity->model->extradata; r_spritedesc.pspriteframe = R_GetSpriteframe (psprite); sprite_width = r_spritedesc.pspriteframe->width; sprite_height = r_spritedesc.pspriteframe->height; // TODO: make this caller-selectable if (psprite->type == SPR_FACING_UPRIGHT) { // generate the sprite's axes, with vup straight up in worldspace, and // r_spritedesc.vright perpendicular to modelorg. // This will not work if the view direction is very close to straight up or // down, because the cross product will be between two nearly parallel // vectors and starts to approach an undefined state, so we don't draw if // the two vectors are less than 1 degree apart VectorNegate(modelorg, tvec); VectorNormalizeFast (tvec); dot = tvec[2]; // same as DotProduct (tvec, r_spritedesc.vup) because r_spritedesc.vup is 0, 0, 1 if (dot > 0.999848 || dot < -0.999848) // cos(1 degree) = 0.999848 return; VectorSet(r_spritedesc.vup, 0, 0, 1); // CrossProduct(r_spritedesc.vup, -modelorg, r_spritedesc.vright) VectorSet(r_spritedesc.vright, tvec[1], -tvec[0], 0); VectorNormalizeFast (r_spritedesc.vright); // CrossProduct (r_spritedesc.vright, r_spritedesc.vup, r_spritedesc.vpn) VectorSet(r_spritedesc.vpn, -r_spritedesc.vright[1], r_spritedesc.vright[0], 0); } else if (psprite->type == SPR_VP_PARALLEL) { // generate the sprite's axes, completely parallel to the viewplane. There // are no problem situations, because the sprite is always in the same // position relative to the viewer for (i = 0; i < 3; i++) { r_spritedesc.vup[i] = vup[i]; r_spritedesc.vright[i] = vright[i]; r_spritedesc.vpn[i] = vpn[i]; } } else if (psprite->type == SPR_VP_PARALLEL_UPRIGHT) { // generate the sprite's axes, with vup straight up in worldspace, and // r_spritedesc.vright parallel to the viewplane. // This will not work if the view direction is very close to straight up or // down, because the cross product will be between two nearly parallel // vectors and starts to approach an undefined state, so we don't draw if // the two vectors are less than 1 degree apart dot = vpn[2]; // same as DotProduct (vpn, r_spritedesc.vup) because r_spritedesc.vup is 0, 0, 1 if ((dot > 0.999848) || (dot < -0.999848)) // cos(1 degree) = 0.999848 return; VectorSet(r_spritedesc.vup, 0, 0, 1); //CrossProduct (r_spritedesc.vup, vpn, r_spritedesc.vright); VectorSet(r_spritedesc.vright, vpn[1], -vpn[0], 0); VectorNormalizeFast (r_spritedesc.vright); // CrossProduct (r_spritedesc.vright, r_spritedesc.vup, r_spritedesc.vpn) VectorSet(r_spritedesc.vpn, -r_spritedesc.vright[1], r_spritedesc.vright[0], 0); } else if (psprite->type == SPR_ORIENTED) { // generate the sprite's axes, according to the sprite's world orientation AngleVectors (currententity->angles, r_spritedesc.vpn, r_spritedesc.vright, r_spritedesc.vup); } else if (psprite->type == SPR_VP_PARALLEL_ORIENTED) { // generate the sprite's axes, parallel to the viewplane, but rotated in // that plane around the center according to the sprite entity's roll // angle. So vpn stays the same, but vright and vup rotate angle = DEG2RAD(currententity->angles[ROLL]); sr = sin(angle); cr = cos(angle); for (i = 0; i < 3; i++) { r_spritedesc.vpn[i] = vpn[i]; r_spritedesc.vright[i] = vright[i] * cr + vup[i] * sr; r_spritedesc.vup[i] = vright[i] * -sr + vup[i] * cr; } } else { Sys_Error ("R_DrawSprite: Bad sprite type %d", psprite->type); } R_RotateSprite (psprite->beamlength); R_SetupAndDrawSprite (); }
// TODO: Clean this mess further (split into helper functions) void G_Damage( gentity_t *target, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int damageFlags, int mod ) { gclient_t *client; int take, loss; int knockback; float modifier; if ( !target || !target->takedamage || target->health <= 0 || level.intermissionQueued ) { return; } client = target->client; // don't handle noclip clients if ( client && client->noclip ) { return; } // set inflictor to world if missing if ( !inflictor ) { inflictor = &g_entities[ ENTITYNUM_WORLD ]; } // set attacker to world if missing if ( !attacker ) { attacker = &g_entities[ ENTITYNUM_WORLD ]; } // don't handle ET_MOVER w/o die or pain function if ( target->s.eType == ET_MOVER && !( target->die || target->pain ) ) { // special case for ET_MOVER with act function in initial position if ( ( target->moverState == MOVER_POS1 || target->moverState == ROTATOR_POS1 ) && target->act ) { target->act( target, inflictor, attacker ); } return; } // do knockback against clients if ( client && !( damageFlags & DAMAGE_NO_KNOCKBACK ) && dir ) { // scale knockback by weapon if ( inflictor->s.weapon != WP_NONE ) { knockback = ( int )( ( float )damage * BG_Weapon( inflictor->s.weapon )->knockbackScale ); } else { knockback = damage; } // apply generic damage to knockback modifier knockback *= DAMAGE_TO_KNOCKBACK; // HACK: Too much knockback from falling makes you bounce and looks silly if ( mod == MOD_FALLING ) { knockback = MIN( knockback, MAX_FALLDMG_KNOCKBACK ); } G_KnockbackByDir( target, dir, knockback, qfalse ); } else { // damage knockback gets saved, so initialize it here knockback = 0; } // godmode prevents damage if ( target->flags & FL_GODMODE ) { return; } // check for protection if ( !( damageFlags & DAMAGE_NO_PROTECTION ) ) { // check for protection from friendly damage if ( target != attacker && G_OnSameTeam( target, attacker ) ) { // check if friendly fire has been disabled if ( !g_friendlyFire.integer ) { return; } // don't do friendly damage on movement attacks switch ( mod ) { case MOD_LEVEL3_POUNCE: case MOD_LEVEL4_TRAMPLE: return; default: break; } // if dretchpunt is enabled and this is a dretch, do dretchpunt instead of damage if ( g_dretchPunt.integer && target->client && ( target->client->ps.stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL0 || target->client->ps.stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL0_UPG ) ) { vec3_t dir, push; VectorSubtract( target->r.currentOrigin, attacker->r.currentOrigin, dir ); VectorNormalizeFast( dir ); VectorScale( dir, ( damage * 10.0f ), push ); push[ 2 ] = 64.0f; VectorAdd( target->client->ps.velocity, push, target->client->ps.velocity ); return; } } // for buildables, never protect from damage dealt by building actions if ( target->s.eType == ET_BUILDABLE && attacker->client && mod != MOD_DECONSTRUCT && mod != MOD_SUICIDE && mod != MOD_REPLACE && mod != MOD_NOCREEP ) { // check for protection from friendly buildable damage if ( G_OnSameTeam( target, attacker ) && !g_friendlyBuildableFire.integer ) { return; } } } // update combat timers if ( target->client && attacker->client && target != attacker ) { target->client->lastCombatTime = level.time; attacker->client->lastCombatTime = level.time; } if ( client ) { // save damage (w/o armor modifier), knockback client->damage_received += damage; client->damage_knockback += knockback; // save damage direction if ( dir ) { VectorCopy( dir, client->damage_from ); client->damage_fromWorld = qfalse; } else { VectorCopy( target->r.currentOrigin, client->damage_from ); client->damage_fromWorld = qtrue; } // drain jetpack fuel client->ps.stats[ STAT_FUEL ] -= damage * JETPACK_FUEL_PER_DMG; if ( client->ps.stats[ STAT_FUEL ] < 0 ) { client->ps.stats[ STAT_FUEL ] = 0; } // apply damage modifier modifier = CalcDamageModifier( point, target, (class_t) client->ps.stats[ STAT_CLASS ], damageFlags ); take = ( int )( ( float )damage * modifier + 0.5f ); // if boosted poison every attack if ( attacker->client && ( attacker->client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) && target->client->pers.team == TEAM_HUMANS && target->client->poisonImmunityTime < level.time ) { switch ( mod ) { case MOD_POISON: case MOD_LEVEL1_PCLOUD: case MOD_LEVEL2_ZAP: break; default: target->client->ps.stats[ STAT_STATE ] |= SS_POISONED; target->client->lastPoisonTime = level.time; target->client->lastPoisonClient = attacker; } } } else { take = damage; } // make sure damage is done if ( take < 1 ) { take = 1; } if ( g_debugDamage.integer > 0 ) { G_Printf( "G_Damage: %3i (%3i → %3i)\n", take, target->health, target->health - take ); } // do the damage target->health = target->health - take; if ( target->client ) { target->client->ps.stats[ STAT_HEALTH ] = target->health; target->client->pers.infoChangeTime = level.time; // ? } target->lastDamageTime = level.time; // TODO: gentity_t->nextRegenTime only affects alien clients, remove it and use lastDamageTime // Optionally (if needed for some reason), move into client struct and add "Alien" to name target->nextRegenTime = level.time + ALIEN_CLIENT_REGEN_WAIT; // handle non-self damage if ( attacker != target ) { if ( target->health < 0 ) { loss = ( take + target->health ); } else { loss = take; } if ( attacker->client ) { // add to the attacker's account on the target target->credits[ attacker->client->ps.clientNum ] += ( float )loss; // notify the attacker of a hit NotifyClientOfHit( attacker ); } // update buildable stats if ( attacker->s.eType == ET_BUILDABLE && attacker->health > 0 ) { attacker->buildableStatsTotal += loss; } } // handle dying target if ( target->health <= 0 ) { // set no knockback flag for clients if ( client ) { target->flags |= FL_NO_KNOCKBACK; } // cap negative health if ( target->health < -999 ) { target->health = -999; } // call die function if ( target->die ) { target->die( target, inflictor, attacker, mod ); } // update buildable stats if ( attacker->s.eType == ET_BUILDABLE && attacker->health > 0 ) { attacker->buildableStatsCount++; } // for non-client victims, fire ON_DIE event if( !target->client ) { G_EventFireEntity( target, attacker, ON_DIE ); } return; } else if ( target->pain ) { target->pain( target, attacker, take ); } }
/** * @brief Calculates normals and tangents for all frames and does vertex merging based on smoothness * @param mesh The mesh to calculate normals for * @param nFrames How many frames the mesh has * @param smoothness How aggressively should normals be smoothed; value is compared with dotproduct of vectors to decide if they should be merged * @sa R_ModCalcNormalsAndTangents */ void R_ModCalcUniqueNormalsAndTangents (mAliasMesh_t *mesh, int nFrames, float smoothness) { int i, j; vec3_t triangleNormals[MAX_ALIAS_TRIS]; vec3_t triangleTangents[MAX_ALIAS_TRIS]; vec3_t triangleBitangents[MAX_ALIAS_TRIS]; const mAliasVertex_t *vertexes = mesh->vertexes; mAliasCoord_t *stcoords = mesh->stcoords; mAliasVertex_t *newVertexes; mAliasComplexVertex_t tmpVertexes[MAX_ALIAS_VERTS]; vec3_t tmpBitangents[MAX_ALIAS_VERTS]; mAliasCoord_t *newStcoords; const int numIndexes = mesh->num_tris * 3; const int32_t *indexArray = mesh->indexes; int32_t *newIndexArray; int indRemap[MAX_ALIAS_VERTS]; int sharedTris[MAX_ALIAS_VERTS]; int numVerts = 0; newIndexArray = (int32_t *)Mem_PoolAlloc(sizeof(int32_t) * numIndexes, vid_modelPool, 0); /* calculate per-triangle surface normals */ for (i = 0, j = 0; i < numIndexes; i += 3, j++) { vec3_t dir1, dir2; vec2_t dir1uv, dir2uv; /* calculate two mostly perpendicular edge directions */ VectorSubtract(vertexes[indexArray[i + 0]].point, vertexes[indexArray[i + 1]].point, dir1); VectorSubtract(vertexes[indexArray[i + 2]].point, vertexes[indexArray[i + 1]].point, dir2); Vector2Subtract(stcoords[indexArray[i + 0]], stcoords[indexArray[i + 1]], dir1uv); Vector2Subtract(stcoords[indexArray[i + 2]], stcoords[indexArray[i + 1]], dir2uv); /* we have two edge directions, we can calculate a third vector from * them, which is the direction of the surface normal */ CrossProduct(dir1, dir2, triangleNormals[j]); /* then we use the texture coordinates to calculate a tangent space */ if ((dir1uv[1] * dir2uv[0] - dir1uv[0] * dir2uv[1]) != 0.0) { const float frac = 1.0 / (dir1uv[1] * dir2uv[0] - dir1uv[0] * dir2uv[1]); vec3_t tmp1, tmp2; /* calculate tangent */ VectorMul(-1.0 * dir2uv[1] * frac, dir1, tmp1); VectorMul(dir1uv[1] * frac, dir2, tmp2); VectorAdd(tmp1, tmp2, triangleTangents[j]); /* calculate bitangent */ VectorMul(-1.0 * dir2uv[0] * frac, dir1, tmp1); VectorMul(dir1uv[0] * frac, dir2, tmp2); VectorAdd(tmp1, tmp2, triangleBitangents[j]); } else { const float frac = 1.0 / (0.00001); vec3_t tmp1, tmp2; /* calculate tangent */ VectorMul(-1.0 * dir2uv[1] * frac, dir1, tmp1); VectorMul(dir1uv[1] * frac, dir2, tmp2); VectorAdd(tmp1, tmp2, triangleTangents[j]); /* calculate bitangent */ VectorMul(-1.0 * dir2uv[0] * frac, dir1, tmp1); VectorMul(dir1uv[0] * frac, dir2, tmp2); VectorAdd(tmp1, tmp2, triangleBitangents[j]); } /* normalize */ VectorNormalizeFast(triangleNormals[j]); VectorNormalizeFast(triangleTangents[j]); VectorNormalizeFast(triangleBitangents[j]); Orthogonalize(triangleTangents[j], triangleBitangents[j]); } /* do smoothing */ for (i = 0; i < numIndexes; i++) { const int idx = (i - i % 3) / 3; VectorCopy(triangleNormals[idx], tmpVertexes[i].normal); VectorCopy(triangleTangents[idx], tmpVertexes[i].tangent); VectorCopy(triangleBitangents[idx], tmpBitangents[i]); for (j = 0; j < numIndexes; j++) { const int idx2 = (j - j % 3) / 3; /* don't add a vertex with itself */ if (j == i) continue; /* only average normals if vertices have the same position * and the normals aren't too far apart to start with */ if (VectorEqual(vertexes[indexArray[i]].point, vertexes[indexArray[j]].point) && DotProduct(triangleNormals[idx], triangleNormals[idx2]) > smoothness) { /* average the normals */ VectorAdd(tmpVertexes[i].normal, triangleNormals[idx2], tmpVertexes[i].normal); /* if the tangents match as well, average them too. * Note that having matching normals without matching tangents happens * when the order of vertices in two triangles sharing the vertex * in question is different. This happens quite frequently if the * modeler does not go out of their way to avoid it. */ if (Vector2Equal(stcoords[indexArray[i]], stcoords[indexArray[j]]) && DotProduct(triangleTangents[idx], triangleTangents[idx2]) > smoothness && DotProduct(triangleBitangents[idx], triangleBitangents[idx2]) > smoothness) { /* average the tangents */ VectorAdd(tmpVertexes[i].tangent, triangleTangents[idx2], tmpVertexes[i].tangent); VectorAdd(tmpBitangents[i], triangleBitangents[idx2], tmpBitangents[i]); } } } VectorNormalizeFast(tmpVertexes[i].normal); VectorNormalizeFast(tmpVertexes[i].tangent); VectorNormalizeFast(tmpBitangents[i]); } /* assume all vertices are unique until proven otherwise */ for (i = 0; i < numIndexes; i++) indRemap[i] = -1; /* merge vertices that have become identical */ for (i = 0; i < numIndexes; i++) { vec3_t n, b, t, v; if (indRemap[i] != -1) continue; for (j = i + 1; j < numIndexes; j++) { if (Vector2Equal(stcoords[indexArray[i]], stcoords[indexArray[j]]) && VectorEqual(vertexes[indexArray[i]].point, vertexes[indexArray[j]].point) && (DotProduct(tmpVertexes[i].normal, tmpVertexes[j].normal) > smoothness) && (DotProduct(tmpVertexes[i].tangent, tmpVertexes[j].tangent) > smoothness)) { indRemap[j] = i; newIndexArray[j] = numVerts; } } VectorCopy(tmpVertexes[i].normal, n); VectorCopy(tmpVertexes[i].tangent, t); VectorCopy(tmpBitangents[i], b); /* normalization here does shared-vertex smoothing */ VectorNormalizeFast(n); VectorNormalizeFast(t); VectorNormalizeFast(b); /* Grahm-Schmidt orthogonalization */ VectorMul(DotProduct(t, n), n, v); VectorSubtract(t, v, t); VectorNormalizeFast(t); /* calculate handedness */ CrossProduct(n, t, v); tmpVertexes[i].tangent[3] = (DotProduct(v, b) < 0.0) ? -1.0 : 1.0; VectorCopy(n, tmpVertexes[i].normal); VectorCopy(t, tmpVertexes[i].tangent); newIndexArray[i] = numVerts++; indRemap[i] = i; } for (i = 0; i < numVerts; i++) sharedTris[i] = 0; for (i = 0; i < numIndexes; i++) sharedTris[newIndexArray[i]]++; /* set up reverse-index that maps Vertex objects to a list of triangle verts */ mesh->revIndexes = (mIndexList_t *)Mem_PoolAlloc(sizeof(mIndexList_t) * numVerts, vid_modelPool, 0); for (i = 0; i < numVerts; i++) { mesh->revIndexes[i].length = 0; mesh->revIndexes[i].list = (int32_t *)Mem_PoolAlloc(sizeof(int32_t) * sharedTris[i], vid_modelPool, 0); } /* merge identical vertexes, storing only unique ones */ newVertexes = (mAliasVertex_t *)Mem_PoolAlloc(sizeof(mAliasVertex_t) * numVerts * nFrames, vid_modelPool, 0); newStcoords = (mAliasCoord_t *)Mem_PoolAlloc(sizeof(mAliasCoord_t) * numVerts, vid_modelPool, 0); for (i = 0; i < numIndexes; i++) { const int idx = indexArray[indRemap[i]]; const int idx2 = newIndexArray[i]; /* add vertex to new vertex array */ VectorCopy(vertexes[idx].point, newVertexes[idx2].point); Vector2Copy(stcoords[idx], newStcoords[idx2]); mesh->revIndexes[idx2].list[mesh->revIndexes[idx2].length++] = i; } /* copy over the points from successive frames */ for (i = 1; i < nFrames; i++) { for (j = 0; j < numIndexes; j++) { const int idx = indexArray[indRemap[j]] + (mesh->num_verts * i); const int idx2 = newIndexArray[j] + (numVerts * i); VectorCopy(vertexes[idx].point, newVertexes[idx2].point); } } /* copy new arrays back into original mesh */ Mem_Free(mesh->stcoords); Mem_Free(mesh->indexes); Mem_Free(mesh->vertexes); mesh->num_verts = numVerts; mesh->vertexes = newVertexes; mesh->stcoords = newStcoords; mesh->indexes = newIndexArray; }