mirror of
https://github.com/rehlds/rehlds.git
synced 2025-04-22 06:13:33 +03:00
1546 lines
38 KiB
C++
1546 lines
38 KiB
C++
/*
|
|
*
|
|
* This program 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.
|
|
*
|
|
* This program 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 this program; if not, write to the Free Software Foundation,
|
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* In addition, as a special exception, the author gives permission to
|
|
* link the code of this program with the Half-Life Game Engine ("HL
|
|
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
|
|
* L.L.C ("Valve"). You must obey the GNU General Public License in all
|
|
* respects for all of the code used other than the HL Engine and MODs
|
|
* from Valve. If you modify this file, you may extend this exception
|
|
* to your version of the file, but you are not obligated to do so. If
|
|
* you do not wish to do so, delete this exception statement from your
|
|
* version.
|
|
*
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
hull_t box_hull;
|
|
hull_t beam_hull;
|
|
|
|
box_clipnodes_t box_clipnodes;
|
|
box_planes_t box_planes;
|
|
beam_planes_t beam_planes;
|
|
areanode_t sv_areanodes[32];
|
|
int sv_numareanodes;
|
|
|
|
#ifdef REHLDS_FIXES
|
|
static link_t *touchLinksNext = nullptr;
|
|
#endif // REHLDS_FIXES
|
|
|
|
cvar_t sv_force_ent_intersection = { "sv_force_ent_intersection", "0", 0, 0.0f, NULL };
|
|
|
|
// ClearLink is used for new headnodes
|
|
void ClearLink(link_t *l)
|
|
{
|
|
l->next = l->prev = l;
|
|
}
|
|
|
|
// Remove link from chain
|
|
void RemoveLink(link_t *l)
|
|
{
|
|
#ifdef REHLDS_FIXES
|
|
if (touchLinksNext == l)
|
|
{
|
|
touchLinksNext = l->next;
|
|
}
|
|
#endif // REHLDS_FIXES
|
|
|
|
l->next->prev = l->prev;
|
|
l->prev->next = l->next;
|
|
}
|
|
|
|
// Kept trigger and solid entities seperate
|
|
void InsertLinkBefore(link_t *l, link_t *before)
|
|
{
|
|
l->next = before;
|
|
l->prev = before->prev;
|
|
l->next->prev = l;
|
|
l->prev->next = l;
|
|
|
|
#ifdef REHLDS_FIXES
|
|
if (touchLinksNext == before)
|
|
{
|
|
touchLinksNext = l;
|
|
}
|
|
#endif // REHLDS_FIXES
|
|
}
|
|
|
|
NOXREF void InsertLinkAfter(link_t *l, link_t *after)
|
|
{
|
|
NOXREFCHECK;
|
|
|
|
l->prev = after;
|
|
l->next = after->next;
|
|
|
|
after->next = l;
|
|
l->next->prev = l;
|
|
}
|
|
|
|
// Set up the planes and clipnodes so that the six floats of a bounding box
|
|
// can just be stored out and get a proper hull_t structure.
|
|
void SV_InitBoxHull()
|
|
{
|
|
box_hull.clipnodes = box_clipnodes;
|
|
box_hull.planes = box_planes;
|
|
box_hull.firstclipnode = 0;
|
|
box_hull.lastclipnode = 5;
|
|
|
|
Q_memcpy(&beam_hull, &box_hull, sizeof(beam_hull));
|
|
beam_hull.planes = beam_planes;
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
int side = i & 1;
|
|
box_clipnodes[i].planenum = i;
|
|
box_clipnodes[i].children[side] = CONTENTS_EMPTY;
|
|
box_clipnodes[i].children[side ^ 1] = (i != 5) ? i + 1 : CONTENTS_SOLID;
|
|
|
|
box_planes[i].type = i >> 1;
|
|
box_planes[i].normal[i >> 1] = 1.0f;
|
|
beam_planes[i].type = 5;
|
|
}
|
|
}
|
|
|
|
// To keep everything totally uniform, bounding boxes are turned into small
|
|
// BSP trees instead of being compared directly.
|
|
hull_t *SV_HullForBox(const vec_t *mins, const vec_t *maxs)
|
|
{
|
|
box_planes[0].dist = maxs[0];
|
|
box_planes[1].dist = mins[0];
|
|
box_planes[2].dist = maxs[1];
|
|
box_planes[3].dist = mins[1];
|
|
box_planes[4].dist = maxs[2];
|
|
box_planes[5].dist = mins[2];
|
|
|
|
return &box_hull;
|
|
}
|
|
|
|
NOXREF hull_t *SV_HullForBeam(const vec_t *start, const vec_t *end, const vec_t *size)
|
|
{
|
|
NOXREFCHECK;
|
|
vec3_t tmp = { 0, 0, 0 };
|
|
|
|
VectorSubtract(end, start, beam_planes[0].normal);
|
|
VectorNormalize(beam_planes[0].normal);
|
|
VectorCopy(beam_planes[0].normal, beam_planes[1].normal);
|
|
|
|
beam_planes[0].dist = _DotProduct(end, beam_planes[0].normal);
|
|
beam_planes[1].dist = _DotProduct(start, beam_planes[0].normal);
|
|
|
|
if (fabs(beam_planes[0].normal[2]) < 0.9f)
|
|
tmp[2] = 1.0f;
|
|
else
|
|
tmp[0] = 1.0f;
|
|
|
|
CrossProduct(beam_planes[0].normal, tmp, beam_planes[2].normal);
|
|
VectorNormalize(beam_planes[2].normal);
|
|
VectorCopy(beam_planes[2].normal, beam_planes[3].normal);
|
|
|
|
beam_planes[2].dist = (start[0] + beam_planes[2].normal[0]) * beam_planes[2].normal[0] + (start[1] + beam_planes[2].normal[1]) * beam_planes[2].normal[1] + (start[2] + beam_planes[2].normal[2]) * beam_planes[2].normal[2];
|
|
|
|
VectorSubtract(start, beam_planes[2].normal, tmp);
|
|
|
|
beam_planes[3].dist = _DotProduct(tmp, beam_planes[2].normal);
|
|
CrossProduct(beam_planes[2].normal, beam_planes[0].normal, beam_planes[4].normal);
|
|
VectorNormalize(beam_planes[4].normal);
|
|
|
|
VectorCopy(beam_planes[4].normal, beam_planes[5].normal);
|
|
|
|
beam_planes[4].dist = _DotProduct(start, beam_planes[4].normal);
|
|
beam_planes[5].dist = (start[0] - beam_planes[4].normal[0]) * beam_planes[4].normal[0] + (start[1] - beam_planes[4].normal[1]) * beam_planes[4].normal[1] + (start[2] - beam_planes[4].normal[2]) * beam_planes[4].normal[2];
|
|
|
|
beam_planes[0].dist += fabs(beam_planes[0].normal[0] * size[0]) + fabs(beam_planes[0].normal[1] * size[1]) + fabs(beam_planes[0].normal[2] * size[2]);
|
|
beam_planes[1].dist -= fabs(beam_planes[1].normal[0] * size[0]) + fabs(beam_planes[1].normal[1] * size[1]) + fabs(beam_planes[1].normal[2] * size[2]);
|
|
beam_planes[2].dist += fabs(beam_planes[2].normal[0] * size[0]) + fabs(beam_planes[2].normal[1] * size[1]) + fabs(beam_planes[2].normal[2] * size[2]);
|
|
beam_planes[3].dist -= fabs(beam_planes[3].normal[0] * size[0]) + fabs(beam_planes[3].normal[1] * size[1]) + fabs(beam_planes[3].normal[2] * size[2]);
|
|
beam_planes[4].dist += fabs(beam_planes[4].normal[0] * size[0]) + fabs(beam_planes[4].normal[1] * size[1]) + fabs(beam_planes[4].normal[2] * size[2]);
|
|
beam_planes[5].dist -= fabs(beam_planes[4].normal[0] * size[0]) + fabs(beam_planes[4].normal[1] * size[1]) + fabs(beam_planes[4].normal[2] * size[2]);
|
|
|
|
return &beam_hull;
|
|
}
|
|
|
|
// Forcing to select BSP hull
|
|
hull_t *SV_HullForBsp(edict_t *ent, const vec_t *mins, const vec_t *maxs, vec_t *offset)
|
|
{
|
|
hull_t *hull;
|
|
model_t *model;
|
|
vec3_t size;
|
|
|
|
model = Mod_Handle(ent->v.modelindex);
|
|
if (!model || model->type != mod_brush)
|
|
{
|
|
Sys_Error("%s: Hit a %s with no model (%s)", __func__, &pr_strings[ent->v.classname], &pr_strings[ent->v.model]);
|
|
}
|
|
|
|
VectorSubtract(maxs, mins, size);
|
|
|
|
if (size[0] <= 8.0f)
|
|
{
|
|
hull = &model->hulls[0];
|
|
VectorCopy(hull->clip_mins, offset);
|
|
}
|
|
else
|
|
{
|
|
if (size[0] <= 36.0f)
|
|
{
|
|
#ifdef REHLDS_FIXES
|
|
if (size[2] <= (player_maxs[1][2] - player_mins[1][2]))
|
|
#else
|
|
if (size[2] <= 36.0f)
|
|
#endif
|
|
hull = &model->hulls[3];
|
|
else
|
|
hull = &model->hulls[1];
|
|
}
|
|
else
|
|
{
|
|
hull = &model->hulls[2];
|
|
}
|
|
|
|
// calculate an offset value to center the origin
|
|
VectorSubtract(hull->clip_mins, mins, offset);
|
|
|
|
#ifdef REHLDS_FIXES
|
|
if (sv_rehlds_hull_centering.value && (mins[0] + maxs[0]) == 0.0f)
|
|
offset[0] = 0.0f;
|
|
|
|
if (sv_rehlds_hull_centering.value && (mins[1] + maxs[1]) == 0.0f)
|
|
offset[1] = 0.0f;
|
|
#endif
|
|
}
|
|
|
|
VectorAdd(offset, ent->v.origin, offset);
|
|
return hull;
|
|
}
|
|
|
|
hull_t *SV_HullForEntity(edict_t *ent, const vec_t *mins, const vec_t *maxs, vec_t *offset)
|
|
{
|
|
vec3_t hullmins;
|
|
vec3_t hullmaxs;
|
|
|
|
// decide which clipping hull to use, based on the size
|
|
if (ent->v.solid == SOLID_BSP)
|
|
{
|
|
// explicit hulls in the BSP model
|
|
if (ent->v.movetype != MOVETYPE_PUSH && ent->v.movetype != MOVETYPE_PUSHSTEP)
|
|
{
|
|
Sys_Error("%s: SOLID_BSP without MOVETYPE_PUSH\nEntity classname = %s, model = %s",
|
|
__func__, STRING(ent->v.classname), STRING(ent->v.model));
|
|
}
|
|
|
|
return SV_HullForBsp(ent, mins, maxs, offset);
|
|
}
|
|
|
|
// create a temp hull from bounding box sizes
|
|
VectorSubtract(ent->v.mins, maxs, hullmins);
|
|
VectorSubtract(ent->v.maxs, mins, hullmaxs);
|
|
VectorCopy(ent->v.origin, offset);
|
|
|
|
return SV_HullForBox(hullmins, hullmaxs);
|
|
}
|
|
|
|
// Builds a uniformly subdivided tree for the given world size
|
|
areanode_t *SV_CreateAreaNode(int depth, vec_t *mins, vec_t *maxs)
|
|
{
|
|
areanode_t *anode;
|
|
vec3_t size;
|
|
vec3_t mins1, maxs2, maxs1, mins2;
|
|
|
|
anode = &sv_areanodes[sv_numareanodes++];
|
|
|
|
ClearLink(&anode->trigger_edicts);
|
|
ClearLink(&anode->solid_edicts);
|
|
|
|
if (depth == AREA_DEPTH)
|
|
{
|
|
anode->axis = -1;
|
|
anode->children[0] = anode->children[1] = nullptr;
|
|
return anode;
|
|
}
|
|
|
|
VectorSubtract(maxs, mins, size);
|
|
|
|
if (size[0] > size[1])
|
|
anode->axis = 0;
|
|
else
|
|
anode->axis = 1;
|
|
|
|
anode->dist = 0.5f * (maxs[anode->axis] + mins[anode->axis]);
|
|
|
|
VectorCopy(mins, mins1);
|
|
VectorCopy(mins, mins2);
|
|
VectorCopy(maxs, maxs1);
|
|
VectorCopy(maxs, maxs2);
|
|
|
|
maxs1[anode->axis] = mins2[anode->axis] = anode->dist;
|
|
|
|
anode->children[0] = SV_CreateAreaNode(depth + 1, mins2, maxs2);
|
|
anode->children[1] = SV_CreateAreaNode(depth + 1, mins1, maxs1);
|
|
|
|
return anode;
|
|
}
|
|
|
|
// called after the world model has been loaded, before linking any entities
|
|
void SV_ClearWorld()
|
|
{
|
|
SV_InitBoxHull();
|
|
|
|
Q_memset(sv_areanodes, 0, sizeof(sv_areanodes));
|
|
sv_numareanodes = 0;
|
|
SV_CreateAreaNode(0, g_psv.worldmodel->mins, g_psv.worldmodel->maxs);
|
|
}
|
|
|
|
// call before removing an entity, and before trying to move one,
|
|
// so it doesn't clip against itself
|
|
// flags ent->v.modified
|
|
void SV_UnlinkEdict(edict_t *ent)
|
|
{
|
|
if (!ent->area.prev)
|
|
{
|
|
// not linked in anywhere
|
|
return;
|
|
}
|
|
|
|
RemoveLink(&ent->area);
|
|
ent->area.prev = ent->area.next = nullptr;
|
|
}
|
|
|
|
void SV_TouchLinks(edict_t *ent, areanode_t *node)
|
|
{
|
|
vec3_t localPosition, offset;
|
|
edict_t *touch;
|
|
|
|
#ifndef REHLDS_FIXES
|
|
link_t *touchLinksNext;
|
|
#endif // REHLDS_FIXES
|
|
|
|
// touch linked edicts
|
|
for (link_t *l = node->trigger_edicts.next; l != &node->trigger_edicts; l = touchLinksNext)
|
|
{
|
|
touchLinksNext = l->next;
|
|
touch = EDICT_FROM_AREA(l);
|
|
|
|
if (touch == ent)
|
|
continue;
|
|
|
|
if (ent->v.groupinfo && touch->v.groupinfo)
|
|
{
|
|
if (g_groupop)
|
|
{
|
|
if (g_groupop == GROUP_OP_NAND && (ent->v.groupinfo & touch->v.groupinfo))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (!(ent->v.groupinfo & touch->v.groupinfo))
|
|
continue;
|
|
}
|
|
}
|
|
|
|
#ifdef REHLDS_FIXES
|
|
if ((ent->v.flags & FL_KILLME) || (touch->v.flags & FL_KILLME))
|
|
continue;
|
|
#endif // REHLDS_FIXES
|
|
|
|
if (touch->v.solid != SOLID_TRIGGER)
|
|
continue;
|
|
|
|
if (ent->v.absmin[0] > touch->v.absmax[0]
|
|
|| ent->v.absmin[1] > touch->v.absmax[1]
|
|
|| ent->v.absmin[2] > touch->v.absmax[2]
|
|
|| ent->v.absmax[0] < touch->v.absmin[0]
|
|
|| ent->v.absmax[1] < touch->v.absmin[1]
|
|
|| ent->v.absmax[2] < touch->v.absmin[2])
|
|
continue;
|
|
|
|
// check brush triggers accuracy
|
|
if (Mod_GetType(touch->v.modelindex) == mod_brush)
|
|
{
|
|
// force to select bsp-hull
|
|
hull_t *hull = SV_HullForBsp(touch, ent->v.mins, ent->v.maxs, offset);
|
|
|
|
// offset the test point appropriately for this hull
|
|
VectorSubtract(ent->v.origin, offset, localPosition);
|
|
|
|
// test hull for intersection with this model
|
|
if (SV_HullPointContents(hull, hull->firstclipnode, localPosition) != CONTENTS_SOLID)
|
|
continue;
|
|
}
|
|
|
|
gGlobalVariables.time = g_psv.time;
|
|
gEntityInterface.pfnTouch(touch, ent);
|
|
}
|
|
|
|
#ifdef REHLDS_FIXES
|
|
touchLinksNext = nullptr;
|
|
#endif // REHLDS_FIXES
|
|
|
|
// recurse down both sides
|
|
if(node->axis == -1)
|
|
return;
|
|
|
|
if (ent->v.absmax[node->axis] > node->dist)
|
|
SV_TouchLinks(ent, node->children[0]);
|
|
|
|
if (node->dist > ent->v.absmin[node->axis])
|
|
SV_TouchLinks(ent, node->children[1]);
|
|
}
|
|
|
|
#ifndef REHLDS_OPT_PEDANTIC
|
|
|
|
void SV_FindTouchedLeafs(edict_t *ent, mnode_t *node, int *topnode)
|
|
{
|
|
if (node->contents == CONTENTS_SOLID)
|
|
return;
|
|
|
|
// add an efrag if the node is a leaf
|
|
if (node->contents < 0)
|
|
{
|
|
if (ent->num_leafs > (MAX_ENT_LEAFS - 1))
|
|
{
|
|
// continue counting leafs,
|
|
// so we know how many it's overrun
|
|
ent->num_leafs = (MAX_ENT_LEAFS + 1);
|
|
}
|
|
else
|
|
{
|
|
mleaf_t *leaf = (mleaf_t *)node;
|
|
int leafnum = leaf - g_psv.worldmodel->leafs - 1;
|
|
ent->leafnums[ent->num_leafs] = leafnum;
|
|
ent->num_leafs++;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// NODE_MIXED
|
|
mplane_t *splitplane = node->plane;
|
|
int sides = BOX_ON_PLANE_SIDE(ent->v.absmin, ent->v.absmax, splitplane);
|
|
|
|
if (sides == 3 && *topnode == -1)
|
|
{
|
|
*topnode = node - g_psv.worldmodel->nodes;
|
|
}
|
|
|
|
if (sides & 1) SV_FindTouchedLeafs(ent, node->children[0], topnode);
|
|
if (sides & 2) SV_FindTouchedLeafs(ent, node->children[1], topnode);
|
|
}
|
|
|
|
#else // REHLDS_OPT_PEDANTIC
|
|
|
|
// unrolled some tail recursion
|
|
void SV_FindTouchedLeafs(edict_t *ent, mnode_t *node, int *topnode)
|
|
{
|
|
while (true)
|
|
{
|
|
// if no collision model
|
|
if (!node)
|
|
return;
|
|
|
|
if (node->contents == CONTENTS_SOLID)
|
|
return;
|
|
|
|
// add an efrag if the node is a leaf
|
|
if (node->contents < 0)
|
|
{
|
|
if (ent->num_leafs > (MAX_ENT_LEAFS - 1))
|
|
{
|
|
// continue counting leafs,
|
|
// so we know how many it's overrun
|
|
ent->num_leafs = (MAX_ENT_LEAFS + 1);
|
|
}
|
|
else
|
|
{
|
|
mleaf_t *leaf = (mleaf_t *)node;
|
|
int leafnum = leaf - g_psv.worldmodel->leafs - 1;
|
|
ent->leafnums[ent->num_leafs] = leafnum;
|
|
ent->num_leafs++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// NODE_MIXED
|
|
mplane_t *splitplane = node->plane;
|
|
int sides = BOX_ON_PLANE_SIDE(ent->v.absmin, ent->v.absmax, splitplane);
|
|
|
|
switch (sides)
|
|
{
|
|
case 1: node = node->children[0]; break; // do only SV_FindTouchedLeafs(ent, node->children[0], topnode);
|
|
case 2: node = node->children[1]; break; // do only SV_FindTouchedLeafs(ent, node->children[1], topnode);
|
|
case 3:
|
|
{
|
|
if (*topnode == -1)
|
|
*topnode = node - g_psv.worldmodel->nodes;
|
|
|
|
// do both children nodes
|
|
SV_FindTouchedLeafs(ent, node->children[0], topnode);
|
|
node = node->children[1];
|
|
break;
|
|
}
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // REHLDS_OPT_PEDANTIC
|
|
|
|
// Needs to be called any time an entity changes origin, mins, maxs, or solid
|
|
// flags ent->v.modified
|
|
// sets ent->v.absmin and ent->v.absmax
|
|
// if touchtriggers, calls prog functions for the intersected triggers
|
|
void SV_LinkEdict(edict_t *ent, qboolean touch_triggers)
|
|
{
|
|
static int iTouchLinkSemaphore = 0; // prevent recursion when SV_TouchLinks is active
|
|
areanode_t *node;
|
|
|
|
// unlink from old position
|
|
if (ent->area.prev)
|
|
SV_UnlinkEdict(ent);
|
|
|
|
// don't add the world or free ents
|
|
if (ent == &g_psv.edicts[0] || ent->free)
|
|
return;
|
|
|
|
// set the abs box
|
|
gEntityInterface.pfnSetAbsBox(ent);
|
|
|
|
if (ent->v.movetype == MOVETYPE_FOLLOW && ent->v.aiment)
|
|
{
|
|
ent->headnode = ent->v.aiment->headnode;
|
|
ent->num_leafs = ent->v.aiment->num_leafs;
|
|
Q_memcpy(ent->leafnums, ent->v.aiment->leafnums, sizeof(ent->leafnums));
|
|
}
|
|
else
|
|
{
|
|
int topnode = -1;
|
|
|
|
// link to PVS leafs
|
|
ent->num_leafs = 0;
|
|
ent->headnode = -1;
|
|
|
|
if (ent->v.modelindex)
|
|
SV_FindTouchedLeafs(ent, g_psv.worldmodel->nodes, &topnode);
|
|
|
|
if (ent->num_leafs > MAX_ENT_LEAFS)
|
|
{
|
|
Q_memset(ent->leafnums, -1, sizeof(ent->leafnums));
|
|
|
|
ent->num_leafs = 0; // so we use headnode instead
|
|
ent->headnode = topnode;
|
|
}
|
|
}
|
|
|
|
// ignore non-solid bodies
|
|
if (ent->v.solid == SOLID_NOT && ent->v.skin >= -1)
|
|
return;
|
|
|
|
if (ent->v.solid == SOLID_BSP && !Mod_Handle(ent->v.modelindex) && Q_strlen(&pr_strings[ent->v.model]) <= 0)
|
|
{
|
|
Con_DPrintf("Inserted %s with no model\n", &pr_strings[ent->v.classname]);
|
|
return;
|
|
}
|
|
|
|
// find the first node that the ent's box crosses
|
|
node = sv_areanodes;
|
|
while (true)
|
|
{
|
|
if (node->axis == -1)
|
|
break;
|
|
|
|
if (ent->v.absmin[node->axis] > node->dist)
|
|
node = node->children[0];
|
|
else if (ent->v.absmax[node->axis] < node->dist)
|
|
node = node->children[1];
|
|
else break; // crosses the node
|
|
}
|
|
|
|
// link it in
|
|
if (ent->v.solid == SOLID_TRIGGER)
|
|
InsertLinkBefore(&ent->area, &node->trigger_edicts);
|
|
else
|
|
InsertLinkBefore(&ent->area, &node->solid_edicts);
|
|
|
|
if (touch_triggers && !iTouchLinkSemaphore)
|
|
{
|
|
iTouchLinkSemaphore = 1;
|
|
SV_TouchLinks(ent, sv_areanodes);
|
|
iTouchLinkSemaphore = 0;
|
|
}
|
|
}
|
|
|
|
int SV_HullPointContents(hull_t *hull, int num, const vec_t *p)
|
|
{
|
|
float d;
|
|
dclipnode_t *node;
|
|
mplane_t *plane;
|
|
|
|
while (num >= 0)
|
|
{
|
|
if (num < hull->firstclipnode || num > hull->lastclipnode)
|
|
Sys_Error("%s: bad node number", __func__);
|
|
|
|
node = &hull->clipnodes[num];
|
|
plane = &hull->planes[node->planenum];
|
|
|
|
if (plane->type < 3)
|
|
d = p[plane->type] - plane->dist;
|
|
else
|
|
d = _DotProduct(plane->normal, p) - plane->dist;
|
|
|
|
if (d < 0)
|
|
num = node->children[1];
|
|
else
|
|
num = node->children[0];
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
int SV_LinkContents(areanode_t *node, const vec_t *pos)
|
|
{
|
|
vec3_t localPosition, offset;
|
|
link_t *next, *l;
|
|
edict_t *touch;
|
|
|
|
#ifdef REHLDS_OPT_PEDANTIC
|
|
// unroll tail recursion
|
|
while (true)
|
|
#endif
|
|
{
|
|
for (l = node->solid_edicts.next; l != &node->solid_edicts; l = next)
|
|
{
|
|
touch = EDICT_FROM_AREA(l);
|
|
next = l->next;
|
|
|
|
if (touch->v.solid != SOLID_NOT)
|
|
continue;
|
|
|
|
if (touch->v.groupinfo)
|
|
{
|
|
if (g_groupop)
|
|
{
|
|
if (g_groupop == GROUP_OP_NAND && (touch->v.groupinfo & g_groupmask))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (!(touch->v.groupinfo & g_groupmask))
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (Mod_GetType(touch->v.modelindex) != mod_brush)
|
|
continue;
|
|
|
|
if (pos[0] > touch->v.absmax[0]
|
|
|| pos[1] > touch->v.absmax[1]
|
|
|| pos[2] > touch->v.absmax[2]
|
|
|| pos[0] < touch->v.absmin[0]
|
|
|| pos[1] < touch->v.absmin[1]
|
|
|| pos[2] < touch->v.absmin[2])
|
|
continue;
|
|
|
|
int contents = touch->v.skin;
|
|
if (contents < -100 || contents > 100)
|
|
Con_DPrintf("Invalid contents on trigger field: %s\n", &pr_strings[touch->v.classname]);
|
|
|
|
// force to select bsp-hull
|
|
hull_t *hull = SV_HullForBsp(touch, vec3_origin, vec3_origin, offset);
|
|
|
|
// offset the test point appropriately for this hull
|
|
VectorSubtract(pos, offset, localPosition);
|
|
|
|
// test hull for intersection with this model
|
|
if (SV_HullPointContents(hull, hull->firstclipnode, localPosition) != CONTENTS_EMPTY)
|
|
return contents;
|
|
}
|
|
|
|
if (node->axis == -1)
|
|
return CONTENTS_EMPTY;
|
|
|
|
#ifndef REHLDS_OPT_PEDANTIC
|
|
if (pos[node->axis] > node->dist)
|
|
return SV_LinkContents(node->children[0], pos);
|
|
|
|
else if (pos[node->axis] < node->dist)
|
|
return SV_LinkContents(node->children[1], pos);
|
|
|
|
#else // REHLDS_OPT_PEDANTIC
|
|
|
|
if (pos[node->axis] > node->dist)
|
|
{
|
|
node = node->children[0];
|
|
continue;
|
|
}
|
|
else if (pos[node->axis] < node->dist)
|
|
{
|
|
node = node->children[1];
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
#endif // REHLDS_OPT_PEDANTIC
|
|
}
|
|
|
|
return CONTENTS_EMPTY;
|
|
}
|
|
|
|
// Returns the CONTENTS_* value from the world at the given point.
|
|
// does not check any entities at all
|
|
int SV_PointContents(const vec_t *p)
|
|
{
|
|
int cont = SV_HullPointContents(g_psv.worldmodel->hulls, 0, p);
|
|
if (cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN)
|
|
{
|
|
cont = CONTENTS_WATER;
|
|
}
|
|
else
|
|
{
|
|
if (cont == CONTENTS_SOLID)
|
|
return CONTENTS_SOLID;
|
|
}
|
|
|
|
int entityContents = SV_LinkContents(&sv_areanodes[0], p);
|
|
return (entityContents != CONTENTS_EMPTY) ? entityContents : cont;
|
|
}
|
|
|
|
// Returns true if the entity is in solid currently
|
|
edict_t *SV_TestEntityPosition(edict_t *ent)
|
|
{
|
|
qboolean monsterClip = (ent->v.flags & FL_MONSTERCLIP) ? TRUE : FALSE;
|
|
trace_t trace = SV_Move(ent->v.origin, ent->v.mins, ent->v.maxs, ent->v.origin, MOVE_NORMAL, ent, monsterClip);
|
|
|
|
if (trace.startsolid)
|
|
{
|
|
SV_SetGlobalTrace(&trace);
|
|
return trace.ent;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const float DIST_EPSILON = 0.03125f;
|
|
|
|
#ifndef REHLDS_OPT_PEDANTIC
|
|
qboolean SV_RecursiveHullCheck(hull_t *hull, int num, float p1f, float p2f, const vec_t *p1, const vec_t *p2, trace_t *trace)
|
|
{
|
|
dclipnode_t *node;
|
|
mplane_t *plane;
|
|
float t1, t2;
|
|
float frac, midf, pointf;
|
|
vec3_t mid;
|
|
int side;
|
|
float pdif = p2f - p1f;
|
|
|
|
if (num < 0)
|
|
{
|
|
if (num != CONTENTS_SOLID)
|
|
{
|
|
trace->allsolid = FALSE;
|
|
|
|
if (num == CONTENTS_EMPTY)
|
|
trace->inopen = TRUE;
|
|
|
|
else if (num != CONTENTS_TRANSLUCENT)
|
|
trace->inwater = TRUE;
|
|
}
|
|
else
|
|
{
|
|
trace->startsolid = TRUE;
|
|
}
|
|
|
|
// empty
|
|
return TRUE;
|
|
}
|
|
|
|
if (num < hull->firstclipnode || num > hull->lastclipnode || !hull->planes)
|
|
Sys_Error("%s: bad node number", __func__);
|
|
|
|
// find the point distances
|
|
node = &hull->clipnodes[num];
|
|
plane = &hull->planes[hull->clipnodes[num].planenum];
|
|
|
|
if (plane->type < 3)
|
|
{
|
|
t1 = p1[plane->type] - plane->dist;
|
|
t2 = p2[plane->type] - plane->dist;
|
|
}
|
|
else
|
|
{
|
|
t1 = _DotProduct(plane->normal, p1) - plane->dist;
|
|
t2 = _DotProduct(plane->normal, p2) - plane->dist;
|
|
}
|
|
|
|
if (t1 >= 0.0f && t2 >= 0.0f)
|
|
return SV_RecursiveHullCheck(hull, node->children[0], p1f, p2f, p1, p2, trace);
|
|
|
|
if (t1 < 0.0f && t2 < 0.0f)
|
|
return SV_RecursiveHullCheck(hull, node->children[1], p1f, p2f, p1, p2, trace);
|
|
|
|
// put the crosspoint DIST_EPSILON pixels on the near side
|
|
if (t1 < 0.0f)
|
|
{
|
|
frac = (t1 + DIST_EPSILON) / (t1 - t2);
|
|
}
|
|
else
|
|
{
|
|
frac = (t1 - DIST_EPSILON) / (t1 - t2);
|
|
}
|
|
|
|
if (frac < 0.0f)
|
|
frac = 0.0f;
|
|
|
|
else if (frac > 1.0f)
|
|
frac = 1.0f;
|
|
|
|
if (IS_NAN(frac))
|
|
{
|
|
// not a number
|
|
return FALSE;
|
|
}
|
|
|
|
midf = p1f + pdif * frac;
|
|
|
|
real3_t point;
|
|
VectorSubtract(p2, p1, point);
|
|
VectorMA(p1, frac, point, mid);
|
|
|
|
side = (t1 < 0.0f) ? 1 : 0;
|
|
|
|
// move up to the node
|
|
if (!SV_RecursiveHullCheck(hull, node->children[side], p1f, midf, p1, mid, trace))
|
|
return FALSE;
|
|
|
|
if (SV_HullPointContents(hull, node->children[side ^ 1], mid) != CONTENTS_SOLID)
|
|
{
|
|
// go past the node
|
|
return SV_RecursiveHullCheck(hull, node->children[side ^ 1], midf, p2f, mid, p2, trace);
|
|
}
|
|
|
|
if (trace->allsolid)
|
|
{
|
|
// never got out of the solid area
|
|
return FALSE;
|
|
}
|
|
|
|
// the other side of the node is solid, this is the impact point
|
|
if (!side)
|
|
{
|
|
VectorCopy(plane->normal, trace->plane.normal);
|
|
trace->plane.dist = plane->dist;
|
|
}
|
|
else
|
|
{
|
|
VectorNegate(plane->normal, trace->plane.normal);
|
|
trace->plane.dist = -plane->dist;
|
|
}
|
|
|
|
while (SV_HullPointContents(hull, hull->firstclipnode, mid) == CONTENTS_SOLID)
|
|
{
|
|
// shouldn't really happen, but does occasionally
|
|
frac -= 0.1f;
|
|
if (frac < 0.0f)
|
|
{
|
|
trace->fraction = midf;
|
|
VectorCopy(mid, trace->endpos);
|
|
Con_DPrintf("backup past 0\n");
|
|
return FALSE;
|
|
}
|
|
|
|
midf = p1f + pdif * frac;
|
|
|
|
real3_t point;
|
|
VectorSubtract(p2, p1, point);
|
|
VectorMA(p1, frac, point, mid);
|
|
}
|
|
|
|
trace->fraction = midf;
|
|
VectorCopy(mid, trace->endpos);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
#else // REHLDS_OPT_PEDANTIC
|
|
|
|
// version with unrolled tail recursion
|
|
qboolean SV_RecursiveHullCheck(hull_t *hull, int num, float p1f, float p2f, const vec_t *p1, const vec_t *p2, trace_t *trace)
|
|
{
|
|
dclipnode_t *node;
|
|
mplane_t *plane;
|
|
float t1, t2;
|
|
float frac, midf;
|
|
vec3_t mid, custom_p1; // for holding custom p1 value
|
|
int side;
|
|
float pdif;
|
|
|
|
while (num >= 0)
|
|
{
|
|
pdif = p2f - p1f;
|
|
|
|
if (num < hull->firstclipnode || num > hull->lastclipnode || !hull->planes)
|
|
Sys_Error("%s: bad node number", __func__);
|
|
|
|
// find the point distances
|
|
node = &hull->clipnodes[num];
|
|
plane = &hull->planes[hull->clipnodes[num].planenum];
|
|
|
|
if (plane->type < 3)
|
|
{
|
|
t1 = p1[plane->type] - plane->dist;
|
|
t2 = p2[plane->type] - plane->dist;
|
|
}
|
|
else
|
|
{
|
|
t1 = _DotProduct(plane->normal, p1) - plane->dist;
|
|
t2 = _DotProduct(plane->normal, p2) - plane->dist;
|
|
}
|
|
|
|
if (t1 >= 0.0f && t2 >= 0.0f)
|
|
{
|
|
num = node->children[0];
|
|
continue;
|
|
}
|
|
|
|
if (t1 < 0.0f && t2 < 0.0f)
|
|
{
|
|
num = node->children[1];
|
|
continue;
|
|
}
|
|
|
|
// put the crosspoint DIST_EPSILON pixels on the near side
|
|
if (t1 < 0.0f)
|
|
{
|
|
frac = (t1 + DIST_EPSILON) / (t1 - t2);
|
|
}
|
|
else
|
|
{
|
|
frac = (t1 - DIST_EPSILON) / (t1 - t2);
|
|
}
|
|
|
|
if (frac < 0.0f)
|
|
frac = 0.0f;
|
|
|
|
else if (frac > 1.0f)
|
|
frac = 1.0f;
|
|
|
|
if (IS_NAN(frac))
|
|
{
|
|
// not a number
|
|
return FALSE;
|
|
}
|
|
|
|
midf = p1f + pdif * frac;
|
|
|
|
real3_t point;
|
|
VectorSubtract(p2, p1, point);
|
|
VectorMA(p1, frac, point, mid);
|
|
|
|
side = (t1 < 0.0f) ? 1 : 0;
|
|
|
|
// move up to the node
|
|
if (!SV_RecursiveHullCheck(hull, node->children[side], p1f, midf, p1, mid, trace))
|
|
return FALSE;
|
|
|
|
if (SV_HullPointContents(hull, node->children[side ^ 1], mid) != CONTENTS_SOLID)
|
|
{
|
|
// go past the node
|
|
num = node->children[side ^ 1];
|
|
p1f = midf;
|
|
p1 = custom_p1;
|
|
VectorCopy(mid, custom_p1);
|
|
continue;
|
|
}
|
|
|
|
if (trace->allsolid)
|
|
{
|
|
// never got out of the solid area
|
|
return FALSE;
|
|
}
|
|
|
|
// the other side of the node is solid, this is the impact point
|
|
if (!side)
|
|
{
|
|
VectorCopy(plane->normal, trace->plane.normal);
|
|
trace->plane.dist = plane->dist;
|
|
}
|
|
else
|
|
{
|
|
VectorNegate(plane->normal, trace->plane.normal);
|
|
trace->plane.dist = -plane->dist;
|
|
}
|
|
|
|
while (SV_HullPointContents(hull, hull->firstclipnode, mid) == CONTENTS_SOLID)
|
|
{
|
|
// shouldn't really happen, but does occasionally
|
|
frac -= 0.1f;
|
|
if (frac < 0.0f)
|
|
{
|
|
trace->fraction = midf;
|
|
VectorCopy(mid, trace->endpos);
|
|
Con_DPrintf("backup past 0\n");
|
|
return FALSE;
|
|
}
|
|
|
|
midf = p1f + pdif * frac;
|
|
|
|
real3_t point;
|
|
VectorSubtract(p2, p1, point);
|
|
VectorMA(p1, frac, point, mid);
|
|
}
|
|
|
|
trace->fraction = midf;
|
|
VectorCopy(mid, trace->endpos);
|
|
return FALSE;
|
|
}
|
|
|
|
if (num != CONTENTS_SOLID)
|
|
{
|
|
trace->allsolid = FALSE;
|
|
|
|
if (num == CONTENTS_EMPTY)
|
|
trace->inopen = TRUE;
|
|
|
|
else if (num != CONTENTS_TRANSLUCENT)
|
|
trace->inwater = TRUE;
|
|
}
|
|
else
|
|
{
|
|
trace->startsolid = TRUE;
|
|
}
|
|
|
|
// empty
|
|
return TRUE;
|
|
}
|
|
#endif // REHLDS_OPT_PEDANTIC
|
|
|
|
void SV_SingleClipMoveToEntity(edict_t *ent, const vec_t *start, const vec_t *mins, const vec_t *maxs, const vec_t *end, trace_t *trace)
|
|
{
|
|
hull_t *hull;
|
|
vec3_t offset;
|
|
bool rotated;
|
|
vec3_t end_l;
|
|
vec3_t start_l;
|
|
int numhulls;
|
|
|
|
// fill in a default trace
|
|
Q_memset(trace, 0, sizeof(trace_t));
|
|
VectorCopy(end, trace->endpos);
|
|
trace->fraction = 1.0f;
|
|
trace->allsolid = TRUE;
|
|
|
|
#ifdef REHLDS_FIXES
|
|
if (Mod_GetType(ent->v.modelindex) == mod_studio)
|
|
#else
|
|
if (g_psv.models[ent->v.modelindex]->type == mod_studio)
|
|
#endif // REHLDS_FIXES
|
|
{
|
|
// get the clipping hull from studio model
|
|
hull = SV_HullForStudioModel(ent, mins, maxs, offset, &numhulls);
|
|
}
|
|
else
|
|
{
|
|
// get the clipping hull
|
|
hull = SV_HullForEntity(ent, mins, maxs, offset);
|
|
numhulls = 1;
|
|
}
|
|
|
|
VectorSubtract(start, offset, start_l);
|
|
VectorSubtract(end, offset, end_l);
|
|
|
|
// rotate start and end into the models frame of reference
|
|
if (ent->v.solid == SOLID_BSP && !VectorIsZero(ent->v.angles))
|
|
{
|
|
vec3_t forward, right, up;
|
|
vec3_t temp;
|
|
|
|
AngleVectors(ent->v.angles, forward, right, up);
|
|
|
|
VectorCopy(start_l, temp);
|
|
start_l[0] = _DotProduct(temp, forward);
|
|
start_l[1] = -_DotProduct(temp, right);
|
|
start_l[2] = _DotProduct(temp, up);
|
|
|
|
VectorCopy(end_l, temp);
|
|
end_l[0] = _DotProduct(temp, forward);
|
|
end_l[1] = -_DotProduct(temp, right);
|
|
end_l[2] = _DotProduct(temp, up);
|
|
|
|
rotated = true;
|
|
}
|
|
else
|
|
{
|
|
rotated = false;
|
|
}
|
|
|
|
// trace a line through the appropriate clipping hull
|
|
if (numhulls == 1)
|
|
{
|
|
SV_RecursiveHullCheck(hull, hull->firstclipnode, 0.0f, 1.0f, start_l, end_l, trace);
|
|
}
|
|
else
|
|
{
|
|
int last_hitgroup = 0;
|
|
for (int i = 0; i < numhulls; i++)
|
|
{
|
|
// fill in a default trace
|
|
trace_t trace_hitbox;
|
|
Q_memset(&trace_hitbox, 0, sizeof(trace_hitbox));
|
|
VectorCopy(end, trace_hitbox.endpos);
|
|
trace_hitbox.fraction = 1.0f;
|
|
trace_hitbox.allsolid = TRUE;
|
|
|
|
SV_RecursiveHullCheck(&hull[i], hull[i].firstclipnode, 0.0f, 1.0f, start_l, end_l, &trace_hitbox);
|
|
|
|
if (i == 0 || trace_hitbox.allsolid || trace_hitbox.startsolid || trace_hitbox.fraction < trace->fraction)
|
|
{
|
|
if (trace->startsolid)
|
|
{
|
|
*trace = trace_hitbox;
|
|
trace->startsolid = TRUE;
|
|
}
|
|
else
|
|
{
|
|
*trace = trace_hitbox;
|
|
}
|
|
|
|
last_hitgroup = i;
|
|
}
|
|
}
|
|
|
|
trace->hitgroup = SV_HitgroupForStudioHull(last_hitgroup);
|
|
}
|
|
|
|
if (trace->fraction != 1.0f)
|
|
{
|
|
if (rotated)
|
|
{
|
|
vec3_t forward, right, up;
|
|
vec3_t temp;
|
|
|
|
AngleVectorsTranspose(ent->v.angles, forward, right, up);
|
|
VectorCopy(trace->plane.normal, temp);
|
|
|
|
trace->plane.normal[0] = _DotProduct(temp, forward);
|
|
trace->plane.normal[1] = _DotProduct(temp, right);
|
|
trace->plane.normal[2] = _DotProduct(temp, up);
|
|
}
|
|
|
|
real3_t point;
|
|
VectorSubtract(end, start, point);
|
|
VectorMA(start, trace->fraction, point, trace->endpos);
|
|
}
|
|
|
|
// did we clip the move?
|
|
if (trace->fraction < 1.0f || trace->startsolid)
|
|
trace->ent = ent;
|
|
}
|
|
|
|
// Handles selection or creation of a clipping hull, and offseting (and eventually rotation) of the end points
|
|
trace_t SV_ClipMoveToEntity(edict_t *ent, const vec_t *start, const vec_t *mins, const vec_t *maxs, const vec_t *end)
|
|
{
|
|
trace_t trace;
|
|
SV_SingleClipMoveToEntity(ent, start, mins, maxs, end, &trace);
|
|
return trace;
|
|
}
|
|
|
|
// Mins and maxs enclose the entire area swept by the move
|
|
void SV_ClipToLinks(areanode_t *node, moveclip_t *clip)
|
|
{
|
|
link_t *next, *l;
|
|
edict_t *touch;
|
|
|
|
// touch linked edicts
|
|
for (l = node->solid_edicts.next; l != &node->solid_edicts; l = next)
|
|
{
|
|
next = l->next;
|
|
touch = EDICT_FROM_AREA(l);
|
|
|
|
if (touch->v.groupinfo && clip->passedict && clip->passedict->v.groupinfo)
|
|
{
|
|
if (g_groupop)
|
|
{
|
|
if (g_groupop == GROUP_OP_NAND && (clip->passedict->v.groupinfo & touch->v.groupinfo))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (!(clip->passedict->v.groupinfo & touch->v.groupinfo))
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (touch->v.solid == SOLID_NOT || touch == clip->passedict)
|
|
continue;
|
|
|
|
if (touch->v.solid == SOLID_TRIGGER)
|
|
Sys_Error("%s: Trigger in clipping list", __func__);
|
|
|
|
if (gNewDLLFunctions.pfnShouldCollide && !gNewDLLFunctions.pfnShouldCollide(touch, clip->passedict))
|
|
#ifdef REHLDS_FIXES
|
|
// https://github.com/dreamstalker/rehlds/issues/46
|
|
continue;
|
|
#else
|
|
return;
|
|
#endif
|
|
|
|
// monsterclip filter
|
|
if (touch->v.solid == SOLID_BSP)
|
|
{
|
|
if ((touch->v.flags & FL_MONSTERCLIP) && !clip->monsterClipBrush)
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// ignore all monsters but pushables
|
|
if (clip->type == MOVE_NOMONSTERS && touch->v.movetype != MOVETYPE_PUSHSTEP)
|
|
continue;
|
|
}
|
|
|
|
if (clip->ignoretrans && touch->v.rendermode != kRenderNormal && !(touch->v.flags & FL_WORLDBRUSH))
|
|
continue;
|
|
|
|
if (clip->boxmins[0] > touch->v.absmax[0]
|
|
|| clip->boxmins[1] > touch->v.absmax[1]
|
|
|| clip->boxmins[2] > touch->v.absmax[2]
|
|
|| clip->boxmaxs[0] < touch->v.absmin[0]
|
|
|| clip->boxmaxs[1] < touch->v.absmin[1]
|
|
|| clip->boxmaxs[2] < touch->v.absmin[2])
|
|
continue;
|
|
|
|
if (touch->v.solid != SOLID_SLIDEBOX
|
|
#ifdef REHLDS_FIXES
|
|
|| sv_force_ent_intersection.value
|
|
#endif
|
|
)
|
|
{
|
|
if (!SV_CheckSphereIntersection(touch, clip->start, clip->end))
|
|
continue;
|
|
}
|
|
|
|
if (clip->passedict && clip->passedict->v.size[0] && !touch->v.size[0])
|
|
continue; // points never interact
|
|
|
|
// might intersect, so do an exact clip
|
|
if (clip->trace.allsolid)
|
|
return;
|
|
|
|
if (clip->passedict)
|
|
{
|
|
if (touch->v.owner == clip->passedict)
|
|
continue; // don't clip against own missiles
|
|
|
|
if (clip->passedict->v.owner == touch)
|
|
continue; // don't clip against owner
|
|
}
|
|
|
|
trace_t trace;
|
|
if (touch->v.flags & FL_MONSTER)
|
|
trace = SV_ClipMoveToEntity(touch, clip->start, clip->mins2, clip->maxs2, clip->end);
|
|
else
|
|
trace = SV_ClipMoveToEntity(touch, clip->start, clip->mins, clip->maxs, clip->end);
|
|
|
|
if (trace.allsolid || trace.startsolid || trace.fraction < clip->trace.fraction)
|
|
{
|
|
trace.ent = touch;
|
|
if (clip->trace.startsolid)
|
|
{
|
|
clip->trace = trace;
|
|
clip->trace.startsolid = TRUE;
|
|
}
|
|
else
|
|
{
|
|
clip->trace = trace;
|
|
}
|
|
}
|
|
}
|
|
|
|
// recurse down both sides
|
|
if (node->axis == -1)
|
|
return;
|
|
|
|
if (clip->boxmaxs[node->axis] > node->dist)
|
|
SV_ClipToLinks(node->children[0], clip);
|
|
|
|
if (node->dist > clip->boxmins[node->axis])
|
|
SV_ClipToLinks(node->children[1], clip);
|
|
}
|
|
|
|
// Mins and maxs enclose the entire area swept by the move
|
|
void SV_ClipToWorldbrush(areanode_t *node, moveclip_t *clip)
|
|
{
|
|
link_t *l;
|
|
link_t *next;
|
|
edict_t *touch;
|
|
|
|
for (l = node->solid_edicts.next; l != &node->solid_edicts; l = next)
|
|
{
|
|
next = l->next;
|
|
touch = EDICT_FROM_AREA(l);
|
|
|
|
if (touch->v.solid != SOLID_BSP)
|
|
continue;
|
|
|
|
if (!(touch->v.flags & FL_WORLDBRUSH))
|
|
continue;
|
|
|
|
if (clip->boxmins[0] > touch->v.absmax[0]
|
|
|| clip->boxmins[1] > touch->v.absmax[1]
|
|
|| clip->boxmins[2] > touch->v.absmax[2]
|
|
|| clip->boxmaxs[0] < touch->v.absmin[0]
|
|
|| clip->boxmaxs[1] < touch->v.absmin[1]
|
|
|| clip->boxmaxs[2] < touch->v.absmin[2])
|
|
continue;
|
|
|
|
if (clip->trace.allsolid)
|
|
return;
|
|
|
|
trace_t trace = SV_ClipMoveToEntity(touch, clip->start, clip->mins, clip->maxs, clip->end);
|
|
if (trace.allsolid || trace.startsolid || trace.fraction < clip->trace.fraction)
|
|
{
|
|
trace.ent = touch;
|
|
if (clip->trace.startsolid)
|
|
{
|
|
clip->trace = trace;
|
|
clip->trace.startsolid = TRUE;
|
|
}
|
|
else
|
|
{
|
|
clip->trace = trace;
|
|
}
|
|
}
|
|
}
|
|
|
|
// recurse down both sides
|
|
if (node->axis == -1)
|
|
return;
|
|
|
|
if (clip->boxmaxs[node->axis] > node->dist)
|
|
SV_ClipToWorldbrush(node->children[0], clip);
|
|
|
|
if (node->dist > clip->boxmins[node->axis])
|
|
SV_ClipToWorldbrush(node->children[1], clip);
|
|
}
|
|
|
|
void SV_MoveBounds(const vec_t *start, const vec_t *mins, const vec_t *maxs, const vec_t *end, vec_t *boxmins, vec_t *boxmaxs)
|
|
{
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
if (end[i] > start[i])
|
|
{
|
|
boxmins[i] = start[i] + mins[i] - 1.0f;
|
|
boxmaxs[i] = end[i] + maxs[i] + 1.0f;
|
|
}
|
|
else
|
|
{
|
|
boxmins[i] = end[i] + mins[i] - 1.0f;
|
|
boxmaxs[i] = start[i] + maxs[i] + 1.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
trace_t SV_MoveNoEnts(const vec_t *start, vec_t *mins, vec_t *maxs, const vec_t *end, int type, edict_t *passedict)
|
|
{
|
|
moveclip_t clip;
|
|
vec3_t trace_endpos;
|
|
float trace_fraction;
|
|
|
|
Q_memset(&clip, 0, sizeof(clip));
|
|
clip.trace = SV_ClipMoveToEntity(g_psv.edicts, start, mins, maxs, end);
|
|
|
|
if (clip.trace.fraction != 0.0f)
|
|
{
|
|
VectorCopy(clip.trace.endpos, trace_endpos);
|
|
|
|
trace_fraction = clip.trace.fraction;
|
|
|
|
clip.trace.fraction = 1.0f;
|
|
clip.start = start;
|
|
clip.end = trace_endpos;
|
|
|
|
clip.type = (type & 0xff);
|
|
clip.ignoretrans = (type >> 8);
|
|
clip.passedict = passedict;
|
|
clip.monsterClipBrush = FALSE;
|
|
|
|
clip.mins = mins;
|
|
clip.maxs = maxs;
|
|
|
|
VectorCopy(mins, clip.mins2);
|
|
VectorCopy(maxs, clip.maxs2);
|
|
|
|
SV_MoveBounds(start, clip.mins2, clip.maxs2, trace_endpos, clip.boxmins, clip.boxmaxs);
|
|
SV_ClipToWorldbrush(sv_areanodes, &clip);
|
|
|
|
clip.trace.fraction *= trace_fraction;
|
|
gGlobalVariables.trace_ent = clip.trace.ent;
|
|
}
|
|
|
|
return clip.trace;
|
|
}
|
|
|
|
// mins and maxs are reletive
|
|
// if the entire move stays in a solid volume, trace.allsolid will be set
|
|
// if the starting point is in a solid, it will be allowed to move out to an open area
|
|
// nomonsters is used for line of sight or edge testing,
|
|
// where mosnters shouldn't be considered solid objects
|
|
// passedict is explicitly excluded from clipping checks (normally NULL)
|
|
trace_t SV_Move(const vec_t *start, const vec_t *mins, const vec_t *maxs, const vec_t *end, int type, edict_t *passedict, qboolean monsterClipBrush)
|
|
{
|
|
moveclip_t clip;
|
|
vec3_t trace_endpos;
|
|
float trace_fraction;
|
|
|
|
Q_memset(&clip, 0, sizeof(clip));
|
|
clip.trace = SV_ClipMoveToEntity(g_psv.edicts, start, mins, maxs, end);
|
|
|
|
if (clip.trace.fraction != 0.0f)
|
|
{
|
|
VectorCopy(clip.trace.endpos, trace_endpos);
|
|
|
|
trace_fraction = clip.trace.fraction;
|
|
|
|
clip.trace.fraction = 1.0f;
|
|
clip.start = start;
|
|
clip.end = trace_endpos;
|
|
|
|
clip.type = (type & 0xff);
|
|
clip.ignoretrans = (type >> 8);
|
|
clip.passedict = passedict;
|
|
clip.monsterClipBrush = monsterClipBrush;
|
|
|
|
clip.mins = mins;
|
|
clip.maxs = maxs;
|
|
|
|
if (type == MOVE_MISSILE)
|
|
{
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
clip.mins2[i] = -15.0f;
|
|
clip.maxs2[i] = +15.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(mins, clip.mins2);
|
|
VectorCopy(maxs, clip.maxs2);
|
|
}
|
|
|
|
SV_MoveBounds(start, clip.mins2, clip.maxs2, trace_endpos, clip.boxmins, clip.boxmaxs);
|
|
SV_ClipToLinks(sv_areanodes, &clip);
|
|
|
|
clip.trace.fraction *= trace_fraction;
|
|
gGlobalVariables.trace_ent = clip.trace.ent;
|
|
}
|
|
|
|
return clip.trace;
|
|
}
|
|
|
|
#ifdef REHLDS_OPT_PEDANTIC
|
|
|
|
// Optimized version of SV_Move routines for moving point hull throw world
|
|
void SV_SingleClipMoveToPoint(const vec_t *start, const vec_t *end, trace_t *trace)
|
|
{
|
|
Q_memset(trace, 0, sizeof(trace_t));
|
|
trace->fraction = 1.0f;
|
|
trace->allsolid = TRUE;
|
|
VectorCopy(end, trace->endpos);
|
|
|
|
hull_t *hull = &g_psv.models[1]->hulls[0]; // world point hull
|
|
SV_RecursiveHullCheck(hull, hull->firstclipnode, 0.0f, 1.0f, start, end, trace);
|
|
|
|
if (trace->fraction != 1.0f)
|
|
{
|
|
trace->endpos[0] = (end[0] - start[0]) * trace->fraction + start[0];
|
|
trace->endpos[1] = (end[1] - start[1]) * trace->fraction + start[1];
|
|
trace->endpos[2] = (end[2] - start[2]) * trace->fraction + start[2];
|
|
}
|
|
|
|
if (trace->fraction < 1.0f || trace->startsolid)
|
|
trace->ent = &g_psv.edicts[0];
|
|
}
|
|
|
|
void SV_MoveBounds_Point(const vec_t *start, const vec_t *end, vec_t *boxmins, vec_t *boxmaxs)
|
|
{
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
if (end[i] > start[i])
|
|
{
|
|
boxmins[i] = start[i] - 1.0f;
|
|
boxmaxs[i] = end[i] + 1.0f;
|
|
}
|
|
else
|
|
{
|
|
boxmins[i] = end[i] - 1.0f;
|
|
boxmaxs[i] = start[i] + 1.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
trace_t SV_Move_Point(const vec_t *start, const vec_t *end, int type, edict_t *passedict)
|
|
{
|
|
moveclip_t clip;
|
|
vec3_t trace_endpos;
|
|
float trace_fraction;
|
|
|
|
Q_memset(&clip, 0, sizeof(clip));
|
|
SV_SingleClipMoveToPoint(start, end, &clip.trace);
|
|
|
|
if (clip.trace.fraction != 0.0f)
|
|
{
|
|
VectorCopy(clip.trace.endpos, trace_endpos);
|
|
|
|
trace_fraction = clip.trace.fraction;
|
|
|
|
clip.trace.fraction = 1.0f;
|
|
clip.start = start;
|
|
clip.end = trace_endpos;
|
|
|
|
clip.type = (type & 0xff);
|
|
clip.ignoretrans = (type >> 8);
|
|
clip.passedict = passedict;
|
|
clip.monsterClipBrush = FALSE;
|
|
|
|
clip.mins = vec3_origin;
|
|
clip.maxs = vec3_origin;
|
|
|
|
if (type == MOVE_MISSILE)
|
|
{
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
clip.mins2[i] = -15.0f;
|
|
clip.maxs2[i] = +15.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorClear(clip.mins2);
|
|
VectorClear(clip.maxs2);
|
|
}
|
|
|
|
SV_MoveBounds_Point(start, trace_endpos, clip.boxmins, clip.boxmaxs);
|
|
SV_ClipToLinks(sv_areanodes, &clip);
|
|
|
|
clip.trace.fraction *= trace_fraction;
|
|
gGlobalVariables.trace_ent = clip.trace.ent;
|
|
}
|
|
|
|
return clip.trace;
|
|
}
|
|
|
|
#endif // REHLDS_OPT_PEDANTIC
|