/* 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 "StdAfx.h" #ifdef _WIN32 #pragma warning(disable : 4786) #endif #include "DBrush.h" #include "DWinding.h" #include "dialogs/dialogs-gtk.h" #include "misc.h" ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// DBrush::DBrush(int ID) { m_nBrushID = ID; bBoundsBuilt = FALSE; QER_brush = NULL; } DBrush::~DBrush() { ClearFaces(); ClearPoints(); } ////////////////////////////////////////////////////////////////////// // Implementation ////////////////////////////////////////////////////////////////////// DPlane* DBrush::AddFace(vec3_t va, vec3_t vb, vec3_t vc, _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(list::const_iterator p1=faceList.begin(); p1!=faceList.end(); p1++) { list::const_iterator p2=p1; for(p2++; p2!=faceList.end(); p2++) { 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 Sys_Printf("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 pointList.size(); } void DBrush::LoadFromBrush_t(brush_t* brush, bool textured) { ClearFaces(); ClearPoints(); for(int i = g_FuncTable.m_pfnGetFaceCount(brush)-1; i >= 0 ; i--) { // running backwards so i dont have to use the count function each time (OPT) _QERFaceData* faceData = g_FuncTable.m_pfnGetFaceData(brush, i); if(faceData == NULL) DoMessageBox("Null pointer returned", "WARNING!", MB_OK); if(textured) AddFace(faceData->m_v1, faceData->m_v2, faceData->m_v3, faceData); else AddFace(faceData->m_v1, faceData->m_v2, faceData->m_v3, NULL); } QER_brush = brush; } int DBrush::PointPosition(vec3_t pnt) { int state = POINT_IN_BRUSH; // if nothing happens point is inside brush for(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(list::const_iterator deadPoint=pointList.begin(); deadPoint!=pointList.end(); deadPoint++) { delete *deadPoint; } pointList.clear(); } void DBrush::ClearFaces() { bBoundsBuilt = FALSE; for(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(list::const_iterator chkPoint=pointList.begin(); chkPoint!=pointList.end(); chkPoint++) { if(**chkPoint == pnt) return TRUE; } return FALSE; } int DBrush::RemoveRedundantPlanes() { int cnt = 0; list::iterator chkPlane; // find duplicate planes list::iterator p1=faceList.begin(); while( p1!=faceList.end() ) { list::iterator p2 = p1; for(p2++; p2!=faceList.end(); p2++) { if(**p1 == **p2) { if(!strcmp((*p1)->texInfo.m_TextureName, "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(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; 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; } brush_t* DBrush::BuildInRadiant(bool allowDestruction, int* changeCnt, entity_t* entity) { if(allowDestruction) { bool kill = TRUE; for(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) { Sys_Printf("Possible Phantom Brush Found, will not rebuild\n"); return NULL; } //-djbob QER_brush = (brush_t*)g_FuncTable.m_pfnCreateBrushHandle(); for(list::const_iterator buildPlane=faceList.begin(); buildPlane!=faceList.end(); buildPlane++) { if((*buildPlane)->AddToBrush_t(QER_brush) && changeCnt) { (*changeCnt)++; } } if(entity) { g_FuncTable.m_pfnCommitBrushHandleToEntity(QER_brush, entity); g_BrushTable.m_pfnBrush_Build(QER_brush, false, false, false, false); g_BrushTable.m_pfnBrush_AddToList(QER_brush, g_AppDataTable.m_pfnSelectedBrushes()); } else { g_FuncTable.m_pfnCommitBrushHandle(QER_brush); } return QER_brush; } 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(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; list::const_iterator iplPlane; for( iplPlane=faceList.begin(); iplPlane!=faceList.end(); iplPlane++) { bool allInFront = TRUE; for(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(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 }; 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; list::const_iterator first = pointList.begin(); VectorCopy((*first)->_pnt, bbox_min); VectorCopy((*first)->_pnt, bbox_max); 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(list* exclusionList) { for(list::const_iterator resetPlane=faceList.begin(); resetPlane!=faceList.end(); resetPlane++) { bool set = FALSE; if(exclusionList) { for(list::iterator eTexture = exclusionList->begin(); eTexture != exclusionList->end(); eTexture++) { if(strstr((*resetPlane)->texInfo.m_TextureName, eTexture->GetBuffer())) { set = TRUE; break; } } } (*resetPlane)->m_bChkOk = set; } } DPlane* DBrush::HasPlaneInverted(DPlane *chkPlane) { for(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(list::const_iterator chkPlane=faceList.begin(); chkPlane!=faceList.end(); chkPlane++) { if(strstr((*chkPlane)->texInfo.m_TextureName, textureName)) return TRUE; } return FALSE; } bool DBrush::IsDetail() { for(list::const_iterator chkPlane=faceList.begin(); chkPlane!=faceList.end(); chkPlane++) { if((*chkPlane)->texInfo.m_nContents & FACE_DETAIL) return TRUE; } return FALSE; } void DBrush::BuildFromWinding(DWinding *w) { if(w->numpoints < 3) { Sys_ERROR("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(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)->texInfo.m_TextureName, (*pp)->texInfo.m_fShift[0], (*pp)->texInfo.m_fShift[1], (*pp)->texInfo.m_fScale[0], (*pp)->texInfo.m_fScale[0], (*pp)->texInfo.m_fRotate); fprintf(pFile, buffer); } fprintf(pFile, "}\n"); } void DBrush::Rotate(vec3_t vOrigin, vec3_t vRotation) { for(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(list::const_iterator resetPlane=faceList.begin(); resetPlane!=faceList.end(); resetPlane++) { if(!strcmp((*resetPlane)->texInfo.m_TextureName, textureName)) { if(bResetTextureName) strcpy((*resetPlane)->texInfo.m_TextureName, newTextureName); if(bResetScale[0]) (*resetPlane)->texInfo.m_fScale[0] = fScale[0]; if(bResetScale[1]) (*resetPlane)->texInfo.m_fScale[1] = fScale[1]; if(bResetShift[0]) (*resetPlane)->texInfo.m_fShift[0] = fShift[0]; if(bResetShift[1]) (*resetPlane)->texInfo.m_fShift[1] = fShift[1]; if(bResetRotation) (*resetPlane)->texInfo.m_fRotate = (float)rotation; changed = TRUE; } } return changed; // no point rebuilding unless we need to, only slows things down } else { for(list::const_iterator resetPlane=faceList.begin(); resetPlane!=faceList.end(); resetPlane++) { if(bResetTextureName) strcpy((*resetPlane)->texInfo.m_TextureName, newTextureName); if(bResetScale[0]) (*resetPlane)->texInfo.m_fScale[0] = fScale[0]; if(bResetScale[1]) (*resetPlane)->texInfo.m_fScale[1] = fScale[1]; if(bResetShift[0]) (*resetPlane)->texInfo.m_fShift[0] = fShift[0]; if(bResetShift[1]) (*resetPlane)->texInfo.m_fShift[1] = fShift[1]; if(bResetRotation) (*resetPlane)->texInfo.m_fRotate = (float)rotation; } return TRUE; } } bool DBrush::operator ==(DBrush* other) { 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(vec3_t va, vec3_t vb, 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; 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( 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( list::const_iterator deadPlane = faceList.begin(); deadPlane != faceList.end(); deadPlane++ ) { if(*deadPlane == plane) { delete *deadPlane; faceList.remove( plane ); } } } void DBrush::RemoveFromRadiant( void ) { if(QER_brush) { g_FuncTable.m_pfnDeleteBrushHandle(QER_brush); } }