mirror of
https://github.com/ptitSeb/Serious-Engine
synced 2024-12-27 07:54:51 +01:00
1146 lines
34 KiB
C++
1146 lines
34 KiB
C++
/* Copyright (c) 2002-2012 Croteam Ltd. All rights reserved. */
|
|
|
|
#include "stdh.h"
|
|
|
|
#include <Engine/Base/Console.h>
|
|
#include <Engine/Math/Float.h>
|
|
#include <Engine/World/World.h>
|
|
#include <Engine/World/WorldEditingProfile.h>
|
|
#include <Engine/Graphics/RenderScene.h>
|
|
#include <Engine/World/WorldSettings.h>
|
|
#include <Engine/Entities/EntityClass.h>
|
|
#include <Engine/Entities/Precaching.h>
|
|
#include <Engine/Entities/EntityProperties.h>
|
|
#include <Engine/Base/ListIterator.inl>
|
|
#include <Engine/Templates/DynamicContainer.cpp>
|
|
#include <Engine/Graphics/Color.h>
|
|
#include <Engine/Brushes/BrushArchive.h>
|
|
#include <Engine/Terrain/TerrainArchive.h>
|
|
#include <Engine/Entities/InternalClasses.h>
|
|
#include <Engine/Network/Network.h>
|
|
#include <Engine/Network/SessionState.h>
|
|
#include <Engine/Templates/DynamicArray.cpp>
|
|
#include <Engine/Brushes/Brush.h>
|
|
#include <Engine/Light/LightSource.h>
|
|
#include <Engine/Base/ProgressHook.h>
|
|
#include <Engine/Templates/StaticArray.cpp>
|
|
#include <Engine/Templates/Selection.cpp>
|
|
#include <Engine/Terrain/Terrain.h>
|
|
|
|
#include <Engine/Templates/Stock_CEntityClass.h>
|
|
|
|
template CDynamicContainer<CEntity>;
|
|
template CBrushPolygonSelection;
|
|
template CBrushSectorSelection;
|
|
template CEntitySelection;
|
|
|
|
extern BOOL _bPortalSectorLinksPreLoaded;
|
|
extern BOOL _bEntitySectorLinksPreLoaded;
|
|
extern INDEX _ctPredictorEntities;
|
|
|
|
// calculate ray placement from origin and target positions (obsolete?)
|
|
static inline CPlacement3D CalculateRayPlacement(
|
|
const FLOAT3D &vOrigin, const FLOAT3D &vTarget)
|
|
{
|
|
CPlacement3D plRay;
|
|
// ray position is at origin
|
|
plRay.pl_PositionVector = vOrigin;
|
|
// calculate ray direction vector
|
|
FLOAT3D vDirection = vTarget-vOrigin;
|
|
// calculate ray orientation from the direction vector
|
|
vDirection.Normalize();
|
|
DirectionVectorToAngles(vDirection, plRay.pl_OrientationAngle);
|
|
return plRay;
|
|
}
|
|
|
|
/* Constructor. */
|
|
CTextureTransformation::CTextureTransformation(void)
|
|
{
|
|
tt_strName = "";
|
|
}
|
|
|
|
/* Constructor. */
|
|
CTextureBlending::CTextureBlending(void)
|
|
{
|
|
tb_strName = "";
|
|
tb_ubBlendingType = STXF_BLEND_OPAQUE;
|
|
tb_colMultiply = C_WHITE|0xFF;
|
|
}
|
|
|
|
/*
|
|
* Constructor.
|
|
*/
|
|
CWorld::CWorld(void)
|
|
: wo_colBackground(C_lGRAY) // clear background color
|
|
, wo_pecWorldBaseClass(NULL) // worldbase class must be obtained before using the world
|
|
, wo_bPortalLinksUpToDate(FALSE) // portal-sector links must be updated
|
|
, wo_baBrushes(*new CBrushArchive)
|
|
, wo_taTerrains(*new CTerrainArchive)
|
|
, wo_ulSpawnFlags(0)
|
|
{
|
|
wo_baBrushes.ba_pwoWorld = this;
|
|
wo_taTerrains.ta_pwoWorld = this;
|
|
|
|
// create empty texture movements
|
|
wo_attTextureTransformations.New(256);
|
|
wo_atbTextureBlendings.New(256);
|
|
wo_astSurfaceTypes.New(256);
|
|
wo_actContentTypes.New(256);
|
|
wo_aetEnvironmentTypes.New(256);
|
|
wo_aitIlluminationTypes.New(256);
|
|
|
|
// initialize collision grid
|
|
InitCollisionGrid();
|
|
|
|
wo_slStateDictionaryOffset = 0;
|
|
wo_strBackdropUp = "";
|
|
wo_strBackdropFt = "";
|
|
wo_strBackdropRt = "";
|
|
wo_strBackdropObject = "";
|
|
wo_fUpW = wo_fUpL = 1.0f; wo_fUpCX = wo_fUpCZ = 0.0f;
|
|
wo_fFtW = wo_fFtH = 1.0f; wo_fFtCX = wo_fFtCY = 0.0f;
|
|
wo_fRtL = wo_fRtH = 1.0f; wo_fRtCZ = wo_fRtCY = 0.0f;
|
|
|
|
wo_ulNextEntityID = 1;
|
|
|
|
// set default placement
|
|
wo_plFocus = CPlacement3D( FLOAT3D(3.0f, 4.0f, 10.0f),
|
|
ANGLE3D(AngleDeg( 20.0f), AngleDeg( -20.0f), 0));
|
|
wo_fTargetDistance = 10.0f;
|
|
|
|
// set default thumbnail placement
|
|
wo_plThumbnailFocus = CPlacement3D( FLOAT3D(3.0f, 4.0f, 10.0f),
|
|
ANGLE3D(AngleDeg( 20.0f), AngleDeg( -20.0f), 0));
|
|
wo_fThumbnailTargetDistance = 10.0f;
|
|
}
|
|
|
|
/*
|
|
* Destructor.
|
|
*/
|
|
CWorld::~CWorld()
|
|
{
|
|
// clear all arrays
|
|
Clear();
|
|
// destroy collision grid
|
|
DestroyCollisionGrid();
|
|
|
|
delete &wo_baBrushes;
|
|
delete &wo_taTerrains;
|
|
}
|
|
|
|
/*
|
|
* Clear all arrays.
|
|
*/
|
|
void CWorld::Clear(void)
|
|
{
|
|
// detach worldbase class
|
|
if (wo_pecWorldBaseClass!=NULL) {
|
|
if ( wo_pecWorldBaseClass->ec_pdecDLLClass!=NULL
|
|
&&wo_pecWorldBaseClass->ec_pdecDLLClass->dec_OnWorldEnd!=NULL) {
|
|
wo_pecWorldBaseClass->ec_pdecDLLClass->dec_OnWorldEnd(this);
|
|
}
|
|
wo_pecWorldBaseClass=NULL;
|
|
}
|
|
|
|
{
|
|
// must be in 24bit mode when managing entities
|
|
CSetFPUPrecision FPUPrecision(FPT_24BIT);
|
|
|
|
// clear background viewer
|
|
SetBackgroundViewer(NULL);
|
|
// make a new container of entities
|
|
CDynamicContainer<CEntity> cenToDestroy = wo_cenEntities;
|
|
// for each of the entities
|
|
{FOREACHINDYNAMICCONTAINER(cenToDestroy, CEntity, iten) {
|
|
// destroy it
|
|
iten->Destroy();
|
|
}}
|
|
// the original container must be empty
|
|
ASSERT(wo_cenEntities.Count()==0);
|
|
ASSERT(wo_cenAllEntities.Count()==0);
|
|
wo_cenEntities.Clear();
|
|
wo_cenAllEntities.Clear();
|
|
cenToDestroy.Clear();
|
|
wo_ulNextEntityID = 1;
|
|
}
|
|
|
|
// clear brushes
|
|
wo_baBrushes.ba_abrBrushes.Clear();
|
|
// clear terrains
|
|
wo_taTerrains.ta_atrTerrains.Clear();
|
|
|
|
extern void ClearMovableEntityCaches(void);
|
|
ClearMovableEntityCaches();
|
|
|
|
// clear collision grid
|
|
ClearCollisionGrid();
|
|
}
|
|
|
|
/*
|
|
* Create a new entity of given class.
|
|
*/
|
|
CEntity *CWorld::CreateEntity(const CPlacement3D &plPlacement, CEntityClass *pecClass)
|
|
{
|
|
// must be in 24bit mode when managing entities
|
|
CSetFPUPrecision FPUPrecision(FPT_24BIT);
|
|
|
|
// if the world base class is not yet remembered and this class is world base
|
|
if (wo_pecWorldBaseClass==NULL
|
|
&& stricmp(pecClass->ec_pdecDLLClass->dec_strName, "WorldBase")==0) {
|
|
// remember it
|
|
wo_pecWorldBaseClass = pecClass;
|
|
// execute the class attach function
|
|
if (pecClass->ec_pdecDLLClass->dec_OnWorldInit!=NULL) {
|
|
pecClass->ec_pdecDLLClass->dec_OnWorldInit(this);
|
|
}
|
|
}
|
|
// gather CRCs of that class
|
|
pecClass->AddToCRCTable();
|
|
|
|
// ask the class to instance a new member
|
|
CEntity *penEntity = pecClass->New();
|
|
// add the reference made by the entity itself
|
|
penEntity->AddReference();
|
|
|
|
// set the entity's world pointer to this world
|
|
penEntity->en_pwoWorld = this;
|
|
// add the new member to this world's entity container
|
|
wo_cenEntities.Add(penEntity);
|
|
wo_cenAllEntities.Add(penEntity);
|
|
// set a new identifier
|
|
penEntity->en_ulID = wo_ulNextEntityID++;
|
|
// set up the placement
|
|
penEntity->en_plPlacement = plPlacement;
|
|
// calculate rotation matrix
|
|
MakeRotationMatrixFast(penEntity->en_mRotation, penEntity->en_plPlacement.pl_OrientationAngle);
|
|
|
|
// if now predicting
|
|
if (_pNetwork->IsPredicting()) {
|
|
// mark entity as a temporary predictor
|
|
penEntity->en_ulFlags |= ENF_PREDICTOR|ENF_TEMPPREDICTOR;
|
|
wo_cenPredictor.Add(penEntity);
|
|
_ctPredictorEntities++;
|
|
}
|
|
|
|
// return it
|
|
return penEntity;
|
|
}
|
|
|
|
/*
|
|
* Create a new entity of given class.
|
|
*/
|
|
CEntity *CWorld::CreateEntity_t(const CPlacement3D &plPlacement,
|
|
const CTFileName &fnmClass) // throw char *
|
|
{
|
|
// obtain a new entity class from global stock
|
|
CEntityClass *pecClass = _pEntityClassStock->Obtain_t(fnmClass);
|
|
// create entity with that class (obtains it once more)
|
|
CEntity *penNew = CreateEntity(plPlacement, pecClass);
|
|
// release the class
|
|
_pEntityClassStock->Release(pecClass);
|
|
// return the entity
|
|
return penNew;
|
|
}
|
|
|
|
/*
|
|
* Destroy one entities.
|
|
*/
|
|
void CWorld::DestroyOneEntity( CEntity *penToDestroy)
|
|
{
|
|
// if the entity is targetable
|
|
if (penToDestroy->IsTargetable()) {
|
|
// remove all eventual pointers to it
|
|
UntargetEntity( penToDestroy);
|
|
}
|
|
// destroy it
|
|
penToDestroy->Destroy();
|
|
}
|
|
|
|
/*
|
|
* Destroy a selection of entities.
|
|
*/
|
|
void CWorld::DestroyEntities(CEntitySelection &senToDestroy)
|
|
{
|
|
// must be in 24bit mode when managing entities
|
|
CSetFPUPrecision FPUPrecision(FPT_24BIT);
|
|
|
|
// for each entity in selection
|
|
FOREACHINDYNAMICCONTAINER(senToDestroy, CEntity, iten) {
|
|
// if the entity is targetable
|
|
if (iten->IsTargetable()) {
|
|
// remove all eventual pointers to it
|
|
UntargetEntity(iten);
|
|
}
|
|
// destroy it
|
|
iten->Destroy();
|
|
}
|
|
// clear the selection on the container level
|
|
/* NOTE: we must not clear the selection directly, since the entity objects
|
|
contained there are already freed and deselecting them would make an access
|
|
violation.
|
|
*/
|
|
senToDestroy.CDynamicContainer<CEntity>::Clear();
|
|
}
|
|
|
|
/*
|
|
* Clear all entity pointers that point to this entity.
|
|
*/
|
|
void CWorld::UntargetEntity(CEntity *penToUntarget)
|
|
{
|
|
// for all entities in this world
|
|
FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, itenInWorld){
|
|
// get the DLL class of this entity
|
|
CDLLEntityClass *pdecDLLClass = itenInWorld->en_pecClass->ec_pdecDLLClass;
|
|
|
|
// for all classes in hierarchy of this entity
|
|
for(;
|
|
pdecDLLClass!=NULL;
|
|
pdecDLLClass = pdecDLLClass->dec_pdecBase) {
|
|
// for all properties
|
|
for(INDEX iProperty=0; iProperty<pdecDLLClass->dec_ctProperties; iProperty++) {
|
|
CEntityProperty &epProperty = pdecDLLClass->dec_aepProperties[iProperty];
|
|
|
|
// if the property type is entity pointer
|
|
if (epProperty.ep_eptType == CEntityProperty::EPT_ENTITYPTR) {
|
|
// get the pointer
|
|
CEntityPointer &penPointed = ENTITYPROPERTY(&*itenInWorld, epProperty.ep_slOffset, CEntityPointer);
|
|
// if it points to the entity to be untargeted
|
|
if (penPointed == penToUntarget) {
|
|
itenInWorld->End();
|
|
// clear the pointer
|
|
penPointed = NULL;
|
|
itenInWorld->Initialize();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// if the entity is background viewer
|
|
if (wo_penBackgroundViewer==penToUntarget) {
|
|
// reset background viewer
|
|
SetBackgroundViewer(NULL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Find an entity with given character.
|
|
*/
|
|
CPlayerEntity *CWorld::FindEntityWithCharacter(CPlayerCharacter &pcCharacter)
|
|
{
|
|
ASSERT(pcCharacter.pc_strName != "");
|
|
|
|
// for each entity
|
|
FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten) {
|
|
CEntity *pen = &*iten;
|
|
// if it is player entity
|
|
if (IsDerivedFromClass(pen, "PlayerEntity")) {
|
|
CPlayerEntity *penPlayer = (CPlayerEntity *)pen;
|
|
// if it has got that character
|
|
if (penPlayer->en_pcCharacter == pcCharacter) {
|
|
// return its pointer
|
|
return penPlayer;
|
|
}
|
|
}
|
|
}
|
|
// otherwise, none exists
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Add an entity to list of thinkers.
|
|
*/
|
|
void CWorld::AddTimer(CRationalEntity *penThinker)
|
|
{
|
|
ASSERT(penThinker->en_timeTimer>_pTimer->CurrentTick());
|
|
ASSERT(GetFPUPrecision()==FPT_24BIT);
|
|
|
|
// if the entity is already in the list
|
|
if (penThinker->en_lnInTimers.IsLinked()) {
|
|
// remove it
|
|
penThinker->en_lnInTimers.Remove();
|
|
}
|
|
// for each entity in the thinker list
|
|
FOREACHINLISTKEEP(CRationalEntity, en_lnInTimers, wo_lhTimers, iten) {
|
|
// if the entity in list has greater or same think time than the one to add
|
|
if (iten->en_timeTimer>=penThinker->en_timeTimer) {
|
|
// stop searching
|
|
break;
|
|
}
|
|
}
|
|
// add the new entity before current one
|
|
iten.InsertBeforeCurrent(penThinker->en_lnInTimers);
|
|
}
|
|
|
|
// set overdue timers to be due in current time
|
|
void CWorld::AdjustLateTimers(TIME tmCurrentTime)
|
|
{
|
|
// must be in 24bit mode when managing entities
|
|
CSetFPUPrecision FPUPrecision(FPT_24BIT);
|
|
|
|
// for each entity in the thinker list
|
|
FOREACHINLIST(CRationalEntity, en_lnInTimers, wo_lhTimers, iten) {
|
|
CRationalEntity &en = *iten;
|
|
// if the entity in list is overdue
|
|
if (en.en_timeTimer<tmCurrentTime) {
|
|
// set it to current time
|
|
en.en_timeTimer = tmCurrentTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Lock all arrays.
|
|
*/
|
|
void CWorld::LockAll(void)
|
|
{
|
|
wo_cenEntities.Lock();
|
|
wo_cenAllEntities.Lock();
|
|
// lock the brush archive
|
|
wo_baBrushes.ba_abrBrushes.Lock();
|
|
// lock the terrain archive
|
|
wo_taTerrains.ta_atrTerrains.Lock();
|
|
}
|
|
|
|
/*
|
|
* Unlock all arrays.
|
|
*/
|
|
void CWorld::UnlockAll(void)
|
|
{
|
|
wo_cenEntities.Unlock();
|
|
wo_cenAllEntities.Unlock();
|
|
// unlock the brush archive
|
|
wo_baBrushes.ba_abrBrushes.Unlock();
|
|
// unlock the brush archive
|
|
wo_taTerrains.ta_atrTerrains.Unlock();
|
|
}
|
|
|
|
/* Get background color for this world. */
|
|
COLOR CWorld::GetBackgroundColor(void)
|
|
{
|
|
return wo_colBackground;
|
|
}
|
|
|
|
/* Set background color for this world. */
|
|
void CWorld::SetBackgroundColor(COLOR colBackground)
|
|
{
|
|
wo_colBackground = colBackground;
|
|
}
|
|
|
|
/* Set background viewer entity for this world. */
|
|
void CWorld::SetBackgroundViewer(CEntity *penEntity)
|
|
{
|
|
wo_penBackgroundViewer = penEntity;
|
|
}
|
|
|
|
/* Get background viewer entity for this world. */
|
|
CEntity *CWorld::GetBackgroundViewer(void)
|
|
{
|
|
// if the background viewer entity is deleted
|
|
if (wo_penBackgroundViewer!=NULL && wo_penBackgroundViewer->en_ulFlags&ENF_DELETED) {
|
|
// clear the pointer
|
|
wo_penBackgroundViewer = NULL;
|
|
}
|
|
return wo_penBackgroundViewer;
|
|
}
|
|
|
|
/* Set description for this world. */
|
|
void CWorld::SetDescription(const CTString &strDescription)
|
|
{
|
|
wo_strDescription = strDescription;
|
|
}
|
|
/* Get description for this world. */
|
|
const CTString &CWorld::GetDescription(void)
|
|
{
|
|
return wo_strDescription;
|
|
}
|
|
|
|
// get/set name of the world
|
|
void CWorld::SetName(const CTString &strName)
|
|
{
|
|
wo_strName = strName;
|
|
}
|
|
const CTString &CWorld::GetName(void)
|
|
{
|
|
return wo_strName;
|
|
}
|
|
|
|
// get/set spawn flags for the world
|
|
void CWorld::SetSpawnFlags(ULONG ulFlags)
|
|
{
|
|
wo_ulSpawnFlags = ulFlags;
|
|
}
|
|
ULONG CWorld::GetSpawnFlags(void)
|
|
{
|
|
return wo_ulSpawnFlags;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// Shadow manipulation functions
|
|
|
|
/*
|
|
* Recalculate all shadow maps that are not valid or of smaller precision.
|
|
*/
|
|
void CWorld::CalculateDirectionalShadows(void)
|
|
{
|
|
extern INDEX _ctShadowLayers;
|
|
extern INDEX _ctShadowClusters;
|
|
CTimerValue tvStart;
|
|
|
|
// clear shadow rendering stats
|
|
tvStart = _pTimer->GetHighPrecisionTimer();
|
|
_ctShadowLayers=0;
|
|
_ctShadowClusters=0;
|
|
|
|
// for each shadow map that is queued for calculation
|
|
FORDELETELIST(CBrushShadowMap, bsm_lnInUncalculatedShadowMaps,
|
|
wo_baBrushes.ba_lhUncalculatedShadowMaps, itbsm) {
|
|
// calculate shadows on it
|
|
itbsm->GetBrushPolygon()->MakeShadowMap(this, TRUE);
|
|
}
|
|
|
|
// report shadow rendering stats
|
|
CTimerValue tvStop = _pTimer->GetHighPrecisionTimer();
|
|
CPrintF("Shadow calculation: total %d clusters in %d layers, %fs\n",
|
|
_ctShadowClusters,
|
|
_ctShadowLayers,
|
|
(tvStop-tvStart).GetSeconds());
|
|
}
|
|
|
|
void CWorld::CalculateNonDirectionalShadows(void)
|
|
{
|
|
// for each shadow map that is queued for calculation
|
|
FORDELETELIST(CBrushShadowMap, bsm_lnInUncalculatedShadowMaps,
|
|
wo_baBrushes.ba_lhUncalculatedShadowMaps, itbsm) {
|
|
// calculate shadows on it
|
|
itbsm->GetBrushPolygon()->MakeShadowMap(this, FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
/* Find all shadow layers near a certain position. */
|
|
void CWorld::FindShadowLayers(
|
|
const FLOATaabbox3D &boxNear,
|
|
BOOL bSelectedOnly /*=FALSE*/,
|
|
BOOL bDirectional /*= TRUE*/)
|
|
{
|
|
_pfWorldEditingProfile.StartTimer(CWorldEditingProfile::PTI_FINDSHADOWLAYERS);
|
|
// for each entity in the world
|
|
FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten) {
|
|
// if it is light entity and it influences the given range
|
|
CLightSource *pls = iten->GetLightSource();
|
|
if (pls!=NULL) {
|
|
FLOATaabbox3D boxLight(iten->en_plPlacement.pl_PositionVector, pls->ls_rFallOff);
|
|
if ( bDirectional && (pls->ls_ulFlags &LSF_DIRECTIONAL)
|
|
||boxLight.HasContactWith(boxNear)) {
|
|
// find layers for that light source
|
|
pls->FindShadowLayers(bSelectedOnly);
|
|
}
|
|
}
|
|
}
|
|
_pfWorldEditingProfile.StopTimer(CWorldEditingProfile::PTI_FINDSHADOWLAYERS);
|
|
}
|
|
/* Discard shadows on all brush polygons in the world. */
|
|
void CWorld::DiscardAllShadows(void)
|
|
{
|
|
FLOATaabbox3D box;
|
|
// for each entity in the world
|
|
FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten) {
|
|
// if it is brush entity
|
|
if (iten->en_RenderType == CEntity::RT_BRUSH) {
|
|
// for each mip in its brush
|
|
FOREACHINLIST(CBrushMip, bm_lnInBrush, iten->en_pbrBrush->br_lhBrushMips, itbm) {
|
|
box|=itbm->bm_boxBoundingBox;
|
|
// for all sectors in this mip
|
|
FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc) {
|
|
// for each polygon in the sector
|
|
FOREACHINSTATICARRAY(itbsc->bsc_abpoPolygons, CBrushPolygon, itbpo) {
|
|
// discard its shadow map
|
|
itbpo->DiscardShadows();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// find all shadow layers in the world
|
|
FindShadowLayers(box);
|
|
}
|
|
/////////////////////////////////////////////////////////////////////
|
|
// Hide/Show functions
|
|
|
|
/*
|
|
* Hide entities contained in given selection.
|
|
*/
|
|
void CWorld::HideSelectedEntities(CEntitySelection &selenEntitiesToHide)
|
|
{
|
|
// for all entities in the selection
|
|
FOREACHINDYNAMICCONTAINER(selenEntitiesToHide, CEntity, iten) {
|
|
if( iten->IsSelected(ENF_SELECTED) &&
|
|
!((iten->en_RenderType==CEntity::RT_BRUSH) && (iten->en_ulFlags&ENF_ZONING)) )
|
|
{
|
|
// hide the entity
|
|
iten->en_ulFlags |= ENF_HIDDEN;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Hide all unselected entities.
|
|
*/
|
|
void CWorld::HideUnselectedEntities(void)
|
|
{
|
|
// for each entity in the world
|
|
FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten)
|
|
{
|
|
if( !iten->IsSelected(ENF_SELECTED) &&
|
|
!((iten->en_RenderType==CEntity::RT_BRUSH)&&(iten->en_ulFlags&ENF_ZONING)) )
|
|
{
|
|
// hide it
|
|
iten->en_ulFlags |= ENF_HIDDEN;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Show all entities.
|
|
*/
|
|
void CWorld::ShowAllEntities(void)
|
|
{
|
|
// for each entity in the world
|
|
FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten)
|
|
{
|
|
iten->en_ulFlags &= ~ENF_HIDDEN;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Hide sectors contained in given selection.
|
|
*/
|
|
void CWorld::HideSelectedSectors(CBrushSectorSelection &selbscSectorsToHide)
|
|
{
|
|
// for all sectors in the selection
|
|
FOREACHINDYNAMICCONTAINER(selbscSectorsToHide, CBrushSector, itbsc) {
|
|
// hide the sector
|
|
itbsc->bsc_ulFlags |= BSCF_HIDDEN;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Hide all unselected sectors.
|
|
*/
|
|
void CWorld::HideUnselectedSectors(void)
|
|
{
|
|
// for each entity in the world
|
|
FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten) {
|
|
// if it is brush entity
|
|
if (iten->en_RenderType == CEntity::RT_BRUSH) {
|
|
// for each mip in its brush
|
|
FOREACHINLIST(CBrushMip, bm_lnInBrush, iten->en_pbrBrush->br_lhBrushMips, itbm) {
|
|
// for all sectors in this mip
|
|
FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc) {
|
|
// if the sector is not selected
|
|
if (!itbsc->IsSelected(BSCF_SELECTED)) {
|
|
// hide it
|
|
itbsc->bsc_ulFlags |= BSCF_HIDDEN;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Show all sectors.
|
|
*/
|
|
void CWorld::ShowAllSectors(void)
|
|
{
|
|
// for each entity in the world
|
|
FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten) {
|
|
// if it is brush entity
|
|
if (iten->en_RenderType == CEntity::RT_BRUSH) {
|
|
// for each mip in its brush
|
|
FOREACHINLIST(CBrushMip, bm_lnInBrush, iten->en_pbrBrush->br_lhBrushMips, itbm) {
|
|
// for all sectors in this mip
|
|
FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc) {
|
|
// show the sector
|
|
itbsc->bsc_ulFlags &= ~BSCF_HIDDEN;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Select all polygons in selected sectors with same texture.
|
|
*/
|
|
void CWorld::SelectByTextureInSelectedSectors(
|
|
CTFileName fnTexture, CBrushPolygonSelection &selbpoSimilar, INDEX iTexture)
|
|
{
|
|
// for each entity in the world
|
|
FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten) {
|
|
// if it is brush entity
|
|
if (iten->en_RenderType == CEntity::RT_BRUSH) {
|
|
// for each mip in its brush
|
|
FOREACHINLIST(CBrushMip, bm_lnInBrush, iten->en_pbrBrush->br_lhBrushMips, itbm) {
|
|
// for all sectors in this mip
|
|
FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc) {
|
|
// if sector is selected
|
|
if (itbsc->IsSelected(BSCF_SELECTED)) {
|
|
// for all polygons in sector
|
|
FOREACHINSTATICARRAY(itbsc->bsc_abpoPolygons, CBrushPolygon, itbpo)
|
|
{
|
|
// if it is not portal and is not selected and has same texture
|
|
if ( (!(itbpo->bpo_ulFlags&BPOF_PORTAL) || (itbpo->bpo_ulFlags&(BPOF_TRANSLUCENT|BPOF_TRANSPARENT))) &&
|
|
!itbpo->IsSelected(BPOF_SELECTED) &&
|
|
(itbpo->bpo_abptTextures[iTexture].bpt_toTexture.GetData() != NULL) &&
|
|
(itbpo->bpo_abptTextures[iTexture].bpt_toTexture.GetData()->GetName()
|
|
== fnTexture) )
|
|
// select this polygon
|
|
selbpoSimilar.Select(*itbpo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Select all polygons in world with same texture.
|
|
*/
|
|
void CWorld::SelectByTextureInWorld(
|
|
CTFileName fnTexture, CBrushPolygonSelection &selbpoSimilar, INDEX iTexture)
|
|
{
|
|
// for each entity in the world
|
|
FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten) {
|
|
// if it is brush entity
|
|
if (iten->en_RenderType == CEntity::RT_BRUSH) {
|
|
// for each mip in its brush
|
|
FOREACHINLIST(CBrushMip, bm_lnInBrush, iten->en_pbrBrush->br_lhBrushMips, itbm) {
|
|
// for all sectors in this mip
|
|
FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc) {
|
|
// for all polygons in sector
|
|
FOREACHINSTATICARRAY(itbsc->bsc_abpoPolygons, CBrushPolygon, itbpo)
|
|
{
|
|
// if it is not non translucent portal and is not selected and has same texture
|
|
if ( (!(itbpo->bpo_ulFlags&BPOF_PORTAL) || (itbpo->bpo_ulFlags&(BPOF_TRANSLUCENT|BPOF_TRANSPARENT))) &&
|
|
!itbpo->IsSelected(BPOF_SELECTED) &&
|
|
(itbpo->bpo_abptTextures[iTexture].bpt_toTexture.GetData() != NULL) &&
|
|
(itbpo->bpo_abptTextures[iTexture].bpt_toTexture.GetData()->GetName()
|
|
== fnTexture) )
|
|
// select this polygon
|
|
selbpoSimilar.Select(*itbpo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Reinitialize entities from their properties. (use only in WEd!)
|
|
*/
|
|
void CWorld::ReinitializeEntities(void)
|
|
{
|
|
_pfWorldEditingProfile.StartTimer(CWorldEditingProfile::PTI_REINITIALIZEENTITIES);
|
|
|
|
// must be in 24bit mode when managing entities
|
|
CSetFPUPrecision FPUPrecision(FPT_24BIT);
|
|
|
|
CTmpPrecachingNow tpn;
|
|
|
|
// for each entity in the world
|
|
FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten) {
|
|
// reinitialize it
|
|
iten->Reinitialize();
|
|
}
|
|
|
|
_pfWorldEditingProfile.StopTimer(CWorldEditingProfile::PTI_REINITIALIZEENTITIES);
|
|
}
|
|
/* Precache data needed by entities. */
|
|
void CWorld::PrecacheEntities_t(void)
|
|
{
|
|
// for each entity in the world
|
|
INDEX ctEntities = wo_cenEntities.Count();
|
|
INDEX iEntity = 0;
|
|
FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten) {
|
|
// precache
|
|
CallProgressHook_t(FLOAT(iEntity)/ctEntities);
|
|
iten->Precache();
|
|
iEntity++;
|
|
}
|
|
}
|
|
// delete all entities that don't fit given spawn flags
|
|
void CWorld::FilterEntitiesBySpawnFlags(ULONG ulFlags)
|
|
{
|
|
// must be in 24bit mode when managing entities
|
|
CSetFPUPrecision FPUPrecision(FPT_24BIT);
|
|
|
|
BOOL bOldAllowRandom = _pNetwork->ga_sesSessionState.ses_bAllowRandom;
|
|
_pNetwork->ga_sesSessionState.ses_bAllowRandom = TRUE;
|
|
|
|
// create an empty selection of entities
|
|
CEntitySelection senToDestroy;
|
|
// for each entity in the world
|
|
{FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten) {
|
|
// if brush
|
|
if (iten->en_RenderType==CEntity::RT_BRUSH
|
|
||iten->en_RenderType==CEntity::RT_FIELDBRUSH) {
|
|
// skip it (brushes must not be deleted on the fly)
|
|
continue;
|
|
}
|
|
|
|
// if it shouldn't exist
|
|
ULONG ulEntityFlags = iten->GetSpawnFlags();
|
|
if (!(ulEntityFlags&ulFlags&SPF_MASK_DIFFICULTY)
|
|
||!(ulEntityFlags&ulFlags&SPF_MASK_GAMEMODE)) {
|
|
// add it to the selection
|
|
senToDestroy.Select(*iten);
|
|
}
|
|
}}
|
|
// destroy all selected entities
|
|
DestroyEntities(senToDestroy);
|
|
_pNetwork->ga_sesSessionState.ses_bAllowRandom = bOldAllowRandom;
|
|
}
|
|
|
|
// create links between zoning-brush sectors and non-zoning entities in sectors
|
|
void CWorld::LinkEntitiesToSectors(void)
|
|
{
|
|
_pfWorldEditingProfile.StartTimer(CWorldEditingProfile::PTI_LINKENTITIESTOSECTORS);
|
|
// must be in 24bit mode when managing entities
|
|
CSetFPUPrecision FPUPrecision(FPT_24BIT);
|
|
// for each entity in the world
|
|
FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten) {
|
|
CEntity &en = *iten;
|
|
// cache eventual collision info
|
|
en.FindCollisionInfo();
|
|
en.UpdateSpatialRange();
|
|
// link it
|
|
if (!_bEntitySectorLinksPreLoaded) {
|
|
en.FindSectorsAroundEntity();
|
|
}
|
|
}
|
|
// NOTE: this is here to force relinking for all moving zoning brushes after loading!
|
|
// for each entity in the world
|
|
{FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten) {
|
|
CEntity &en = *iten;
|
|
if (en.en_RenderType==CEntity::RT_BRUSH &&
|
|
(en.en_ulFlags&ENF_ZONING) && (en.en_ulPhysicsFlags&EPF_MOVABLE)){
|
|
// recalculate all bounding boxes relative to new position
|
|
extern BOOL _bDontDiscardLinks;
|
|
_bDontDiscardLinks = TRUE;
|
|
en.en_pbrBrush->CalculateBoundingBoxes();
|
|
_bDontDiscardLinks = FALSE;
|
|
// FPU must be in 53-bit mode
|
|
CSetFPUPrecision FPUPrecision(FPT_53BIT);
|
|
|
|
// for all brush mips
|
|
FOREACHINLIST(CBrushMip, bm_lnInBrush, en.en_pbrBrush->br_lhBrushMips, itbm) {
|
|
// for all sectors in the mip
|
|
{FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc) {
|
|
// find entities in sector
|
|
itbsc->FindEntitiesInSector();
|
|
}}
|
|
}
|
|
}
|
|
}}
|
|
_pfWorldEditingProfile.StopTimer(CWorldEditingProfile::PTI_LINKENTITIESTOSECTORS);
|
|
}
|
|
|
|
// rebuild all links in world
|
|
void CWorld::RebuildLinks(void)
|
|
{
|
|
wo_baBrushes.LinkPortalsAndSectors();
|
|
_bEntitySectorLinksPreLoaded = FALSE;
|
|
LinkEntitiesToSectors();
|
|
}
|
|
|
|
/* Update sectors during brush vertex moving */
|
|
void CWorld::UpdateSectorsDuringVertexChange( CBrushVertexSelection &selVertex)
|
|
{
|
|
// create container of sectors that will need to be updated
|
|
CDynamicContainer<CBrushSector> cbscToUpdate;
|
|
|
|
{FOREACHINDYNAMICCONTAINER( selVertex, CBrushVertex, itbvx)
|
|
{
|
|
// add the sector of that vertex to list for updating
|
|
if (!cbscToUpdate.IsMember(itbvx->bvx_pbscSector)) {
|
|
cbscToUpdate.Add(itbvx->bvx_pbscSector);
|
|
}
|
|
}}
|
|
|
|
// for each sector to be updated
|
|
{FOREACHINDYNAMICCONTAINER( cbscToUpdate, CBrushSector, itbsc){
|
|
// recalculate planes for polygons from their vertices
|
|
itbsc->MakePlanesFromVertices();
|
|
}}
|
|
}
|
|
|
|
/* Update sectors after brush vertex moving */
|
|
void CWorld::UpdateSectorsAfterVertexChange( CBrushVertexSelection &selVertex)
|
|
{
|
|
// create container of sectors that will need to be updated
|
|
CDynamicContainer<CBrushSector> cbscToUpdate;
|
|
|
|
{FOREACHINDYNAMICCONTAINER( selVertex, CBrushVertex, itbvx)
|
|
{
|
|
// add the sector of that vertex to list for updating
|
|
if (!cbscToUpdate.IsMember(itbvx->bvx_pbscSector)) {
|
|
cbscToUpdate.Add(itbvx->bvx_pbscSector);
|
|
}
|
|
}}
|
|
|
|
// for each sector to be updated
|
|
{FOREACHINDYNAMICCONTAINER( cbscToUpdate, CBrushSector, itbsc){
|
|
// update it
|
|
itbsc->UpdateVertexChanges();
|
|
}}
|
|
}
|
|
|
|
/* Triangularize polygons that contain vertices from given selection */
|
|
void CWorld::TriangularizeForVertices( CBrushVertexSelection &selVertex)
|
|
{
|
|
// create container of sectors that contain polygons that need to be triangularized
|
|
CDynamicContainer<CBrushSector> cbscToTriangularize;
|
|
|
|
{FOREACHINDYNAMICCONTAINER( selVertex, CBrushVertex, itbvx)
|
|
{
|
|
// add the sector of that vertex to list for triangularizing
|
|
if (!cbscToTriangularize.IsMember(itbvx->bvx_pbscSector)) {
|
|
cbscToTriangularize.Add(itbvx->bvx_pbscSector);
|
|
}
|
|
}}
|
|
|
|
// for each sector to be updated
|
|
{FOREACHINDYNAMICCONTAINER( cbscToTriangularize, CBrushSector, itbsc){
|
|
// update it
|
|
itbsc->TriangularizeForVertices(selVertex);
|
|
}}
|
|
}
|
|
|
|
// add this entity to prediction
|
|
void CEntity::AddToPrediction(void)
|
|
{
|
|
// this function may be called even for NULLs - so ignore it
|
|
if (this==NULL) {
|
|
return;
|
|
}
|
|
// if already added
|
|
if (en_ulFlags&ENF_WILLBEPREDICTED) {
|
|
// do nothing
|
|
return;
|
|
}
|
|
// mark as added
|
|
en_ulFlags|=ENF_WILLBEPREDICTED;
|
|
en_pwoWorld->wo_cenWillBePredicted.Add(this);
|
|
// add your dependents
|
|
AddDependentsToPrediction();
|
|
}
|
|
|
|
// mark all predictable entities that will be predicted using user-set criterions
|
|
void CWorld::MarkForPrediction(void)
|
|
{
|
|
extern INDEX cli_bPredictIfServer;
|
|
extern INDEX cli_bPredictLocalPlayers;
|
|
extern INDEX cli_bPredictRemotePlayers;
|
|
extern FLOAT cli_fPredictEntitiesRange;
|
|
static CStaticStackArray<FLOAT3D> avLocalPlayers;
|
|
avLocalPlayers.PopAll();
|
|
|
|
// for each player
|
|
for (INDEX iPlayer=0; iPlayer<CEntity::GetMaxPlayers(); iPlayer++) {
|
|
CEntity *pen = CEntity::GetPlayerEntity(iPlayer);
|
|
// if it exists
|
|
if (pen!=NULL) {
|
|
// find whether it is local
|
|
BOOL bLocal = _pNetwork->IsPlayerLocal(pen);
|
|
// if allowed for prediction
|
|
if ( bLocal && cli_bPredictLocalPlayers
|
|
|| !bLocal && cli_bPredictRemotePlayers) {
|
|
// add it
|
|
pen->AddToPrediction();
|
|
}
|
|
// if local
|
|
if (bLocal) {
|
|
// remember coordinates of the original entity, and eventual predictor coords
|
|
avLocalPlayers.Push() = pen->GetPlacement().pl_PositionVector;
|
|
avLocalPlayers.Push() = _pNetwork->ga_sesSessionState.GetPlayerPredictorPosition(iPlayer);
|
|
}
|
|
}
|
|
}
|
|
|
|
TIME tmNow = _pNetwork->ga_sesSessionState.ses_tmPredictionHeadTick;
|
|
|
|
// for each predictable entity
|
|
{FOREACHINDYNAMICCONTAINER(wo_cenPredictable, CEntity, iten){
|
|
CEntity &en = *iten;
|
|
// it must not be void (so that its coordinates are relevant)
|
|
ASSERT(en.GetRenderType()!=CEntity::RT_VOID);
|
|
|
|
// get its upper time limit for prediction
|
|
TIME tmLimit = en.GetPredictionTime();
|
|
// if now inside time prediction interval
|
|
if (tmNow<tmLimit) {
|
|
// add it to prediction
|
|
iten->AddToPrediction();
|
|
continue;
|
|
}
|
|
|
|
// if predicting entities by range
|
|
if (cli_fPredictEntitiesRange>0) {
|
|
FLOAT fRange = en.GetPredictionRange();
|
|
if (fRange<=0) {
|
|
continue;
|
|
}
|
|
fRange = Min(fRange, cli_fPredictEntitiesRange);
|
|
|
|
// get its coordinates and maximal prediction range
|
|
const FLOAT3D &v = en.GetPlacement().pl_PositionVector;
|
|
// check if it is within range of any local player
|
|
BOOL bInRange = FALSE;
|
|
for(INDEX i=0; i<avLocalPlayers.Count(); i++) {
|
|
if ((avLocalPlayers[i]-v).Length()<fRange) {
|
|
bInRange = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
// if it is within the range
|
|
if (bInRange) {
|
|
// add it
|
|
iten->AddToPrediction();
|
|
}
|
|
}
|
|
|
|
}}
|
|
}
|
|
|
|
// unmark all predictable entities marked for prediction
|
|
void CWorld::UnmarkForPrediction(void)
|
|
{
|
|
// for each entity marked
|
|
{FOREACHINDYNAMICCONTAINER(wo_cenWillBePredicted, CEntity, iten){
|
|
// unmark for prediction
|
|
iten->en_ulFlags&=~ENF_WILLBEPREDICTED;
|
|
}}
|
|
wo_cenWillBePredicted.Clear();
|
|
}
|
|
|
|
// create predictors for predictable entities that are marked for prediction
|
|
void CWorld::CreatePredictors(void)
|
|
{
|
|
CDynamicContainer<CEntity> cenForPrediction;
|
|
// for each entity marked
|
|
{FOREACHINDYNAMICCONTAINER(wo_cenWillBePredicted, CEntity, iten){
|
|
// if not deleted
|
|
if (!(iten->en_ulFlags&ENF_DELETED)) {
|
|
// add to container
|
|
cenForPrediction.Add(iten);
|
|
}
|
|
// unmark
|
|
iten->en_ulFlags&=~ENF_WILLBEPREDICTED;
|
|
}}
|
|
wo_cenWillBePredicted.Clear();
|
|
// CPrintF("for prediction: %d\n", cenForPrediction.Count());
|
|
|
|
// create copies of those entities as predictors
|
|
CopyEntitiesToPredictors(cenForPrediction);
|
|
}
|
|
|
|
// delete all predictor entities
|
|
void CWorld::DeletePredictors(void)
|
|
{
|
|
// must be in 24bit mode when managing entities
|
|
CSetFPUPrecision FPUPrecision(FPT_24BIT);
|
|
|
|
// first remember eventual predicted player positions
|
|
_pNetwork->ga_sesSessionState.RememberPlayerPredictorPositions();
|
|
|
|
// make a copy of predictor container (for safe iteration)
|
|
CDynamicContainer<CEntity> cenPredictor = wo_cenPredictor;
|
|
// for each predictor
|
|
{FOREACHINDYNAMICCONTAINER( cenPredictor, CEntity, iten){
|
|
CEntity &en = *iten;
|
|
ASSERT(en.IsPredictor());
|
|
// destroy it
|
|
en.Destroy();
|
|
}}
|
|
|
|
// for each predicted
|
|
{FOREACHINDYNAMICCONTAINER( wo_cenPredicted, CEntity, iten){
|
|
CEntity &en = *iten;
|
|
ASSERT(en.IsPredicted());
|
|
// kill its pointer to predictor
|
|
en.SetPredictionPair(NULL);
|
|
// mark as not predicted
|
|
en.en_ulFlags&=~ENF_PREDICTED;
|
|
}}
|
|
|
|
ASSERT(_ctPredictorEntities==0);
|
|
|
|
wo_cenPredictor.Clear();
|
|
wo_cenPredicted.Clear();
|
|
|
|
// for each entity in the world
|
|
FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten) {
|
|
CEntity &en = *iten;
|
|
ASSERT(!en.IsPredictor());
|
|
}
|
|
}
|
|
|
|
// get entity by its ID
|
|
CEntity *CWorld::EntityFromID(ULONG ulID)
|
|
{
|
|
FOREACHINDYNAMICCONTAINER(wo_cenAllEntities, CEntity, iten) {
|
|
if (iten->en_ulID==ulID) {
|
|
return iten;
|
|
}
|
|
}
|
|
ASSERT(FALSE);
|
|
return NULL;
|
|
}
|
|
|
|
/* Triangularize selected polygons. */
|
|
void CWorld::TriangularizePolygons(CDynamicContainer<CBrushPolygon> &dcPolygons)
|
|
{
|
|
ClearMarkedForUseFlag();
|
|
CDynamicContainer<CBrushSector> cbscToProcess;
|
|
// for each polyon in selection
|
|
FOREACHINDYNAMICCONTAINER(dcPolygons, CBrushPolygon, itbpo)
|
|
{
|
|
CBrushPolygon &bp=*itbpo;
|
|
bp.bpo_ulFlags |= BPOF_MARKED_FOR_USE;
|
|
CBrushSector *pbsc=bp.bpo_pbscSector;
|
|
if( !cbscToProcess.IsMember( pbsc))
|
|
{
|
|
cbscToProcess.Add( pbsc);
|
|
}
|
|
}
|
|
|
|
FOREACHINDYNAMICCONTAINER(cbscToProcess, CBrushSector, itbsc)
|
|
{
|
|
itbsc->TriangularizeMarkedPolygons();
|
|
itbsc->UpdateVertexChanges();
|
|
}
|
|
}
|
|
|
|
// Clear marked for use flag on all polygons in world
|
|
void CWorld::ClearMarkedForUseFlag(void)
|
|
{
|
|
// for each entity in the world
|
|
FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten) {
|
|
// if it is brush entity
|
|
if (iten->en_RenderType == CEntity::RT_BRUSH) {
|
|
// for each mip in its brush
|
|
FOREACHINLIST(CBrushMip, bm_lnInBrush, iten->en_pbrBrush->br_lhBrushMips, itbm) {
|
|
// for all sectors in this mip
|
|
FOREACHINDYNAMICARRAY(itbm->bm_abscSectors, CBrushSector, itbsc) {
|
|
// for each polygon in the sector
|
|
FOREACHINSTATICARRAY(itbsc->bsc_abpoPolygons, CBrushPolygon, itbpo) {
|
|
// discard marked for use flag
|
|
itbpo->bpo_ulFlags &= ~BPOF_MARKED_FOR_USE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |