/* BobToolz plugin for GtkRadiant Copyright (C) 2001 Gordon Biggans This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // DBrush.cpp: implementation of the DBrush class. // ////////////////////////////////////////////////////////////////////// #include "DBrush.h" #ifdef WIN32 #pragma warning(disable : 4786) #endif #include #include "str.h" #include "DPoint.h" #include "DPlane.h" #include "DEPair.h" #include "DPatch.h" #include "DEntity.h" #include "DWinding.h" #include "dialogs/dialogs-gtk.h" #include "misc.h" #include "iundo.h" #include "generic/referencecounted.h" #include "scenelib.h" ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// DBrush::DBrush( int ID ){ m_nBrushID = ID; bBoundsBuilt = false; QER_entity = NULL; QER_brush = NULL; } DBrush::~DBrush(){ ClearFaces(); ClearPoints(); } ////////////////////////////////////////////////////////////////////// // Implementation ////////////////////////////////////////////////////////////////////// DPlane* DBrush::AddFace( const vec3_t va, const vec3_t vb, const vec3_t vc, const _QERFaceData* texData ){ #ifdef _DEBUG // Sys_Printf("(%f %f %f) (%f %f %f) (%f %f %f)\n", va[0], va[1], va[2], vb[0], vb[1], vb[2], vc[0], vc[1], vc[2]); #endif bBoundsBuilt = false; DPlane* newFace = new DPlane( va, vb, vc, texData ); faceList.push_back( newFace ); return newFace; } int DBrush::BuildPoints(){ ClearPoints(); if ( faceList.size() <= 3 ) { // if less than 3 faces, there can be no points return 0; // with only 3 faces u can't have a bounded soild } for ( std::list::const_iterator p1 = faceList.begin(); p1 != faceList.end(); p1++ ) { std::list::const_iterator p2 = p1; for ( p2++; p2 != faceList.end(); p2++ ) { std::list::const_iterator p3 = p2; for ( p3++; p3 != faceList.end(); p3++ ) { vec3_t pnt; if ( ( *p1 )->PlaneIntersection( *p2, *p3, pnt ) ) { int pos = PointPosition( pnt ); if ( pos == POINT_IN_BRUSH ) { // ???? shouldn't happen here globalErrorStream() << "ERROR:: Build Brush Points: Point IN brush!!!\n"; } else if ( pos == POINT_ON_BRUSH ) { // normal point if ( !HasPoint( pnt ) ) { AddPoint( pnt ); } /* else Sys_Printf("Duplicate Point Found, pyramids ahoy!!!!!\n");*/ // point lies on more that 3 planes } // otherwise point is removed due to another plane.. // Sys_Printf("(%f, %f, %f)\n", pnt[0], pnt[1], pnt[2]); } } } } #ifdef _DEBUG // Sys_Printf("%i points on brush\n", pointList.size()); #endif return static_cast( pointList.size() ); } void DBrush_addFace( DBrush& brush, const _QERFaceData& faceData ){ brush.AddFace( vector3_to_array( faceData.m_p0 ), vector3_to_array( faceData.m_p1 ), vector3_to_array( faceData.m_p2 ), 0 ); } typedef ReferenceCaller1 DBrushAddFaceCaller; void DBrush_addFaceTextured( DBrush& brush, const _QERFaceData& faceData ){ brush.AddFace( vector3_to_array( faceData.m_p0 ), vector3_to_array( faceData.m_p1 ), vector3_to_array( faceData.m_p2 ), &faceData ); } typedef ReferenceCaller1 DBrushAddFaceTexturedCaller; void DBrush::LoadFromBrush( scene::Instance& brush, bool textured ){ ClearFaces(); ClearPoints(); GlobalBrushCreator().Brush_forEachFace( brush.path().top(), textured ? BrushFaceDataCallback( DBrushAddFaceTexturedCaller( *this ) ) : BrushFaceDataCallback( DBrushAddFaceCaller( *this ) ) ); QER_entity = brush.path().parent().get_pointer(); QER_brush = brush.path().top().get_pointer(); } int DBrush::PointPosition( vec3_t pnt ){ int state = POINT_IN_BRUSH; // if nothing happens point is inside brush for ( std::list::const_iterator chkPlane = faceList.begin(); chkPlane != faceList.end(); chkPlane++ ) { float dist = ( *chkPlane )->DistanceToPoint( pnt ); if ( dist > MAX_ROUND_ERROR ) { return POINT_OUT_BRUSH; // if point is in front of plane, it CANT be in the brush } else if ( fabs( dist ) < MAX_ROUND_ERROR ) { state = POINT_ON_BRUSH; // if point is ON plane point is either ON the brush } // or outside it, it can no longer be in it } return state; } void DBrush::ClearPoints(){ for ( std::list::const_iterator deadPoint = pointList.begin(); deadPoint != pointList.end(); deadPoint++ ) { delete *deadPoint; } pointList.clear(); } void DBrush::ClearFaces(){ bBoundsBuilt = false; for ( std::list::const_iterator deadPlane = faceList.begin(); deadPlane != faceList.end(); deadPlane++ ) { delete *deadPlane; } faceList.clear(); } void DBrush::AddPoint( vec3_t pnt ){ DPoint* newPoint = new DPoint; VectorCopy( pnt, newPoint->_pnt ); pointList.push_back( newPoint ); } bool DBrush::HasPoint( vec3_t pnt ){ for ( std::list::const_iterator chkPoint = pointList.begin(); chkPoint != pointList.end(); chkPoint++ ) { if ( **chkPoint == pnt ) { return true; } } return false; } int DBrush::RemoveRedundantPlanes(){ int cnt = 0; std::list::iterator chkPlane; // find duplicate planes std::list::iterator p1 = faceList.begin(); while ( p1 != faceList.end() ) { std::list::iterator p2 = p1; for ( p2++; p2 != faceList.end(); p2++ ) { if ( **p1 == **p2 ) { if ( !strcmp( ( *p1 )->m_shader.c_str(), "textures/common/caulk" ) ) { delete *p1; p1 = faceList.erase( p1 ); // duplicate plane } else { delete *p2; p2 = faceList.erase( p2 ); // duplicate plane } cnt++; break; } } if ( p2 == faceList.end() ) { p1++; } } //+djbob kill planes with bad normal, they are more of a nuisance than losing a brush chkPlane = faceList.begin(); while ( chkPlane != faceList.end() ) { if ( VectorLength( ( *chkPlane )->normal ) == 0 ) { // plane has bad normal delete *chkPlane; chkPlane = faceList.erase( chkPlane ); cnt++; } else { chkPlane++; } } //-djbob if ( pointList.size() == 0 ) { // if points may not have been built, build them /* if(BuildPoints() == 0) // just let the planes die if they are all bad return cnt;*/ BuildPoints(); } chkPlane = faceList.begin(); while ( chkPlane != faceList.end() ) { if ( ( *chkPlane )->IsRedundant( pointList ) ) { // checks that plane "0wnz" :), 3 or more points delete *chkPlane; chkPlane = faceList.erase( chkPlane ); cnt++; } else{ chkPlane++; } } return cnt; } bool DBrush::GetBounds( vec3_t min, vec3_t max ){ BuildBounds(); if ( !bBoundsBuilt ) { return false; } VectorCopy( bbox_min, min ); VectorCopy( bbox_max, max ); return true; } bool DBrush::BBoxCollision( DBrush* chkBrush ){ vec3_t min1, min2; vec3_t max1, max2; GetBounds( min1, max1 ); chkBrush->GetBounds( min2, max2 ); if ( min1[0] >= max2[0] ) { return false; } if ( min1[1] >= max2[1] ) { return false; } if ( min1[2] >= max2[2] ) { return false; } if ( max1[0] <= min2[0] ) { return false; } if ( max1[1] <= min2[1] ) { return false; } if ( max1[2] <= min2[2] ) { return false; } return true; } DPlane* DBrush::HasPlane( DPlane* chkPlane ){ for ( std::list::const_iterator brushPlane = faceList.begin(); brushPlane != faceList.end(); brushPlane++ ) { if ( **brushPlane == *chkPlane ) { return *brushPlane; } } return NULL; } bool DBrush::IsCutByPlane( DPlane *cuttingPlane ){ bool isInFront; if ( pointList.size() == 0 ) { if ( BuildPoints() == 0 ) { return false; } } std::list::const_iterator chkPnt = pointList.begin(); if ( chkPnt == pointList.end() ) { return false; } float dist = cuttingPlane->DistanceToPoint( ( *chkPnt )->_pnt ); if ( dist > MAX_ROUND_ERROR ) { isInFront = false; } else if ( dist < MAX_ROUND_ERROR ) { isInFront = true; } else{ return true; } for ( chkPnt++ = pointList.begin(); chkPnt != pointList.end(); chkPnt++ ) { dist = cuttingPlane->DistanceToPoint( ( *chkPnt )->_pnt ); if ( dist > MAX_ROUND_ERROR ) { if ( isInFront ) { return true; } } else if ( dist < MAX_ROUND_ERROR ) { if ( !isInFront ) { return true; } } else{ return true; } } return false; } scene::Node* DBrush::BuildInRadiant( bool allowDestruction, int* changeCnt, scene::Node* entity ){ if ( allowDestruction ) { bool kill = true; for ( std::list::const_iterator chkPlane = faceList.begin(); chkPlane != faceList.end(); chkPlane++ ) { if ( ( *chkPlane )->m_bChkOk ) { kill = false; break; } } if ( kill ) { return NULL; } } //+djbob: fixed bug when brush had no faces "phantom brush" in radiant. if ( faceList.size() < 4 ) { globalErrorStream() << "Possible Phantom Brush Found, will not rebuild\n"; return NULL; } //-djbob NodeSmartReference node( GlobalBrushCreator().createBrush() ); for ( std::list::const_iterator buildPlane = faceList.begin(); buildPlane != faceList.end(); buildPlane++ ) { if ( ( *buildPlane )->AddToBrush( node ) && changeCnt ) { ( *changeCnt )++; } } if ( entity ) { Node_getTraversable( *entity )->insert( node ); } else { Node_getTraversable( GlobalRadiant().getMapWorldEntity() )->insert( node ); } QER_entity = entity; QER_brush = node.get_pointer(); return node.get_pointer(); } void DBrush::CutByPlane( DPlane *cutPlane, DBrush **newBrush1, DBrush **newBrush2 ){ if ( !IsCutByPlane( cutPlane ) ) { *newBrush1 = NULL; *newBrush2 = NULL; return; } DBrush* b1 = new DBrush; DBrush* b2 = new DBrush; for ( std::list::const_iterator parsePlane = faceList.begin(); parsePlane != faceList.end(); parsePlane++ ) { b1->AddFace( ( *parsePlane )->points[0], ( *parsePlane )->points[1], ( *parsePlane )->points[2], NULL ); b2->AddFace( ( *parsePlane )->points[0], ( *parsePlane )->points[1], ( *parsePlane )->points[2], NULL ); } b1->AddFace( cutPlane->points[0], cutPlane->points[1], cutPlane->points[2], NULL ); b2->AddFace( cutPlane->points[2], cutPlane->points[1], cutPlane->points[0], NULL ); b1->RemoveRedundantPlanes(); b2->RemoveRedundantPlanes(); *newBrush1 = b1; *newBrush2 = b2; } bool DBrush::IntersectsWith( DBrush *chkBrush ){ if ( pointList.size() == 0 ) { if ( BuildPoints() == 0 ) { return false; // invalid brush!!!! } } if ( chkBrush->pointList.size() == 0 ) { if ( chkBrush->BuildPoints() == 0 ) { return false; // invalid brush!!!! } } if ( !BBoxCollision( chkBrush ) ) { return false; } std::list::const_iterator iplPlane; for ( iplPlane = faceList.begin(); iplPlane != faceList.end(); iplPlane++ ) { bool allInFront = true; for ( std::list::const_iterator iPoint = chkBrush->pointList.begin(); iPoint != chkBrush->pointList.end(); iPoint++ ) { if ( ( *iplPlane )->DistanceToPoint( ( *iPoint )->_pnt ) < -MAX_ROUND_ERROR ) { allInFront = false; break; } } if ( allInFront ) { return false; } } for ( iplPlane = chkBrush->faceList.begin(); iplPlane != chkBrush->faceList.end(); iplPlane++ ) { bool allInFront = true; for ( std::list::const_iterator iPoint = pointList.begin(); iPoint != pointList.end(); iPoint++ ) { if ( ( *iplPlane )->DistanceToPoint( ( *iPoint )->_pnt ) < -MAX_ROUND_ERROR ) { allInFront = false; break; } } if ( allInFront ) { return false; } } return true; } bool DBrush::IntersectsWith( DPlane* p1, DPlane* p2, vec3_t v ) { vec3_t vDown = { 0, 0, -1 }; std::list::const_iterator iplPlane; for ( iplPlane = faceList.begin(); iplPlane != faceList.end(); iplPlane++ ) { DPlane* p = ( *iplPlane ); vec_t d = DotProduct( p->normal, vDown ); if ( d >= 0 ) { continue; } if ( p->PlaneIntersection( p1, p2, v ) ) { if ( PointPosition( v ) != POINT_OUT_BRUSH ) { return true; } } } return false; } void DBrush::BuildBounds(){ if ( !bBoundsBuilt ) { if ( pointList.size() == 0 ) { // if points may not have been built, build them if ( BuildPoints() == 0 ) { return; } } std::list::const_iterator first = pointList.begin(); VectorCopy( ( *first )->_pnt, bbox_min ); VectorCopy( ( *first )->_pnt, bbox_max ); std::list::const_iterator point = pointList.begin(); for ( point++; point != pointList.end(); point++ ) { if ( ( *point )->_pnt[0] > bbox_max[0] ) { bbox_max[0] = ( *point )->_pnt[0]; } if ( ( *point )->_pnt[1] > bbox_max[1] ) { bbox_max[1] = ( *point )->_pnt[1]; } if ( ( *point )->_pnt[2] > bbox_max[2] ) { bbox_max[2] = ( *point )->_pnt[2]; } if ( ( *point )->_pnt[0] < bbox_min[0] ) { bbox_min[0] = ( *point )->_pnt[0]; } if ( ( *point )->_pnt[1] < bbox_min[1] ) { bbox_min[1] = ( *point )->_pnt[1]; } if ( ( *point )->_pnt[2] < bbox_min[2] ) { bbox_min[2] = ( *point )->_pnt[2]; } } bBoundsBuilt = true; } } bool DBrush::BBoxTouch( DBrush *chkBrush ){ vec3_t min1, min2; vec3_t max1, max2; GetBounds( min1, max1 ); chkBrush->GetBounds( min2, max2 ); if ( ( min1[0] - max2[0] ) > MAX_ROUND_ERROR ) { return false; } if ( ( min1[1] - max2[1] ) > MAX_ROUND_ERROR ) { return false; } if ( ( min1[2] - max2[2] ) > MAX_ROUND_ERROR ) { return false; } if ( ( min2[0] - max1[0] ) > MAX_ROUND_ERROR ) { return false; } if ( ( min2[1] - max1[1] ) > MAX_ROUND_ERROR ) { return false; } if ( ( min2[2] - max1[2] ) > MAX_ROUND_ERROR ) { return false; } int cnt = 0; if ( ( min2[0] - max1[0] ) == 0 ) { cnt++; } if ( ( min2[1] - max1[1] ) == 0 ) { cnt++; } if ( ( min2[2] - max1[2] ) == 0 ) { cnt++; } if ( ( min1[0] - max2[0] ) == 0 ) { cnt++; } if ( ( min1[1] - max2[1] ) == 0 ) { cnt++; } if ( ( min1[2] - max2[2] ) == 0 ) { cnt++; } if ( cnt > 1 ) { return false; } return true; } void DBrush::ResetChecks( std::list* exclusionList ){ for ( std::list::const_iterator resetPlane = faceList.begin(); resetPlane != faceList.end(); resetPlane++ ) { bool set = false; if ( exclusionList ) { for ( std::list::iterator eTexture = exclusionList->begin(); eTexture != exclusionList->end(); eTexture++ ) { if ( strstr( ( *resetPlane )->m_shader.c_str(), eTexture->GetBuffer() ) ) { set = true; break; } } } ( *resetPlane )->m_bChkOk = set; } } DPlane* DBrush::HasPlaneInverted( DPlane *chkPlane ){ for ( std::list::const_iterator brushPlane = faceList.begin(); brushPlane != faceList.end(); brushPlane++ ) { if ( **brushPlane != *chkPlane ) { if ( fabs( ( *brushPlane )->_d + chkPlane->_d ) < 0.1 ) { return ( *brushPlane ); } } } return NULL; } bool DBrush::HasTexture( const char *textureName ){ for ( std::list::const_iterator chkPlane = faceList.begin(); chkPlane != faceList.end(); chkPlane++ ) { if ( strstr( ( *chkPlane )->m_shader.c_str(), textureName ) ) { return true; } } return false; } bool DBrush::IsDetail(){ for ( std::list::const_iterator chkPlane = faceList.begin(); chkPlane != faceList.end(); chkPlane++ ) { if ( ( *chkPlane )->texInfo.contents & FACE_DETAIL ) { return true; } } return false; } void DBrush::BuildFromWinding( DWinding *w ){ if ( w->numpoints < 3 ) { globalErrorStream() << "Winding has invalid number of points"; return; } DPlane* wPlane = w->WindingPlane(); DWinding* w2; w2 = w->CopyWinding(); int i; for ( i = 0; i < w2->numpoints; i++ ) VectorAdd( w2->p[i], wPlane->normal, w2->p[i] ); AddFace( w2->p[0], w2->p[1], w2->p[2], NULL ); AddFace( w->p[2], w->p[1], w->p[0], NULL ); for ( i = 0; i < w->numpoints - 1; i++ ) AddFace( w2->p[i], w->p[i], w->p[i + 1], NULL ); AddFace( w2->p[w->numpoints - 1], w->p[w->numpoints - 1], w->p[0], NULL ); delete wPlane; delete w2; } void DBrush::SaveToFile( FILE *pFile ){ fprintf( pFile, "{\n" ); for ( std::list::const_iterator pp = faceList.begin(); pp != faceList.end(); pp++ ) { char buffer[512]; sprintf( buffer, "( %.0f %.0f %.0f ) ( %.0f %.0f %.0f ) ( %.0f %.0f %.0f ) %s %.0f %.0f %f %f %.0f 0 0 0\n", ( *pp )->points[0][0], ( *pp )->points[0][1], ( *pp )->points[0][2], ( *pp )->points[1][0], ( *pp )->points[1][1], ( *pp )->points[1][2], ( *pp )->points[2][0], ( *pp )->points[2][1], ( *pp )->points[2][2], ( *pp )->m_shader.c_str(), ( *pp )->texInfo.m_texdef.shift[0], ( *pp )->texInfo.m_texdef.shift[1], ( *pp )->texInfo.m_texdef.scale[0], ( *pp )->texInfo.m_texdef.scale[0], ( *pp )->texInfo.m_texdef.rotate ); fprintf( pFile, buffer ); } fprintf( pFile, "}\n" ); } void DBrush::Rotate( vec3_t vOrigin, vec3_t vRotation ){ for ( std::list::const_iterator rotPlane = faceList.begin(); rotPlane != faceList.end(); rotPlane++ ) { for ( int i = 0; i < 3; i++ ) VectorRotate( ( *rotPlane )->points[i], vRotation, vOrigin ); ( *rotPlane )->Rebuild(); } } void DBrush::RotateAboutCentre( vec3_t vRotation ){ vec3_t min, max, centre; GetBounds( min, max ); VectorAdd( min, max, centre ); VectorScale( centre, 0.5f, centre ); Rotate( centre, vRotation ); } bool DBrush::ResetTextures( const char* textureName, float fScale[2], float fShift[2], int rotation, const char* newTextureName, int bResetTextureName, int bResetScale[2], int bResetShift[2], int bResetRotation ){ if ( textureName ) { bool changed = false; for ( std::list::const_iterator resetPlane = faceList.begin(); resetPlane != faceList.end(); resetPlane++ ) { if ( !strcmp( ( *resetPlane )->m_shader.c_str(), textureName ) ) { if ( bResetTextureName ) { ( *resetPlane )->m_shader = newTextureName; } if ( bResetScale[0] ) { ( *resetPlane )->texInfo.m_texdef.scale[0] = fScale[0]; } if ( bResetScale[1] ) { ( *resetPlane )->texInfo.m_texdef.scale[1] = fScale[1]; } if ( bResetShift[0] ) { ( *resetPlane )->texInfo.m_texdef.shift[0] = fShift[0]; } if ( bResetShift[1] ) { ( *resetPlane )->texInfo.m_texdef.shift[1] = fShift[1]; } if ( bResetRotation ) { ( *resetPlane )->texInfo.m_texdef.rotate = (float)rotation; } changed = true; } } return changed; // no point rebuilding unless we need to, only slows things down } else { for ( std::list::const_iterator resetPlane = faceList.begin(); resetPlane != faceList.end(); resetPlane++ ) { if ( bResetTextureName ) { ( *resetPlane )->m_shader = newTextureName; } if ( bResetScale[0] ) { ( *resetPlane )->texInfo.m_texdef.scale[0] = fScale[0]; } if ( bResetScale[1] ) { ( *resetPlane )->texInfo.m_texdef.scale[1] = fScale[1]; } if ( bResetShift[0] ) { ( *resetPlane )->texInfo.m_texdef.shift[0] = fShift[0]; } if ( bResetShift[1] ) { ( *resetPlane )->texInfo.m_texdef.shift[1] = fShift[1]; } if ( bResetRotation ) { ( *resetPlane )->texInfo.m_texdef.rotate = (float)rotation; } } return true; } } bool DBrush::operator ==( DBrush* other ){ std::list::const_iterator chkPlane; for ( chkPlane = faceList.begin(); chkPlane != faceList.end(); chkPlane++ ) { if ( !other->HasPlane( ( *chkPlane ) ) ) { return false; } } for ( chkPlane = faceList.begin(); chkPlane != faceList.end(); chkPlane++ ) { if ( !HasPlane( ( *chkPlane ) ) ) { return false; } } return true; } DPlane* DBrush::AddFace( const vec3_t va, const vec3_t vb, const vec3_t vc, const char *textureName, bool bDetail ){ bBoundsBuilt = false; DPlane* newFace = new DPlane( va, vb, vc, textureName, bDetail ); faceList.push_back( newFace ); return newFace; } DPlane* DBrush::FindPlaneWithClosestNormal( vec_t* normal ) { vec_t bestDot = -2; DPlane* bestDotPlane = NULL; std::list::const_iterator chkPlane; for ( chkPlane = faceList.begin(); chkPlane != faceList.end(); chkPlane++ ) { DPlane* pPlane = ( *chkPlane ); vec_t dot = DotProduct( pPlane->normal, normal ); if ( dot > bestDot ) { bestDot = dot; bestDotPlane = pPlane; } } return bestDotPlane; } int DBrush::FindPointsForPlane( DPlane* plane, DPoint** pnts, int maxpnts ) { int numpnts = 0; if ( !maxpnts ) { return 0; } BuildPoints(); for ( std::list::const_iterator points = pointList.begin(); points != pointList.end(); points++ ) { DPoint* point = ( *points ); if ( fabs( plane->DistanceToPoint( point->_pnt ) ) < MAX_ROUND_ERROR ) { pnts[numpnts] = point; numpnts++; if ( numpnts >= maxpnts ) { return numpnts; } } } return numpnts; } void DBrush::RemovePlane( DPlane* plane ) { bBoundsBuilt = false; for ( std::list::const_iterator deadPlane = faceList.begin(); deadPlane != faceList.end(); deadPlane++ ) { if ( *deadPlane == plane ) { delete *deadPlane; faceList.remove( plane ); } } }