openmohaa/code/fgame/VehicleWheelsX2.cpp

688 lines
20 KiB
C++

/*
===========================================================================
Copyright (C) 2023 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA 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.
OpenMoHAA 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "vehicle.h"
#include "player.h"
#include "debuglines.h"
#include "g_phys.h"
CLASS_DECLARATION(DrivableVehicle, VehicleWheelsX2, "VehicleWheelsX2") {
{&EV_Damage, &VehicleWheelsX2::EventDamage},
{&EV_Killed, &VehicleWheelsX2::Killed },
{NULL, NULL }
};
VehicleWheelsX2::VehicleWheelsX2()
{
gravity = 1.0;
m_fDifferentialRatio = 4.88f;
m_fGearEfficiency = 0.7f;
m_fGearRatio[0] = -2.95f;
m_fGearRatio[1] = 2.95f;
m_fGearRatio[2] = 1.95f;
m_fGearRatio[3] = 1.0f;
m_iGear = 1;
m_iRPM = 0;
m_fAccelerator = 0.0f;
m_bAutomatic = qtrue;
m_bBackSlipping = qfalse;
m_bFrontSlipping = qfalse;
}
void VehicleWheelsX2::Killed(Event *ev)
{
deadflag = DEAD_DEAD;
}
float VehicleWheelsX2::TorqueLookup(int rpm)
{
if (rpm < 2100) {
return 100.0;
} else if (rpm >= 3600) {
return 0.0;
} else {
return (100 * (3600 - rpm)) / 1000.f;
}
}
void VehicleWheelsX2::UpdateVariables(
Vector *acceleration, Vector *vpn, Vector *vup, Vector *vright, Vector *t_vpn, Vector *t_vup, Vector *t_vright
)
{
int i;
Vector vNewCG;
Vector vBoxSize;
float longspeed;
Vector _i, _j, _k;
Vector r_i, r_j, r_k;
Vector r_velocity;
float dots[4];
r_velocity = velocity;
m_bIsSkidding = false;
vBoxSize = maxs - mins;
SetControllerAngles(0, Vector(0, turnangle, 0));
SetControllerAngles(1, Vector(0, turnangle, 0));
vNewCG = localorigin;
if (vNewCG != m_vCG) {
m_vCG = vNewCG;
vNewCG -= localorigin;
m_fWheelBase = vBoxSize.x;
m_fWheelFrontDist = vBoxSize.x * 0.5 - vNewCG.x;
m_fWheelBackDist = vBoxSize.x * -0.5 + vNewCG.x;
m_fTrackWidth = vBoxSize.y;
}
Vector vNewVpn = angles;
vNewVpn.AngleVectorsLeft(&r_i, &r_j, &r_k);
vNewVpn.y += turnangle;
vNewVpn.AngleVectorsLeft(&_i, &_j, &_k);
dots[3] = r_velocity * *vpn * 0.02;
dots[2] = r_velocity * *vright * 0.02;
dots[1] = r_velocity * *t_vpn * 0.02;
dots[0] = r_velocity * *t_vright * 0.02;
m_sWheels[0].fTraction = 0.8f;
m_sWheels[1].fTraction = 0.9f;
m_sWheels[0].fYawOffset = 0;
m_sWheels[1].fYawOffset = DEG2RAD(turnangle);
m_sWheels[0].fLongDist = m_fWheelBackDist * 0.02;
m_sWheels[1].fLongDist = m_fWheelFrontDist * 0.02;
m_sWheels[0].fLatDist = m_fTrackWidth * -0.02;
m_sWheels[1].fLatDist = m_fTrackWidth * 0.02;
// initialize the wheels
for (i = 0; i < 2; i++) {
m_sWheels[i].fLongForce = 0;
m_sWheels[i].fForce = 0;
m_sWheels[i].vLongForce = vec_zero;
m_sWheels[i].vLatForce = vec_zero;
m_sWheels[i].vForce = vec_zero;
m_sWheels[i].vVelocity = vec_zero;
m_sWheels[i].bSkidding = false;
m_sWheels[i].bSpinning = false;
m_sWheels[i].fTorque = 0;
m_sWheels[i].fLatTorque = 0;
m_sWheels[i].fLongTorque = 0;
}
for (i = 0; i < 2; i++) {
Vector p1, p3, p4;
Vector vTp, vTr, vTu;
vec3_t p2;
p3 = Corners[i];
p3.normalize();
vectoangles(p3, p2);
p1 = vec_zero;
p1[1] = angles[1] + p2[1] + 90;
p1.AngleVectorsLeft(&vTp, &vTr, &vTu);
m_sWheels[i].vVelocity = vTp * RAD2DEG(m_vAngularVelocity.y) * fabs(m_sWheels[i].fLongDist) * level.frametime;
G_LineWidth(5.0);
G_DebugLine(
origin + Corners[i][0] * r_i + Corners[i][1] * r_j,
origin + Corners[i][0] * r_i + Corners[i][1] * r_j + m_sWheels[i].vVelocity * 50,
0.7f,
0.5f,
1.0f,
1.0f
);
p1 = vec_zero;
p1[1] = angles[1] + RAD2DEG(m_sWheels[i].fYawOffset);
p1.AngleVectorsLeft(&vTp, &vTr, &vTu);
m_sWheels[i].vOrientation = vTp;
G_LineWidth(5.0);
G_DebugLine(
origin + Corners[i][0] * r_i + Corners[i][1] * r_j,
origin + Corners[i][0] * r_i + Corners[i][1] * r_j + m_sWheels[i].vOrientation * 50,
0.7f,
0.0f,
1.0f,
1.0f
);
}
Vector start, end;
Vector boxoffset;
Vector t_mins(-8, -8, -8);
Vector t_maxs(8, 8, 8);
trace_t trace;
start = origin + *vpn * (m_vCG[0] - localorigin[0]) + *vright * (m_vCG[1] - localorigin[1])
+ *vup * (m_vCG[2] - localorigin[2]);
end = start;
end[2] -= maxtracedist;
trace = G_Trace(start, t_mins, t_maxs, end, this, MASK_SOLID, qfalse, "Vehicle::PostThink Corners");
m_fInertia = m_fMass * 100;
m_fWheelFrontLoad = m_fMass * 9.81 * fabs(m_fWheelFrontDist / m_fWheelBase);
m_fWheelBackLoad = m_fMass * 9.81 * fabs(m_fWheelBackDist / m_fWheelBase);
longspeed = m_vAcceleration.length() * 0.02 * m_fMass / m_fWheelBase;
m_fWheelFrontLoad -= longspeed;
m_fWheelBackLoad += longspeed;
m_sWheels[0].fLoad = m_fWheelBackLoad;
m_sWheels[1].fLoad = m_fWheelFrontLoad;
float fMassShifted = m_fTireRadius * M_PI;
m_fTireRotationalSpeed = dots[3] / (fMassShifted + fMassShifted);
m_iRPM = m_fTireRotationalSpeed * m_fGearRatio[m_iGear] * m_fDifferentialRatio * 10;
m_fMaxTraction =
TorqueLookup(m_iRPM) * m_fGearRatio[m_iGear] * m_fDifferentialRatio * m_fGearEfficiency / m_fTireRadius;
m_fTractionForce = m_fAccelerator * m_fMaxTraction;
if (m_bIsBraking && dots[3] > 0) {
m_fTractionForce = -fSign(dots[3]) * 1000 * dots[3];
}
for (i = 0; i < 2; i++) {
float maxgrip;
maxgrip = m_sWheels[i].fTraction * m_sWheels[i].fLoad * 9.81;
if (m_fTractionForce > maxgrip) {
m_fTractionForce = maxgrip * 0.6;
m_sWheels[i].bSpinning = true;
m_sWheels[i].bSkidding = true;
Com_Printf("Skidding!\n");
}
if (m_fTractionForce < -maxgrip) {
m_fTractionForce = -maxgrip * 0.6;
m_sWheels[i].bSkidding = true;
Com_Printf("Skidding!\n");
}
m_sWheels[i].fLongForce = this->m_sWheels[i].fLongForce + m_fTractionForce;
m_sWheels[i].vLongForce += m_sWheels[i].vOrientation * m_sWheels[i].fLongForce;
}
m_vResistance[0] =
-(velocity[0] * 0.02 * m_fRollingResistance + velocity[0] * 0.02 * m_fDrag * fabs(velocity[0] * 0.02));
m_vResistance[1] =
-(velocity[1] * 0.02 * m_fRollingResistance + velocity[1] * 0.02 * m_fDrag * fabs(velocity[1] * 0.02));
m_vResistance[2] =
-(velocity[2] * 0.02 * m_fRollingResistance + velocity[2] * 0.02 * m_fDrag * fabs(velocity[2] * 0.02));
m_fBrakingPerformance = -m_fRollingResistance - *vpn * Vector(0, 0, -1) * m_fMass;
if (dots[3] == 0) {
m_fBrakingPerformance = 0;
} else {
m_fBrakingPerformance = (dots[3] - (m_fTireRotationalSpeed * m_fTireRadius - m_fBrakingPerformance)) / dots[3];
m_fBrakingPerformance = (1.0 - m_fBrakingPerformance) * -(-m_fFrontBrakingForce - m_fBackBrakingForce);
}
if (r_velocity.length() * 0.02 > 0.01) {
m_fTangForce = 0;
m_vTangForce = vec_zero;
for (i = 0; i < 2; i++) {
Vector va, vr;
Vector vVel;
Vector v1, v2;
va = angles;
va.x = 0;
va.y += RAD2DEG(m_sWheels[i].fYawOffset);
va.z = 0;
va.AngleVectorsLeft(NULL, &vr, NULL);
vVel = m_sWheels[i].vVelocity + velocity;
// get the direction
v1 = vVel;
v1.normalize();
// get the angles
vectoangles(v1, v2);
m_sWheels[i].fSlipAngle = DEG2RAD(angledist(va.y - v2.y));
m_sWheels[i].fGripCoef = m_sWheels[i].fSlipAngle * 0.15;
m_sWheels[i].fGripCoef = Q_clamp_float(m_sWheels[i].fGripCoef, -1.2f, 1.2f);
m_sWheels[i].fLatForce += m_sWheels[i].fGripCoef * m_sWheels[i].fLoad * 9.81;
if (m_sWheels[i].fLoad * 2 * m_sWheels[i].fTraction < fabs(m_sWheels[i].fLatForce)) {
m_sWheels[i].fLatForce = m_sWheels[i].fLatForce / 9.81;
m_sWheels[i].bSkidding = true;
Com_Printf("Skidding %i!\n", i);
}
m_sWheels[i].vLatForce += vr * m_sWheels[i].fLatForce;
G_LineWidth(3.0);
G_DebugLine(
origin + Corners[i][0] * r_i + Corners[i][1] * r_j,
origin + Corners[i][0] * r_i + Corners[i][1] * r_j + m_sWheels[i].vLatForce * 50 * (1.0 / m_fMass),
0.7f,
0.5f,
0.2f,
1.0f
);
}
} else {
m_fTangForce = 0;
m_vTangForce = vec_zero;
}
m_vForce = vec_zero;
for (i = 0; i < 2; i++) {
m_sWheels[i].vForce = m_sWheels[i].vLatForce + m_sWheels[i].vLongForce;
m_vForce += m_sWheels[i].vForce;
}
m_vForce += m_vResistance;
G_LineWidth(5.0);
G_DebugLine(origin, origin + m_vTangForce * (1.0 / m_fMass) * 50, 1.0, 0.0, 1.0, 1.0);
m_vAcceleration = m_vForce * (1.0 / m_fMass) * 50;
m_fTorque = 0;
for (i = 0; i < 2; i++) {
m_sWheels[i].fTorque += m_sWheels[i].fLongTorque + m_sWheels[i].fLatTorque;
m_fTorque += m_sWheels[i].fTorque;
}
m_vAngularAcceleration[1] = angledist(RAD2DEG(m_fTorque));
m_vAngularVelocity += m_vAngularAcceleration * level.frametime;
m_vAngularVelocity *= 0.825f;
avelocity = m_vAngularVelocity;
if (m_bAutomatic) {
//
// set the gear
//
if (m_iRPM >= 2100 && m_iGear <= 2) {
m_iGear++;
}
if (m_iRPM <= 600 && m_iGear > 1) {
m_iGear--;
}
}
prev_moveimpulse = moveimpulse;
}
void VehicleWheelsX2::Think()
{
vmove_t vm;
flags |= FL_POSTTHINK;
prev_velocity = velocity;
SetMoveInfo(&vm);
VmoveSingle(&vm);
GetMoveInfo(&vm);
}
void VehicleWheelsX2::Postthink()
{
float turn;
Vector i, j, k;
int index;
trace_t trace;
Vector normalsum;
Vector normalsum_Front;
int numnormals_Front;
Vector normalsum_Back;
int numnormals_Back;
Vector temp;
Vector pitch, roll;
Vector acceleration;
Vector atmp, atmp2, aup;
VehicleBase *v;
VehicleBase *last;
float drivespeed;
Vector primal_angles;
Vector vTmp;
Vector n_angles;
primal_angles = angles;
if (drivable) {
currentspeed = moveimpulse;
turnangle = turnangle * 0.25f + turnimpulse;
turnangle = Q_clamp_float(turnangle, -maxturnrate, maxturnrate);
if (turnangle > maxturnrate) {
turnangle = maxturnrate;
} else if (turnangle < -maxturnrate) {
turnangle = -maxturnrate;
} else if (fabs(turnangle) < 2) {
turnangle = 0;
}
real_velocity = origin - prev_origin;
prev_origin = origin;
prev_acceleration = real_acceleration;
real_acceleration = real_velocity - prev_velocity;
prev_velocity = real_velocity;
acceleration = real_acceleration - prev_acceleration;
angles[0] = 0;
angles[2] = 0;
temp[0] = angles[0];
temp[1] = angles[1];
temp[2] = angles[2];
temp.AngleVectorsLeft(&i, &j, &k);
j = vec_zero - j;
index = 0;
numnormals_Front = 0;
numnormals_Back = 0;
for (index = 0; index < 4; index++) {
Vector start, end;
Vector boxoffset;
Vector t_mins(-8, -8, -8);
Vector t_maxs(8, 8, 8);
boxoffset = Corners[index];
start = origin + i * boxoffset[0] + j * boxoffset[1] + k * boxoffset[2];
end = start;
end[2] -= maxtracedist;
trace = G_Trace(start, mins, maxs, end, this, MASK_SOLID, qfalse, "Vehicle::PostThink Corners");
if (trace.fraction != 1 && !trace.startsolid) {
if (index > 2) {
normalsum_Front += trace.plane.normal;
numnormals_Front++;
} else {
normalsum_Back += trace.plane.normal;
numnormals_Back++;
}
normalsum += trace.plane.normal;
}
}
if (angles[2] < 60 || angles[2] > 300) {
if (numnormals_Front > 1) {
m_vFrontNormal = normalsum_Front * (1.0 / numnormals_Front);
temp = m_vFrontNormal;
m_vFrontAngles[0] = -temp.CrossProduct(temp, j).toPitch();
// no yaw
m_vFrontAngles[1] = 0;
temp = m_vFrontNormal;
m_vFrontAngles[2] = temp.CrossProduct(temp, i).toPitch();
}
if (numnormals_Back > 1) {
m_vBackNormal = normalsum_Back * (1.0 / numnormals_Back);
temp = m_vBackNormal;
m_vBackAngles[0] = -temp.CrossProduct(temp, j).toPitch();
// no yaw
m_vBackAngles[1] = 0;
temp = m_vBackNormal;
m_vBackAngles[2] = temp.CrossProduct(temp, i).toPitch();
}
if (numnormals_Front + numnormals_Back > 1) {
m_vBaseNormal = (normalsum_Front + normalsum_Back) * (1.0 / numnormals_Front + numnormals_Back);
temp = m_vBaseNormal;
m_vBaseAngles[0] = -temp.CrossProduct(temp, j).toPitch();
// no yaw
m_vBaseAngles[1] = 0;
temp = m_vBaseNormal;
m_vBaseAngles[2] = temp.CrossProduct(temp, i).toPitch();
}
}
n_angles = m_vBaseAngles - angles;
n_angles[0] = angledist(n_angles[0]);
n_angles[1] = angledist(n_angles[1]);
n_angles[2] = angledist(n_angles[2]);
angles[0] += n_angles[0] * 4.5 * level.frametime;
angles[2] += n_angles[2] * 4.5 * level.frametime;
m_vPrevNormal = m_vBaseNormal;
if (currentspeed < 0 && velocity * i > 0) {
m_fFrontBrakingForce = 0.2f;
m_fBackBrakingForce = 0.1f;
} else {
m_fFrontBrakingForce = 0;
m_fBackBrakingForce = 0;
}
turn = turnangle * 0.005;
Vector _i, _j, _k;
Vector vNewVpn = angles;
vNewVpn.y += turnangle;
vNewVpn.AngleVectorsLeft(&_i, &_j, &_k);
UpdateVariables(&acceleration, &i, &j, &k, &_i, &_j, &_k);
m_vAngles = angles;
if (currentspeed < 0 && velocity * i > 0) {
currentspeed = 0;
}
normalsum = vec_zero;
for (index = 0; index < 4; index++) {
Vector start, end;
Vector boxoffset;
Vector t_mins(-8, -8, -8);
Vector t_maxs(8, 8, 8);
boxoffset = Corners[index];
start = origin + i * boxoffset[0] + j * boxoffset[1] + k * boxoffset[2] + Vector(0, 0, 10);
end = start;
end[2] -= maxtracedist;
trace = G_Trace(start, mins, maxs, end, this, MASK_SOLID, qfalse, "Vehicle::PostThink Corners");
if (trace.fraction != 1.0) {
float fCoef;
if (index > 2) {
fCoef = m_fWheelFrontLoad / (m_fWheelFrontDist / m_fWheelBase * m_fMass);
fCoef = Q_clamp_float(fCoef, 0, 1);
normalsum += _i * currentspeed * fCoef;
normalsum += i * currentspeed * (1.0 - fCoef);
} else {
fCoef = m_fWheelBackLoad / (m_fWheelBackDist / m_fWheelBase * m_fMass);
fCoef = Q_clamp_float(fCoef, 0, 1);
normalsum += i * currentspeed * fCoef;
normalsum += _i * currentspeed * (1.0 - fCoef);
}
}
}
AnglesToAxis(angles, orientation);
vmove_t vm;
SetMoveInfo(&vm);
m_sMoveGrid->SetMoveInfo(&vm);
m_sMoveGrid->SetOrientation(orientation);
if (m_sMoveGrid->CheckStuck()) {
angles = primal_angles;
AnglesToAxis(angles, orientation);
}
if (groundentity && (angles[2] < 60 || angles[2] > 300)) {
float dot;
Vector newvel, flatvel;
velocity[0] *= 0.925f;
velocity[1] *= 0.925f;
newvel = orientation[0];
velocity += newvel * currentspeed;
newvel[2] = 0;
dot = velocity * newvel;
dot = Q_clamp_float(dot, -speed, speed);
if (dot > speed) {
dot = speed;
} else if (dot < -speed) {
dot = -speed;
} else if (fabs(dot) < 20) {
dot = 0;
}
flatvel = newvel * dot;
velocity[0] = flatvel[0];
velocity[1] = flatvel[1];
velocity[2] += jumpimpulse * dot;
avelocity *= 0.05f;
if (steerinplace) {
if (dot < 350) {
dot = 350;
}
avelocity.y += turn * dot;
} else {
avelocity.y += turn * dot;
}
} else {
if (angles[2] > 60) {
angles[2]; // FIXME: ??
}
velocity += m_vAcceleration * level.frametime;
}
angles += avelocity * level.frametime;
setAngles(angles);
prev_origin = origin;
}
drivespeed = acceleration * orientation[0];
if (drivable && driver.ent) {
str sound_name;
if (currentspeed > 0) {
sound_name = "snd_forward";
} else if (currentspeed == 0) {
sound_name = "snd_idle";
} else {
sound_name = "snd_backward";
}
LoopSound(sound_name);
}
i = orientation[0];
j = orientation[1];
k = orientation[2];
if (driver.ent) {
driver.ent->setOrigin(origin + i * driveroffset[0] + j * driveroffset[1] + k * driveroffset[2]);
if (drivable) {
// clear the driver velocity
driver.ent->velocity = vec_zero;
// set the driver angles to the vehicle's angle
driver.ent->setAngles(angles);
}
}
vTmp = (angles - primal_angles) * level.frametime;
if (vTmp.x > 180 || vTmp.x < -180) {
vTmp.x = 0;
}
if (vTmp.y > 180 || vTmp.y < -180) {
vTmp.y = 0;
}
if (vTmp.z > 180 || vTmp.z < -180) {
vTmp.z = 0;
}
if (vTmp.x > -1 || vTmp.x < 1) {
vTmp.x = 0;
}
if (vTmp.y > -1 || vTmp.y < 1) {
vTmp.y = 0;
}
if (vTmp.z > -1 || vTmp.z < 1) {
vTmp.z = 0;
}
avelocity = vTmp;
for (last = this; last->vlink; last = v) {
v = last->vlink;
v->setOrigin(origin + i * v->offset.x + j * v->offset.y + k * v->offset.z);
v->avelocity = avelocity;
v->velocity = velocity;
v->angles[ROLL] = angles[ROLL];
v->angles[YAW] = angles[YAW];
v->angles[PITCH] = (int)(drivespeed / 4.f + v->angles[PITCH]) % 360;
v->setAngles(v->angles);
}
CheckWater();
WorldEffects();
last_origin = origin;
if (!driver.ent && !velocity.length() && groundentity && !(watertype & CONTENTS_LAVA)) {
// make the vehicle stationary if it's completely immobile
flags &= ~FL_POSTTHINK;
if (drivable) {
setMoveType(MOVETYPE_STATIONARY);
}
}
}