openmohaa/code/fgame/g_mmove.cpp
smallmodel 97c9075503
Fix a small mistake in normal calculations for MM_ClipVelocity2D
Same error as commit c76dda1523. This fixes an issue where AI would run too slowly, like the crate guy in the intro of e1l1, the crate guy was too slow that it ran through the intro vehicle
2024-09-26 18:20:25 +02:00

567 lines
16 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_mmove.cpp : AI/Path movement code.
//
#include "g_local.h"
#include "entity.h"
#include "game.h"
typedef struct {
qboolean validGroundTrace;
trace_t groundTrace;
float previous_origin[3];
float previous_velocity[3];
} mml_t;
mmove_t *mm;
mml_t mml;
void MM_ClipVelocity(float *in, float *normal, float *out, float overbounce)
{
float backoff;
float dir_z;
float normal2[3];
if (normal[2] >= MIN_WALK_NORMAL) {
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;
}
}
qboolean MM_AddTouchEnt(int entityNum)
{
int i;
qboolean blockEnt;
Entity *ent;
if (entityNum == ENTITYNUM_NONE || entityNum == ENTITYNUM_WORLD) {
return qtrue;
}
ent = G_GetEntity(entityNum);
blockEnt = ent->BlocksAIMovement();
if (!blockEnt) {
if (ent->IsSubclassOfPlayer()) {
mm->hit_temp_obstacle |= 1;
} else if (ent->IsSubclassOfDoor()) {
mm->hit_temp_obstacle |= 2;
}
}
if (mm->numtouch == MAXTOUCH) {
return blockEnt;
}
// see if it is already added
for (i = 0; i < mm->numtouch; i++) {
if (mm->touchents[i] == entityNum) {
return blockEnt;
}
}
// add it
mm->touchents[mm->numtouch] = entityNum;
mm->numtouch++;
return blockEnt;
}
qboolean MM_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;
qboolean bBlockEnt;
if (gravity) {
mm->velocity[2] = mm->velocity[2] - mm->frametime * sv_gravity->integer;
if (mm->groundPlane) {
MM_ClipVelocity(mm->velocity, mm->groundPlaneNormal, mm->velocity, OVERCLIP);
}
}
time_left = mm->frametime;
if (mm->groundPlane) {
numplanes = 1;
VectorCopy(mm->groundPlaneNormal, planes[0]);
} else {
numplanes = 0;
}
// never turn against original velocity
VectorNormalize2(mm->velocity, planes[numplanes]);
numplanes++;
for (bumpcount = 0; bumpcount < 4; bumpcount++) {
// calculate position we are trying to move to
VectorMA(mm->origin, time_left, mm->velocity, end);
// see if we can make it there
gi.trace(&trace, mm->origin, mm->mins, mm->maxs, end, mm->entityNum, mm->tracemask, qtrue, qfalse);
if (trace.allsolid) {
break;
}
if (trace.fraction > 0) {
// actually covered some distance
VectorCopy(trace.endpos, mm->origin);
}
if (trace.fraction == 1) {
return bumpcount != 0;
}
// save entity for contact
bBlockEnt = MM_AddTouchEnt(trace.entityNum);
if (trace.plane.normal[2] < MIN_WALK_NORMAL) {
if (trace.plane.normal[2] > -0.999f && bBlockEnt && mm->groundPlane) {
if (!mm->hit_obstacle) {
mm->hit_obstacle = true;
VectorCopy(mm->origin, mm->hit_origin);
}
VectorAdd(mm->obstacle_normal, trace.plane.normal, mm->obstacle_normal);
}
} else {
memcpy(&mml.groundTrace, &trace, sizeof(mml.groundTrace));
mml.validGroundTrace = true;
}
time_left -= time_left * trace.fraction;
if (numplanes >= MAX_CLIP_PLANES) {
VectorClear(mm->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, mm->velocity, mm->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(mm->velocity, planes[i]) >= 0.1) {
continue; // move doesn't interact with the plane
}
// slide along the plane
MM_ClipVelocity(mm->velocity, planes[i], clipVelocity, OVERCLIP);
// see if there is a second plane that the new move enters
for (j = 0; j < numplanes; j++) {
if (j == i) {
continue;
}
if (DotProduct(clipVelocity, planes[j]) >= 0.1) {
continue; // move doesn't interact with the plane
}
// slide along the plane
MM_ClipVelocity(clipVelocity, planes[j], clipVelocity, OVERCLIP);
if (DotProduct(clipVelocity, planes[i]) >= 0) {
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, mm->velocity);
VectorScale(dir, d, clipVelocity);
// 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.1) {
continue; // move doesn't interact with the plane
}
// stop dead at a tripple plane interaction
VectorClear(mm->velocity);
return qtrue;
}
}
// if we have fixed all interactions, try another move
VectorCopy(clipVelocity, mm->velocity);
break;
}
}
}
if (mm->velocity[0] || mm->velocity[1]) {
if (mm->groundPlane) {
VectorCopy(mm->velocity, dir);
VectorNegate(dir, dir);
VectorNormalize(dir);
if (MM_AddTouchEnt(trace.entityNum)) {
if (!mm->hit_obstacle) {
mm->hit_obstacle = true;
VectorCopy(mm->origin, mm->hit_origin);
}
VectorAdd(mm->obstacle_normal, dir, mm->obstacle_normal);
}
}
VectorClear(mm->velocity);
return true;
}
mm->velocity[2] = 0;
return false;
}
void MM_GroundTraceInternal(void)
{
if (mml.groundTrace.fraction == 1.0f) {
mm->groundPlane = qfalse;
mm->walking = qfalse;
return;
}
if (mm->velocity[2] > 0.0f) {
if (DotProduct(mm->velocity, mml.groundTrace.plane.normal) > 10.0f) {
mm->groundPlane = qfalse;
mm->walking = qfalse;
return;
}
}
// slopes that are too steep will not be considered onground
if (mml.groundTrace.plane.normal[2] < MIN_WALK_NORMAL) {
vec3_t oldvel;
float d;
VectorCopy(mm->velocity, oldvel);
VectorSet(mm->velocity, 0, 0, -1.0f / mm->frametime);
MM_SlideMove(qfalse);
d = VectorLength(mm->velocity);
VectorCopy(oldvel, mm->velocity);
if (d > (0.1f / mm->frametime)) {
mm->groundPlane = qtrue;
mm->walking = qfalse;
VectorCopy(mml.groundTrace.plane.normal, mm->groundPlaneNormal);
return;
}
}
mm->groundPlane = qtrue;
mm->walking = qtrue;
VectorCopy(mml.groundTrace.plane.normal, mm->groundPlaneNormal);
MM_AddTouchEnt(mml.groundTrace.entityNum);
}
void MM_GroundTrace(void)
{
float point[3];
point[0] = mm->origin[0];
point[1] = mm->origin[1];
point[2] = mm->origin[2] - 0.25f;
gi.trace(&mml.groundTrace, mm->origin, mm->mins, mm->maxs, point, mm->entityNum, mm->tracemask, qtrue, qfalse);
MM_GroundTraceInternal();
}
void MM_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(mm->origin, start_o);
VectorCopy(mm->velocity, start_v);
start_hit_wall = mm->hit_obstacle;
VectorCopy(mm->hit_origin, start_hit_origin);
VectorCopy(mm->obstacle_normal, start_wall_normal);
if (MM_SlideMove(qtrue) == 0) {
if (mml.validGroundTrace) {
MM_GroundTraceInternal();
} else {
MM_GroundTrace();
}
return;
}
VectorCopy(start_o, down);
down[2] -= STEPSIZE;
gi.trace(&trace, start_o, mm->mins, mm->maxs, down, mm->entityNum, mm->tracemask, qtrue, qfalse);
VectorSet(up, 0, 0, 1);
// never step up when you still have up velocity
if (mm->velocity[2] > 0 && (trace.fraction == 1.0f || DotProduct(trace.plane.normal, up) < MIN_WALK_NORMAL)) {
if (mml.validGroundTrace) {
MM_GroundTraceInternal();
} else {
MM_GroundTrace();
}
return;
}
if (mm->groundPlane && mm->groundPlaneNormal[2] >= MIN_WALK_NORMAL) {
bWasOnGoodGround = qtrue;
} else {
bWasOnGoodGround = qfalse;
}
VectorCopy(start_o, up);
up[2] += 18;
VectorCopy(mm->origin, nostep_o);
VectorCopy(mm->velocity, nostep_v);
memcpy(&nostep_groundTrace, &mml.groundTrace, sizeof(trace_t));
VectorCopy(up, mm->origin);
VectorCopy(start_v, mm->velocity);
first_hit_wall = mm->hit_obstacle;
VectorCopy(mm->hit_origin, first_hit_origin);
VectorCopy(mm->obstacle_normal, first_wall_normal);
mm->hit_obstacle = start_hit_wall;
VectorCopy(start_hit_origin, mm->hit_origin);
VectorCopy(start_wall_normal, mm->obstacle_normal);
MM_SlideMove(qtrue);
VectorCopy(mm->origin, down);
down[2] -= STEPSIZE * 2;
// test the player position if they were a stepheight higher
gi.trace(&trace, mm->origin, mm->mins, mm->maxs, down, mm->entityNum, mm->tracemask, qtrue, qfalse);
if (trace.entityNum != ENTITYNUM_WORLD && trace.entityNum != ENTITYNUM_NONE) {
VectorCopy(nostep_o, mm->origin);
VectorCopy(nostep_v, mm->velocity);
memcpy(&mml.groundTrace, &nostep_groundTrace, sizeof(mml.groundTrace));
mm->hit_obstacle = first_hit_wall;
VectorCopy(first_hit_origin, mm->hit_origin);
VectorCopy(first_wall_normal, mm->obstacle_normal);
if (mml.validGroundTrace) {
MM_GroundTraceInternal();
} else {
MM_GroundTrace();
}
return;
}
if (!trace.allsolid) {
memcpy(&mml.groundTrace, &trace, sizeof(mml.groundTrace));
mml.validGroundTrace = qtrue;
if (bWasOnGoodGround && trace.fraction < 1 && trace.plane.normal[2] < MIN_WALK_NORMAL) {
VectorCopy(nostep_o, mm->origin);
VectorCopy(nostep_v, mm->velocity);
if (first_hit_wall) {
mm->hit_obstacle = first_hit_wall;
VectorCopy(first_hit_origin, mm->hit_origin);
VectorCopy(first_wall_normal, mm->obstacle_normal);
}
MM_GroundTraceInternal();
return;
}
VectorCopy(trace.endpos, mm->origin);
}
if (trace.fraction < 1) {
MM_ClipVelocity(mm->velocity, trace.plane.normal, mm->velocity, OVERCLIP);
}
if (mml.validGroundTrace) {
MM_GroundTraceInternal();
} else {
MM_GroundTrace();
}
}
void MM_ClipVelocity2D(float *in, float *normal, float *out, float overbounce)
{
float backoff;
float dir_z;
float normal2[3];
if (normal[2] >= MIN_WALK_NORMAL) {
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 = 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 MmoveSingle(mmove_t *mmove)
{
float point[3];
trace_t trace;
mm = mmove;
mmove->numtouch = 0;
mm->hit_obstacle = false;
VectorCopy(vec3_origin, mm->obstacle_normal);
mm->hit_temp_obstacle = false;
memset(&mml, 0, sizeof(mml_t));
VectorCopy(mm->origin, mml.previous_origin);
VectorCopy(mm->velocity, mml.previous_velocity);
if (mm->walking) {
if (mm->desired_speed < 1.0f) {
VectorClear2D(mm->velocity);
MM_GroundTrace();
return;
}
vec3_t wishdir;
MM_ClipVelocity2D(mm->desired_dir, mm->groundPlaneNormal, wishdir, OVERCLIP);
VectorNormalize(wishdir);
mm->velocity[0] = mm->desired_speed * wishdir[0];
mm->velocity[1] = mm->desired_speed * wishdir[1];
} else if (mm->groundPlane) {
MM_ClipVelocity(mm->velocity, mm->groundPlaneNormal, mm->velocity, OVERCLIP);
}
MM_StepSlideMove();
if (!mm->walking && mml.previous_velocity[2] >= 0.0f && mm->velocity[2] <= 0.0f) {
point[0] = mm->origin[0];
point[1] = mm->origin[1];
point[2] = mm->origin[2] - 18.0f;
gi.trace(&trace, mm->origin, mm->mins, mm->maxs, point, mm->entityNum, mm->tracemask, qtrue, qfalse);
if (trace.fraction < 1.0f && !trace.allsolid) {
VectorCopy(trace.endpos, mm->origin);
MM_GroundTrace();
}
}
}