openmohaa/code/fgame/g_vmove.cpp
smallmodel c6e3dfdbe6
Fixed velocity clip (sliding along plane)
This fixes the issue where vehicles would not be moving precisely where it should be moving (incorrect clipping of the velocity). For example, the minesweeper tank in e1l2 kept getting stuck because it didn't move appropriately (fixes #261)
2024-03-04 22:40:49 +01:00

648 lines
19 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
===========================================================================
*/
// g_vmove.cpp : Vehicle movement code
//
#include "g_local.h"
#include "entity.h"
#include "movegrid.h"
typedef struct {
qboolean validGroundTrace;
float previous_origin[3];
float previous_velocity[3];
} vml_t;
vmove_t *vm;
vml_t vml;
void VM_ClipVelocity(float *in, float *normal, float *out, float overbounce)
{
float backoff;
float dir_z;
float normal2[3];
if (normal[2] >= 0.70f) {
if (in[0] == 0.0f && in[1] == 0.0f) {
VectorClear(out);
return;
}
normal2[0] = in[0] + DotProduct2D(in, normal);
normal2[1] = in[1] + DotProduct2D(in, normal);
normal2[2] = normal[2] * DotProduct2D(in, in);
VectorNormalize(normal2);
dir_z = -normal2[2];
out[0] = in[0];
out[1] = in[1];
out[2] = DotProduct2D(in, normal2) / dir_z;
} else {
backoff = DotProduct(in, normal);
if (backoff < 0) {
backoff *= overbounce;
} else {
backoff /= overbounce;
}
out[0] = in[0] - normal[0] * backoff;
out[1] = in[1] - normal[1] * backoff;
out[2] = in[2] - normal[2] * backoff;
}
}
void VM_AddTouchEnt(int entityNum)
{
int i;
if (entityNum == ENTITYNUM_NONE || entityNum == ENTITYNUM_WORLD) {
return;
}
if (vm->numtouch > 32) {
return;
}
// see if it is already added
for (i = 0; i < vm->numtouch; i++) {
if (vm->touchents[i] == entityNum) {
return;
}
}
// add it
vm->touchents[vm->numtouch] = entityNum;
vm->numtouch++;
}
qboolean VM_SlideMove(qboolean gravity)
{
int bumpcount;
vec3_t dir;
float d;
int numplanes;
vec3_t planes[5];
vec3_t clipVelocity;
int i;
int j;
int k;
trace_t trace;
vec3_t end;
float time_left;
vec3_t endVelocity;
vec3_t endClipVelocity;
VectorCopy(vm->vs->velocity, endVelocity);
if (gravity) {
endVelocity[2] = vm->vs->velocity[2] - vm->frametime * sv_gravity->integer;
vm->vs->velocity[2] = (vm->vs->velocity[2] + endVelocity[2]) * 0.5;
if (vm->vs->groundPlane) {
VM_ClipVelocity(vm->vs->velocity, vm->vs->groundTrace.plane.normal, vm->vs->velocity, OVERCLIP);
}
}
time_left = vm->frametime;
if (vm->vs->groundPlane) {
numplanes = 1;
VectorCopy(vm->vs->groundTrace.plane.normal, planes[0]);
} else {
numplanes = 0;
}
// never turn against original velocity
VectorNormalize2(vm->vs->velocity, planes[numplanes]);
numplanes++;
for (bumpcount = 0; bumpcount < 4; bumpcount++) {
// calculate position we are trying to move to
VectorMA(vm->vs->origin, time_left, vm->vs->velocity, end);
// see if we can make it there
gi.trace(&trace, vm->vs->origin, vm->mins, vm->maxs, end, vm->vs->entityNum, vm->tracemask, qtrue, qfalse);
if (trace.allsolid) {
if (vm->vs->velocity[0] || vm->vs->velocity[1]) {
if (vm->vs->groundPlane) {
VectorCopy(vm->vs->velocity, dir);
VectorNegate(dir, dir);
VectorNormalize(dir);
if (!vm->vs->hit_obstacle) {
vm->vs->hit_obstacle = qtrue;
VectorCopy(vm->vs->origin, vm->vs->hit_origin);
}
VectorAdd(vm->vs->obstacle_normal, dir, vm->vs->obstacle_normal);
}
VectorClear(vm->vs->velocity);
VM_AddTouchEnt(trace.entityNum);
return qtrue;
}
vm->vs->velocity[2] = 0;
bumpcount = 0;
break;
}
if (trace.fraction > 0) {
// actually covered some distance
VectorCopy(trace.endpos, vm->vs->origin);
}
if (trace.fraction == 1) {
break;
}
if (trace.plane.normal[2] >= MIN_WALK_NORMAL) {
memcpy(&vm->vs->groundTrace, &trace, sizeof(vm->vs->groundTrace));
vml.validGroundTrace = qtrue;
} else if (trace.plane.normal[2] > -0.999f && vm->vs->groundPlane) {
if (!vm->vs->hit_obstacle) {
vm->vs->hit_obstacle = qtrue;
VectorCopy(vm->vs->origin, vm->vs->hit_origin);
}
VectorAdd(vm->vs->obstacle_normal, trace.plane.normal, vm->vs->obstacle_normal);
}
// save entity for contact
VM_AddTouchEnt(trace.entityNum);
time_left -= time_left * trace.fraction;
if (numplanes >= MAX_CLIP_PLANES) {
VectorClear(vm->vs->velocity);
return qtrue;
}
//
// if this is the same plane we hit before, nudge velocity
// out along it, which fixes some epsilon issues with
// non-axial planes
//
for (i = 0; i < numplanes; i++) {
if (DotProduct(trace.plane.normal, planes[i]) > 0.99) {
VectorAdd(trace.plane.normal, vm->vs->velocity, vm->vs->velocity);
break;
}
}
if (i >= numplanes) {
//
// modify velocity so it parallels all of the clip planes
//
VectorCopy(trace.plane.normal, planes[numplanes]);
numplanes++;
// find a plane that it enters
for (i = 0; i < numplanes; i++) {
if (DotProduct(vm->vs->velocity, planes[i]) >= 0.1) {
continue; // move doesn't interact with the plane
}
// slide along the plane
VM_ClipVelocity(vm->vs->velocity, planes[i], clipVelocity, OVERCLIP);
VM_ClipVelocity(endVelocity, planes[i], endClipVelocity, OVERCLIP);
// see if there is a second plane that the new move enters
for (j = 0; j < numplanes; j++) {
if (j == i) {
continue;
}
// slide along the plane
VM_ClipVelocity(clipVelocity, planes[j], clipVelocity, OVERCLIP);
VM_ClipVelocity(endClipVelocity, planes[j], endClipVelocity, OVERCLIP);
if (DotProduct(clipVelocity, planes[j]) >= 0.0f) {
continue; // move doesn't interact with the plane
}
// slide the original velocity along the crease
CrossProduct(planes[i], planes[j], dir);
VectorNormalize(dir);
d = DotProduct(dir, vm->vs->velocity);
VectorScale(dir, d, clipVelocity);
d = DotProduct(dir, endVelocity);
VectorScale(dir, d, endClipVelocity);
// see if there is a third plane the the new move enters
for (k = 0; k < numplanes; k++) {
if (k == i || k == j) {
continue;
}
if (DotProduct(clipVelocity, planes[k]) >= 0.1f) {
continue; // move doesn't interact with the plane
}
// stop dead at a tripple plane interaction
VectorClear(vm->vs->velocity);
return qtrue;
}
}
// if we have fixed all interactions, try another move
VectorCopy(clipVelocity, vm->vs->velocity);
VectorCopy(endClipVelocity, endVelocity);
break;
}
}
}
if (gravity) {
VectorCopy(endVelocity, vm->vs->velocity);
}
return bumpcount != 0;
}
static void VM_GroundTraceInternal2(void);
void VM_GroundTraceInternal(void)
{
if (vm->vs->groundTrace.fraction == 1) {
vm->vs->groundEntityNum = ENTITYNUM_NONE;
vm->vs->groundPlane = qfalse;
vm->vs->walking = qfalse;
return;
}
if (vm->vs->velocity[2] > 0.0f && DotProduct(vm->vs->velocity, vm->vs->groundTrace.plane.normal) > 10.0f) {
vm->vs->groundEntityNum = ENTITYNUM_NONE;
vm->vs->groundPlane = qfalse;
vm->vs->walking = qfalse;
return;
}
// slopes that are too steep will not be considered onground
if (vm->vs->groundTrace.plane.normal[2] < MIN_WALK_NORMAL) {
vec3_t oldvel;
float d;
VectorCopy(vm->vs->velocity, oldvel);
VectorSet(vm->vs->velocity, 0, 0, -1.0f / vm->frametime);
VM_SlideMove(qfalse);
d = VectorLength(vm->vs->velocity);
VectorCopy(oldvel, vm->vs->velocity);
if (d > (0.1f / vm->frametime)) {
vm->vs->groundEntityNum = ENTITYNUM_NONE;
vm->vs->groundPlane = qtrue;
vm->vs->walking = qfalse;
return;
}
}
vm->vs->groundPlane = qtrue;
vm->vs->walking = qtrue;
vm->vs->groundEntityNum = vm->vs->groundTrace.entityNum;
VM_AddTouchEnt(vm->vs->groundTrace.entityNum);
}
void VM_GroundTraceInternal2(void)
{
if (vm->vs->groundTrace.fraction == 1.0f) {
vm->vs->groundEntityNum = ENTITYNUM_NONE;
vm->vs->groundPlane = qfalse;
vm->vs->walking = qfalse;
return;
}
if (vm->vs->velocity[2] > 0.0f && DotProduct(vm->vs->velocity, vm->vs->groundTrace.plane.normal) > 10.0f) {
vm->vs->groundEntityNum = ENTITYNUM_NONE;
vm->vs->groundPlane = qfalse;
vm->vs->walking = qfalse;
return;
}
// slopes that are too steep will not be considered onground
if (vm->vs->groundTrace.plane.normal[2] < MIN_WALK_NORMAL) {
vec3_t oldvel;
float d;
VectorCopy(vm->vs->velocity, oldvel);
VectorSet(vm->vs->velocity, 0, 0, -1.0f / vm->frametime);
VM_SlideMove(qfalse);
d = VectorLength(vm->vs->velocity);
VectorCopy(oldvel, vm->vs->velocity);
if (d > (0.1f / vm->frametime)) {
vm->vs->groundEntityNum = ENTITYNUM_NONE;
vm->vs->groundPlane = qtrue;
vm->vs->walking = qfalse;
return;
}
}
vm->vs->groundPlane = qtrue;
vm->vs->walking = qtrue;
vm->vs->groundEntityNum = vm->vs->groundTrace.entityNum;
}
void VM_GroundTrace(void)
{
float point[3];
point[0] = vm->vs->origin[0];
point[1] = vm->vs->origin[1];
point[2] = vm->vs->origin[2] - 0.25f;
gi.trace(
&vm->vs->groundTrace, vm->vs->origin, vm->mins, vm->maxs, point, vm->vs->entityNum, vm->tracemask, qtrue, qfalse
);
VM_GroundTraceInternal();
}
void VM_StepSlideMove(void)
{
vec3_t start_o;
vec3_t start_v;
vec3_t nostep_o;
vec3_t nostep_v;
trace_t trace;
qboolean bWasOnGoodGround;
vec3_t up;
vec3_t down;
qboolean start_hit_wall;
vec3_t start_wall_normal;
qboolean first_hit_wall;
vec3_t first_wall_normal;
vec3_t start_hit_origin;
vec3_t first_hit_origin;
trace_t nostep_groundTrace;
VectorCopy(vm->vs->origin, start_o);
VectorCopy(vm->vs->velocity, start_v);
start_hit_wall = vm->vs->hit_obstacle;
VectorCopy(vm->vs->hit_origin, start_hit_origin);
VectorCopy(vm->vs->obstacle_normal, start_wall_normal);
if (VM_SlideMove(vm->vs->useGravity) == 0) {
if (vml.validGroundTrace) {
VM_GroundTraceInternal();
} else {
VM_GroundTrace();
}
return;
}
VectorCopy(start_o, down);
down[2] -= STEPSIZE;
gi.trace(&trace, start_o, vm->mins, vm->maxs, down, vm->vs->entityNum, vm->tracemask, qtrue, qfalse);
VectorSet(up, 0, 0, 1);
// never step up when you still have up velocity
if (vm->vs->velocity[2] > 0 && (trace.fraction == 1.0f || DotProduct(trace.plane.normal, up) < MIN_WALK_NORMAL)) {
if (vml.validGroundTrace) {
VM_GroundTraceInternal();
} else {
VM_GroundTrace();
}
return;
}
if (vm->vs->groundPlane && vm->vs->groundTrace.plane.normal[2] >= MIN_WALK_NORMAL) {
bWasOnGoodGround = qtrue;
} else {
bWasOnGoodGround = qfalse;
}
VectorCopy(start_o, up);
up[2] += STEPSIZE;
VectorCopy(vm->vs->origin, nostep_o);
VectorCopy(vm->vs->velocity, nostep_v);
memcpy(&nostep_groundTrace, &vm->vs->groundTrace, sizeof(trace_t));
VectorCopy(up, vm->vs->origin);
VectorCopy(start_v, vm->vs->velocity);
first_hit_wall = vm->vs->hit_obstacle;
VectorCopy(vm->vs->hit_origin, first_hit_origin);
VectorCopy(vm->vs->obstacle_normal, first_wall_normal);
vm->vs->hit_obstacle = start_hit_wall;
VectorCopy(start_hit_origin, vm->vs->hit_origin);
VectorCopy(start_wall_normal, vm->vs->obstacle_normal);
VM_SlideMove(vm->vs->useGravity);
VectorCopy(vm->vs->origin, down);
down[2] -= STEPSIZE * 2;
// test the player position if they were a stepheight higher
gi.trace(&trace, vm->vs->origin, vm->mins, vm->maxs, down, vm->vs->entityNum, vm->tracemask, qtrue, qfalse);
if (trace.entityNum != ENTITYNUM_WORLD && trace.entityNum != ENTITYNUM_WORLD) {
VectorCopy(nostep_o, vm->vs->origin);
VectorCopy(nostep_v, vm->vs->velocity);
memcpy(&vm->vs->groundTrace, &nostep_groundTrace, sizeof(vm->vs->groundTrace));
vm->vs->hit_obstacle = first_hit_wall;
VectorCopy(first_hit_origin, vm->vs->hit_origin);
VectorCopy(first_wall_normal, vm->vs->obstacle_normal);
if (vml.validGroundTrace) {
VM_GroundTraceInternal();
} else {
VM_GroundTrace();
}
return;
}
if (!trace.allsolid) {
memcpy(&vm->vs->groundTrace, &trace, sizeof(vm->vs->groundTrace));
vml.validGroundTrace = qtrue;
if (bWasOnGoodGround && trace.fraction < 1 && trace.plane.normal[2] < MIN_WALK_NORMAL) {
VectorCopy(nostep_o, vm->vs->origin);
VectorCopy(nostep_v, vm->vs->velocity);
if (first_hit_wall) {
vm->vs->hit_obstacle = first_hit_wall;
VectorCopy(first_hit_origin, vm->vs->hit_origin);
VectorCopy(first_wall_normal, vm->vs->obstacle_normal);
}
VM_GroundTraceInternal();
return;
}
VectorCopy(trace.endpos, vm->vs->origin);
}
if (trace.fraction < 1) {
VM_ClipVelocity(vm->vs->velocity, trace.plane.normal, vm->vs->velocity, OVERCLIP);
}
if (vml.validGroundTrace) {
VM_GroundTraceInternal();
} else {
VM_GroundTrace();
}
}
void VM_Friction(void)
{
vec3_t vec;
float *vel;
float speed, newspeed, control;
float drop;
vel = vm->vs->velocity;
VectorCopy(vel, vec);
if (vm->vs->walking) {
// ignore slope movement
vec[2] = 0;
}
speed = VectorLength(vec);
if (speed < 1) {
// allow sinking underwater
vel[0] = 0;
vel[1] = 0;
return;
}
drop = 0;
if (vm->vs->walking) {
control = (speed < 50.0f) ? 50.0f : speed;
// if getting knocked back, no friction
drop += control * 6.0f * vm->frametime;
}
// scale the velocity
newspeed = speed - drop;
if (newspeed < 0) {
newspeed = 0;
}
newspeed /= speed;
vel[0] = vel[0] * newspeed;
vel[1] = vel[1] * newspeed;
vel[2] = vel[2] * newspeed;
}
void VM_ClipVelocity2D(float *in, float *normal, float *out, float overbounce)
{
float backoff;
float dir_z;
float normal2[3];
if (normal[2] >= 0.70f) {
if (in[0] == 0.0f && in[1] == 0.0f) {
VectorClear(out);
return;
}
normal2[0] = in[0] + DotProduct2D(in, normal);
normal2[1] = in[1] + DotProduct2D(in, normal);
normal2[2] = normal[2] * DotProduct2D(in, in);
VectorNormalize(normal2);
dir_z = -normal2[2];
out[0] = in[0];
out[1] = in[1];
out[2] = DotProduct2D(in, normal2) / -normal2[2];
} else {
backoff = DotProduct2D(in, normal);
if (backoff < 0) {
backoff *= overbounce;
} else {
backoff /= overbounce;
}
out[0] = in[0] - normal[0] * backoff;
out[1] = in[1] - normal[1] * backoff;
out[2] = -(backoff * normal[2]);
}
}
void VmoveSingle(vmove_t *vmove)
{
float point[3];
trace_t trace;
bool walking;
vm = vmove;
vmove->numtouch = 0;
vmove->vs->hit_obstacle = false;
VectorCopy(vec_origin, vmove->vs->obstacle_normal);
memset(&vml, 0, sizeof(vml_t));
VectorCopy(vmove->vs->origin, vml.previous_origin);
VectorCopy(vmove->vs->velocity, vml.previous_velocity);
VM_GroundTraceInternal2();
walking = vm->vs->walking;
if (walking) {
float wishdir[3];
VM_Friction();
VM_ClipVelocity2D(vm->vs->desired_dir, vm->vs->groundTrace.plane.normal, wishdir, OVERCLIP);
VectorNormalize(wishdir);
vm->vs->velocity[0] = vm->desired_speed * wishdir[0];
vm->vs->velocity[1] = vm->desired_speed * wishdir[1];
if (!vm->vs->velocity[0] && !vm->vs->velocity[1]) {
VM_GroundTrace();
return;
}
} else if (vm->vs->groundPlane) {
VM_ClipVelocity(vm->vs->velocity, vm->vs->groundTrace.plane.normal, vm->vs->velocity, OVERCLIP);
}
VM_StepSlideMove();
if (!vm->vs->walking && (walking || (vml.previous_velocity[2] >= 0.0f && vm->vs->velocity[2] <= 0.0f))) {
point[0] = vm->vs->origin[0];
point[1] = vm->vs->origin[1];
point[2] = vm->vs->origin[2] - 18;
gi.trace(&trace, vm->vs->origin, vm->mins, vm->maxs, point, vm->vs->entityNum, vm->tracemask, qtrue, qfalse);
if (trace.fraction < 1 && !trace.allsolid) {
VectorCopy(trace.endpos, vm->vs->origin);
VM_GroundTrace();
}
}
}