ioef/code/bspc/aas_gsubdiv.c

656 lines
21 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena 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.
Quake III Arena 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 Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "qbsp.h"
#include "../botlib/aasfile.h"
#include "aas_create.h"
#include "aas_store.h"
#include "aas_cfg.h"
#define FACECLIP_EPSILON 0.2
#define FACE_EPSILON 1.0
int numgravitationalsubdivisions = 0;
int numladdersubdivisions = 0;
//NOTE: only do gravitational subdivision BEFORE area merging!!!!!!!
// because the bsp tree isn't refreshes like with ladder subdivision
//===========================================================================
// NOTE: the original face is invalid after splitting
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void AAS_SplitFace(tmp_face_t *face, vec3_t normal, float dist,
tmp_face_t **frontface, tmp_face_t **backface)
{
winding_t *frontw, *backw;
//
*frontface = *backface = NULL;
ClipWindingEpsilon(face->winding, normal, dist, FACECLIP_EPSILON, &frontw, &backw);
#ifdef DEBUG
//
if (frontw)
{
if (WindingIsTiny(frontw))
{
Log_Write("AAS_SplitFace: tiny back face\r\n");
FreeWinding(frontw);
frontw = NULL;
} //end if
} //end if
if (backw)
{
if (WindingIsTiny(backw))
{
Log_Write("AAS_SplitFace: tiny back face\r\n");
FreeWinding(backw);
backw = NULL;
} //end if
} //end if
#endif //DEBUG
//if the winding was split
if (frontw)
{
//check bounds
(*frontface) = AAS_AllocTmpFace();
(*frontface)->planenum = face->planenum;
(*frontface)->winding = frontw;
(*frontface)->faceflags = face->faceflags;
} //end if
if (backw)
{
//check bounds
(*backface) = AAS_AllocTmpFace();
(*backface)->planenum = face->planenum;
(*backface)->winding = backw;
(*backface)->faceflags = face->faceflags;
} //end if
} //end of the function AAS_SplitFace
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
winding_t *AAS_SplitWinding(tmp_area_t *tmparea, int planenum)
{
tmp_face_t *face;
plane_t *plane;
int side;
winding_t *splitwinding;
//
plane = &mapplanes[planenum];
//create a split winding, first base winding for plane
splitwinding = BaseWindingForPlane(plane->normal, plane->dist);
//chop with all the faces of the area
for (face = tmparea->tmpfaces; face && splitwinding; face = face->next[side])
{
//side of the face the original area was on
side = face->frontarea != tmparea;
plane = &mapplanes[face->planenum ^ side];
ChopWindingInPlace(&splitwinding, plane->normal, plane->dist, 0); // PLANESIDE_EPSILON);
} //end for
return splitwinding;
} //end of the function AAS_SplitWinding
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
int AAS_TestSplitPlane(tmp_area_t *tmparea, vec3_t normal, float dist,
int *facesplits, int *groundsplits, int *epsilonfaces)
{
int j, side, front, back, planenum;
float d, d_front, d_back;
tmp_face_t *face;
winding_t *w;
*facesplits = *groundsplits = *epsilonfaces = 0;
planenum = FindFloatPlane(normal, dist);
w = AAS_SplitWinding(tmparea, planenum);
if (!w) return false;
FreeWinding(w);
//
for (face = tmparea->tmpfaces; face; face = face->next[side])
{
//side of the face the area is on
side = face->frontarea != tmparea;
if ((face->planenum & ~1) == (planenum & ~1))
{
Log_Print("AAS_TestSplitPlane: tried face plane as splitter\n");
return false;
} //end if
w = face->winding;
//reset distance at front and back side of plane
d_front = d_back = 0;
//reset front and back flags
front = back = 0;
for (j = 0; j < w->numpoints; j++)
{
d = DotProduct(w->p[j], normal) - dist;
if (d > d_front) d_front = d;
if (d < d_back) d_back = d;
if (d > 0.4) // PLANESIDE_EPSILON)
front = 1;
if (d < -0.4) // PLANESIDE_EPSILON)
back = 1;
} //end for
//check for an epsilon face
if ( (d_front > FACECLIP_EPSILON && d_front < FACE_EPSILON)
|| (d_back < -FACECLIP_EPSILON && d_back > -FACE_EPSILON) )
{
(*epsilonfaces)++;
} //end if
//if the face has points at both sides of the plane
if (front && back)
{
(*facesplits)++;
if (face->faceflags & FACE_GROUND)
{
(*groundsplits)++;
} //end if
} //end if
} //end for
return true;
} //end of the function AAS_TestSplitPlane
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void AAS_SplitArea(tmp_area_t *tmparea, int planenum, tmp_area_t **frontarea, tmp_area_t **backarea)
{
int side;
tmp_area_t *facefrontarea, *facebackarea, *faceotherarea;
tmp_face_t *face, *frontface, *backface, *splitface, *nextface;
winding_t *splitwinding;
plane_t *splitplane;
/*
#ifdef AW_DEBUG
int facesplits, groundsplits, epsilonface;
Log_Print("\n----------------------\n");
Log_Print("splitting area %d\n", areanum);
Log_Print("with normal = \'%f %f %f\', dist = %f\n", normal[0], normal[1], normal[2], dist);
AAS_TestSplitPlane(areanum, normal, dist,
&facesplits, &groundsplits, &epsilonface);
Log_Print("face splits = %d\nground splits = %d\n", facesplits, groundsplits);
if (epsilonface) Log_Print("aaahh epsilon face\n");
#endif //AW_DEBUG*/
//the original area
AAS_FlipAreaFaces(tmparea);
AAS_CheckArea(tmparea);
//
splitplane = &mapplanes[planenum];
/* //create a split winding, first base winding for plane
splitwinding = BaseWindingForPlane(splitplane->normal, splitplane->dist);
//chop with all the faces of the area
for (face = tmparea->tmpfaces; face && splitwinding; face = face->next[side])
{
//side of the face the original area was on
side = face->frontarea != tmparea->areanum;
plane = &mapplanes[face->planenum ^ side];
ChopWindingInPlace(&splitwinding, plane->normal, plane->dist, 0); // PLANESIDE_EPSILON);
} //end for*/
splitwinding = AAS_SplitWinding(tmparea, planenum);
if (!splitwinding)
{
/*
#ifdef DEBUG
AAS_TestSplitPlane(areanum, normal, dist,
&facesplits, &groundsplits, &epsilonface);
Log_Print("\nface splits = %d\nground splits = %d\n", facesplits, groundsplits);
if (epsilonface) Log_Print("aaahh epsilon face\n");
#endif //DEBUG*/
Error("AAS_SplitArea: no split winding when splitting area %d\n", tmparea->areanum);
} //end if
//create a split face
splitface = AAS_AllocTmpFace();
//get the map plane
splitface->planenum = planenum;
//store the split winding
splitface->winding = splitwinding;
//the new front area
(*frontarea) = AAS_AllocTmpArea();
(*frontarea)->presencetype = tmparea->presencetype;
(*frontarea)->contents = tmparea->contents;
(*frontarea)->modelnum = tmparea->modelnum;
(*frontarea)->tmpfaces = NULL;
//the new back area
(*backarea) = AAS_AllocTmpArea();
(*backarea)->presencetype = tmparea->presencetype;
(*backarea)->contents = tmparea->contents;
(*backarea)->modelnum = tmparea->modelnum;
(*backarea)->tmpfaces = NULL;
//add the split face to the new areas
AAS_AddFaceSideToArea(splitface, 0, (*frontarea));
AAS_AddFaceSideToArea(splitface, 1, (*backarea));
//split all the faces of the original area
for (face = tmparea->tmpfaces; face; face = nextface)
{
//side of the face the original area was on
side = face->frontarea != tmparea;
//next face of the original area
nextface = face->next[side];
//front area of the face
facefrontarea = face->frontarea;
//back area of the face
facebackarea = face->backarea;
//remove the face from both the front and back areas
if (facefrontarea) AAS_RemoveFaceFromArea(face, facefrontarea);
if (facebackarea) AAS_RemoveFaceFromArea(face, facebackarea);
//split the face
AAS_SplitFace(face, splitplane->normal, splitplane->dist, &frontface, &backface);
//free the original face
AAS_FreeTmpFace(face);
//get the number of the area at the other side of the face
if (side) faceotherarea = facefrontarea;
else faceotherarea = facebackarea;
//if there is an area at the other side of the original face
if (faceotherarea)
{
if (frontface) AAS_AddFaceSideToArea(frontface, !side, faceotherarea);
if (backface) AAS_AddFaceSideToArea(backface, !side, faceotherarea);
} //end if
//add the front and back part left after splitting the original face to the new areas
if (frontface) AAS_AddFaceSideToArea(frontface, side, (*frontarea));
if (backface) AAS_AddFaceSideToArea(backface, side, (*backarea));
} //end for
if (!(*frontarea)->tmpfaces) Log_Print("AAS_SplitArea: front area without faces\n");
if (!(*backarea)->tmpfaces) Log_Print("AAS_SplitArea: back area without faces\n");
tmparea->invalid = true;
/*
#ifdef AW_DEBUG
for (i = 0, face = frontarea->tmpfaces; face; face = face->next[side])
{
side = face->frontarea != frontarea->areanum;
i++;
} //end for
Log_Print("created front area %d with %d faces\n", frontarea->areanum, i);
for (i = 0, face = backarea->tmpfaces; face; face = face->next[side])
{
side = face->frontarea != backarea->areanum;
i++;
} //end for
Log_Print("created back area %d with %d faces\n", backarea->areanum, i);
#endif //AW_DEBUG*/
AAS_FlipAreaFaces((*frontarea));
AAS_FlipAreaFaces((*backarea));
//
AAS_CheckArea((*frontarea));
AAS_CheckArea((*backarea));
} //end of the function AAS_SplitArea
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
int AAS_FindBestAreaSplitPlane(tmp_area_t *tmparea, vec3_t normal, float *dist)
{
int side1, side2;
int foundsplitter, facesplits, groundsplits, epsilonfaces, bestepsilonfaces;
float bestvalue, value;
tmp_face_t *face1, *face2;
vec3_t tmpnormal, invgravity;
float tmpdist;
//get inverse of gravity direction
VectorCopy(cfg.phys_gravitydirection, invgravity);
VectorInverse(invgravity);
foundsplitter = false;
bestvalue = -999999;
bestepsilonfaces = 0;
//
#ifdef AW_DEBUG
Log_Print("finding split plane for area %d\n", tmparea->areanum);
#endif //AW_DEBUG
for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1])
{
//side of the face the area is on
side1 = face1->frontarea != tmparea;
//
if (WindingIsTiny(face1->winding))
{
Log_Write("gsubdiv: area %d has a tiny winding\r\n", tmparea->areanum);
continue;
} //end if
//if the face isn't a gap or ground there's no split edge
if (!(face1->faceflags & FACE_GROUND) && !AAS_GapFace(face1, side1)) continue;
//
for (face2 = face1->next[side1]; face2; face2 = face2->next[side2])
{
//side of the face the area is on
side2 = face2->frontarea != tmparea;
//
if (WindingIsTiny(face1->winding))
{
Log_Write("gsubdiv: area %d has a tiny winding\r\n", tmparea->areanum);
continue;
} //end if
//if the face isn't a gap or ground there's no split edge
if (!(face2->faceflags & FACE_GROUND) && !AAS_GapFace(face2, side2)) continue;
//only split between gaps and ground
if (!(((face1->faceflags & FACE_GROUND) && AAS_GapFace(face2, side2)) ||
((face2->faceflags & FACE_GROUND) && AAS_GapFace(face1, side1)))) continue;
//find a plane seperating the windings of the faces
if (!FindPlaneSeperatingWindings(face1->winding, face2->winding, invgravity,
tmpnormal, &tmpdist)) continue;
#ifdef AW_DEBUG
Log_Print("normal = \'%f %f %f\', dist = %f\n",
tmpnormal[0], tmpnormal[1], tmpnormal[2], tmpdist);
#endif //AW_DEBUG
//get metrics for this vertical plane
if (!AAS_TestSplitPlane(tmparea, tmpnormal, tmpdist,
&facesplits, &groundsplits, &epsilonfaces))
{
continue;
} //end if
#ifdef AW_DEBUG
Log_Print("face splits = %d\nground splits = %d\n",
facesplits, groundsplits);
#endif //AW_DEBUG
value = 100 - facesplits - 2 * groundsplits;
//avoid epsilon faces
value += epsilonfaces * -1000;
if (value > bestvalue)
{
VectorCopy(tmpnormal, normal);
*dist = tmpdist;
bestvalue = value;
bestepsilonfaces = epsilonfaces;
foundsplitter = true;
} //end if
} //end for
} //end for
if (bestepsilonfaces)
{
Log_Write("found %d epsilon faces trying to split area %d\r\n",
epsilonfaces, tmparea->areanum);
} //end else
return foundsplitter;
} //end of the function AAS_FindBestAreaSplitPlane
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
tmp_node_t *AAS_SubdivideArea_r(tmp_node_t *tmpnode)
{
int planenum;
tmp_area_t *frontarea, *backarea;
tmp_node_t *tmpnode1, *tmpnode2;
vec3_t normal;
float dist;
if (AAS_FindBestAreaSplitPlane(tmpnode->tmparea, normal, &dist))
{
qprintf("\r%6d", ++numgravitationalsubdivisions);
//
planenum = FindFloatPlane(normal, dist);
//split the area
AAS_SplitArea(tmpnode->tmparea, planenum, &frontarea, &backarea);
//
tmpnode->tmparea = NULL;
tmpnode->planenum = FindFloatPlane(normal, dist);
//
tmpnode1 = AAS_AllocTmpNode();
tmpnode1->planenum = 0;
tmpnode1->tmparea = frontarea;
//
tmpnode2 = AAS_AllocTmpNode();
tmpnode2->planenum = 0;
tmpnode2->tmparea = backarea;
//subdivide the areas created by splitting recursively
tmpnode->children[0] = AAS_SubdivideArea_r(tmpnode1);
tmpnode->children[1] = AAS_SubdivideArea_r(tmpnode2);
} //end if
return tmpnode;
} //end of the function AAS_SubdivideArea_r
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
tmp_node_t *AAS_GravitationalSubdivision_r(tmp_node_t *tmpnode)
{
//if this is a solid leaf
if (!tmpnode) return NULL;
//negative so it's an area
if (tmpnode->tmparea) return AAS_SubdivideArea_r(tmpnode);
//do the children recursively
tmpnode->children[0] = AAS_GravitationalSubdivision_r(tmpnode->children[0]);
tmpnode->children[1] = AAS_GravitationalSubdivision_r(tmpnode->children[1]);
return tmpnode;
} //end of the function AAS_GravitationalSubdivision_r
//===========================================================================
// NOTE: merge faces and melt edges first
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void AAS_GravitationalSubdivision(void)
{
Log_Write("AAS_GravitationalSubdivision\r\n");
numgravitationalsubdivisions = 0;
qprintf("%6i gravitational subdivisions", numgravitationalsubdivisions);
//start with the head node
AAS_GravitationalSubdivision_r(tmpaasworld.nodes);
qprintf("\n");
Log_Write("%6i gravitational subdivisions\r\n", numgravitationalsubdivisions);
} //end of the function AAS_GravitationalSubdivision
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
tmp_node_t *AAS_RefreshLadderSubdividedTree_r(tmp_node_t *tmpnode, tmp_area_t *tmparea,
tmp_node_t *tmpnode1, tmp_node_t *tmpnode2, int planenum)
{
//if this is a solid leaf
if (!tmpnode) return NULL;
//negative so it's an area
if (tmpnode->tmparea)
{
if (tmpnode->tmparea == tmparea)
{
tmpnode->tmparea = NULL;
tmpnode->planenum = planenum;
tmpnode->children[0] = tmpnode1;
tmpnode->children[1] = tmpnode2;
} //end if
return tmpnode;
} //end if
//do the children recursively
tmpnode->children[0] = AAS_RefreshLadderSubdividedTree_r(tmpnode->children[0],
tmparea, tmpnode1, tmpnode2, planenum);
tmpnode->children[1] = AAS_RefreshLadderSubdividedTree_r(tmpnode->children[1],
tmparea, tmpnode1, tmpnode2, planenum);
return tmpnode;
} //end of the function AAS_RefreshLadderSubdividedTree_r
//===========================================================================
// find an area with ladder faces and ground faces that are not connected
// split the area with a horizontal plane at the lowest vertex of all
// ladder faces in the area
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
tmp_node_t *AAS_LadderSubdivideArea_r(tmp_node_t *tmpnode)
{
int side1, i, planenum;
int foundladderface, foundgroundface;
float dist;
tmp_area_t *tmparea, *frontarea, *backarea;
tmp_face_t *face1;
tmp_node_t *tmpnode1, *tmpnode2;
vec3_t lowestpoint, normal = {0, 0, 1};
plane_t *plane;
winding_t *w;
tmparea = tmpnode->tmparea;
//skip areas with a liquid
if (tmparea->contents & (AREACONTENTS_WATER
| AREACONTENTS_LAVA
| AREACONTENTS_SLIME)) return tmpnode;
//must be possible to stand in the area
if (!(tmparea->presencetype & PRESENCE_NORMAL)) return tmpnode;
//
foundladderface = false;
foundgroundface = false;
lowestpoint[2] = 99999;
//
for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1])
{
//side of the face the area is on
side1 = face1->frontarea != tmparea;
//if the face is a ladder face
if (face1->faceflags & FACE_LADDER)
{
plane = &mapplanes[face1->planenum];
//the ladder face plane should be pretty much vertical
if (DotProduct(plane->normal, normal) > -0.1)
{
foundladderface = true;
//find lowest point
for (i = 0; i < face1->winding->numpoints; i++)
{
if (face1->winding->p[i][2] < lowestpoint[2])
{
VectorCopy(face1->winding->p[i], lowestpoint);
} //end if
} //end for
} //end if
} //end if
else if (face1->faceflags & FACE_GROUND)
{
foundgroundface = true;
} //end else if
} //end for
//
if ((!foundladderface) || (!foundgroundface)) return tmpnode;
//
for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1])
{
//side of the face the area is on
side1 = face1->frontarea != tmparea;
//if the face isn't a ground face
if (!(face1->faceflags & FACE_GROUND)) continue;
//the ground plane
plane = &mapplanes[face1->planenum];
//get the difference between the ground plane and the lowest point
dist = DotProduct(plane->normal, lowestpoint) - plane->dist;
//if the lowest point is very near one of the ground planes
if (dist > -1 && dist < 1)
{
return tmpnode;
} //end if
} //end for
//
dist = DotProduct(normal, lowestpoint);
planenum = FindFloatPlane(normal, dist);
//
w = AAS_SplitWinding(tmparea, planenum);
if (!w) return tmpnode;
FreeWinding(w);
//split the area with a horizontal plane through the lowest point
qprintf("\r%6d", ++numladdersubdivisions);
//
AAS_SplitArea(tmparea, planenum, &frontarea, &backarea);
//
tmpnode->tmparea = NULL;
tmpnode->planenum = planenum;
//
tmpnode1 = AAS_AllocTmpNode();
tmpnode1->planenum = 0;
tmpnode1->tmparea = frontarea;
//
tmpnode2 = AAS_AllocTmpNode();
tmpnode2->planenum = 0;
tmpnode2->tmparea = backarea;
//subdivide the areas created by splitting recursively
tmpnode->children[0] = AAS_LadderSubdivideArea_r(tmpnode1);
tmpnode->children[1] = AAS_LadderSubdivideArea_r(tmpnode2);
//refresh the tree
AAS_RefreshLadderSubdividedTree_r(tmpaasworld.nodes, tmparea, tmpnode1, tmpnode2, planenum);
//
return tmpnode;
} //end of the function AAS_LadderSubdivideArea_r
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
tmp_node_t *AAS_LadderSubdivision_r(tmp_node_t *tmpnode)
{
//if this is a solid leaf
if (!tmpnode) return 0;
//negative so it's an area
if (tmpnode->tmparea) return AAS_LadderSubdivideArea_r(tmpnode);
//do the children recursively
tmpnode->children[0] = AAS_LadderSubdivision_r(tmpnode->children[0]);
tmpnode->children[1] = AAS_LadderSubdivision_r(tmpnode->children[1]);
return tmpnode;
} //end of the function AAS_LadderSubdivision_r
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void AAS_LadderSubdivision(void)
{
Log_Write("AAS_LadderSubdivision\r\n");
numladdersubdivisions = 0;
qprintf("%6i ladder subdivisions", numladdersubdivisions);
//start with the head node
AAS_LadderSubdivision_r(tmpaasworld.nodes);
//
qprintf("\n");
Log_Write("%6i ladder subdivisions\r\n", numladdersubdivisions);
} //end of the function AAS_LadderSubdivision