/* Copyright (c) 2002-2012 Croteam Ltd. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "stdh.h" #include #include #include #include #include #include #include #include #include #include #include #include #include template CDynamicArray; extern BOOL _bPortalSectorLinksPreLoaded = FALSE; extern BOOL _bEntitySectorLinksPreLoaded = FALSE; /* * Calculate bounding boxes in all brushes. */ void CBrushArchive::CalculateBoundingBoxes(void) { _pfWorldEditingProfile.StartTimer(CWorldEditingProfile::PTI_CALCULATEBOUNDINGBOXES); // for each of the brushes FOREACHINDYNAMICARRAY(ba_abrBrushes, CBrush3D, itbr) { // if the brush has no entity if (itbr->br_penEntity==NULL) { // skip it continue; } // calculate its boxes itbr->CalculateBoundingBoxes(); } _pfWorldEditingProfile.StopTimer(CWorldEditingProfile::PTI_CALCULATEBOUNDINGBOXES); } /* Make indices for all brush elements. */ void CBrushArchive::MakeIndices(void) { // NOTE: Mips and brushes don't have indices, because it is not needed yet. // Polygon and sector indices are needed for loading/saving of portal-sector links. INDEX ctBrushes=0; INDEX ctMips=0; INDEX ctSectors=0; INDEX ctPolygons=0; // for each brush FOREACHINDYNAMICARRAY(ba_abrBrushes, CBrush3D, itbr) { // for each mip in the brush FOREACHINLIST(CBrushMip, bm_lnInBrush, itbr->br_lhBrushMips, itbm) { // for each sector in the brush mip FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc) { // for each polygon in the sector FOREACHINSTATICARRAY(itbsc->bsc_abpoPolygons, CBrushPolygon, itbpo) { itbpo->bpo_iInWorld = ctPolygons; ctPolygons++; } itbsc->bsc_iInWorld = ctSectors; ctSectors++; } ctMips++; } ctBrushes++; } // make arrays of pointers to sectors and polygons ba_apbpo.Clear(); ba_apbpo.New(ctPolygons); ba_apbsc.Clear(); ba_apbsc.New(ctSectors); {FOREACHINDYNAMICARRAY(ba_abrBrushes, CBrush3D, itbr) { FOREACHINLIST(CBrushMip, bm_lnInBrush, itbr->br_lhBrushMips, itbm) { FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc) { ba_apbsc[itbsc->bsc_iInWorld] = itbsc; FOREACHINSTATICARRAY(itbsc->bsc_abpoPolygons, CBrushPolygon, itbpo) { ba_apbpo[itbpo->bpo_iInWorld] = itbpo; } } } }} } #define DISTANCE_EPSILON 0.1f /* Create links between portals and sectors on their other side. */ void CBrushArchive::LinkPortalsAndSectors(void) { _pfWorldEditingProfile.StartTimer(CWorldEditingProfile::PTI_LINKPORTALSANDSECTORS); ASSERT(GetFPUPrecision()==FPT_53BIT); // for each of the zoning brushes FOREACHINDYNAMICARRAY(ba_abrBrushes, CBrush3D, itbr1) { if (itbr1->br_penEntity==NULL || !(itbr1->br_penEntity->en_ulFlags&ENF_ZONING)) { continue; } // for each mip FOREACHINLIST(CBrushMip, bm_lnInBrush, itbr1->br_lhBrushMips, itbm1) { // for each sector in the brush mip FOREACHINDYNAMICARRAY(itbm1->bm_abscSectors, CBrushSector, itbsc1) { // for each of the zoning brushes FOREACHINDYNAMICARRAY(ba_abrBrushes, CBrush3D, itbr2) { if (itbr2->br_penEntity==NULL || !(itbr2->br_penEntity->en_ulFlags&ENF_ZONING)) { continue; } // for each mip that might have contact with the sector FOREACHINLIST(CBrushMip, bm_lnInBrush, itbr2->br_lhBrushMips, itbm2) { if (!itbm2->bm_boxBoundingBox.HasContactWith(itbsc1->bsc_boxBoundingBox, DISTANCE_EPSILON)) { continue; } // for each sector in the brush mip that might have contact, except current one FOREACHINDYNAMICARRAY(itbm2->bm_abscSectors, CBrushSector, itbsc2) { if (&*itbsc1==&*itbsc2) { continue; } if (!itbsc2->bsc_boxBoundingBox.HasContactWith(itbsc1->bsc_boxBoundingBox, DISTANCE_EPSILON)) { continue; } // for all portals in this sector that might have contact FOREACHINSTATICARRAY(itbsc2->bsc_abpoPolygons, CBrushPolygon, itbpo2) { if (!(itbpo2->bpo_ulFlags&(BPOF_PORTAL|BPOF_PASSABLE))) { continue; } if (!itbpo2->bpo_boxBoundingBox.HasContactWith(itbsc1->bsc_boxBoundingBox, DISTANCE_EPSILON)) { continue; } // create a BSP polygon from the brush polygon CBrushPolygon &brpo2 = *itbpo2; BSPPolygon bspo2; brpo2.CreateBSPPolygonNonPrecise(bspo2); // split the polygon with the BSP of the sector DOUBLEbspcutter3D bcCutter(bspo2, *itbsc1->bsc_bspBSPTree.bt_pbnRoot); // if anything remains on the border looking outside if (bcCutter.bc_abedInside.Count()>0 ||bcCutter.bc_abedBorderInside.Count()>0 ||bcCutter.bc_abedBorderOutside.Count()>0) { // relate the sector to the portal AddRelationPair( itbpo2->bpo_rsOtherSideSectors, itbsc1->bsc_rdOtherSidePortals); } } } } } } } } _pfWorldEditingProfile.StopTimer(CWorldEditingProfile::PTI_LINKPORTALSANDSECTORS); } // remove shadow layers without valid light source in all brushes void CBrushArchive::RemoveDummyLayers(void) { // for each brush FOREACHINDYNAMICARRAY(ba_abrBrushes, CBrush3D, itbr) { // for each mip if( itbr->br_penEntity==NULL) continue; // skip brush without entity FOREACHINLIST(CBrushMip, bm_lnInBrush, itbr->br_lhBrushMips, itbm) { // for each sector in the brush mip FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc) { // for each polygon in the sector FOREACHINSTATICARRAY(itbsc->bsc_abpoPolygons, CBrushPolygon, itbpo) { CBrushPolygon &bpo = *itbpo; bpo.bpo_smShadowMap.RemoveDummyLayers(); // remove shadow layers without valid light source } } } } } // cache all shadowmaps void CBrushArchive::CacheAllShadowmaps(void) { // count all shadowmaps INDEX ctShadowMaps=0; {FOREACHINDYNAMICARRAY( ba_abrBrushes, CBrush3D, itbr) { // for each mip if( itbr->br_penEntity==NULL) continue; // skip brush without entity FOREACHINLIST( CBrushMip, bm_lnInBrush, itbr->br_lhBrushMips, itbm) { // for each sector in the brush mip FOREACHINDYNAMICARRAY( itbm->bm_abscSectors, CBrushSector, itbsc) { // for each polygon in the sector FOREACHINSTATICARRAY( itbsc->bsc_abpoPolygons, CBrushPolygon, itbpo) { if( !itbpo->bpo_smShadowMap.bsm_lhLayers.IsEmpty()) ctShadowMaps++; // count shadowmap if the one exist } } } }} try { SetProgressDescription( TRANS("caching shadowmaps")); CallProgressHook_t(0.0f); // for each brush INDEX iCurrentShadowMap=0; {FOREACHINDYNAMICARRAY( ba_abrBrushes, CBrush3D, itbr) { // for each mip if( itbr->br_penEntity==NULL) continue; // skip brush without entity FOREACHINLIST( CBrushMip, bm_lnInBrush, itbr->br_lhBrushMips, itbm) { // for each sector in the brush mip FOREACHINDYNAMICARRAY( itbm->bm_abscSectors, CBrushSector, itbsc) { // for each polygon in the sector FOREACHINSTATICARRAY( itbsc->bsc_abpoPolygons, CBrushPolygon, itbpo) { // cache shadowmap if the one exist CBrushShadowMap &bsm = itbpo->bpo_smShadowMap; if( bsm.bsm_lhLayers.IsEmpty()) continue; bsm.CheckLayersUpToDate(); bsm.Prepare(); bsm.SetAsCurrent(); iCurrentShadowMap++; CallProgressHook_t( (FLOAT)iCurrentShadowMap/ctShadowMaps); } } } }} // all done CallProgressHook_t(1.0f); } catch( char*) { NOTHING; } } void CBrushArchive::ReadPortalSectorLinks_t( CTStream &strm) // throw char * { // links are not ok if they fail loading _bPortalSectorLinksPreLoaded = FALSE; _pfWorldEditingProfile.StartTimer(CWorldEditingProfile::PTI_READPORTALSECTORLINKS); // first make indices for all sectors and polygons MakeIndices(); // if the chunk is not there if (!(strm.PeekID_t()==CChunkID("PSLS"))) { // portal-sector links // do nothing; _pfWorldEditingProfile.StopTimer(CWorldEditingProfile::PTI_READPORTALSECTORLINKS); return; } // read the version strm.ExpectID_t("PSLS"); // portal-sector links INDEX iVersion; strm>>iVersion; ASSERT(iVersion==1); // read chunk size SLONG slChunkSizePos = strm.GetPos_t(); SLONG slChunkSize; strm>>slChunkSize; // repeat FOREVER { // read sector index INDEX iSector; strm>>iSector; // if end marker if (iSector==-1) { // stop loading break; } // get the sector CBrushSector *pbsc = ba_apbsc[iSector]; ASSERT(pbsc->bsc_iInWorld==iSector); // read number of links INDEX ctLinks; strm>>ctLinks; // for each link for(INDEX iLink=0; iLink>iPolygon; CBrushPolygon *pbpo = ba_apbpo[iPolygon]; ASSERT(pbpo->bpo_iInWorld==iPolygon); // relate the sector to the portal AddRelationPair( pbpo->bpo_rsOtherSideSectors, pbsc->bsc_rdOtherSidePortals); } pbsc->bsc_ulTempFlags|=BSCTF_PRELOADEDLINKS; } // check chunk size ASSERT(strm.GetPos_t()-slChunkSizePos-sizeof(INDEX)==slChunkSize); // check end id strm.ExpectID_t("PSLE"); // portal-sector links end // mark that links are ok _bPortalSectorLinksPreLoaded = TRUE; _pfWorldEditingProfile.StopTimer(CWorldEditingProfile::PTI_READPORTALSECTORLINKS); } void CBrushArchive::ReadEntitySectorLinks_t( CTStream &strm) // throw char * { // links are not ok if they fail loading _bEntitySectorLinksPreLoaded = FALSE; _pfWorldEditingProfile.StartTimer(CWorldEditingProfile::PTI_READPORTALSECTORLINKS); // first make indices for all sectors and polygons MakeIndices(); // if the chunk is not there if (!(strm.PeekID_t()==CChunkID("ESL2"))) { // entity-sector links v2 // do nothing; _pfWorldEditingProfile.StopTimer(CWorldEditingProfile::PTI_READPORTALSECTORLINKS); return; } // read the version strm.ExpectID_t("ESL2"); // entity-sector links v2 INDEX iVersion; strm>>iVersion; ASSERT(iVersion==1); // read chunk size SLONG slChunkSizePos = strm.GetPos_t(); SLONG slChunkSize; strm>>slChunkSize; // repeat FOREVER { // read sector index INDEX iSector; strm>>iSector; // if end marker if (iSector==-1) { // stop loading break; } // get the sector CBrushSector *pbsc = ba_apbsc[iSector]; ASSERT(pbsc->bsc_iInWorld==iSector); // read number of links INDEX ctLinks; strm>>ctLinks; // for each link for(INDEX iLink=0; iLink>iEntity; CEntity *pen = ba_pwoWorld->EntityFromID(iEntity); // relate the sector to the entity AddRelationPair(pbsc->bsc_rsEntities, pen->en_rdSectors); } } // check chunk size ASSERT(strm.GetPos_t()-slChunkSizePos-sizeof(INDEX)==slChunkSize); // check end id strm.ExpectID_t("ESLE"); // entity-sector links end // mark that links are ok _bEntitySectorLinksPreLoaded = TRUE; _pfWorldEditingProfile.StopTimer(CWorldEditingProfile::PTI_READPORTALSECTORLINKS); } void CBrushArchive::WritePortalSectorLinks_t( CTStream &strm) // throw char * { // first make indices for all sectors and polygons MakeIndices(); // write chunk id and current version strm.WriteID_t("PSLS"); // portal-sector links strm<br_lhBrushMips, itbm) { FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc) { CBrushSector *pbsc = itbsc; // get number of portal links that it has INDEX ctLinks = pbsc->bsc_rdOtherSidePortals.Count(); // if it has no links if (ctLinks==0) { // skip it continue; } // write sector index and number of links strm<bsc_iInWorld<bsc_rdOtherSidePortals, CBrushPolygon, bpo_rsOtherSideSectors, pbpo) // write the polygon index strm<bpo_iInWorld; ENDFOR} } } }} // write sector index -1 as end marker strm<br_lhBrushMips, itbm) { FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc) { CBrushSector *pbsc = itbsc; // get number of entity links that it has INDEX ctLinks = pbsc->bsc_rsEntities.Count(); // if it has no links if (ctLinks==0) { // skip it continue; } // write sector index and number of links strm<bsc_iInWorld<bsc_rsEntities, CEntity, en_rdSectors, pen) // write the entity index strm<en_ulID; ENDFOR} } } }} // write sector index -1 as end marker strm<ExpectID_t("BRAR"); // brush archive INDEX ctBrushes; // read number of brushes (*istrFile)>>ctBrushes; // if there are some brushes if (ctBrushes!=0) { // create that much brushes CBrush3D *abrBrushes = ba_abrBrushes.New(ctBrushes); // for each of the new brushes for (INDEX iBrush=0; iBrushExpectID_t("EOAR"); // end of archive } /* * Write to stream. */ void CBrushArchive::Write_t( CTStream *ostrFile) // throw char * { ostrFile->WriteID_t("BRAR"); // brush archive // write the number of brushes (*ostrFile)<Write_t(ostrFile); } // write links WritePortalSectorLinks_t(*ostrFile); ostrFile->WriteID_t("EOAR"); // end of archive }