mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-12-11 21:31:30 +00:00
db7f8a461e
While the libraries are probably getting a little out of hand, the separation into its own directory is probably a good thing as an ECS should not be tied to scenes. This should make the ECS more generally useful.
550 lines
14 KiB
C
550 lines
14 KiB
C
/*
|
|
sw_r_bsp.c
|
|
|
|
(description)
|
|
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
|
|
|
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:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "qfalloca.h"
|
|
|
|
#include "QF/render.h"
|
|
#include "QF/sys.h"
|
|
|
|
#include "QF/scene/entity.h"
|
|
|
|
#include "r_internal.h"
|
|
|
|
typedef struct glbspctx_s {
|
|
mod_brush_t *brush;
|
|
uint32_t render_id;
|
|
} swbspctx_t;
|
|
|
|
// current entity info
|
|
qboolean insubmodel;
|
|
vec3_t r_worldmodelorg;
|
|
mvertex_t *r_pcurrentvertbase;
|
|
static float entity_rotation[3][3];
|
|
|
|
int r_currentbkey;
|
|
|
|
typedef enum { touchessolid, drawnode, nodrawnode } solidstate_t;
|
|
|
|
#define MAX_BMODEL_VERTS 500 // 6K
|
|
#define MAX_BMODEL_EDGES 1000 // 12K
|
|
|
|
static mvertex_t *pbverts;
|
|
static bedge_t *pbedges;
|
|
static int numbverts, numbedges;
|
|
|
|
int numbtofpolys;
|
|
static btofpoly_t *pbtofpolys;
|
|
|
|
static mvertex_t *pfrontenter, *pfrontexit;
|
|
|
|
static qboolean makeclippededge;
|
|
|
|
|
|
static void
|
|
R_EntityRotate (vec3_t vec)
|
|
{
|
|
vec3_t tvec;
|
|
|
|
VectorCopy (vec, tvec);
|
|
vec[0] = DotProduct (entity_rotation[0], tvec);
|
|
vec[1] = DotProduct (entity_rotation[1], tvec);
|
|
vec[2] = DotProduct (entity_rotation[2], tvec);
|
|
}
|
|
|
|
|
|
void
|
|
R_RotateBmodel (vec4f_t *mat)
|
|
{
|
|
VectorCopy (mat[0], entity_rotation[0]);
|
|
VectorCopy (mat[1], entity_rotation[1]);
|
|
VectorCopy (mat[2], entity_rotation[2]);
|
|
|
|
// rotate modelorg and the transformation matrix
|
|
R_EntityRotate (modelorg);
|
|
R_EntityRotate (vfwd);
|
|
R_EntityRotate (vright);
|
|
R_EntityRotate (vup);
|
|
|
|
R_TransformFrustum ();
|
|
}
|
|
|
|
|
|
static void
|
|
R_RecursiveClipBPoly (uint32_t render_id, bedge_t *pedges, mnode_t *pnode,
|
|
msurface_t *psurf)
|
|
{
|
|
bedge_t *psideedges[2], *pnextedge, *ptedge;
|
|
int i, side, lastside;
|
|
float dist, frac, lastdist;
|
|
plane_t *splitplane, tplane;
|
|
mvertex_t *pvert, *plastvert, *ptvert;
|
|
mnode_t *pn;
|
|
|
|
psideedges[0] = psideedges[1] = NULL;
|
|
|
|
makeclippededge = false;
|
|
|
|
// transform the BSP plane into model space
|
|
// FIXME: cache these?
|
|
splitplane = (plane_t *) &pnode->plane;
|
|
tplane.dist = splitplane->dist +
|
|
DotProduct (r_entorigin, splitplane->normal);
|
|
tplane.normal[0] = DotProduct (entity_rotation[0], splitplane->normal);
|
|
tplane.normal[1] = DotProduct (entity_rotation[1], splitplane->normal);
|
|
tplane.normal[2] = DotProduct (entity_rotation[2], splitplane->normal);
|
|
|
|
// clip edges to BSP plane
|
|
for (; pedges; pedges = pnextedge) {
|
|
pnextedge = pedges->pnext;
|
|
|
|
// set the status for the last point as the previous point
|
|
// FIXME: cache this stuff somehow?
|
|
plastvert = pedges->v[0];
|
|
lastdist = DotProduct (plastvert->position, tplane.normal) +
|
|
tplane.dist;
|
|
|
|
if (lastdist > 0)
|
|
lastside = 0;
|
|
else
|
|
lastside = 1;
|
|
|
|
pvert = pedges->v[1];
|
|
|
|
dist = DotProduct (pvert->position, tplane.normal) + tplane.dist;
|
|
|
|
if (dist > 0)
|
|
side = 0;
|
|
else
|
|
side = 1;
|
|
|
|
if (side != lastside) {
|
|
// clipped
|
|
if (numbverts >= MAX_BMODEL_VERTS)
|
|
return;
|
|
|
|
// generate the clipped vertex
|
|
frac = lastdist / (lastdist - dist);
|
|
ptvert = &pbverts[numbverts++];
|
|
ptvert->position[0] = plastvert->position[0] +
|
|
frac * (pvert->position[0] - plastvert->position[0]);
|
|
ptvert->position[1] = plastvert->position[1] +
|
|
frac * (pvert->position[1] - plastvert->position[1]);
|
|
ptvert->position[2] = plastvert->position[2] +
|
|
frac * (pvert->position[2] - plastvert->position[2]);
|
|
|
|
// split into two edges, one on each side, and remember entering
|
|
// and exiting points
|
|
// FIXME: share the clip edge by having a winding direction flag?
|
|
if (numbedges >= (MAX_BMODEL_EDGES - 1)) {
|
|
Sys_Printf ("Out of edges for bmodel\n");
|
|
return;
|
|
}
|
|
|
|
ptedge = &pbedges[numbedges];
|
|
ptedge->pnext = psideedges[lastside];
|
|
psideedges[lastside] = ptedge;
|
|
ptedge->v[0] = plastvert;
|
|
ptedge->v[1] = ptvert;
|
|
|
|
ptedge = &pbedges[numbedges + 1];
|
|
ptedge->pnext = psideedges[side];
|
|
psideedges[side] = ptedge;
|
|
ptedge->v[0] = ptvert;
|
|
ptedge->v[1] = pvert;
|
|
|
|
numbedges += 2;
|
|
|
|
if (side == 0) {
|
|
// entering for front, exiting for back
|
|
pfrontenter = ptvert;
|
|
makeclippededge = true;
|
|
} else {
|
|
pfrontexit = ptvert;
|
|
makeclippededge = true;
|
|
}
|
|
} else {
|
|
// add the edge to the appropriate side
|
|
pedges->pnext = psideedges[side];
|
|
psideedges[side] = pedges;
|
|
}
|
|
}
|
|
|
|
// if anything was clipped, reconstitute and add the edges along the clip
|
|
// plane to both sides (but in opposite directions)
|
|
if (makeclippededge) {
|
|
if (numbedges >= (MAX_BMODEL_EDGES - 2)) {
|
|
Sys_Printf ("Out of edges for bmodel\n");
|
|
return;
|
|
}
|
|
|
|
ptedge = &pbedges[numbedges];
|
|
ptedge->pnext = psideedges[0];
|
|
psideedges[0] = ptedge;
|
|
ptedge->v[0] = pfrontexit;
|
|
ptedge->v[1] = pfrontenter;
|
|
|
|
ptedge = &pbedges[numbedges + 1];
|
|
ptedge->pnext = psideedges[1];
|
|
psideedges[1] = ptedge;
|
|
ptedge->v[0] = pfrontenter;
|
|
ptedge->v[1] = pfrontexit;
|
|
|
|
numbedges += 2;
|
|
}
|
|
// draw or recurse further
|
|
for (i = 0; i < 2; i++) {
|
|
if (psideedges[i]) {
|
|
// draw if we've reached a non-solid leaf, done if all that's left
|
|
// is a solid leaf, and continue down the tree if it's not a leaf
|
|
int child_id = pnode->children[i];
|
|
pn = r_refdef.worldmodel->brush.nodes + child_id;
|
|
|
|
// we're done with this branch if the node or leaf isn't in the PVS
|
|
if (child_id < 0) {
|
|
mleaf_t *leaf = r_refdef.worldmodel->brush.leafs + ~child_id;
|
|
if (r_leaf_visframes[~child_id] == r_visframecount
|
|
&& leaf->contents != CONTENTS_SOLID) {
|
|
r_currentbkey = leaf->key;
|
|
R_RenderBmodelFace (render_id, psideedges[i], psurf);
|
|
}
|
|
} else {
|
|
if (r_node_visframes[child_id] == r_visframecount) {
|
|
R_RecursiveClipBPoly (render_id, psideedges[i], pn, psurf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
R_DrawSolidClippedSubmodelPolygons (uint32_t render_id, mod_brush_t *brush,
|
|
mnode_t *topnode)
|
|
{
|
|
int i, j, lindex;
|
|
vec_t dot;
|
|
msurface_t *psurf;
|
|
int numsurfaces;
|
|
plane_t *pplane;
|
|
mvertex_t bverts[MAX_BMODEL_VERTS];
|
|
bedge_t bedges[MAX_BMODEL_EDGES], *pbedge;
|
|
medge_t *pedge, *pedges;
|
|
|
|
// FIXME: use bounding-box-based frustum clipping info?
|
|
|
|
psurf = &brush->surfaces[brush->firstmodelsurface];
|
|
numsurfaces = brush->nummodelsurfaces;
|
|
pedges = brush->edges;
|
|
|
|
for (i = 0; i < numsurfaces; i++, psurf++) {
|
|
// find which side of the node we are on
|
|
pplane = psurf->plane;
|
|
|
|
dot = DotProduct (modelorg, pplane->normal) - pplane->dist;
|
|
|
|
// draw the polygon
|
|
if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) ||
|
|
(!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) {
|
|
// FIXME: use bounding-box-based frustum clipping info?
|
|
|
|
// copy the edges to bedges, flipping if necessary so always
|
|
// clockwise winding
|
|
// FIXME: if edges and vertices get caches, these assignments must
|
|
// move outside the loop, and overflow checking must be done here
|
|
pbverts = bverts;
|
|
pbedges = bedges;
|
|
numbverts = numbedges = 0;
|
|
|
|
if (psurf->numedges > 0) {
|
|
pbedge = &bedges[numbedges];
|
|
numbedges += psurf->numedges;
|
|
|
|
for (j = 0; j < psurf->numedges; j++) {
|
|
lindex = brush->surfedges[psurf->firstedge + j];
|
|
|
|
if (lindex > 0) {
|
|
pedge = &pedges[lindex];
|
|
pbedge[j].v[0] = &r_pcurrentvertbase[pedge->v[0]];
|
|
pbedge[j].v[1] = &r_pcurrentvertbase[pedge->v[1]];
|
|
} else {
|
|
lindex = -lindex;
|
|
pedge = &pedges[lindex];
|
|
pbedge[j].v[0] = &r_pcurrentvertbase[pedge->v[1]];
|
|
pbedge[j].v[1] = &r_pcurrentvertbase[pedge->v[0]];
|
|
}
|
|
|
|
pbedge[j].pnext = &pbedge[j + 1];
|
|
}
|
|
|
|
pbedge[j - 1].pnext = NULL; // mark end of edges
|
|
|
|
R_RecursiveClipBPoly (render_id, pbedge, topnode, psurf);
|
|
} else {
|
|
Sys_Error ("no edges in bmodel");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
R_DrawSubmodelPolygons (uint32_t render_id, mod_brush_t *brush, int clipflags,
|
|
mleaf_t *topleaf)
|
|
{
|
|
int i;
|
|
vec_t dot;
|
|
msurface_t *psurf;
|
|
int numsurfaces;
|
|
plane_t *pplane;
|
|
|
|
// FIXME: use bounding-box-based frustum clipping info?
|
|
|
|
psurf = &brush->surfaces[brush->firstmodelsurface];
|
|
numsurfaces = brush->nummodelsurfaces;
|
|
|
|
for (i = 0; i < numsurfaces; i++, psurf++) {
|
|
// find which side of the node we are on
|
|
pplane = psurf->plane;
|
|
|
|
dot = DotProduct (modelorg, pplane->normal) - pplane->dist;
|
|
|
|
// draw the polygon
|
|
if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) ||
|
|
(!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) {
|
|
r_currentkey = topleaf->key;
|
|
|
|
// FIXME: use bounding-box-based frustum clipping info?
|
|
R_RenderFace (render_id, psurf, clipflags);
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
visit_leaf (mleaf_t *leaf)
|
|
{
|
|
// deal with model fragments in this leaf
|
|
if (leaf->efrags)
|
|
R_StoreEfrags (leaf->efrags);
|
|
leaf->key = r_currentkey;
|
|
r_currentkey++; // all bmodels in a leaf share the same key
|
|
}
|
|
|
|
static inline int
|
|
get_side (mnode_t *node)
|
|
{
|
|
// find which side of the node we are on
|
|
vec4f_t org = r_refdef.frame.position;
|
|
|
|
return dotf (org, node->plane)[0] < 0;
|
|
}
|
|
|
|
static void
|
|
visit_node (swbspctx_t *bctx, mnode_t *node, int side, int clipflags)
|
|
{
|
|
int c;
|
|
msurface_t *surf;
|
|
uint32_t render_id = bctx->render_id;
|
|
mod_brush_t *brush = bctx->brush;
|
|
|
|
// sneaky hack for side = side ? SURF_PLANEBACK : 0;
|
|
side = (~side + 1) & SURF_PLANEBACK;
|
|
// draw stuff
|
|
if ((c = node->numsurfaces)) {
|
|
int surf_id = node->firstsurface;
|
|
surf = brush->surfaces + surf_id;
|
|
for (; c; c--, surf++, surf_id++) {
|
|
if (r_face_visframes[surf_id] != r_visframecount)
|
|
continue;
|
|
|
|
// side is either 0 or SURF_PLANEBACK
|
|
if (side ^ (surf->flags & SURF_PLANEBACK))
|
|
continue; // wrong side
|
|
|
|
if (r_drawpolys) {
|
|
if (r_worldpolysbacktofront) {
|
|
if (numbtofpolys < MAX_BTOFPOLYS) {
|
|
pbtofpolys[numbtofpolys].clipflags = clipflags;
|
|
pbtofpolys[numbtofpolys].psurf = surf;
|
|
numbtofpolys++;
|
|
}
|
|
} else {
|
|
R_RenderPoly (render_id, surf, clipflags);
|
|
}
|
|
} else {
|
|
R_RenderFace (render_id, surf, clipflags);
|
|
}
|
|
}
|
|
// all surfaces on the same node share the same sequence number
|
|
r_currentkey++;
|
|
}
|
|
}
|
|
|
|
static inline int
|
|
test_node (swbspctx_t *bctx, int node_id, int *clipflags)
|
|
{
|
|
int i, *pindex;
|
|
vec3_t acceptpt, rejectpt;
|
|
double d;
|
|
|
|
if (node_id < 0)
|
|
return 0;
|
|
if (r_node_visframes[node_id] != r_visframecount)
|
|
return 0;
|
|
// cull the clipping planes if not trivial accept
|
|
// FIXME: the compiler is doing a lousy job of optimizing here; it could be
|
|
// twice as fast in ASM
|
|
if (*clipflags) {
|
|
mnode_t *node = bctx->brush->nodes + node_id;
|
|
for (i = 0; i < 4; i++) {
|
|
if (!(*clipflags & (1 << i)))
|
|
continue; // don't need to clip against it
|
|
|
|
// generate accept and reject points
|
|
// FIXME: do with fast look-ups or integer tests based on the
|
|
// sign bit of the floating point values
|
|
|
|
pindex = pfrustum_indexes[i];
|
|
|
|
rejectpt[0] = (float) node->minmaxs[pindex[0]];
|
|
rejectpt[1] = (float) node->minmaxs[pindex[1]];
|
|
rejectpt[2] = (float) node->minmaxs[pindex[2]];
|
|
|
|
d = DotProduct (rejectpt, view_clipplanes[i].normal);
|
|
d -= view_clipplanes[i].dist;
|
|
|
|
if (d <= 0)
|
|
return 0;
|
|
|
|
acceptpt[0] = (float) node->minmaxs[pindex[3 + 0]];
|
|
acceptpt[1] = (float) node->minmaxs[pindex[3 + 1]];
|
|
acceptpt[2] = (float) node->minmaxs[pindex[3 + 2]];
|
|
|
|
d = DotProduct (acceptpt, view_clipplanes[i].normal);
|
|
d -= view_clipplanes[i].dist;
|
|
if (d >= 0)
|
|
*clipflags &= ~(1 << i); // node is entirely on screen
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
R_VisitWorldNodes (swbspctx_t *bctx, int clipflags)
|
|
{
|
|
typedef struct {
|
|
int node_id;
|
|
int side, clipflags;
|
|
} rstack_t;
|
|
rstack_t *node_ptr;
|
|
rstack_t *node_stack;
|
|
int front;
|
|
int side, cf;
|
|
int node_id;
|
|
mod_brush_t *brush = bctx->brush;
|
|
|
|
// +2 for paranoia
|
|
node_stack = alloca ((brush->depth + 2) * sizeof (rstack_t));
|
|
node_ptr = node_stack;
|
|
|
|
node_id = 0;
|
|
cf = clipflags;
|
|
while (1) {
|
|
while (test_node (bctx, node_id, &cf)) {
|
|
mnode_t *node = bctx->brush->nodes + node_id;
|
|
cf = clipflags;
|
|
side = get_side (node);
|
|
front = node->children[side];
|
|
if (test_node (bctx, front, &cf)) {
|
|
node_ptr->node_id = node_id;
|
|
node_ptr->side = side;
|
|
node_ptr->clipflags = clipflags;
|
|
node_ptr++;
|
|
clipflags = cf;
|
|
node_id = front;
|
|
continue;
|
|
}
|
|
if (front < 0) {
|
|
mleaf_t *leaf = bctx->brush->leafs + ~front;
|
|
if (leaf->contents != CONTENTS_SOLID) {
|
|
visit_leaf (leaf);
|
|
}
|
|
}
|
|
visit_node (bctx, node, side, clipflags);
|
|
node_id = node->children[side ^ 1];
|
|
}
|
|
if (node_id < 0) {
|
|
mleaf_t *leaf = bctx->brush->leafs + ~node_id;
|
|
if (leaf->contents != CONTENTS_SOLID) {
|
|
visit_leaf (leaf);
|
|
}
|
|
}
|
|
if (node_ptr != node_stack) {
|
|
node_ptr--;
|
|
node_id = node_ptr->node_id;
|
|
mnode_t *node = bctx->brush->nodes + node_id;
|
|
side = node_ptr->side;
|
|
clipflags = node_ptr->clipflags;
|
|
visit_node (bctx, node, side, clipflags);
|
|
node_id = node->children[side ^ 1];
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
R_RenderWorld (void)
|
|
{
|
|
int i;
|
|
btofpoly_t btofpolys[MAX_BTOFPOLYS];
|
|
mod_brush_t *brush = &r_refdef.worldmodel->brush;
|
|
swbspctx_t bspctx = {
|
|
brush,
|
|
0,
|
|
};
|
|
|
|
pbtofpolys = btofpolys;
|
|
|
|
brush = &r_refdef.worldmodel->brush;
|
|
r_pcurrentvertbase = brush->vertexes;
|
|
|
|
R_VisitWorldNodes (&bspctx, 15);
|
|
|
|
// if the driver wants the polygons back to front, play the visible ones
|
|
// back in that order
|
|
if (r_worldpolysbacktofront) {
|
|
for (i = numbtofpolys - 1; i >= 0; i--) {
|
|
R_RenderPoly (0, btofpolys[i].psurf, btofpolys[i].clipflags);
|
|
}
|
|
}
|
|
}
|