/* =========================================================================== 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