/* =========================================================================== 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(); } } }