/* ============ idAASBuild::LedgeSubdivFlood_r ============ */ void idAASBuild::LedgeSubdivFlood_r( idBrushBSPNode *node, const idLedge *ledge ) { int s1, i; idBrushBSPPortal *p1; idWinding *w; idList<idBrushBSPNode *> nodeList; if( node->GetFlags() & NODE_VISITED ) { return; } // if this is not already a ledge area if( !( node->GetFlags() & AREA_LEDGE ) ) { for( p1 = node->GetPortals(); p1; p1 = p1->Next( s1 ) ) { s1 = ( p1->GetNode( 1 ) == node ); if( !( p1->GetFlags() & FACE_FLOOR ) ) { continue; } // split the area if some part of the floor portal is inside the expanded ledge w = ledge->ChopWinding( p1->GetWinding() ); if( !w ) { continue; } delete w; for( i = 0; i < ledge->numSplitPlanes; i++ ) { if( node->PlaneSide( ledge->planes[i], 0.1f ) != SIDE_CROSS ) { continue; } if( !node->Split( ledge->planes[i], -1 ) ) { continue; } numLedgeSubdivisions++; DisplayRealTimeString( "\r%6d", numLedgeSubdivisions ); node->GetChild( 0 )->SetFlag( NODE_VISITED ); LedgeSubdivFlood_r( node->GetChild( 1 ), ledge ); return; } node->SetFlag( AREA_LEDGE ); break; } } node->SetFlag( NODE_VISITED ); // get all nodes we might need to flood into for( p1 = node->GetPortals(); p1; p1 = p1->Next( s1 ) ) { s1 = ( p1->GetNode( 1 ) == node ); if( p1->GetNode( !s1 )->GetContents() & AREACONTENTS_SOLID ) { continue; } // flood through this portal if the portal is partly inside the expanded ledge w = ledge->ChopWinding( p1->GetWinding() ); if( !w ) { continue; } delete w; // add to list, cannot flood directly cause portals might be split on the way nodeList.Append( p1->GetNode( !s1 ) ); } // flood into other nodes for( i = 0; i < nodeList.Num(); i++ ) { LedgeSubdivLeafNodes_r( nodeList[i], ledge ); } }
/* ================ idAASBuild::GetAreaForLeafNode ================ */ bool idAASBuild::GetAreaForLeafNode( idBrushBSPNode* node, int* areaNum ) { int s, faceNum; idBrushBSPPortal* p; aasArea_t area; if( node->GetAreaNum() ) { *areaNum = -node->GetAreaNum(); return true; } area.flags = node->GetFlags(); area.cluster = area.clusterAreaNum = 0; area.contents = node->GetContents(); area.firstFace = file->faceIndex.Num(); area.numFaces = 0; area.reach = NULL; area.rev_reach = NULL; for( p = node->GetPortals(); p; p = p->Next( s ) ) { s = ( p->GetNode( 1 ) == node ); if( !GetFaceForPortal( p, s, &faceNum ) ) { continue; } file->faceIndex.Append( faceNum ); area.numFaces++; if( faceNum > 0 ) { file->faces[abs( faceNum )].areas[0] = file->areas.Num(); } else { file->faces[abs( faceNum )].areas[1] = file->areas.Num(); } } if( !area.numFaces ) { *areaNum = 0; return false; } *areaNum = -file->areas.Num(); node->SetAreaNum( file->areas.Num() ); file->areas.Append( area ); DisplayRealTimeString( "\r%6d", file->areas.Num() ); return true; }
/* ============ idAASBuild::MergeWithAdjacentLeafNodes ============ */ bool idAASBuild::MergeWithAdjacentLeafNodes( idBrushBSP &bsp, idBrushBSPNode *node ) { int s, numMerges = 0, otherNodeFlags; idBrushBSPPortal *p; do { for ( p = node->GetPortals(); p; p = p->Next(s) ) { s = (p->GetNode(1) == node); // both leaf nodes must have the same contents if ( node->GetContents() != p->GetNode(!s)->GetContents() ) { continue; } // cannot merge leaf nodes if one is near a ledge and the other is not if ( (node->GetFlags() & AREA_LEDGE) != (p->GetNode(!s)->GetFlags() & AREA_LEDGE) ) { continue; } // cannot merge leaf nodes if one has a floor portal and the other a gap portal if ( node->GetFlags() & AREA_FLOOR ) { if ( p->GetNode(!s)->GetFlags() & AREA_GAP ) { if ( !AllGapsLeadToOtherNode( p->GetNode(!s), node ) ) { continue; } } } else if ( node->GetFlags() & AREA_GAP ) { if ( p->GetNode(!s)->GetFlags() & AREA_FLOOR ) { if ( !AllGapsLeadToOtherNode( node, p->GetNode(!s) ) ) { continue; } } } otherNodeFlags = p->GetNode(!s)->GetFlags(); // try to merge the leaf nodes if ( bsp.TryMergeLeafNodes( p, s ) ) { node->SetFlag( otherNodeFlags ); if ( node->GetFlags() & AREA_FLOOR ) { node->RemoveFlag( AREA_GAP ); } numMerges++; DisplayRealTimeString( "\r%6d", ++numMergedLeafNodes ); break; } } } while( p ); if ( numMerges ) { return true; } return false; }
/* ============ idBrushList::Merge ============ */ void idBrushList::Merge( bool ( *MergeAllowed )( idBrush* b1, idBrush* b2 ) ) { idPlaneSet planeList; idBrush* b1, *b2, *nextb2; int numMerges; common->Printf( "[Brush Merge]\n" ); common->Printf( "%6d original brushes\n", Num() ); CreatePlaneList( planeList ); numMerges = 0; for( b1 = Head(); b1; b1 = b1->next ) { for( b2 = Head(); b2; b2 = nextb2 ) { nextb2 = b2->Next(); if( b2 == b1 ) { continue; } if( MergeAllowed && !MergeAllowed( b1, b2 ) ) { continue; } if( b1->TryMerge( b2, planeList ) ) { Delete( b2 ); DisplayRealTimeString( "\r%6d", ++numMerges ); nextb2 = Head(); } } } common->Printf( "\r%6d brushes merged\n", numMerges ); }
/* ============ idBrushList::Chop ============ */ void idBrushList::Chop( bool ( *ChopAllowed )( idBrush* b1, idBrush* b2 ) ) { idBrush* b1, *b2, *next; idBrushList sub1, sub2, keep; int i, j, c1, c2; idPlaneSet planeList; #ifdef OUTPUT_CHOP_STATS common->Printf( "[Brush CSG]\n" ); common->Printf( "%6d original brushes\n", this->Num() ); #endif CreatePlaneList( planeList ); for( b1 = this->Head(); b1; b1 = this->Head() ) { for( b2 = b1->next; b2; b2 = next ) { next = b2->next; for( i = 0; i < 3; i++ ) { if( b1->bounds[0][i] >= b2->bounds[1][i] ) { break; } if( b1->bounds[1][i] <= b2->bounds[0][i] ) { break; } } if( i < 3 ) { continue; } for( i = 0; i < b1->GetNumSides(); i++ ) { for( j = 0; j < b2->GetNumSides(); j++ ) { if( b1->GetSide( i )->GetPlaneNum() == ( b2->GetSide( j )->GetPlaneNum() ^ 1 ) ) { // opposite planes, so not touching break; } } if( j < b2->GetNumSides() ) { break; } } if( i < b1->GetNumSides() ) { continue; } sub1.Clear(); sub2.Clear(); c1 = 999999; c2 = 999999; // if b2 may chop up b1 if( !ChopAllowed || ChopAllowed( b2, b1 ) ) { if( !b1->Subtract( b2, sub1 ) ) { // didn't really intersect continue; } if( sub1.IsEmpty() ) { // b1 is swallowed by b2 this->Delete( b1 ); break; } c1 = sub1.Num(); } // if b1 may chop up b2 if( !ChopAllowed || ChopAllowed( b1, b2 ) ) { if( !b2->Subtract( b1, sub2 ) ) { // didn't really intersect continue; } if( sub2.IsEmpty() ) { // b2 is swallowed by b1 sub1.Free(); this->Delete( b2 ); continue; } c2 = sub2.Num(); } if( sub1.IsEmpty() && sub2.IsEmpty() ) { continue; } // don't allow too much fragmentation if( c1 > 2 && c2 > 2 ) { sub1.Free(); sub2.Free(); continue; } if( c1 < c2 ) { sub2.Free(); this->AddToTail( sub1 ); this->Delete( b1 ); break; } else { sub1.Free(); this->AddToTail( sub2 ); this->Delete( b2 ); continue; } } if( !b2 ) { // b1 is no longer intersecting anything, so keep it this->Remove( b1 ); keep.AddToTail( b1 ); #ifdef OUTPUT_CHOP_STATS DisplayRealTimeString( "\r%6d", keep.numBrushes ); #endif } } *this = keep; #ifdef OUTPUT_CHOP_STATS common->Printf( "\r%6d output brushes\n", Num() ); #endif }
void idAASBuild::GravSubdivLeafNode( idBrushBSPNode *node ) { int s1, s2, i, j, k, side1; int numSplits, numSplitters; idBrushBSPPortal *p1, *p2; idWinding *w1, *w2; idVec3 normal; idPlane plane; idPlaneSet planeList; float d, min, max; int *splitterOrder; int *bestNumSplits; int floor, gap, numFloorChecked; // if this leaf node is already classified it cannot have a combination of floor and gap portals if ( node->GetFlags() & (AREA_FLOOR|AREA_GAP) ) { return; } floor = gap = 0; // check if the area has a floor for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) { s1 = (p1->GetNode(1) == node); if ( p1->GetFlags() & FACE_FLOOR ) { floor++; } } // find seperating planes between gap and floor portals for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) { s1 = (p1->GetNode(1) == node); // if the portal is a gap seen from this side if ( PortalIsGap( p1, s1 ) ) { gap++; // if the area doesn't have a floor if ( !floor ) { break; } } else { continue; } numFloorChecked = 0; w1 = p1->GetWinding(); // test all edges of the gap for ( i = 0; i < w1->GetNumPoints(); i++ ) { // create a plane through the edge of the gap parallel to the direction of gravity normal = (*w1)[(i+1)%w1->GetNumPoints()].ToVec3() - (*w1)[i].ToVec3(); normal = normal.Cross( aasSettings->invGravityDir ); if ( normal.Normalize() < 0.2f ) { continue; } plane.SetNormal( normal ); plane.FitThroughPoint( (*w1)[i].ToVec3() ); // get the side of the plane the gap is on side1 = w1->PlaneSide( plane, GRAVSUBDIV_EPSILON ); if ( side1 == SIDE_ON ) { break; } // test if the plane through the edge of the gap seperates the gap from a floor portal for ( p2 = node->GetPortals(); p2; p2 = p2->Next(s2) ) { s2 = (p2->GetNode(1) == node); if ( !( p2->GetFlags() & FACE_FLOOR ) ) { continue; } if ( p2->GetFlags() & FACE_CHECKED ) { continue; } w2 = p2->GetWinding(); min = 2.0f * GRAVSUBDIV_EPSILON; max = GRAVSUBDIV_EPSILON; if ( side1 == SIDE_FRONT ) { for ( j = 0; j < w2->GetNumPoints(); j++ ) { d = plane.Distance( (*w2)[j].ToVec3() ); if ( d >= GRAVSUBDIV_EPSILON ) { break; // point at the same side of the plane as the gap } d = idMath::Fabs( d ); if ( d < min ) { min = d; } if ( d > max ) { max = d; } } } else { for ( j = 0; j < w2->GetNumPoints(); j++ ) { d = plane.Distance( (*w2)[j].ToVec3() ); if ( d <= -GRAVSUBDIV_EPSILON ) { break; // point at the same side of the plane as the gap } d = idMath::Fabs( d ); if ( d < min ) { min = d; } if ( d > max ) { max = d; } } } // a point of the floor portal was found to be at the same side of the plane as the gap if ( j < w2->GetNumPoints() ) { continue; } // if the floor portal touches the plane if ( min < GRAVSUBDIV_EPSILON && max > GRAVSUBDIV_EPSILON ) { planeList.FindPlane( plane, 0.00001f, 0.1f ); } p2->SetFlag( FACE_CHECKED ); numFloorChecked++; } if ( numFloorChecked == floor ) { break; } } for ( p2 = node->GetPortals(); p2; p2 = p2->Next(s2) ) { s2 = (p2->GetNode(1) == node); p2->RemoveFlag( FACE_CHECKED ); } } // if the leaf node does not have both floor and gap portals if ( !( gap && floor) ) { if ( floor ) { node->SetFlag( AREA_FLOOR ); } else if ( gap ) { node->SetFlag( AREA_GAP ); } return; } // if no valid seperators found if ( planeList.Num() == 0 ) { // NOTE: this should never happend, if it does the leaf node has degenerate portals return; } splitterOrder = (int *) _alloca( planeList.Num() * sizeof( int ) ); bestNumSplits = (int *) _alloca( planeList.Num() * sizeof( int ) ); numSplitters = 0; // test all possible seperators and sort them from best to worst for ( i = 0; i < planeList.Num(); i += 2 ) { numSplits = 0; for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) { s1 = (p1->GetNode(1) == node); if ( p1->GetWinding()->PlaneSide( planeList[i], 0.1f ) == SIDE_CROSS ) { numSplits++; } } for ( j = 0; j < numSplitters; j++ ) { if ( numSplits < bestNumSplits[j] ) { for ( k = numSplitters; k > j; k-- ) { bestNumSplits[k] = bestNumSplits[k-1]; splitterOrder[k] = splitterOrder[k-1]; } bestNumSplits[j] = numSplits; splitterOrder[j] = i; numSplitters++; break; } } if ( j >= numSplitters ) { bestNumSplits[j] = numSplits; splitterOrder[j] = i; numSplitters++; } } // try all seperators in order from best to worst for ( i = 0; i < numSplitters; i++ ) { if ( node->Split( planeList[splitterOrder[i]], -1 ) ) { // we found a seperator that works break; } } if ( i >= numSplitters) { return; } DisplayRealTimeString( "\r%6d", ++numGravitationalSubdivisions ); // test children for further splits GravSubdivLeafNode( node->GetChild(0) ); GravSubdivLeafNode( node->GetChild(1) ); }