q3rally/engine/code/game/bg_physics.c
2023-12-10 05:44:50 +05:00

2392 lines
74 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2002-2021 Q3Rally Team (Per Thormann - q3rally@gmail.com)
This file is part of q3rally source code.
q3rally source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
q3rally source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with q3rally; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "../qcommon/q_shared.h"
#ifdef GAME
#include "g_local.h"
#else
#include "bg_public.h"
//#include "../cgame/cg_local.h"
#endif
#include "bg_local.h"
float CP_CURRENT_GRAVITY;
// not actually used now, use cvars instead
float CP_SPRING_STRENGTH = 110 * (CP_FRAME_MASS / 350.0f) * CP_GRAVITY; // 110
float CP_SHOCK_STRENGTH = 13 * CP_GRAVITY; // 12
float CP_SWAYBAR_STRENGTH = 21 * CP_GRAVITY; // 20 * grav
float CP_M_2_QU = CP_FT_2_QU / CP_FT_2_M; // 35.66
float CP_WR_STRENGTH = 2400.0f * CP_WHEEL_MASS;
float CP_WR_DAMP_STRENGTH = 140.0f * CP_WHEEL_MASS;
static int numTraces;
/*
================================================================================
PM_DebugDynamics
================================================================================
*/
void PM_DebugDynamics( carBody_t *body, carPoint_t *points ){
Com_Printf("\n");
Com_Printf("PM_DebugDynamics: point ORIGIN - %.3f, %.3f, %.3f\n", points[pm->pDebug-1].r[0], points[pm->pDebug-1].r[1], points[pm->pDebug-1].r[2]);
Com_Printf("PM_DebugDynamics: point VELOCITY - %.3f, %.3f, %.3f\n", points[pm->pDebug-1].v[0], points[pm->pDebug-1].v[1], points[pm->pDebug-1].v[2]);
Com_Printf("PM_DebugDynamics: point ONGROUND - %i\n", points[pm->pDebug-1].onGround);
Com_Printf("PM_DebugDynamics: point NORMAL 1- %.3f, %.3f, %.3f\n", points[pm->pDebug-1].normals[0][0], points[pm->pDebug-1].normals[0][1], points[pm->pDebug-1].normals[0][2]);
Com_Printf("PM_DebugDynamics: point NORMAL 2- %.3f, %.3f, %.3f\n", points[pm->pDebug-1].normals[1][0], points[pm->pDebug-1].normals[1][1], points[pm->pDebug-1].normals[1][2]);
Com_Printf("PM_DebugDynamics: point NORMAL 3- %.3f, %.3f, %.3f\n", points[pm->pDebug-1].normals[2][0], points[pm->pDebug-1].normals[2][1], points[pm->pDebug-1].normals[2][2]);
// Com_Printf("PM_DebugDynamics: point MASS - %.3f\n", points[pm->pDebug-1].mass);
Com_Printf("PM_DebugDynamics: body ORIGIN - %.3f, %.3f, %.3f\n", body->r[0], body->r[1], body->r[2]);
Com_Printf("PM_DebugDynamics: body VELOCITY - %.3f, %.3f, %.3f\n", body->v[0], body->v[1], body->v[2]);
Com_Printf("PM_DebugDynamics: body ANG VELOCITY - %.3f, %.3f, %.3f\n", body->w[0], body->w[1], body->w[2]);
// Com_Printf("PM_DebugDynamics: body COM - %.3f, %.3f, %.3f\n", body->CoM[0], body->CoM[1], body->CoM[2]);
// Com_Printf("PM_DebugDynamics: body MASS - %.3f\n", body->mass);
Com_Printf("Direction vectors ----------------------------------------\n");
Com_Printf("PM_DebugDynamics: body FORWARD - %.3f, %.3f, %.3f\n", body->forward[0], body->forward[1], body->forward[2]);
Com_Printf("PM_DebugDynamics: body RIGHT - %.3f, %.3f, %.3f\n", body->right[0], body->right[1], body->right[2]);
Com_Printf("PM_DebugDynamics: body UP - %.3f, %.3f, %.3f\n", body->up[0], body->up[1], body->up[2]);
Com_Printf("\n");
}
/*
================================================================================
PM_DebugForces
================================================================================
*/
void PM_DebugForces( carBody_t *body, carPoint_t *points ){
Com_Printf("\n");
Com_Printf("PM_DebugForces: point GRAVITY - %.3f, %.3f, %.3f\n", points[pm->pDebug-1].forces[GRAVITY][0], points[pm->pDebug-1].forces[GRAVITY][1], points[pm->pDebug-1].forces[GRAVITY][2]);
Com_Printf("PM_DebugForces: point NORMAL - %.3f, %.3f, %.3f\n", points[pm->pDebug-1].forces[NORMAL][0], points[pm->pDebug-1].forces[NORMAL][1], points[pm->pDebug-1].forces[NORMAL][2]);
Com_Printf("PM_DebugForces: point SHOCK - %.3f, %.3f, %.3f\n", points[pm->pDebug-1].forces[SHOCK][0], points[pm->pDebug-1].forces[SHOCK][1], points[pm->pDebug-1].forces[SHOCK][2]);
Com_Printf("PM_DebugForces: point SPRING - %.3f, %.3f, %.3f\n", points[pm->pDebug-1].forces[SPRING][0], points[pm->pDebug-1].forces[SPRING][1], points[pm->pDebug-1].forces[SPRING][2]);
Com_Printf("PM_DebugForces: point ROAD - %.3f, %.3f, %.3f\n", points[pm->pDebug-1].forces[ROAD][0], points[pm->pDebug-1].forces[ROAD][1], points[pm->pDebug-1].forces[ROAD][2]);
Com_Printf("PM_DebugForces: point INTERNAL - %.3f, %.3f, %.3f\n", points[pm->pDebug-1].forces[INTERNAL][0], points[pm->pDebug-1].forces[INTERNAL][1], points[pm->pDebug-1].forces[INTERNAL][2]);
Com_Printf("PM_DebugForces: point AIR_FRICTION - %.3f, %.3f, %.3f\n", points[pm->pDebug-1].forces[AIR_FRICTION][0], points[pm->pDebug-1].forces[AIR_FRICTION][1], points[pm->pDebug-1].forces[AIR_FRICTION][2]);
Com_Printf("PM_DebugForces: point NETFORCE - %.3f, %.3f, %.3f\n", points[pm->pDebug-1].netForce[0], points[pm->pDebug-1].netForce[1], points[pm->pDebug-1].netForce[2]);
Com_Printf("PM_DebugForces: body netForce - %.3f, %.3f, %.3f\n", body->netForce[0], body->netForce[1], body->netForce[2]);
Com_Printf("PM_DebugForces: body netMoment - %.3f, %.3f, %.3f\n", body->netMoment[0], body->netMoment[1], body->netMoment[2]);
Com_Printf("\n");
}
/*
================================================================================
PM_CopyTargetToSource
================================================================================
*/
static void PM_CopyTargetToSource( carBody_t *tBody, carBody_t *sBody, carPoint_t *tPoints, carPoint_t *sPoints){
memcpy(sBody, tBody, sizeof(carBody_t));
memcpy(sPoints, tPoints, NUM_CAR_POINTS * sizeof(carPoint_t));
}
/*
===================
PM_SetCoM
===================
*/
void PM_SetCoM( carBody_t *body, carPoint_t *points ){
vec3_t temp, origin;
//vec3_t delta;
float totalMass, length;
int i;
VectorClear(temp);
totalMass = CP_CAR_MASS;
for (i = FIRST_FRAME_POINT; i < LAST_FRAME_POINT; i++){
VectorMA(temp, points[i].mass, points[i].r, temp);
// totalMass += points[i].mass;
}
for (i = 0; i < FIRST_FRAME_POINT; i++){
// VectorSubtract( points[i+4].r, points[i].r, delta );
// length = DotProduct( body->up, delta );
length = body->curSpringLengths[i];
if ( length < CP_SPRING_MINLEN )
{
length = CP_SPRING_MINLEN;
VectorMA( points[i+4].r, length, body->up, origin );
VectorMA( temp, points[i].mass, origin, temp );
}
else if ( length > CP_SPRING_MAXLEN )
{
length = CP_SPRING_MAXLEN;
VectorMA( points[i+4].r, length, body->up, origin );
VectorMA( temp, points[i].mass, origin, temp );
}
else
VectorMA( temp, points[i].mass, points[i].r, temp );
// totalMass += points[i].mass;
}
VectorScale( temp, 1.0f / totalMass, body->CoM );
}
/*
===========================================================================================
POINT FUNCTIONS
===========================================================================================
*/
/*
================================================================================
PM_ClearCarForces
================================================================================
*/
static void PM_ClearCarForces( carBody_t *body, carPoint_t *points){
int i, j;
VectorClear( body->netForce );
VectorClear( body->netMoment);
for ( i = 0; i < NUM_CAR_POINTS; i++ ){
VectorClear( points[i].netForce );
points[i].netMoment = 0;
for ( j = 0; j < NUM_CAR_FORCES; j++ ){
VectorClear( points[i].forces[j] );
}
}
}
/*
===================
PM_CalculateNetForce
===================
*/
void PM_CalculateNetForce( carPoint_t *point, int pointIndex ){
int i;
float dot;
// vec3_t vecForce, dV;
VectorClear( point->netForce );
for (i = 0; i < NUM_CAR_FORCES; i++){
if (i == NORMAL) continue;
/*
if (VectorLength(point->forces[i]) >= 1<<23){
Com_Printf("PM_CalculateNetForce: Car point force %d is greater than %d\n", i, 1<<23);
Com_Printf("PM_CalculateNetForce: Car point force is %f\n", VectorLength(point->forces[i]));
VectorNormalize(point->forces[i]);
VectorScale(point->forces[i], 1<<23, point->forces[i]);
}
*/
if ( VectorNAN( point->forces[i] ) )
{
Com_Printf( "Force %i on point %i was NAN\n", i, pointIndex );
continue;
}
VectorAdd( point->netForce, point->forces[i], point->netForce );
}
VectorClear( point->forces[NORMAL] );
// VectorClear( vecForce );
// calculate normal force
if ( point->onGround )
{
for ( i = 0; i < 3; i++ )
{
if ( VectorLengthSquared( point->normals[i] ) < 10e-2 ) continue;
// calculate new velocity of wheel after collision
dot = DotProduct( point->normals[i], point->v );
if ( dot < -10.0f )
{
// VectorScale(point->normals[i], -(1 + point->elasticity) * dot, dV);
// VectorAdd(point->v, dV, point->v);
VectorMA( point->v, -(1.0f + point->elasticity) * dot, point->normals[i], point->v );
}
else if ( dot < 1.0f )
{
// going slow enough so just stop it so it doesnt vibrate
// VectorScale(point->normals[i], -dot, dV);
// VectorAdd(point->v, dV, point->v);
VectorMA( point->v, -dot, point->normals[i], point->v );
}
// add normal force to balance the other forces
dot = OVERCLIP * DotProduct( point->normals[i], point->netForce );
if (dot < 0)
{
// VectorScale(point->normals[i], -dot, vecForce);
// VectorAdd(point->forces[NORMAL], vecForce, point->forces[NORMAL]);
VectorMA( point->forces[NORMAL], -dot, point->normals[i], point->forces[NORMAL] );
}
}
}
/*
if (VectorLength(point->forces[NORMAL]) >= 1<<23) {
Com_Printf("PM_CalculateNetForce: Car point normal force is greater than %d\n", 1<<23);
VectorNormalize(point->forces[NORMAL]);
VectorScale(point->forces[NORMAL], 1<<23, point->forces[NORMAL]);
}
*/
VectorAdd( point->netForce, point->forces[NORMAL], point->netForce );
}
/*
===================
PM_AccelerateAndMove
===================
*/
static void PM_AccelerateAndMove( car_t *car, carPoint_t *source, carPoint_t *target, float time )
{
float alpha;
vec3_t vec, avgAccel;
// vec3_t avgVel;
// kill car if forces too high
if ( VectorNAN( source->netForce ) )
{
Com_Printf( "Blowing up car because of car point force\n" );
pm->damage.damage = 32000;
pm->damage.dflags = DAMAGE_NO_PROTECTION;
pm->damage.otherEnt = -1;
pm->damage.mod = MOD_HIGH_FORCES;
return;
}
// using const acceleration
// NOTE: this method is really not right but it works so oh well.
// target has last netForce still in it
VectorScale( source->netForce, 0.4f / source->mass, avgAccel );
VectorMA( source->v, time, avgAccel, target->v );
VectorAdd( source->v, target->v, vec );
VectorMA( source->r, time / 2, vec, target->r );
// UPDATE: use a moment of inertia different than .5*mr^2?
alpha = (source->netMoment + target->netMoment) / (0.75f * source->mass * WHEEL_RADIUS * WHEEL_RADIUS);
target->w = source->w + (time / 6.0f) * alpha;
if (source->w < 0 && target->w > 0)
{
target->w = 0;
target->netMoment = 0;
}
if (source->w > 0 && target->w < 0)
{
target->w = 0;
target->netMoment = 0;
}
// wheels are trying to turn in the wrong direction..
// FIXME: use force in opposite diretion instead of just
// stopping the wheels from turning.
// Play transmission grinding sound if the force is too big
// PHYSICS TEMP
if ( car->gear > 0 && target->w > -1.5f )
{
// Com_Printf("You're screwing up your transmission %f\n", target->w);
target->w = 0;
}
else if ( car->gear < 0 && target->w < 1.5f )
{
// Com_Printf("You're screwing up your transmission %f\n", target->w);
target->w = 0;
}
target->slipping = source->slipping;
// copy netForce and netMoment to target so we can use it during the AccelerateandMove next frame
VectorCopy( source->netForce, target->netForce );
target->netMoment = source->netMoment;
}
/*
===========================================================================================
INITIALIZATION FUNCTIONS
===========================================================================================
*/
/*
===================
PM_UpdateFrameVelocities
===================
*/
static void PM_UpdateFrameVelocities( carBody_t *body, carPoint_t *points )
{
vec3_t arm;
vec3_t cross;
int i;
for ( i = FIRST_FRAME_POINT; i < NUM_CAR_POINTS; i++ )
{
VectorSubtract( points[i].r, body->CoM, arm );
CrossProduct( body->w, arm, cross );
VectorAdd( body->v, cross, points[i].v );
}
}
/*
===================
PM_InitializeFrame
===================
*/
static void PM_InitializeFrame( carBody_t *body, carPoint_t *point, float f, float r, float u )
{
vec3_t arm;
vec3_t cross;
VectorScale( body->forward, f * WHEEL_FORWARD, arm );
VectorMA( arm, r * WHEEL_RIGHT, body->right, arm );
VectorMA( arm, -u * WHEEL_UP, body->up, arm );
VectorAdd( body->r, arm, point->r );
CrossProduct( body->w, arm, cross );
VectorAdd( body->v, cross, point->v );
VectorClear( point->normals[0] );
VectorClear( point->normals[1] );
VectorClear( point->normals[2] );
point->onGround = qfalse;
}
/*
===================
PM_InitializeWheel
===================
*/
static void PM_InitializeWheel( carBody_t *body, carPoint_t *points, int i, int f, int r )
{
vec3_t arm;
vec3_t cross;
VectorScale( body->forward, f * WHEEL_FORWARD, arm );
VectorMA( arm, r * WHEEL_RIGHT, body->right, arm );
VectorMA( arm, WHEEL_UP, body->up, arm );
VectorAdd( body->r, arm, points[i].r );
CrossProduct( body->w, arm, cross );
VectorAdd( body->v, cross, points[i].v );
VectorClear( points[i].normals[0] );
VectorClear( points[i].normals[1] );
VectorClear( points[i].normals[2] );
// UPDATE: added for client prediction, should just leave normals instead?
points[i].onGround = qfalse;
// recalculate spring length
VectorSubtract( points[i+4].r, points[i].r, arm );
body->curSpringLengths[i] = DotProduct( arm, body->up );
}
/*
===================
PM_InitializeVehicle
===================
*/
void PM_InitializeVehicle( car_t *car, vec3_t origin, vec3_t angles, vec3_t velocity ){
float m[3][3];
int i;
float halfWidth, halfLength, halfHeight;
float forwardScale, rightScale, upScale;
// UPDATE: use memset?
VectorCopy(origin, car->sBody.r);
AnglesToOrientation(angles, car->sBody.t);
// AnglesToQuaternion(angles, car->sBody.q);
OrientationToVectors(car->sBody.t, car->sBody.forward, car->sBody.right, car->sBody.up);
VectorCopy(velocity, car->sBody.v);
VectorClear(car->sBody.w);
VectorClear(car->sBody.L);
// set locations and velocities of frame points
PM_InitializeFrame(&car->sBody, &car->sPoints[FL_FRAME], 1.0f, -1.0f, 0.0f);
PM_InitializeFrame(&car->sBody, &car->sPoints[FR_FRAME], 1.0f, 1.0f, 0.0f);
PM_InitializeFrame(&car->sBody, &car->sPoints[RL_FRAME], -1.0f, -1.0f, 0.0f);
PM_InitializeFrame(&car->sBody, &car->sPoints[RR_FRAME], -1.0f, 1.0f, 0.0f);
// body collision points
forwardScale = ((CAR_LENGTH/2.0f) - BODY_RADIUS) / WHEEL_FORWARD;
rightScale = ((CAR_WIDTH/2.0f) - BODY_RADIUS) / WHEEL_RIGHT;
upScale = (3.0f) / -WHEEL_UP;
PM_InitializeFrame(&car->sBody, &car->sPoints[FL_BODY], forwardScale, -rightScale, upScale);
PM_InitializeFrame(&car->sBody, &car->sPoints[FR_BODY], forwardScale, rightScale, upScale);
PM_InitializeFrame(&car->sBody, &car->sPoints[ML_BODY], 0.0f, -rightScale, upScale);
PM_InitializeFrame(&car->sBody, &car->sPoints[MR_BODY], 0.0f, rightScale, upScale);
PM_InitializeFrame(&car->sBody, &car->sPoints[RL_BODY], -forwardScale, -rightScale, upScale);
PM_InitializeFrame(&car->sBody, &car->sPoints[RR_BODY], -forwardScale, rightScale, upScale);
PM_InitializeFrame(&car->sBody, &car->sPoints[ML_ROOF], 0.0f, -rightScale, 0.6f);
PM_InitializeFrame(&car->sBody, &car->sPoints[MR_ROOF], 0.0f, rightScale, 0.6f);
// set locations and velocities of wheel points
PM_InitializeWheel(&car->sBody, car->sPoints, FL_WHEEL, 1.0f, -1.0f);
PM_InitializeWheel(&car->sBody, car->sPoints, FR_WHEEL, 1.0f, 1.0f);
PM_InitializeWheel(&car->sBody, car->sPoints, RL_WHEEL, -1.0f, -1.0f);
PM_InitializeWheel(&car->sBody, car->sPoints, RR_WHEEL, -1.0f, 1.0f);
car->sBody.mass = 4 * CP_FRAME_MASS;
car->tBody.mass = 4 * CP_FRAME_MASS;
for (i = 0; i < FIRST_FRAME_POINT; i++){
car->sPoints[i].mass = CP_WHEEL_MASS;
car->sPoints[i].elasticity = CP_WHEEL_ELASTICITY;
car->sPoints[i].scof = CP_SCOF;
car->sPoints[i].kcof = CP_KCOF;
car->sPoints[i].slipping = qfalse;
car->sPoints[i].onGround = qfalse;
car->sPoints[i].onGroundTime = 0;
car->sPoints[i].radius = WHEEL_RADIUS;
car->tPoints[i].mass = CP_WHEEL_MASS;
car->tPoints[i].elasticity = CP_WHEEL_ELASTICITY;
car->tPoints[i].scof = CP_SCOF;
car->tPoints[i].kcof = CP_KCOF;
car->tPoints[i].slipping = qfalse;
car->tPoints[i].onGround = qfalse;
car->tPoints[i].onGroundTime = 0;
car->tPoints[i].radius = WHEEL_RADIUS;
}
for (; i < NUM_CAR_POINTS; i++){
if (i < LAST_FRAME_POINT){
if( i < 6 )
{
car->sPoints[i].mass = 0.5f * car->sBody.mass * pm->car_frontweight_dist;
car->tPoints[i].mass = 0.5f * car->sBody.mass * pm->car_frontweight_dist;
}
else
{
car->sPoints[i].mass = 0.5f * car->sBody.mass * (1.0f - pm->car_frontweight_dist);
car->tPoints[i].mass = 0.5f * car->sBody.mass * (1.0f - pm->car_frontweight_dist);
}
}
else {
car->sPoints[i].mass = 0.0f;
car->tPoints[i].mass = 0.0f;
}
car->sPoints[i].scof = CP_SCOF;
car->sPoints[i].kcof = CP_KCOF;
car->sPoints[i].elasticity = pm->car_body_elasticity;
car->sPoints[i].slipping = qfalse;
car->sPoints[i].onGround = qfalse;
car->sPoints[i].onGroundTime = 0;
car->sPoints[i].radius = BODY_RADIUS;
car->tPoints[i].scof = CP_SCOF;
car->tPoints[i].kcof = CP_KCOF;
car->tPoints[i].elasticity = pm->car_body_elasticity;
car->tPoints[i].slipping = qfalse;
car->tPoints[i].onGround = qfalse;
car->tPoints[i].onGroundTime = 0;
car->tPoints[i].radius = BODY_RADIUS;
}
car->sBody.elasticity = car->sPoints[4].elasticity;
car->tBody.elasticity = car->sPoints[4].elasticity;
PM_SetCoM(&car->sBody, car->sPoints);
halfWidth = CAR_WIDTH / 2.0f;
halfLength = CAR_LENGTH / 2.0f;
halfHeight = CAR_HEIGHT / 2.0f;
car->inverseBodyInertiaTensor[0][0] = pm->car_IT_xScale * 3.0f / (car->sBody.mass * (halfLength * halfLength + halfHeight * halfHeight));
car->inverseBodyInertiaTensor[1][1] = pm->car_IT_yScale * 3.0f / (car->sBody.mass * (halfWidth * halfWidth + halfHeight * halfHeight));
car->inverseBodyInertiaTensor[2][2] = pm->car_IT_zScale * 3.0f / (car->sBody.mass * (halfWidth * halfWidth + halfLength * halfLength));
car->springStrength = CP_SPRING_STRENGTH;
car->springMaxLength = CP_SPRING_MAXLEN;
car->springMinLength = CP_SPRING_MINLEN;
// car->shockStrength = CP_SHOCK_STRENGTH;
car->gear = 1;
car->rpm = CP_RPM_MIN;
// car->aCOF = CP_AIR_COF;
// car->dfCOF = CP_FRAC_TO_DF;
// car->ewCOF = CP_ENGINE_TIRE_COF;
MatrixMultiply(car->sBody.t, car->inverseBodyInertiaTensor, car->sBody.inverseWorldInertiaTensor);
MatrixTranspose(car->sBody.t, m);
MatrixMultiply(car->sBody.inverseWorldInertiaTensor, m, car->sBody.inverseWorldInertiaTensor);
PM_ClearCarForces(&car->sBody, car->sPoints);
PM_ClearCarForces(&car->tBody, car->tPoints);
PM_CopyTargetToSource(&car->sBody, &car->tBody, car->sPoints, car->tPoints);
}
/*
===================
PM_SetFluidDensity
===================
*/
static void PM_SetFluidDensity(carPoint_t *points, int i){
vec3_t dest;
int cont, level, type;
// check waterlevel
level = 0;
type = 0;
VectorCopy( points[i].r, dest );
dest[2] = points[i].r[2] - (points[i].radius - 0.5f);
cont = pm->pointcontents( dest, pm->ps->clientNum );
if ( cont & MASK_WATER ){ // bottom of point is in the water
level++;
type = cont;
}
dest[2] = points[i].r[2];
cont = pm->pointcontents (dest, pm->ps->clientNum );
if ( cont & MASK_WATER ){ // middle of point is in the water
level++;
type = cont;
}
dest[2] = points[i].r[2] + (points[i].radius - 0.5f);
cont = pm->pointcontents (dest, pm->ps->clientNum );
if ( cont & MASK_WATER ) { // top of point is in the water
level++;
type = cont;
}
if ( level )
{
if (type & CONTENTS_WATER){
points[i].fluidDensity = (level * CP_WATER_DENSITY + (3.0f - level) * CP_AIR_DENSITY) / 3.0f;
}
else if (type & CONTENTS_LAVA){
points[i].fluidDensity = (level * CP_LAVA_DENSITY + (3.0f - level) * CP_AIR_DENSITY) / 3.0f;
}
else if (type & CONTENTS_SLIME){
points[i].fluidDensity = (level * CP_SLIME_DENSITY + (3.0f - level) * CP_AIR_DENSITY) / 3.0f;
}
else{
points[i].fluidDensity = CP_AIR_DENSITY;
}
// Com_Printf("PM_SetFluidDensity: level %i, density %.3f\n", level, points[i].fluidDensity);
}
else
{
points[i].fluidDensity = CP_AIR_DENSITY;
}
}
/*
===================
PM_CheckSurfaceFlags
===================
*/
static void PM_CheckSurfaceFlags( trace_t *trace, carPoint_t *point ){
// TODO: include rolling friction... harder to roll tires in sand or mud
if( pm->frictionFunc( point, &point->scof, &point->kcof ) )
{
point->kcof *= pm->car_friction_scale;
point->scof *= pm->car_friction_scale;
return;
}
else if (trace->surfaceFlags & SURF_SLICK){
point->kcof = CP_ICE_KCOF;
point->scof = CP_ICE_SCOF;
}
else if (trace->surfaceFlags & SURF_ICE) {
point->kcof = CP_ICE_KCOF;
point->scof = CP_ICE_SCOF;
}
else if (trace->surfaceFlags & SURF_GRASS){
point->kcof = CP_GRASS_KCOF;
point->scof = CP_GRASS_SCOF;
}
else if (trace->surfaceFlags & SURF_DUST){
point->kcof = CP_DIRT_KCOF;
point->scof = CP_DIRT_SCOF;
}
else if (trace->surfaceFlags & SURF_SAND){
point->kcof = CP_SAND_KCOF;
point->scof = CP_SAND_SCOF;
}
else if (trace->surfaceFlags & SURF_SNOW){
point->kcof = CP_SNOW_KCOF;
point->scof = CP_SNOW_SCOF;
}
else if (trace->surfaceFlags & SURF_GRAVEL) {
point->kcof = CP_GRAVEL_KCOF;
point->scof = CP_GRAVEL_SCOF;
}
else if (trace->surfaceFlags & SURF_DIRT) {
point->kcof = CP_DIRT_KCOF;
point->scof = CP_DIRT_SCOF;
}
else {
point->kcof = CP_KCOF;
point->scof = CP_SCOF;
}
if (trace->surfaceFlags & SURF_WET){
point->kcof *= CP_WET_SCALE;
point->scof *= CP_WET_SCALE;
}
point->kcof *= pm->car_friction_scale;
point->scof *= pm->car_friction_scale;
}
/*
================================================================================
PM_ApplyForce
This function is largely based on the physics articles and
source from Chris Hecker: http://www.d6.com/users/checker/dynamics.htm
================================================================================
*/
void PM_ApplyForce( carBody_t *body, vec3_t force, vec3_t at ){
vec3_t arm, moment;
VectorSubtract( at, body->CoM, arm );
VectorAdd( body->netForce, force, body->netForce );
CrossProduct( arm, force, moment);
VectorAdd( body->netMoment, moment, body->netMoment );
}
/*
================================================================================
PM_ApplyCollision
Rigid body <-> world collision function.
This function is largely based on the physics articles and
source from Chris Hecker: http://www.d6.com/users/checker/dynamics.htm
Given: rigid body, impact origin, impact normal, and elasticity
Find: the new linear and angular velocities as a result of the impact.
================================================================================
*/
static float PM_ApplyCollision( carBody_t *body, carPoint_t *points, vec3_t at, vec3_t normal, float elasticity ){
vec3_t arm;
vec3_t vP1;
vec3_t impulse, impulseMoment;
vec3_t delta, cross, cross2;
float impulseNum, oppositeImpulseNum, impulseDen, dot;
int i;
VectorSubtract(at, body->CoM, arm);
// VectorSubtract(at, body->r, arm);
if (pm->pDebug){
Com_Printf("PM_ApplyCollision: arm %0.3f, %0.3f, %0.3f\n", arm[0], arm[1], arm[2]);
Com_Printf("PM_ApplyCollision: normal %0.3f, %0.3f, %0.3f\n", normal[0], normal[1], normal[2]);
}
CrossProduct(body->w, arm, cross);
VectorAdd(body->v, cross, vP1);
// added from collision
VectorClear(impulse);
dot = DotProduct(normal, vP1);
if ( dot < 0 ){
impulseNum = -(1.0f + elasticity) * dot;
oppositeImpulseNum = -(1.0f - elasticity) * dot;
CrossProduct(arm, normal, cross);
VectorRotate(cross, body->inverseWorldInertiaTensor, cross2);
CrossProduct(cross2, arm, cross);
// Com_Printf( "oneOverMass %f, cross.normal %f\n", 1.0f / body->mass, DotProduct(cross, normal) );
impulseDen = 1.0f / body->mass + DotProduct(cross, normal);
VectorScale(normal, impulseNum / impulseDen, impulse);
}
else {
// not hitting surface
// Com_Printf( "PM_ApplyCollision: not hitting surface, %f\n", dot );
return 0.0f;
}
// apply impulse to primary quantities
CrossProduct(arm, impulse, impulseMoment);
VectorScale( impulse, 1.0 / body->mass, impulse );
VectorAdd(body->v, impulse, body->v);
VectorAdd(body->L, impulseMoment, body->L);
// compute affected auxiliary quantities
VectorRotate(body->L, body->inverseWorldInertiaTensor, body->w);
// temp to help wheel movement when hitting walls
// FIXME: is this still needed?
VectorMA(impulse, -DotProduct(impulse, body->up), body->up, delta);
for (i = 0; i < FIRST_FRAME_POINT; i++){
VectorAdd(points[i].v, delta, points[i].v);
}
PM_UpdateFrameVelocities(body, points);
if ( fabs(oppositeImpulseNum / impulseDen) > 5000.0f )
return fabs(oppositeImpulseNum / impulseDen);
else
return 0;
}
#if 0
/*
================================================================================
PM_ApplyBodyBodyCollision
This function is a hacked up version of PM_ApplyCollision to try to simulate
the collision between two cars.
Given: 2 rigid bodies, impact origin, impact normal, and elasticity
Find: the new linear and angular velocities of the two objects as a result of the impact.
================================================================================
*/
static float PM_ApplyBodyBodyCollision( carBody_t *body1, carPoint_t *points1, carBody_t *body2, carPoint_t *points2, vec3_t at, vec3_t normal, float elasticity ){
//vec3_t arm1, arm2;
vec3_t vP1, vP2;
//vec3_t impulse, impulseMoment;
//vec3_t cross, cross2;
vec3_t diff1, diff2, delta;
//float impulseNum, oppositeImpulseNum, impulseDen;
//float totalMass;
int i;
/*
totalMass = body1->mass + body2->mass;
VectorSubtract(at, body1->CoM, arm1);
VectorSubtract(at, body2->CoM, arm2);
if (pm->pDebug){
Com_Printf("PM_ApplyBodyBodyCollision: arm1 %0.3f, %0.3f, %0.3f\n", arm1[0], arm1[1], arm1[2]);
Com_Printf("PM_ApplyBodyBodyCollision: arm2 %0.3f, %0.3f, %0.3f\n", arm2[0], arm2[1], arm2[2]);
Com_Printf("PM_ApplyBodyBodyCollision: normal %0.3f, %0.3f, %0.3f\n", normal[0], normal[1], normal[2]);
}
CrossProduct(body1->w, arm1, cross);
VectorAdd(body1->v, cross, vP1);
CrossProduct(body2->w, arm2, cross);
VectorAdd(body2->v, cross, vP2);
*/
// hacked up physics
VectorCopy( body1->v, vP1 );
VectorCopy( body1->L, vP2 );
PM_ApplyCollision( body1, points1, at, normal, elasticity / 2.0f );
// VectorMA( body1->v, body2->mass / totalMass, vP2, body1->v );
// Com_Printf( "PM_ApplyBodyBodyCollision: v before %0.3f, %0.3f, %0.3f\n", body2->v[0], body2->v[1], body2->v[2] );
VectorSubtract( vP1, body1->v, diff1 );
VectorSubtract( vP2, body1->L, diff2 );
VectorAdd( body2->v, diff1, body2->v );
VectorAdd( body2->L, diff2, body2->L );
// compute affected auxiliary quantities
VectorRotate( body2->L, body2->inverseWorldInertiaTensor, body2->w );
// temp to help wheel movement when hitting walls
// FIXME: is this still needed?
VectorMA( diff1, -DotProduct(diff1, body2->up), body2->up, delta );
for ( i = 0; i < FIRST_FRAME_POINT; i++ ){
VectorAdd( points2[i].v, delta, points2[i].v );
}
PM_UpdateFrameVelocities( body2, points2 );
// VectorInverse( normal );
// PM_ApplyCollision( body2, points2, at, normal, elasticity );
// VectorMA( body2->v, body1->mass / totalMass, vP1, body2->v );
// Com_Printf( "PM_ApplyBodyBodyCollision: v after %0.3f, %0.3f, %0.3f\n", body2->v[0], body2->v[1], body2->v[2] );
return 0;
/*
VectorSubtract(vP1, vP2, vP1);
// added from collision
VectorClear(impulse);
// if (DotProduct(normal, vP1) < 0){
// massFraction = (2.0f * body1->mass) / (body1->mass + body2->mass);
// impulseNum = massFraction * DotProduct(normal, vP1);
// massFraction = ((1.0f + elasticity) * body1->mass) / (body1->mass + body2->mass);
impulseNum = -(1 + elasticity) * DotProduct(normal, vP1);
oppositeImpulseNum = -(1.0f - elasticity) * DotProduct(normal, vP1);
// impulseDen = 1.0f / body1->mass;
impulseDen = 1.0f / body1->mass + 1.0f / body2->mass;
CrossProduct(arm1, normal, cross);
VectorRotate(cross, body1->inverseWorldInertiaTensor, cross2);
CrossProduct(cross2, arm1, cross);
//impulseDen = 1.0f / body1->mass + DotProduct(cross, normal);
impulseDen += DotProduct(cross, normal);
CrossProduct(arm2, normal, cross);
VectorRotate(cross, body2->inverseWorldInertiaTensor, cross2);
CrossProduct(cross2, arm2, cross);
impulseDen += DotProduct(cross, normal);
VectorScale(normal, impulseNum / impulseDen, impulse);
// }
// else {
// // not hitting surface
// return;
// }
// apply impulse to primary quantities
VectorMA(body1->v, 1.0 / body1->mass, impulse, body1->v);
CrossProduct(arm1, impulse, impulseMoment);
VectorAdd(body1->L, impulseMoment, body1->L);
// compute affected auxiliary quantities
VectorRotate(body1->L, body1->inverseWorldInertiaTensor, body1->w);
// apply impulse to primary quantities of second object
VectorInverse(impulse);
VectorMA(body2->v, 1.0 / body2->mass, impulse, body2->v);
CrossProduct(arm2, impulse, impulseMoment);
VectorAdd(body2->L, impulseMoment, body2->L);
// compute affected auxiliary quantities of second object
VectorRotate(body2->L, body2->inverseWorldInertiaTensor, body2->w);
// temp to help wheel movement when hitting walls
// VectorMA(impulse, -DotProduct(impulse, body->up), body->up, delta);
// for (i = 0; i < FIRST_FRAME_POINT; i++){
// VectorMA(points[i].v, 1.0 / body->mass, delta, points[i].v);
// }
PM_UpdateFrameVelocities(body1, points1);
if (fabs(oppositeImpulseNum / impulseDen) > 5000.0f)
return fabs(oppositeImpulseNum / impulseDen);
else
return 0;
*/
}
/*
================================================================================
PM_ApplyPointBodyCollision
This function is a hacked up version of PM_ApplyCollision to try to simulate
the collision between a tire and the body of a car (like when the suspension bottoms out).
Given: rigid body, impact origin, impact normal, and elasticity
Find: the new linear and angular velocities as a result of the impact.
================================================================================
*/
static void PM_ApplyPointBodyCollision( carBody_t *body, carPoint_t *points, carPoint_t *pt, vec3_t at, vec3_t normal, float elasticity ){
vec3_t arm;
vec3_t vP1;
vec3_t impulse, impulseMoment;
vec3_t cross, cross2;
float impulseNum, impulseDen;
float massFraction;
VectorSubtract(at, body->CoM, arm);
// VectorSubtract(at, body->r, arm);
if (pm->pDebug){
Com_Printf("PM_ApplyPointBodyCollision: arm %0.3f, %0.3f, %0.3f\n", arm[0], arm[1], arm[2]);
Com_Printf("PM_ApplyPointBodyCollision: normal %0.3f, %0.3f, %0.3f\n", normal[0], normal[1], normal[2]);
}
CrossProduct(body->w, arm, cross);
VectorAdd(body->v, cross, vP1);
// added from collision
VectorClear(impulse);
// if (DotProduct(normal, vP1) < 0){
massFraction = (2.0f * pt->mass) / (pt->mass + body->mass);
impulseNum = massFraction * (DotProduct(normal, pt->v) - DotProduct(normal, vP1));
CrossProduct(arm, normal, cross);
VectorRotate(cross, body->inverseWorldInertiaTensor, cross2);
CrossProduct(cross2, arm, cross);
impulseDen = 1.0 / body->mass + DotProduct(cross, normal);
VectorScale(normal, impulseNum / impulseDen, impulse);
// }
// apply impulse to primary quantities of rigid body
VectorMA(body->v, 1.0 / body->mass, impulse, body->v);
CrossProduct(arm, impulse, impulseMoment);
VectorAdd(body->L, impulseMoment, body->L);
// compute affected auxiliary quantities
VectorRotate(body->L, body->inverseWorldInertiaTensor, body->w);
VectorMA(pt->v, -1.0 / pt->mass, impulse, pt->v);
/*
// temp to help wheel movement when hitting walls
VectorMA(impulse, -DotProduct(impulse, body->up), body->up, delta);
for (i = 0; i < FIRST_FRAME_POINT; i++){
VectorMA(points[i].v, 1.0 / body->mass, delta, points[i].v);
}
*/
PM_UpdateFrameVelocities(body, points);
}
#endif
#ifdef CGAME
void CG_Sparks( const vec3_t origin, const vec3_t normal, const vec3_t direction, const float speed );
#endif
/*
================================================================================
PM_CarBodyFrictionForces
Friction forces between the car body and the world
================================================================================
*/
static void PM_CarBodyFrictionForces( car_t *car, carBody_t *body, carPoint_t *points, int i ){
vec3_t vel, force;
float normalForce;
float speed;
normalForce = DotProduct( body->netForce, points[i].normals[0] );
if ( normalForce >= 0.0f ) return;
VectorMA( body->netForce, -normalForce, points[i].normals[0], force );
VectorMA( points[i].v, -DotProduct( points[i].v, points[i].normals[0] ), points[i].normals[0], vel );
speed = VectorNormalize( vel );
if ( VectorLength(force) > fabs( 0.7f * normalForce ) || speed > 2.0f ){
// VectorNormalize(vel);
VectorScale( vel, 0.4f * normalForce, force );
}
else {
VectorInverse( force );
}
PM_ApplyForce( body, force, points[i].r );
#ifdef CGAME
if( speed > 20.0f )
{
vec3_t origin;
float r;
r = random() * 5000 / speed;
if( r < 80 )
{
VectorMA( points[i].r, -BODY_RADIUS / 1.5f, points[i].normals[0], origin );
// VectorInverse( vel );
VectorMA( vel, 0.1f, points[i].normals[0], vel );
VectorNormalize( vel );
CG_Sparks( origin, points[i].normals[0], vel, speed * 0.5f );
}
}
#endif
}
/*
================================================================================
PM_Generate_SwayBar_Forces
Simulates ARBs in the car
================================================================================
*/
static void PM_Generate_SwayBar_Forces(car_t *car, carBody_t *body, carPoint_t *points, int leftWheel, int rightWheel ){
// vec3_t lSpring, rSpring;
// vec3_t lengthDiff;
float dot, force;
// carPoint_t *lWheel;
// carPoint_t *rWheel;
// lWheel = &points[leftWheel];
// rWheel = &points[rightWheel];
if ( fabs( DotProduct( body->v, body->forward ) ) < 50.0f)
return;
// VectorSubtract(points[leftWheel+4].v, lWheel->v, lSpring);
// VectorSubtract(points[rightWheel+4].v, rWheel->v, rSpring);
// VectorSubtract(lSpring, rSpring, lengthDiff);
dot = body->curSpringLengths[leftWheel] - body->curSpringLengths[rightWheel];
// if dotproduct(lengthDiff, up) < 0, rSpring > lSpring
// else rSpring < lSpring
// dot = DotProduct(body->up, lengthDiff);
force = dot * pm->car_swaybar * CP_GRAVITY;
if (force < 0 && !points[leftWheel].onGround)
return;
else if (force > 0 && !points[rightWheel].onGround)
return;
if (force < 0 && pm->ps->viewangles[ROLL] > 0){
// Com_Printf("Sway bars shouldnt be forcing\n");
return;
}
else if (force > 0 && pm->ps->viewangles[ROLL] < 0){
// Com_Printf("Sway bars shouldnt be forcing\n");
return;
}
// Com_Printf("PM_Generate_SwayBar_Forces: force %f, roll %f\n", force, pm->ps->viewangles[ROLL]);
// VectorScale(body->up, force, lWheel->forces[SWAY_BAR]);
VectorScale(body->up, -force, points[leftWheel+4].forces[SWAY_BAR]);
// VectorScale(body->up, -force, rWheel->forces[SWAY_BAR]);
VectorScale(body->up, force, points[rightWheel+4].forces[SWAY_BAR]);
}
/*
================================================================================
PM_Generate_FrameWheel_Forces
Handles spring and damper physics for the suspension
================================================================================
*/
static int PM_Generate_FrameWheel_Forces(car_t *car, carPoint_t *points, int frameIndex, int wheelIndex ){
vec3_t delta, diff;
float compression, springVel, length;
int hitType;
carPoint_t *frame, *wheel;
hitType = HTYPE_NO_HIT;
frame = &points[frameIndex];
wheel = &points[wheelIndex];
//
// Calculate forces on the frame
//
// spring force based on length of spring (origin difference of the two points)
length = car->sBody.curSpringLengths[wheelIndex];
// shock force based on velocity difference of the two points
VectorSubtract(frame->v, wheel->v, delta);
springVel = DotProduct(car->sBody.up, delta);
if (springVel > 0){
VectorScale(car->sBody.up, -pm->car_shock_up * CP_GRAVITY * springVel, frame->forces[SHOCK]);
}
else {
VectorScale(car->sBody.up, -pm->car_shock_down * CP_GRAVITY * springVel, frame->forces[SHOCK]);
}
// compression = springVel > 0 ? 0 : springVel;
// VectorScale(car->sBody.up, -car->shockStrength * compression, frame->forces[SHOCK]);
if (length < car->springMinLength){
if (springVel < 0){
VectorScale(car->sBody.up, -springVel, diff);
VectorSubtract(wheel->v, diff, wheel->v);
if (wheel->onGround){
// suspension bottomed out and tires are on the ground so we have
// to treat this as a collision of the frame with the ground
hitType = HTYPE_BOTTOMED_OUT;
}
}
// length = car->springMinLength;
}
else if (length > car->springMaxLength){
if (springVel > 0){
// PM_ApplyPointBodyCollision(&car->sBody, &car->sPoints, car->sBody.up, 1.0);
/*
VectorScale(car->sBody.up, DotProduct(car->sBody.up, wheelVelocity), diff);
VectorSubtract(wheelVelocity, diff, delta);
VectorScale(car->sBody.up, DotProduct(car->sBody.up, frameVelocity), diff);
VectorAdd(delta, diff, wheel->v);
*/
// move wheel so its not too far out
VectorMA( wheel->r, length - car->springMaxLength, car->sBody.up, wheel->r );
car->sBody.curSpringLengths[wheelIndex] = car->springMaxLength;
}
hitType = HTYPE_MAXED_OUT;
// length = car->springMaxLength;
}
// linear
// compression = car->springMaxLength - length;
/*
if (length > car->springMaxLength)
compression = car->springMaxLength - length;
else
compression = (car->springMaxLength*2.0f - length) / 5.0f;
*/
// x^2
// compression = car->springMaxLength - length;
// compression *= compression;
// exp(x/max dist)
compression = car->springMaxLength - length;
if( compression > 0 )
compression = (car->springMaxLength - car->springMinLength) * (exp( compression / (car->springMaxLength - car->springMinLength)) - 1 ) / 1.718281828f;
if (compression < 0)
VectorScale(car->sBody.up, 3.0f * CP_SPRING_STRENGTH * compression, frame->forces[SPRING]);
else
VectorScale(car->sBody.up, car->springStrength * compression, frame->forces[SPRING]);
/*
{
float shock = VectorLength( frame->forces[SHOCK] );
if( shock > CP_MAX_SHOCK_FORCE )
{
#ifdef GAME
Com_Printf( "%i Shock Force: %f\n", (wheel - &car->sPoints[0]), shock );
#endif
VectorScale( frame->forces[SHOCK], CP_MAX_SHOCK_FORCE / shock, frame->forces[SHOCK] );
}
}
*/
//
// Calculate forces on the tire
//
VectorScale(frame->forces[SPRING], -1, wheel->forces[SPRING]);
VectorScale(frame->forces[SHOCK], -1, wheel->forces[SHOCK]);
return hitType;
}
/*
================================================================================
PM_CalculateForces
Main force calculating function for the entire car and the wheels
================================================================================
*/
static void PM_CalculateForces( car_t *car, carBody_t *body, carPoint_t *points, float sec ){
vec3_t delta, diff;
//vec3_t arm;
vec3_t force, hitOrigin, normal;
float length, k, b, springVel, dot;
int count;
int i, hitType, n;
//float impulseDamage;
VectorClear(force);
// add gravity forces
for (i = 0; i < NUM_CAR_POINTS; i++){
VectorSet(points[i].forces[GRAVITY], 0, 0, -CP_CURRENT_GRAVITY * points[i].mass);
}
VectorClear(hitOrigin);
count = 0;
PM_Generate_SwayBar_Forces(car, body, points, 0, 1);
PM_Generate_SwayBar_Forces(car, body, points, 2, 3);
// add spring forces
for (i = 0; i < FIRST_FRAME_POINT; i++)
{
// VectorSubtract(points[i+4].r, points[i].r, delta);
if ( body->curSpringLengths[i] > 20 )
{
// Com_Printf("Initialize wheel %i, frame %f,%f,%f, wheel %f,%f,%f\n", i, points[i+4].r[0], points[i+4].r[1], points[i+4].r[2], points[i].r[0], points[i].r[1], points[i].r[2]);
if (i == 0)
PM_InitializeWheel(body, points, i, 1.0f, -1.0f);
else if (i == 1)
PM_InitializeWheel(body, points, i, 1.0f, 1.0f);
else if (i == 2)
PM_InitializeWheel(body, points, i, -1.0f, -1.0f);
else if (i == 3)
PM_InitializeWheel(body, points, i, -1.0f, 1.0f);
}
hitType = PM_Generate_FrameWheel_Forces(car, points, i+4, i);
if ( VectorNAN( points[i].netForce ) )
Com_Printf("Blowing up car because of car point force on wheel %d\n", i );
if (hitType == HTYPE_BOTTOMED_OUT)
{
// add a normal to the frame point
for ( n = 0; n < 3; n++ )
{
if ( VectorLength( points[i+4].normals[n] ) ) continue;
VectorCopy( body->up, points[i+4].normals[n] );
break;
}
VectorAdd( hitOrigin, points[i+4].r, hitOrigin );
count++;
}
else if (hitType == HTYPE_MAXED_OUT)
{
// add a normal to the frame point
for ( n = 0; n < 3; n++ )
{
if ( VectorLength( points[i+4].normals[n] ) ) continue;
VectorScale( body->up, -1.0f, points[i+4].normals[n] );
break;
}
VectorAdd( points[i].forces[SPRING], points[i].forces[SHOCK], force );
dot = DotProduct( force, body->up );
if ( dot < 0.0f )
VectorMA( points[i].forces[SPRING], -dot, body->up, points[i].forces[SPRING] );
}
}
if ( count != 0 )
{
if (pm->pDebug)
Com_Printf("PM_CalculateForces: Frame-Wheel Collision with %i wheels\n", count);
VectorScale(hitOrigin, 1.0 / (float)count, hitOrigin);
/*impulseDamage = */PM_ApplyCollision(body, points, hitOrigin, car->sBody.up, body->elasticity);
// add normal force
// FIXME - change this so i can do it only once right before acceleration
// FIXME: replace with proper normal.netForce
/*
VectorSet(delta, 0, 0, -CP_CURRENT_GRAVITY * CP_CAR_MASS);
if (DotProduct(body->up, delta) < 0.0f){
VectorScale(body->up, -OVERCLIP * DotProduct(body->up, delta), body->normalForce);
PM_ApplyForce(body, body->normalForce, hitOrigin);
}
*/
// damage stuff
// FIXME: taking too much damage during normal driving
/*
impulseDamage /= (2000.0f / sec) / count;
if (impulseDamage > 0.00001f){
VectorCopy(hitOrigin, pm->damage.origin);
VectorCopy(car->sBody.up, pm->damage.dir);
pm->damage.damage += impulseDamage;
pm->damage.mod = MOD_BO_SHOCKS;
}
*/
VectorCopy( hitOrigin, pm->damage.origin );
VectorCopy( car->sBody.up, pm->damage.dir );
pm->damage.dflags = DAMAGE_NO_KNOCKBACK;
pm->damage.mod = MOD_BO_SHOCKS;
}
// UPDATE - moved lower down
// PM_Generate_SwayBar_Forces(body, points, 0, 1);
// PM_Generate_SwayBar_Forces(body, points, 2, 3);
// calculate frame hits
VectorClear(hitOrigin);
VectorClear(normal);
count = 0;
for (i = FIRST_FRAME_POINT; i < NUM_CAR_POINTS; i++){
if ( !points[i].onGround ) continue;
if ( DotProduct(points[i].v, points[i].normals[0] ) > 0.0f) continue;
VectorAdd( normal, points[i].normals[0], normal );
VectorAdd( hitOrigin, points[i].r, hitOrigin );
count++;
}
if ( count != 0 )
{
VectorScale(hitOrigin, 1.0 / (float)count, hitOrigin);
VectorNormalize(normal);
if (pm->pDebug)
Com_Printf("PM_CalculateForces: Frame hit a surface at %i spots\n", count);
/*impulseDamage = */PM_ApplyCollision( body, points, hitOrigin, normal, 0.0f );
// add normal force
// FIXME - change this so i can do it only once right before acceleration
// FIXME: replace with proper normal.netForce
/*
VectorSet(delta, 0, 0, -CP_CURRENT_GRAVITY * CP_CAR_MASS);
if (DotProduct(normal, delta) < 0.0f){
VectorScale(body->up, -OVERCLIP * DotProduct(body->up, delta), body->normalForce);
PM_ApplyForce(body, body->normalForce, hitOrigin);
}
*/
// damage stuff - FIXME: dont take damage for sitting up against walls
// divide by number of spots hit maybe?
/*
impulseDamage /= (50000.0f / sec) / count;
if (impulseDamage > 0.00001f){
VectorCopy(hitOrigin, pm->damage.origin);
VectorCopy(normal, pm->damage.dir);
pm->damage.damage += impulseDamage;
pm->damage.mod = MOD_WORLD_COLLISION;
}
*/
VectorCopy(hitOrigin, pm->damage.origin);
VectorCopy(normal, pm->damage.dir);
pm->damage.dflags = DAMAGE_NO_KNOCKBACK;
pm->damage.mod = MOD_WORLD_COLLISION;
}
// steering, acceleration, braking, etc
PM_AddRoadForces(car, body, points, sec);
for (i = 0; i < FIRST_FRAME_POINT; i++)
{
// new
// VectorMA(car->sPoints[i].r, -WHEEL_RADIUS, car->sPoints[i].normals[0], arm);
// remove component of force in body up direction
VectorCopy(points[i].forces[ROAD], force);
VectorMA(force, -DotProduct(force, car->sBody.up), car->sBody.up, force);
// apply force to axle, not tire-ground contact
PM_ApplyForce(body, force, car->sPoints[i].r);
// PM_ApplyForce(body, force, arm);
// end new
VectorScale(points[i].forces[ROAD], points[i].mass / points[i+4].mass, points[i].forces[ROAD]);
// }
// internal forces to return the wheel to perpendicular
// for (i = 0; i < FIRST_FRAME_POINT; i++)
// {
VectorSubtract( points[i+4].r, points[i].r, delta );
VectorMA( delta, -DotProduct(delta, car->sBody.up), car->sBody.up, diff );
length = VectorNormalize(diff);
if ( length > 2.0f ){
// k = 10000; // good at 90fps
k = pm->car_wheel * CP_WHEEL_MASS;
// b = 2 * sqrt(k * points[i].mass);
b = pm->car_wheel_damp * CP_WHEEL_MASS;
VectorScale(diff, -k * length, force);
VectorSubtract(points[i+4].v, points[i].v, delta);
VectorMA(delta, -DotProduct(delta, car->sBody.up), car->sBody.up, diff);
springVel = VectorNormalize(diff);
VectorMA(force, -b * springVel, diff, force);
// VectorCopy(force, points[i+4].forces[INTERNAL]);
VectorScale(force, -1, points[i].forces[INTERNAL]);
}
}
// calculate net forces
for (i = 0; i < NUM_CAR_POINTS; i++){
PM_CalculateNetForce(&points[i], i);
}
}
/*
================================================================================
PM_AccelerateAndMoveBody
This function is largely based on the physics articles and
source from Chris Hecker: http://www.d6.com/users/checker/dynamics.htm
================================================================================
*/
static void PM_AccelerateAndMoveBody( car_t *car, carBody_t *sBody, carBody_t *tBody, float time ){
float m[3][3];
float m2[3][3];
vec3_t vec;
vec3_t force;
int i, n;
float dot, impulseDamage;
float time_2 = time / 2.0f;
// FIXME: handle collisions here as well?
// FIXME: just have one normal force for the entire body instead of for each point?
for (i = FIRST_FRAME_POINT; i < NUM_CAR_POINTS; i++){
if (!car->sPoints[i].onGround) continue;
for (n = 0; n < 3; n++){
if (!VectorLength(car->sPoints[i].normals[n])) continue;
dot = DotProduct(car->sPoints[i].normals[n], sBody->netForce);
if (dot >= 0.0f) continue;
VectorScale(car->sPoints[i].normals[n], -OVERCLIP * dot, force);
PM_ApplyForce(sBody, force, car->sPoints[i].r);
}
}
// damage stuff
VectorSubtract(tBody->v, sBody->v, vec);
impulseDamage = VectorLength(vec);
if (impulseDamage > 400.0f){
// Com_Printf("impulseDamage %f\n", impulseDamage);
pm->damage.damage += impulseDamage / 25.0f;
}
// linear
// NOTE: this method is really not right but it works so oh well.
// tBody contains netForce from last frame
VectorAdd(sBody->netForce, tBody->netForce, vec);
VectorMA(sBody->v, time_2 / sBody->mass, vec, tBody->v);
VectorAdd(sBody->v, tBody->v, vec);
VectorMA(sBody->r, time_2, vec, tBody->r);
// angular
VectorAdd(sBody->netMoment, tBody->netMoment, vec);
VectorMA(sBody->L, time_2, vec, tBody->L);
VectorRotate(tBody->L, sBody->inverseWorldInertiaTensor, tBody->w);
VectorAdd(sBody->w, tBody->w, vec);
m[0][0] = 0; m[0][1] = time_2 * -vec[2]; m[0][2] = time_2 * vec[1];
m[1][0] = time_2 * vec[2]; m[1][1] = 0; m[1][2] = time_2 * -vec[0];
m[2][0] = time_2 * -vec[1]; m[2][1] = time_2 * vec[0]; m[2][2] = 0;
MatrixMultiply(m, sBody->t, m2);
MatrixAdd(sBody->t, m2, tBody->t);
OrthonormalizeOrientation(tBody->t);
// Optimization note: check if inverseWorldInertiaTensor is only really
// needed for collisions. If it is then might be best just to calculate
// it there since collisions dont usually happen every frame.
// Note: only used in collisions and converting L to w. If I can get w
// some other way then I can move it.
// MatrixMultiply(tBody->t, car->inverseBodyInertiaTensor, m);
// This should be the same as MatrixMultiply(tBody->t, car->inverseBodyInertiaTensor, m);
// without so many multiplies and adds.
m[0][0] = car->inverseBodyInertiaTensor[0][0] * tBody->t[0][0];
m[1][0] = car->inverseBodyInertiaTensor[0][0] * tBody->t[1][0];
m[2][0] = car->inverseBodyInertiaTensor[0][0] * tBody->t[2][0];
m[0][1] = car->inverseBodyInertiaTensor[1][1] * tBody->t[0][1];
m[1][1] = car->inverseBodyInertiaTensor[1][1] * tBody->t[1][1];
m[2][1] = car->inverseBodyInertiaTensor[1][1] * tBody->t[2][1];
m[0][2] = car->inverseBodyInertiaTensor[2][2] * tBody->t[0][2];
m[1][2] = car->inverseBodyInertiaTensor[2][2] * tBody->t[1][2];
m[2][2] = car->inverseBodyInertiaTensor[2][2] * tBody->t[2][2];
MatrixTranspose(tBody->t, m2);
MatrixMultiply(m, m2, tBody->inverseWorldInertiaTensor);
// generate direction vectors
OrientationToVectors(tBody->t, tBody->forward, tBody->right, tBody->up);
// copy netForce and netMoment to target so we can use it next frame
VectorCopy(sBody->netForce, tBody->netForce);
VectorCopy(sBody->netMoment, tBody->netMoment);
}
/*
================================================================================
PM_CalculateSecondaryQuantities
Used in cgame to calculate w and forward, right, up from the new known
primary values from the server
================================================================================
*/
void PM_CalculateSecondaryQuantities( car_t *car, carBody_t *body, carPoint_t *points ){
float m[3][3];
float m2[3][3];
float forwardScale, rightScale, upScale;
vec3_t diff;
int i;
// MatrixMultiply(body->t, car->inverseBodyInertiaTensor, m);
m[0][0] = car->inverseBodyInertiaTensor[0][0] * body->t[0][0];
m[1][0] = car->inverseBodyInertiaTensor[0][0] * body->t[1][0];
m[2][0] = car->inverseBodyInertiaTensor[0][0] * body->t[2][0];
m[0][1] = car->inverseBodyInertiaTensor[1][1] * body->t[0][1];
m[1][1] = car->inverseBodyInertiaTensor[1][1] * body->t[1][1];
m[2][1] = car->inverseBodyInertiaTensor[1][1] * body->t[2][1];
m[0][2] = car->inverseBodyInertiaTensor[2][2] * body->t[0][2];
m[1][2] = car->inverseBodyInertiaTensor[2][2] * body->t[1][2];
m[2][2] = car->inverseBodyInertiaTensor[2][2] * body->t[2][2];
MatrixTranspose(body->t, m2);
MatrixMultiply(m, m2, body->inverseWorldInertiaTensor);
VectorRotate(body->L, body->inverseWorldInertiaTensor, body->w);
OrientationToVectors(body->t, body->forward, body->right, body->up);
// set locations and velocities of frame points
PM_InitializeFrame(body, &points[FL_FRAME], 1.0f, -1.0f, 0.0f);
PM_InitializeFrame(body, &points[FR_FRAME], 1.0f, 1.0f, 0.0f);
PM_InitializeFrame(body, &points[RL_FRAME], -1.0f, -1.0f, 0.0f);
PM_InitializeFrame(body, &points[RR_FRAME], -1.0f, 1.0f, 0.0f);
// body collision points
forwardScale = ((CAR_LENGTH/2.0f) - BODY_RADIUS) / WHEEL_FORWARD;
rightScale = ((CAR_WIDTH/2.0f) - BODY_RADIUS) / WHEEL_RIGHT;
upScale = (0) / -WHEEL_UP;
PM_InitializeFrame(body, &points[FL_BODY], forwardScale, -rightScale, upScale);
PM_InitializeFrame(body, &points[FR_BODY], forwardScale, rightScale, upScale);
PM_InitializeFrame(body, &points[ML_BODY], 0.0f, -rightScale, upScale);
PM_InitializeFrame(body, &points[MR_BODY], 0.0f, rightScale, upScale);
PM_InitializeFrame(body, &points[RL_BODY], -forwardScale, -rightScale, upScale);
PM_InitializeFrame(body, &points[RR_BODY], -forwardScale, rightScale, upScale);
PM_InitializeFrame(body, &points[ML_ROOF], 0.0f, -rightScale, 0.6f);
PM_InitializeFrame(body, &points[MR_ROOF], 0.0f, rightScale, 0.6f);
// calculate spring lengths
for( i = 0; i < FIRST_FRAME_POINT; i++ )
{
VectorSubtract( points[i+4].r, points[i].r, diff );
body->curSpringLengths[i] = DotProduct( diff, body->up );
}
PM_SetCoM(body, points);
PM_ClearCarForces(body, points);
PM_CopyTargetToSource(&car->sBody, &car->tBody, car->sPoints, car->tPoints);
/*
VectorCopy( car->sBody.v, car->oldBodies[2].v );
VectorCopy( car->sBody.w, car->oldBodies[2].w );
VectorCopy( car->sBody.netForce, car->oldBodies[2].netForce );
VectorCopy( car->sBody.netMoment, car->oldBodies[2].netMoment );
VectorCopy( car->sBody.v, car->oldBodies[1].v );
VectorCopy( car->sBody.w, car->oldBodies[1].w );
VectorCopy( car->sBody.netForce, car->oldBodies[1].netForce );
VectorCopy( car->sBody.netMoment, car->oldBodies[1].netMoment );
VectorCopy( car->sBody.v, car->oldBodies[0].v );
VectorCopy( car->sBody.w, car->oldBodies[0].w );
VectorCopy( car->sBody.netForce, car->oldBodies[0].netForce );
VectorCopy( car->sBody.netMoment, car->oldBodies[0].netMoment );
for( i = 0; i < LAST_RW_POINT; i++ )
{
VectorCopy( car->sPoints[i].netForce, car->oldPoints[2][i].netForce );
VectorCopy( car->sPoints[i].v, car->oldPoints[2][i].v );
car->oldPoints[2][i].netMoment = car->sPoints[i].netMoment;
VectorCopy( car->sPoints[i].netForce, car->oldPoints[1][i].netForce );
VectorCopy( car->sPoints[i].v, car->oldPoints[1][i].v );
car->oldPoints[1][i].netMoment = car->sPoints[i].netMoment;
VectorCopy( car->sPoints[i].netForce, car->oldPoints[0][i].netForce );
VectorCopy( car->sPoints[i].v, car->oldPoints[0][i].v );
car->oldPoints[0][i].netMoment = car->sPoints[i].netMoment;
}
*/
}
/*
================================================================================
PM_CalculateTargetBody
Integrates the current state to find the next state.
================================================================================
*/
static void PM_CalculateTargetBody(car_t *car, carBody_t *sBody, carBody_t *tBody, carPoint_t *sPoints, carPoint_t *tPoints, float time){
int i;
float forwardScale, rightScale, upScale;
vec3_t diff;
// move body
PM_AccelerateAndMoveBody( car, sBody, tBody, time );
// move wheels
for (i = 0; i < FIRST_FRAME_POINT; i++){
PM_AccelerateAndMove( car, &sPoints[i], &tPoints[i], time );
}
// set locations and velocities of frame points
PM_InitializeFrame(tBody, &tPoints[FL_FRAME], 1.0f, -1.0f, 0.0f);
PM_InitializeFrame(tBody, &tPoints[FR_FRAME], 1.0f, 1.0f, 0.0f);
PM_InitializeFrame(tBody, &tPoints[RL_FRAME], -1.0f, -1.0f, 0.0f);
PM_InitializeFrame(tBody, &tPoints[RR_FRAME], -1.0f, 1.0f, 0.0f);
// body collision points
forwardScale = ((CAR_LENGTH/2.0f) - BODY_RADIUS) / WHEEL_FORWARD;
rightScale = ((CAR_WIDTH/2.0f) - BODY_RADIUS) / WHEEL_RIGHT;
upScale = (0) / -WHEEL_UP;
PM_InitializeFrame(tBody, &tPoints[FL_BODY], forwardScale, -rightScale, upScale);
PM_InitializeFrame(tBody, &tPoints[FR_BODY], forwardScale, rightScale, upScale);
PM_InitializeFrame(tBody, &tPoints[ML_BODY], 0.0f, -rightScale, upScale);
PM_InitializeFrame(tBody, &tPoints[MR_BODY], 0.0f, rightScale, upScale);
PM_InitializeFrame(tBody, &tPoints[RL_BODY], -forwardScale, -rightScale, upScale);
PM_InitializeFrame(tBody, &tPoints[RR_BODY], -forwardScale, rightScale, upScale);
PM_InitializeFrame(tBody, &tPoints[ML_ROOF], 0.0f, -rightScale, 0.6f);
PM_InitializeFrame(tBody, &tPoints[MR_ROOF], 0.0f, rightScale, 0.6f);
// calculate spring lengths
for( i = 0; i < FIRST_FRAME_POINT; i++ )
{
VectorSubtract( tPoints[i+4].r, tPoints[i].r, diff );
tBody->curSpringLengths[i] = DotProduct( diff, tBody->up );
}
// set the center of mass
PM_SetCoM(tBody, tPoints);
}
/*
qboolean PM_Hit_Car( trace_t trace ){
// hit another car
vec3_t f, r, u, delta;
float dotF, dotR, dotU;
qboolean lengthHitOK, widthHitOK, heightHitOK;
lengthHitOK = qfalse;
widthHitOK = qfalse;
heightHitOK = qfalse;
VectorSubtract(trace.endpos, pm->cars[trace.entityNum]->sBody.r, delta);
OrientationToVectors(pm->cars[trace.entityNum]->sBody.t, f, r, u);
// need to add radius of trace box to this check?
dotF = DotProduct(delta, f);
dotR = DotProduct(delta, r);
dotU = DotProduct(delta, u);
if (dotU < CAR_HEIGHT/2 + WHEEL_RADIUS && dotU > -CAR_HEIGHT/2 - WHEEL_RADIUS){
heightHitOK = qtrue;
}
if (dotF < CAR_LENGTH/2 + WHEEL_RADIUS && dotF > -CAR_LENGTH/2 - WHEEL_RADIUS){
lengthHitOK = qtrue;
}
if (dotR < CAR_WIDTH/2 + WHEEL_RADIUS && dotR > -CAR_WIDTH/2 - WHEEL_RADIUS){
widthHitOK = qtrue;
}
// if (heightHitOK){
#ifdef GAME
// G_LogPrintf( "%d hit %d\n", pm->ps->clientNum, trace.entityNum);
// G_LogPrintf( "trace.endpos : %f, %f, %f\n", trace.endpos[0], trace.endpos[1], trace.endpos[2]);
// G_LogPrintf( "pm->cars[trace.entityNum]->sBody.r : %f, %f, %f\n", pm->cars[trace.entityNum]->sBody.r[0], pm->cars[trace.entityNum]->sBody.r[1], pm->cars[trace.entityNum]->sBody.r[2]);
// G_LogPrintf( "delta : %f, %f, %f\n", delta[0], delta[1], delta[2]);
// G_LogPrintf( "height dist: %f, max %f\n", dotU, CAR_HEIGHT/2 + WHEEL_RADIUS);
// G_LogPrintf( "length dist: %f, max %f\n", dotF, CAR_LENGTH/2 + WHEEL_RADIUS);
// G_LogPrintf( "width dist: %f, max %f\n", dotR, CAR_WIDTH/2 + WHEEL_RADIUS);
// G_LogPrintf( "f : %f, %f, %f\n", f[0], f[1], f[2]);
// G_LogPrintf( "r : %f, %f, %f\n", r[0], r[1], r[2]);
// G_LogPrintf( "u : %f, %f, %f\n", u[0], u[1], u[2]);
#endif
// }
if (widthHitOK){
#ifdef GAME
G_LogPrintf("width is ok\n");
#endif
return qtrue;
}
if (lengthHitOK && heightHitOK && widthHitOK){
#ifdef GAME
G_LogPrintf("all dimensions are ok\n");
#endif
return qtrue;
}
return qfalse;
}
*/
/*
===================
PM_Trace_Points
Traces out the points from current state to next state to check for collisions.
Partially based on the PM_SlideMove function.
===================
*/
#define MAX_CLIP_PLANES 3
static void PM_Trace_Points( car_t *car, carPoint_t *sPoints, carPoint_t *tPoints, float time ){
vec3_t maxs, mins;
vec3_t start, dest, dir;
//vec3_t normal;
trace_t trace;
int i, j;
//float minTrace = 1.0f;
//int hitFirst, hitEnt;
carPoint_t *sPoint, *tPoint;
//int count = 0;
vec3_t hitOrigin;
// new stuff
int bumpcount, numbumps;
float d;
int numplanes;
vec3_t vel;
vec3_t clipVelocity;
int k;
//int l;
float time_left;
float into;
numbumps = 4;
// end
VectorClear(hitOrigin);
// should actually do all points
for( i = 0 ; i < NUM_CAR_POINTS ; i++ ) {
// skip collision detection of suspension points
if( i >= FIRST_FRAME_POINT && i < LAST_FRAME_POINT ) continue;
sPoint = &sPoints[i];
tPoint = &tPoints[i];
VectorSet( mins, -sPoint->radius, -sPoint->radius, -sPoint->radius );
VectorSet( maxs, sPoint->radius, sPoint->radius, sPoint->radius );
if( i >= LAST_FRAME_POINT ) {
mins[2] /= 1.5f;
maxs[2] /= 1.5f;
}
/*
VectorSubtract( car->sBody.r, sPoints[i].r, dir);
if (VectorLength(dir))
VectorScale(dir, 20.0f / VectorLength(dir), dir);
VectorCopy( sPoints[i].r, start );
VectorAdd( start, dir, start );
VectorCopy( tPoints[i].r, dest );
// trace the frame to target position
pm->trace( &trace, start, mins, maxs, dest, pm->ps->clientNum, pm->tracemask );
#ifdef GAME
if (trace.contents & CONTENTS_BODY){
// G_LogPrintf("car was hit\n");
if (trace.startsolid)
Com_Printf("start solid and hit CONTENTS_BODY\n");
if (trace.allsolid)
Com_Printf("all solid and hit CONTENTS_BODY\n");
// need to make it at least a tiny ways
if (trace.fraction != 0.0f){
if (trace.fraction < minTrace){
minTrace = trace.fraction;
VectorClear(hitOrigin);
count = 0;
}
if (trace.fraction == minTrace){
VectorAdd(hitOrigin, trace.endpos, hitOrigin);
count++;
if (g_entities[trace.entityNum].flags & FL_EXTRA_BBOX)
hitEnt = g_entities[trace.entityNum].r.ownerNum;
else
hitEnt = trace.entityNum;
// PM_CheckSurfaceFlags( &trace, &tPoints[i] );
}
// PM_SetFluidDensity(tPoints, i);
// continue;
}
if (trace.fraction == 0)
Com_Printf("fraction == 0 and hit CONTENTS_BODY\n");
// trace the frame to target position but skip other cars
pm->trace( &trace, start, mins, maxs, dest, pm->ps->clientNum, pm->tracemask & ~CONTENTS_BODY );
}
VectorClear(tPoints[i].normals[1]);
#endif
if (trace.contents & CONTENTS_BODY){
Com_Printf("hit body again?\n");
}
// if the frame is flush on the ground
if( trace.fraction == 0.0F ) {
tPoints[i].onGround = qtrue;
if( VectorLength(trace.plane.normal) > 0.0F ) {
VectorCopy( trace.plane.normal, tPoints[i].normals[0] );
}
PM_CheckSurfaceFlags( &trace, &tPoints[i] );
} else if (trace.fraction < 1.0F){
tPoints[i].onGround = qtrue;
VectorCopy( trace.plane.normal, tPoints[i].normals[0] );
PM_CheckSurfaceFlags( &trace, &tPoints[i] );
} else {
tPoints[i].onGround = qfalse;
continue;
}
*/
// move the point in towards the center of the car
// this is to try to stop the wheel bboxes from sitting directly on the surface
// and then not getting a valid trace because it is startsoild
VectorSubtract( car->sBody.r, sPoint->r, dir);
if ( VectorLengthSquared(dir) )
VectorScale( dir, 20.0f / VectorLength(dir), dir );
VectorAdd( sPoint->r, dir, start );
VectorCopy( tPoint->r, dest );
/*
numTraces++;
pm->trace( &trace, sPoints[i].r, mins, maxs, sPoints[i].r, pm->ps->clientNum, pm->tracemask );
if( trace.startsolid )
{
// if we are starting solid then offset the starting point otherwise
// we dont need to.
Com_Printf( "the start is solid!\n" );
// try adding on a non zero velocity and the previous normal vector
// if( VectorLengthSquared( sPoints[i].v ) == 0.0f )
// VectorMA( sPoints[i].lastNonZeroVelocity, -1.0f, sPoints[i].normals[0], dir );
// else
VectorMA( sPoints[i].v, -20.0f, sPoints[i].normals[0], dir );
VectorNormalize( dir );
VectorSubtract( sPoints[i].r, dir, start );
}
else
VectorCopy( sPoints[i].r, start );
VectorCopy( tPoints[i].r, dest );
*/
// OPTIMIZE: just use sPoints[i].v if not startsolid?
// VectorSubtract( dest, start, vel );
VectorSubtract( tPoint->r, start, vel );
VectorScale( vel, 1 / time, vel );
numplanes = 0;
time_left = time;
for ( bumpcount=0 ; bumpcount < numbumps ; bumpcount++ ) {
VectorMA( start, time_left, vel, dest );
// see if we can make it there
numTraces++;
pm->trace ( &trace, start, mins, maxs, dest, pm->ps->clientNum, pm->tracemask);
if ( trace.startsolid && !bumpcount ) {
VectorCopy( sPoint->r, start );
VectorSubtract( dest, start, vel );
VectorScale( vel, 1 / time, vel );
// Com_Printf("Start point %d is in a solid\n", i);
continue;
}
if (trace.allsolid) {
// entity is completely trapped in another solid
// Com_Printf("trace %d is all solid\n", i);
break;
}
if (trace.fraction > 0) {
// actually covered some distance
// VectorCopy ( trace.endpos, start );
VectorScale ( trace.endpos, 0.999f, start );
}
if ( trace.fraction == 1 ) {
if ( !bumpcount ){
tPoint->onGround = qfalse;
VectorClear(tPoint->normals[0]);
VectorClear(tPoint->normals[1]);
VectorClear(tPoint->normals[2]);
}
break; // moved the entire distance
}
time_left -= time_left * trace.fraction;
if ( numplanes >= MAX_CLIP_PLANES ) {
// this shouldn't really happen
// Com_Printf("numplanes >= MAX_CLIP_PLANES\n");
break;
}
//
// if this is the same plane we hit before, nudge velocity
// out along it, which fixes some epsilon issues with
// non-axial planes
//
for ( j = 0 ; j < numplanes; j++ ) {
if ( DotProduct( trace.plane.normal, tPoint->normals[j] ) > 0.99 ) {
VectorAdd( trace.plane.normal, vel, vel );
break;
}
}
if ( j < numplanes ) {
continue;
}
#ifdef GAME
if (trace.contents & CONTENTS_BODY){
if (g_entities[trace.entityNum].flags & FL_EXTRA_BBOX){
hitFirst = g_entities[trace.entityNum].r.ownerNum;
// Com_Printf("Hit extra bbox\n");
}
else
hitFirst = trace.entityNum;
if (g_entities[hitFirst].client){
// G_LogPrintf("car was hit\n");
// if (trace.startsolid)
// Com_Printf("start solid and hit CONTENTS_BODY\n");
// if (trace.allsolid)
// Com_Printf("all solid and hit CONTENTS_BODY\n");
// need to make it at least a tiny ways
if (trace.fraction != 0.0f)
{
if (trace.fraction < minTrace)
{
minTrace = trace.fraction;
VectorClear(hitOrigin);
count = 0;
}
if (trace.fraction == minTrace)
{
VectorAdd(hitOrigin, trace.endpos, hitOrigin);
count++;
hitEnt = hitFirst;
tPoints[i].onGround = qtrue;
// PM_CheckSurfaceFlags( &trace, &tPoints[i] );
}
// PM_SetFluidDensity(tPoints, i);
// continue;
}
else
{
// inside another car
// Com_Printf("fraction == 0 and hit CONTENTS_BODY\n");
}
// trace the frame to target position but skip other cars
// pm->trace( &trace, start, mins, maxs, dest, pm->ps->clientNum, pm->tracemask & ~CONTENTS_BODY );
pm->tracemask &= ~CONTENTS_BODY;
continue;
}
else {
// Com_Printf( "hit non player CONTENTS_BODY\n" );
PM_AddTouchEnt( trace.entityNum, trace.endpos );
pm->tracemask &= ~CONTENTS_BODY;
continue;
}
}
#endif
VectorCopy ( trace.plane.normal, tPoint->normals[numplanes] );
tPoint->onGround = qtrue;
PM_CheckSurfaceFlags( &trace, tPoint );
numplanes++;
//
// modify velocity so it parallels all of the clip planes
//
// find a plane that it enters
for ( j = 0 ; j < numplanes ; j++ ) {
into = DotProduct( vel, tPoint->normals[j] );
if ( into > 0.01f ) {
continue; // move doesn't interact with the plane
}
// see how hard we are hitting things
// if ( -into > pml.impactSpeed ) {
// pml.impactSpeed = -into;
// }
// slide along the plane
VectorMA( vel, -into * OVERCLIP, tPoint->normals[j], clipVelocity );
// PM_ClipVelocity ( vel, tPoints[i].normals[j], clipVelocity, OVERCLIP );
// see if there is a second plane that the new move enters
for ( k = 0 ; k < numplanes ; k++ ) {
if ( k == j ) {
continue;
}
into = DotProduct( clipVelocity, tPoint->normals[k] );
if ( into > 0.01f ) {
continue; // move doesn't interact with the plane
}
// try clipping the move to the plane
VectorMA( clipVelocity, -into * OVERCLIP, tPoint->normals[j], clipVelocity );
// PM_ClipVelocity( clipVelocity, tPoints[i].normals[k], clipVelocity, OVERCLIP );
// see if it goes back into the first clip plane
if ( DotProduct( clipVelocity, tPoint->normals[j] ) >= 0 ) {
continue;
}
// slide the original velocity along the crease
CrossProduct ( tPoint->normals[j], tPoint->normals[k], dir );
VectorNormalize( dir );
d = DotProduct( dir, vel );
VectorScale( dir, d, clipVelocity );
// dont do this because i never see that triple plane interaction message
// so it probably only rarely happens, if ever
/*
// see if there is a third plane the the new move enters
for ( l = 0 ; l < numplanes ; l++ ) {
if ( l == j || l == k ) {
continue;
}
if ( DotProduct( clipVelocity, tPoints[i].normals[l] ) >= 0.1 ) {
continue; // move doesn't interact with the plane
}
// stop dead at a tripple plane interaction
Com_Printf( "triple plane interaction\n" );
return;
}
*/
}
// if we have fixed all interactions, try another move
VectorCopy( clipVelocity, vel );
// VectorCopy( endClipVelocity, endVelocity );
break;
}
}
// NEW- copy final position to the point
// VectorCopy( start, tPoints[i].r );
PM_SetFluidDensity( tPoints, i );
}
#ifdef GAME
if ( count && pm->cars[hitEnt] )
{
float impulseDamage;
// G_LogPrintf( "minTrace %f\n", minTrace );
// G_LogPrintf("count = %d\n", count);
// G_LogPrintf("pml.physicsSplit = %d\n", pml.physicsSplit);
// calculate car position at collision
// G_LogPrintf( "car was hit with %f traced\n", minTrace );
VectorScale(hitOrigin, 1.0f / count, hitOrigin);
// G_LogPrintf("hitOrigin = %f, %f, %f\n", hitOrigin[0], hitOrigin[1], hitOrigin[2]);
PM_CalculateTargetBody(car, &car->sBody, &car->tBody, car->sPoints, car->tPoints, time * minTrace);
// VectorSubtract(car->tBody.r, pm->cars[hitEnt]->sBody.r, normal);
VectorSubtract(hitOrigin, pm->cars[hitEnt]->sBody.r, normal);
VectorNormalize(normal);
impulseDamage = PM_ApplyBodyBodyCollision(&car->tBody, car->tPoints, &pm->cars[hitEnt]->sBody, pm->cars[hitEnt]->sPoints, hitOrigin, normal, 0.25f);
// set normal on this car
for (i = 0; i < 3; i++){
if (!VectorLength(tPoints[hitEnt].normals[i])){
VectorScale(normal, -1.0f, tPoints[hitEnt].normals[i]);
break;
}
}
// damage stuff
VectorCopy(hitOrigin, pm->damage.origin);
VectorCopy(normal, pm->damage.dir);
pm->damage.dflags = DAMAGE_NO_KNOCKBACK;
pm->damage.mod = MOD_CAR_COLLISION;
pm->damage.otherEnt = trace.entityNum;
PM_CopyTargetToSource(&car->tBody, &car->sBody, tPoints, sPoints);
// run physics again but remove CONTENTS_BODY first so it cant collide with cars
pml.physicsSplit++;
pm->tracemask &= ~CONTENTS_BODY;
PM_DriveMove(car, time * (1.0f - minTrace), qfalse);
}
#endif
// break the frame up into parts
// fix this so it doesnt cause infinite loops
/*
if (minTrace < 1.0f){
// Check for touching multiple surfaces with different normals
// (wheel on ground and against wall)
VectorCopy( sPoints[hitFirst].r, start );
VectorCopy( tPoints[hitFirst].r, dest );
VectorSubtract(dest, start, dir);
length = VectorNormalize(dir);
VectorMA(start, (length * minTrace) - 0.05f, dir, start);
VectorClear(normal);
for (i = 0; i < 3; i++){
VectorCopy(start, dest);
dest[i] += dir[i] > 0.0f ? 0.10f : -0.10f;
pm->trace( &trace, start, mins, maxs, dest, pm->ps->clientNum, pm->tracemask );
if (trace.fraction < 1.0F){
uniqueNormal = qtrue;
for(j = 0; j < 3; j++){
if (i == j) continue;
if(VectorCompare(trace.plane.normal, tPoints[hitFirst].normals[j])){
uniqueNormal = qfalse;
break;
}
}
if (uniqueNormal && VectorLength(trace.plane.normal)){
VectorCopy(trace.plane.normal, tPoints[hitFirst].normals[i]);
}
}
}
if (pml.physicsSplit > 5){
// Com_Printf("Breaking out of physics loop\n");
}
else {
pml.physicsSplit++;
PM_CalculateTargetBody(car, &car->sBody, &car->tBody, car->sPoints, car->tPoints, time * minTrace);
PM_CopyTargetToSource(&car->tBody, &car->sBody, car->tPoints, car->sPoints);
PM_DriveMove(car, time * (1.0f - minTrace));
}
// }
}
*/
}
//int trap_Milliseconds( void );
/*
================================================================================
PM_DriveMove
The mother functioner of Q3Rally car movement. Calculates the forces and torques
on the car, integrates to get the next state and traces to handle collisions with
the world and other objects.
================================================================================
*/
void PM_DriveMove( car_t *car, float time, qboolean includeBodies )
{
int i;
//int t, t1, t2, t3, t4, t5;
if( car->initializeOnNextMove )
{
PM_InitializeVehicle( car, pm->ps->origin, pm->ps->viewangles, pm->ps->velocity );
car->initializeOnNextMove = qfalse;
}
//t = trap_Milliseconds();
numTraces = 0;
if (pml.physicsSplit > 2){
Com_Printf("forcing end of frame\n");
return;
}
if (time == 0)
return;
// if ( VectorNAN(car->sBody.r) || VectorNAN(car->sBody.v) || VectorNAN(car->sBody.L) )
if ( VectorNAN(car->sBody.r) || VectorNAN(car->sBody.v) ||
car->sBody.L[0] > 1<<25 ||
car->sBody.L[1] > 1<<25 ||
car->sBody.L[2] > 1<<25 )
{
// Com_Printf( "Blowing up car because of car body\n" );
pm->damage.damage = 32000;
pm->damage.dflags = DAMAGE_NO_PROTECTION;
pm->damage.otherEnt = -1;
pm->damage.mod = MOD_HIGH_FORCES;
return;
}
CP_CURRENT_GRAVITY = (pm->ps->gravity / 800.0f) * CP_GRAVITY;
// set spring strengths for "jump" and "crouch"
// FIXME: change this so it works in cgame and game
#if GAME
car->springStrength = pm->car_spring * (CP_FRAME_MASS / 350.0f) * CP_GRAVITY;
#else
car->springStrength = CP_SPRING_STRENGTH;
#endif
// car->shockStrength = CP_SHOCK_STRENGTH;
if (pm->cmd.upmove > 0){
car->springStrength *= 5.0f;
}
else if (pm->cmd.upmove < 0){
car->springStrength /= 5.0f;
}
pm->damage.damage = 0;
pm->damage.otherEnt = -1;
//t1 = trap_Milliseconds();
// calculate target positions etc
// -------------------------------------------------------------------------
// calculate forces
PM_CalculateForces(car, &car->sBody, car->sPoints, time);
//t2 = trap_Milliseconds();
// apply frame forces to the body
// for (i = FIRST_FRAME_POINT; i < LAST_FRAME_POINT; i++){
for (i = FIRST_FRAME_POINT; i < NUM_CAR_POINTS; i++){
PM_ApplyForce(&car->sBody, car->sPoints[i].netForce, car->sPoints[i].r);
// }
// for (i = FIRST_FRAME_POINT; i < NUM_CAR_POINTS; i++){
if (!car->sPoints[i].onGround) continue;
PM_CarBodyFrictionForces( car, &car->sBody, car->sPoints, i );
if (VectorNAN(car->sPoints[i].netForce))
Com_Printf("Blowing up car because of car point force on frame\n");
}
//t3 = trap_Milliseconds();
// print out the forces
/*
if (pm->pDebug > 0 && pm->pDebug <= 8){
if (pm->client)
Com_Printf("client sBody -----------------------------------------\n");
else
Com_Printf("server sBody -----------------------------------------\n");
PM_DebugForces(&car->sBody, car->sPoints);
PM_DebugDynamics(&car->sBody, car->sPoints);
}
*/
PM_CalculateTargetBody(car, &car->sBody, &car->tBody, car->sPoints, car->tPoints, time);
//t4 = trap_Milliseconds();
PM_Trace_Points(car, car->sPoints, car->tPoints, time);
//t5 = trap_Milliseconds();
// print out the forces
/*
if (pm->pDebug > 0 && pm->pDebug <= 8){
if (pm->client)
Com_Printf("client tBody -----------------------------------------\n");
else
Com_Printf("server tBody -----------------------------------------\n");
// PM_DebugForces(&car->tBody, car->tPoints);
PM_DebugDynamics(&car->tBody, car->tPoints);
}
*/
PM_CopyTargetToSource(&car->tBody, &car->sBody, car->tPoints, car->sPoints);
// clear forces at the end of the frame instead of at the beginning so we
// can apply forces from nearby explosions between frames
PM_ClearCarForces(&car->sBody, car->sPoints);
// #ifdef CGAME
// Com_Printf( "numTraces %i\n", numTraces );
// #endif
// Com_Printf( "t1 %d, t2 %d, t3 %d, t4 %d, t5 %d\n", t1 - t, t2 - t, t3 - t, t4 - t, t5 - t );
}