/* 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. */


// for generating unique IDs for lens flares
// (32 bit should be enough for generating 1 lens flare per second during approx. 136 years)
static INDEX _iNextLensFlareID = 1; // 0 is reserved for no-id

extern void SelectEntityOnRender( CProjection3D &prProjection, CEntity &en);


// creates oriented bounding box for a model
inline void CreateModelOBBox( CEntity *penModel, FLOAT3D &vHandle, FLOATmatrix3D &mAbsToView, FLOATobbox3D &obbox)
{
  FLOAT3D vAntiStretch = penModel->GetClassificationBoxStretch();
  vAntiStretch(1) = 1.0f/vAntiStretch(1);
  vAntiStretch(2) = 1.0f/vAntiStretch(2);
  vAntiStretch(3) = 1.0f/vAntiStretch(3);
  FLOATaabbox3D boxModel = penModel->en_boxSpatialClassification;
  boxModel.StretchByVector( vAntiStretch);
  obbox = FLOATobbox3D( boxModel, vHandle, mAbsToView*penModel->en_mRotation);
}


/*
 * Add a model entity to rendering.
 */
void CRenderer::AddModelEntity(CEntity *penModel)
{
  // if the entity is currently active or hidden, don't add it again
  if( penModel->en_ulFlags&(ENF_INRENDERING|ENF_HIDDEN)) return;
  // skip the entity if predicted, and predicted entities should not be rendered
  if( penModel->IsPredicted() && !gfx_bRenderPredicted) return;
  _pfRenderProfile.StartTimer(CRenderProfile::PTI_ADDMODELENTITY);

  // get its model object
  CModelObject *pmoModelObject;
  if( penModel->en_RenderType!=CEntity::RT_BRUSH &&
    penModel->en_RenderType != CEntity::RT_FIELDBRUSH) {
    pmoModelObject = penModel->GetModelForRendering();
  } else {
    // empty brushes are also rendered as models
    pmoModelObject = _wrpWorldRenderPrefs.wrp_pmoEmptyBrush;
  }

  // mark the entity as active in rendering
  penModel->en_ulFlags |= ENF_INRENDERING;

  // add it to container of all drawn entities
  re_cenDrawn.Add(penModel);
  // add it to a container for delayed rendering
  CDelayedModel &dm = re_admDelayedModels.Push();
  dm.dm_penModel = penModel;
  dm.dm_pmoModel = pmoModelObject;
  dm.dm_ulFlags  = NONE; // invisible until proved otherwise

  // get proper projection for the entity
  CProjection3D *pprProjection;
  if (re_bBackgroundEnabled && (penModel->en_ulFlags & ENF_BACKGROUND)) {
    pprProjection = re_prBackgroundProjection;
  } else {
    pprProjection = re_prProjection;
  }

  // transform its handle to view space
  FLOAT3D vHandle;
  pprProjection->PreClip(penModel->en_plPlacement.pl_PositionVector, vHandle);
  // setup mip factor
  FLOAT fDistance  = vHandle(3)+penModel->GetDepthSortOffset();

  FLOAT fMipFactor = pprProjection->MipFactor(fDistance);

  penModel->AdjustMipFactor(fMipFactor);
  dm.dm_fDistance  = fDistance;
  dm.dm_fMipFactor = fMipFactor;

  FLOAT fR = penModel->en_fSpatialClassificationRadius;
  if( penModel->en_RenderType==CEntity::RT_BRUSH
   || penModel->en_RenderType==CEntity::RT_FIELDBRUSH) {
    fR = 1.0f;
  }
  ASSERT(fR>0.0f);

  // test object sphere to frustum
  FLOATobbox3D boxEntity;
  BOOL  bModelHasBox = FALSE;
  INDEX iFrustumTest = pprProjection->TestSphereToFrustum( vHandle, fR);
  // if test is indeterminate
  if( iFrustumTest==0) {
    // create oriented box and test it to frustum
    CreateModelOBBox( penModel, vHandle, pprProjection->pr_ViewerRotationMatrix, boxEntity);
    bModelHasBox = TRUE; // mark that box has been created
    iFrustumTest = pprProjection->TestBoxToFrustum(boxEntity);
  }
  // if not inside 
  if( iFrustumTest<0) {
    // don't add it
    _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDMODELENTITY);
    return;
  }

  // do additional check for eventual mirror plane (if allowed)
  INDEX iMirrorPlaneTest = -1;
  extern INDEX gap_iOptimizeClipping;
  if( gap_iOptimizeClipping>0 && (pprProjection->pr_bMirror || pprProjection->pr_bWarp)) {
    // test sphere against plane
    const FLOAT fPlaneDistance = pprProjection->pr_plMirrorView.PointDistance(vHandle);
         if( fPlaneDistance < -fR) iMirrorPlaneTest = -1;
    else if( fPlaneDistance > +fR) iMirrorPlaneTest = +1;
    else { // if test is indeterminate
      // create box entity if needed
      if( !bModelHasBox) {
        CreateModelOBBox( penModel, vHandle, pprProjection->pr_ViewerRotationMatrix, boxEntity);
        bModelHasBox = TRUE;
      } // test it to mirror/warp plane
      iMirrorPlaneTest = (INDEX) (boxEntity.TestAgainstPlane(pprProjection->pr_plMirrorView));
    }
    // if not in mirror
    if( iMirrorPlaneTest<0) {
      // don't add it
      _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDMODELENTITY);
      return;
    }
  } 

  // if it has a light source with a lens flare and we're not rendering shadows
  CLightSource *pls = penModel->GetLightSource();
  if( !re_bRenderingShadows && pls!=NULL && pls->ls_plftLensFlare!=NULL) {
    // add the lens flare to rendering
    AddLensFlare( penModel, pls, pprProjection, re_iIndex);
  }

  // adjust model flags
  if( pmoModelObject->HasAlpha()) dm.dm_ulFlags |= DMF_HASALPHA;
  if( re_bCurrentSectorHasFog)    dm.dm_ulFlags |= DMF_FOG;
  if( re_bCurrentSectorHasHaze)   dm.dm_ulFlags |= DMF_HAZE;
  if( iFrustumTest>0)             dm.dm_ulFlags |= DMF_INSIDE;    // mark the need for clipping
  if( iMirrorPlaneTest>0)         dm.dm_ulFlags |= DMF_INMIRROR;  // mark the need for clipping to mirror/warp plane

  // if this is an editor model and editor models are disabled
  if( penModel->en_RenderType==CEntity::RT_EDITORMODEL
   && !_wrpWorldRenderPrefs.IsEditorModelsOn()) {
    // don't render it
    _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDMODELENTITY);
    return;
  }

  // safety check
  if( pmoModelObject==NULL) {
    _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDMODELENTITY);
    return;
  }

  // if the object is not visible at its distance
  if( (!re_bRenderingShadows && !pmoModelObject->IsModelVisible(fMipFactor))) {
    // don't add it
    _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDMODELENTITY);
    return;
  }

  // if entity selecting by laso requested, test for selecting
  if( _pselenSelectOnRender != NULL) SelectEntityOnRender( *pprProjection, *penModel);
  
  // allow its rendering
  dm.dm_ulFlags |= DMF_VISIBLE;
  ASSERT(pmoModelObject!=NULL);
  // if rendering shadows use only first mip level
  if( re_bRenderingShadows) dm.dm_fMipFactor = 0;

  _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDMODELENTITY);
}
/*
 * Add a ska model entity to rendering.
 */
void CRenderer::AddSkaModelEntity(CEntity *penModel)
{
  // if the entity is currently active or hidden, don't add it again
  if( penModel->en_ulFlags&(ENF_INRENDERING|ENF_HIDDEN)) return;
  // skip the entity if predicted, and predicted entities should not be rendered
  if( penModel->IsPredicted() && !gfx_bRenderPredicted) return;
  _pfRenderProfile.StartTimer(CRenderProfile::PTI_ADDMODELENTITY);

  // get its model object
  CModelInstance *pmiModelInstance = penModel->GetModelInstance();

  // mark the entity as active in rendering
  penModel->en_ulFlags |= ENF_INRENDERING;

  // add it to container of all drawn entities
  re_cenDrawn.Add(penModel);
  // add it to a container for delayed rendering
  CDelayedModel &dm = re_admDelayedModels.Push();
  dm.dm_penModel = penModel;
  dm.dm_ulFlags  = NONE; // invisible until proved otherwise
  // dm.dm_pmoModel = pmoModelObject;

  // get proper projection for the entity
  CProjection3D *pprProjection;
  if (re_bBackgroundEnabled && (penModel->en_ulFlags & ENF_BACKGROUND)) {
    pprProjection = re_prBackgroundProjection;
  } else {
    pprProjection = re_prProjection;
  }

  // transform its handle to view space
  FLOAT3D vHandle;
  pprProjection->PreClip(penModel->en_plPlacement.pl_PositionVector, vHandle);
  // setup mip factor
  FLOAT fDistance  = vHandle(3)+penModel->GetDepthSortOffset();

  FLOAT fMipFactor = pprProjection->MipFactor(fDistance);
  fMipFactor = -fDistance;

  penModel->AdjustMipFactor(fMipFactor);
  dm.dm_fDistance  = fDistance;
  dm.dm_fMipFactor = fMipFactor;

  FLOAT fR = penModel->en_fSpatialClassificationRadius;
  ASSERT(fR>0.0f);

  // test object sphere to frustum
  FLOATobbox3D boxEntity;
  BOOL  bModelHasBox = FALSE;
  INDEX iFrustumTest = pprProjection->TestSphereToFrustum( vHandle, fR);
  // if test is indeterminate
  if( iFrustumTest==0) {
    // create oriented box and test it to frustum
    CreateModelOBBox( penModel, vHandle, pprProjection->pr_ViewerRotationMatrix, boxEntity);
    bModelHasBox = TRUE; // mark that box has been created
    iFrustumTest = pprProjection->TestBoxToFrustum(boxEntity);
  }
  // if not inside 
  if( iFrustumTest<0) {
    // don't add it
    // _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDMODELENTITY);
    return;
  }

  // do additional check for eventual mirror plane (if allowed)
  INDEX iMirrorPlaneTest = -1;
  extern INDEX gap_iOptimizeClipping;
  if( gap_iOptimizeClipping>0 && (pprProjection->pr_bMirror || pprProjection->pr_bWarp)) {
    // test sphere against plane
    const FLOAT fPlaneDistance = pprProjection->pr_plMirrorView.PointDistance(vHandle);
         if( fPlaneDistance < -fR) iMirrorPlaneTest = -1;
    else if( fPlaneDistance > +fR) iMirrorPlaneTest = +1;
    else { // if test is indeterminate
      // create box entity if needed
      if( !bModelHasBox) {
        CreateModelOBBox( penModel, vHandle, pprProjection->pr_ViewerRotationMatrix, boxEntity);
        bModelHasBox = TRUE;
      } // test it to mirror/warp plane
      iMirrorPlaneTest = (INDEX) boxEntity.TestAgainstPlane(pprProjection->pr_plMirrorView);
    }
    // if not in mirror
    if( iMirrorPlaneTest<0) {
      // don't add it
      // _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDMODELENTITY);
      return;
    }
  } 

  // if it has a light source with a lens flare and we're not rendering shadows
  CLightSource *pls = penModel->GetLightSource();
  if( !re_bRenderingShadows && pls!=NULL && pls->ls_plftLensFlare!=NULL) {
    // add the lens flare to rendering
    AddLensFlare( penModel, pls, pprProjection, re_iIndex);
  }

  // adjust model flags
  if( pmiModelInstance->HasAlpha()) dm.dm_ulFlags |= DMF_HASALPHA;
  if( re_bCurrentSectorHasFog)      dm.dm_ulFlags |= DMF_FOG;
  if( re_bCurrentSectorHasHaze)     dm.dm_ulFlags |= DMF_HAZE;
  if( iFrustumTest>0)               dm.dm_ulFlags |= DMF_INSIDE;    // mark the need for clipping
  if( iMirrorPlaneTest>0)           dm.dm_ulFlags |= DMF_INMIRROR;  // mark the need for clipping to mirror/warp plane

  // if this is an editor model and editor models are disabled
  if( penModel->en_RenderType==CEntity::RT_SKAEDITORMODEL
   && !_wrpWorldRenderPrefs.IsEditorModelsOn()) {
    // don't render it
    // _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDMODELENTITY);
    return;
  }

  // safety check
  if( pmiModelInstance==NULL) {
    // _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDMODELENTITY);
    return;
  }

  // if the object is not visible at its distance
  if( (!re_bRenderingShadows && !pmiModelInstance->IsModelVisible(fMipFactor))) {
    // don't add it
    // _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDMODELENTITY);
    return;
  }

  // if entity selecting by laso requested, test for selecting
  if( _pselenSelectOnRender != NULL) SelectEntityOnRender( *pprProjection, *penModel);
  
  // allow its rendering
  dm.dm_ulFlags |= DMF_VISIBLE;
  // if rendering shadows use only first mip level
  if( re_bRenderingShadows) dm.dm_fMipFactor = 0;

  // _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDMODELENTITY);
}


/* Add a lens flare to rendering. */
void CRenderer::AddLensFlare( CEntity *penLight, CLightSource *pls, CProjection3D *pprProjection, INDEX iMirrorLevel/*=0*/)
{
  // if flares are off
  if (_wrpWorldRenderPrefs.wrp_lftLensFlares == CWorldRenderPrefs::LFT_NONE) {
    // don't add any new flares
    return;
  }

  // project the light source position to view space
  FLOAT3D vRotated;
  pprProjection->PreClip(penLight->GetLerpedPlacement().pl_PositionVector, vRotated);
  // if it is behind near clip plane
  if (-vRotated(3)<pprProjection->NearClipDistanceR()) {
    // skip it
    return;
  }
  // project it to screen
  FLOAT3D vScreen;
  pprProjection->PostClip(vRotated, vScreen);

  ASSERT( re_pdpDrawPort!=NULL);
  const ULONG ulDrawPortID = re_pdpDrawPort->GetID();

  // for each existing lens flare
  CLensFlareInfo *plfi = NULL;
  const INDEX ctFlares = re_alfiLensFlares.Count();
  {for(INDEX iFlare=0; iFlare<ctFlares; iFlare++) {
    CLensFlareInfo &lfiOld = re_alfiLensFlares[iFlare];
    // if it is this one
    if( lfiOld.lfi_plsLightSource==pls
    && (lfiOld.lfi_ulDrawPortID==ulDrawPortID || lfiOld.lfi_iMirrorLevel>0)) {
      // use it
      plfi = &lfiOld;
      break;
    }
  }}
  // if it is not found
  if (plfi==NULL) {
    // create a new one
    plfi = &re_alfiLensFlares.Push();
    plfi->lfi_iID = _iNextLensFlareID++;
    plfi->lfi_tmLastFrame  = _pTimer->GetRealTimeTick()-0.05f;
    plfi->lfi_iMirrorLevel = iMirrorLevel;
    plfi->lfi_fFadeFactor  = 0.0f;
    plfi->lfi_ulFlags = NONE;
  }

  CLensFlareInfo &lfi = *plfi;
  lfi.lfi_ulDrawPortID = ulDrawPortID;
  lfi.lfi_plsLightSource = pls;
  lfi.lfi_fI = vScreen(1);
  lfi.lfi_fJ = vScreen(2);
  lfi.lfi_fDistance = -vRotated(3);
  lfi.lfi_fOoK = (1-pprProjection->pr_fDepthBufferFactor/vRotated(3))
               * pprProjection->pr_fDepthBufferMul+pprProjection->pr_fDepthBufferAdd;
  lfi.lfi_vProjected = vRotated;
  lfi.lfi_ulFlags |=  LFF_ACTIVE;
  lfi.lfi_ulFlags &= ~LFF_FOG;
  lfi.lfi_ulFlags &= ~LFF_HAZE;
  if( re_bCurrentSectorHasFog)  lfi.lfi_ulFlags |= LFF_FOG;
  if( re_bCurrentSectorHasHaze) lfi.lfi_ulFlags |= LFF_HAZE;
}



/* Add a moving brush entity to rendering list (add all sectors immediately). */
void CRenderer::AddNonZoningBrush( CEntity *penBrush, CBrushSector *pbscThatAdds)
{
  _pfRenderProfile.StartTimer(CRenderProfile::PTI_ADDNONZONINGBRUSH);
  ASSERT( penBrush!=NULL);
  // get its brush
  CBrush3D &brBrush = *penBrush->en_pbrBrush;

  // if hidden
  if( penBrush->en_ulFlags&ENF_HIDDEN) {
    // skip it
    _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDNONZONINGBRUSH);
    return;
  }

  // if the brush is already added
  if( brBrush.br_lnInActiveBrushes.IsLinked()) {
    // skip it
    _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDNONZONINGBRUSH);
    return;
  }

  const BOOL bDisableVisTweaks = _wrpWorldRenderPrefs.wrp_bDisableVisTweaks;
  // if visibility tweaking is enabled
  if( !bDisableVisTweaks)
  {
    // if vistweaks exclude this brush from rendering in this position
    ULONG ulVisTweaks = penBrush->GetVisTweaks();
    if ((pbscThatAdds!=NULL && (VISM_DONTCLASSIFY&pbscThatAdds->bsc_ulVisFlags&ulVisTweaks))
      ||(ulVisTweaks&re_ulVisExclude&VISM_INCLUDEEXCLUDE)
      ||(re_ulVisInclude!=0 && !(ulVisTweaks&re_ulVisInclude&VISM_INCLUDEEXCLUDE) ) ) {
      // skip it
      _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDNONZONINGBRUSH);
      return;
    }
  }


  // skip whole non-zoning brush if invisible for rendering and not in wireframe mode
  const BOOL bWireFrame = _wrpWorldRenderPrefs.wrp_ftEdges != CWorldRenderPrefs::FT_NONE
                       || _wrpWorldRenderPrefs.wrp_ftVertices != CWorldRenderPrefs::FT_NONE;
  if( !(penBrush->en_ulFlags&ENF_ZONING) && !bWireFrame)
  { // test every brush polygon for it's visibility flag
    // for every sector in brush
    FOREACHINDYNAMICARRAY( brBrush.GetFirstMip()->bm_abscSectors, CBrushSector, itbsc) {
      // for all polygons in sector
      FOREACHINSTATICARRAY( itbsc->bsc_abpoPolygons, CBrushPolygon, itpo) {
        // advance to next polygon if invisible
        CBrushPolygon &bpo = *itpo;
        if( !(bpo.bpo_ulFlags&BPOF_INVISIBLE)) goto addBrush;
      }
    }
    // skip this brush
    _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDNONZONINGBRUSH);
    return;
  }

addBrush:
  // prepare the brush entity for rendering if not yet prepared
  PrepareBrush(brBrush.br_penEntity);

  // get relevant mip factor for that brush and current rendering prefs
  CBrushMip *pbm = brBrush.GetBrushMipByDistance(
    _wrpWorldRenderPrefs.GetCurrentMipBrushingFactor(brBrush.br_prProjection->MipFactor()));
  // if brush mip exists for that mip factor
  if (pbm!=NULL) {
    // if entity selecting by laso requested
    if( _pselenSelectOnRender != NULL)
    {
      // test for selecting
      SelectEntityOnRender( *re_prProjection, *penBrush);
    }
    // for each sector
    FOREACHINDYNAMICARRAY(pbm->bm_abscSectors, CBrushSector, itbsc) {
      // if the sector is not hidden
      if(!((itbsc->bsc_ulFlags&BSCF_HIDDEN) && !re_bRenderingShadows)) {
        // add that sector to active sectors
        AddActiveSector(itbsc.Current());
      }
    }
  }
  _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDNONZONINGBRUSH);
}

/* Add a terrain entity to rendering list. */
void CRenderer::AddTerrainEntity(CEntity *penTerrain)
{
  // _pfRenderProfile.StartTimer(CRenderProfile::PTI_ADDNONZONINGBRUSH);
  ASSERT( penTerrain!=NULL);
  // get its terrain
  CTerrain &trTerrain = *penTerrain->GetTerrain();

  // if hidden
  if(penTerrain->en_ulFlags&ENF_HIDDEN) {
    // skip it
    // _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDNONZONINGBRUSH);
    return;
  }

  if(re_bCurrentSectorHasFog) {
    trTerrain.AddFlag(TR_HAS_FOG);
  }
  if(re_bCurrentSectorHasHaze) {
    trTerrain.AddFlag(TR_HAS_HAZE);
  }
  // if the brush is already added
  if( trTerrain.tr_lnInActiveTerrains.IsLinked()) {
    // skip it
    // _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDNONZONINGBRUSH);
    return;
  }

  // add it to list of active terrains
  re_lhActiveTerrains.AddTail(trTerrain.tr_lnInActiveTerrains);
  // add it to container of all drawn entities
  re_cenDrawn.Add(penTerrain);
}

/* Add to rendering all entities in the world (used in special cases in world editor). */
void CRenderer::AddAllEntities(void)
{
  // for all entities in world
  FOREACHINDYNAMICCONTAINER(re_pwoWorld->wo_cenEntities, CEntity, iten) {
    // if it is brush
    if (iten->en_RenderType==CEntity::RT_BRUSH
     ||(iten->en_RenderType==CEntity::RT_FIELDBRUSH 
        && _wrpWorldRenderPrefs.IsFieldBrushesOn())) {
      // add all of its sectors
      AddNonZoningBrush(&iten.Current(), NULL);

    // if it is model, or editor model that should be drawn
    } else if (iten->en_RenderType==CEntity::RT_MODEL
            ||(iten->en_RenderType==CEntity::RT_EDITORMODEL 
               && _wrpWorldRenderPrefs.IsEditorModelsOn())) {
      // add it as a model
      AddModelEntity(&iten.Current());
    } else if (iten->en_RenderType==CEntity::RT_SKAMODEL
            ||(iten->en_RenderType==CEntity::RT_SKAEDITORMODEL 
               && _wrpWorldRenderPrefs.IsEditorModelsOn())) {
      AddSkaModelEntity(&iten.Current());
    // if this is terrain
    } else if(iten->en_RenderType==CEntity::RT_TERRAIN) {
      // add it as a terrain
      AddTerrainEntity(&iten.Current());
    }
  }
}

/* Add to rendering all entities that are inside an zoning brush sector. */
void CRenderer::AddEntitiesInSector(CBrushSector *pbscSectorInside)
{
  // if we don't have a relevant sector to test with 
  if (pbscSectorInside==NULL || pbscSectorInside->bsc_bspBSPTree.bt_pbnRoot==NULL) {
    // do nothing
    return;
  }

  _pfRenderProfile.StartTimer(CRenderProfile::PTI_ADDENTITIESINSECTOR);

  // for all entities in the sector
  {FOREACHDSTOFSRC(pbscSectorInside->bsc_rsEntities, CEntity, en_rdSectors, pen)
     // if it is brush
    if (pen->en_RenderType==CEntity::RT_BRUSH
     ||(pen->en_RenderType==CEntity::RT_FIELDBRUSH 
        && _wrpWorldRenderPrefs.IsFieldBrushesOn())) {
      // add all of its sectors
      AddNonZoningBrush(pen, pbscSectorInside);

    // if it is model, or editor model that should be drawn
    } else if (pen->en_RenderType==CEntity::RT_MODEL||pen->en_RenderType==CEntity::RT_EDITORMODEL) {
      // add it as a model
      AddModelEntity(pen);
    // if it is a ska model, or editor model that should be drawn
    } else if(pen->en_RenderType==CEntity::RT_SKAMODEL||pen->en_RenderType==CEntity::RT_SKAEDITORMODEL) {
      AddSkaModelEntity(pen);
    // if it is a terrain
    } else if(pen->en_RenderType==CEntity::RT_TERRAIN) {
      AddTerrainEntity(pen);
    }
  ENDFOR}
  _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDENTITIESINSECTOR);
};

/* Add to rendering all entities that are inside a given box. */
void CRenderer::AddEntitiesInBox(const FLOATaabbox3D &boxNear)
{
  _pfRenderProfile.StartTimer(CRenderProfile::PTI_ADDENTITIESINBOX);

  // for all entities in world
  FOREACHINDYNAMICCONTAINER(re_pwoWorld->wo_cenEntities, CEntity, iten) {
    // if it is brush
    if (iten->en_RenderType==CEntity::RT_BRUSH
     ||(iten->en_RenderType==CEntity::RT_FIELDBRUSH 
        && _wrpWorldRenderPrefs.IsFieldBrushesOn())) {
      // if it is zoning
      if (iten->en_ulFlags&ENF_ZONING) {
        // skip it
        continue;
      }

      // if the first mip of the brush has contact with the box
      CEntity *penBrush = iten;
      CBrushMip *pbmFirst = penBrush->en_pbrBrush->GetFirstMip();
      if (pbmFirst->bm_boxBoundingBox.HasContactWith(boxNear)) {
        // add all of its sectors
        AddNonZoningBrush(&iten.Current(), NULL);
      }

    // if it is model, or editor model that should be drawn
    } else if (iten->en_RenderType==CEntity::RT_MODEL
            ||iten->en_RenderType==CEntity::RT_EDITORMODEL) {
      // get model's bounding box for current frame
      FLOATaabbox3D boxModel;
      iten->en_pmoModelObject->GetCurrentFrameBBox(boxModel);
      // get center and radius of the bounding sphere
      FLOAT fSphereRadius = Max( boxModel.Min().Length(), boxModel.Max().Length() );
      FLOAT3D vSphereCenter = iten->en_plPlacement.pl_PositionVector;
      // create maximum box for the model (in cases of any rotation) from the sphere
      FLOATaabbox3D boxMaxModel(vSphereCenter, fSphereRadius);
      // if the model box is near the given box
      if (boxMaxModel.HasContactWith(boxNear)) {
        // add it as a model
        AddModelEntity(&iten.Current());
      }
    } else if( iten->en_RenderType==CEntity::RT_SKAMODEL
      || iten->en_RenderType==CEntity::RT_SKAEDITORMODEL) {
      // get model's bounding box for current frame
      FLOATaabbox3D boxModel;
      iten->GetModelInstance()->GetCurrentColisionBox(boxModel);
      // get center and radius of the bounding sphere
      FLOAT fSphereRadius = Max( boxModel.Min().Length(), boxModel.Max().Length() );
      FLOAT3D vSphereCenter = iten->en_plPlacement.pl_PositionVector;
      // create maximum box for the model (in cases of any rotation) from the sphere
      FLOATaabbox3D boxMaxModel(vSphereCenter, fSphereRadius);
      // if the model box is near the given box
      AddSkaModelEntity(&iten.Current());
    // if this is terrain entity
    } else if( iten->en_RenderType==CEntity::RT_TERRAIN) {
      // get model's bounding box for current frame
      #pragma message(">> Is terrain visible")
      FLOATaabbox3D boxTerrain;
      iten->GetTerrain()->GetAllTerrainBBox(boxTerrain);
      // get center and radius of the bounding sphere
      FLOAT fSphereRadius = Max( boxTerrain.Min().Length(), boxTerrain.Max().Length() );
      FLOAT3D vSphereCenter = iten->en_plPlacement.pl_PositionVector;
      // create maximum box for the model (in cases of any rotation) from the sphere
      FLOATaabbox3D boxMaxTerrain(vSphereCenter, fSphereRadius);
      // if the model box is near the given box
      AddTerrainEntity(&iten.Current());
    }
  }
  _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDENTITIESINBOX);
};

// update VisTweak flags with given zoning sector
inline void CRenderer::UpdateVisTweaks(CBrushSector *pbsc)
{
  // if not showing vis tweaks
  if (!(_wrpWorldRenderPrefs.wrp_bShowVisTweaksOn && _pselbscVisTweaks!=NULL)) {
    // add tweaks for this sector
    if (pbsc->bsc_ulFlags2&BSCF2_VISIBILITYINCLUDE) {
      re_ulVisInclude = pbsc->bsc_ulVisFlags&VISM_INCLUDEEXCLUDE;
    } else {
      re_ulVisExclude |= pbsc->bsc_ulVisFlags&VISM_INCLUDEEXCLUDE;
    }
  }
}

/* Add to rendering all zoning brush sectors that an entity is in. */
void CRenderer::AddZoningSectorsAroundEntity(CEntity *pen, const FLOAT3D &vEyesPos)
{
  _pfRenderProfile.StartTimer(CRenderProfile::PTI_ADDZONINGSECTORS);

  // works only for non-zoning entities
  ASSERT(!(pen->en_ulFlags&ENF_ZONING));

  // make parameters for minimum sphere to add
  re_vdViewSphere = vEyesPos;
  re_dViewSphereR = re_prProjection->NearClipDistanceR()*1.5f;

  CListHead lhToAdd;
  // for all sectors this entity is in
  {FOREACHSRCOFDST(pen->en_rdSectors, CBrushSector, bsc_rsEntities, pbsc)
    // if the sector is not active
    if (!pbsc->bsc_lnInActiveSectors.IsLinked()) {
      // add to list of sectors to add
      lhToAdd.AddTail(pbsc->bsc_lnInActiveSectors);
    }
  ENDFOR}

  // for each active sector
  while (!lhToAdd.IsEmpty()) {
    CBrushSector *pbsc = LIST_HEAD(lhToAdd, CBrushSector, bsc_lnInActiveSectors);
    // remove it from list of sectors to add
    pbsc->bsc_lnInActiveSectors.Remove();
    // add it to final list
    AddGivenZoningSector(pbsc);
    // if isn't really added (wrong mip)
    if (!pbsc->bsc_lnInActiveSectors.IsLinked()) {
      // skip it
      continue;
    }

    // for each portal in the sector
    FOREACHINSTATICARRAY(pbsc->bsc_abpoPolygons, CBrushPolygon, itbpo) {
      CBrushPolygon *pbpo = itbpo;                      
      if (!(pbpo->bpo_ulFlags&BPOF_PORTAL)) {
        continue;
      }
      // for each sector related to the portal
      {FOREACHDSTOFSRC(pbpo->bpo_rsOtherSideSectors, CBrushSector, bsc_rdOtherSidePortals, pbscRelated)
        // if the sector is not active
        if (!pbscRelated->bsc_lnInActiveSectors.IsLinked()) {
          // if the view sphere is in the sector
          if (pbscRelated->bsc_bspBSPTree.TestSphere(
              re_vdViewSphere, re_dViewSphereR)>=0) {
            // add it to list to add
            lhToAdd.AddTail(pbscRelated->bsc_lnInActiveSectors);
          }
        }
      ENDFOR}
    }
  }
  _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDZONINGSECTORS);
}

/* Add to rendering one particular zoning brush sector. */
void CRenderer::AddGivenZoningSector(CBrushSector *pbsc)
{
  // get the sector's brush mip, brush and entity
  CBrushMip *pbmBrushMip = pbsc->bsc_pbmBrushMip;
  CBrush3D *pbrBrush = pbmBrushMip->bm_pbrBrush;
  ASSERT(pbrBrush!=NULL);
  CEntity *penBrush = pbrBrush->br_penEntity;
  ASSERT(penBrush!=NULL);

  // if the brush is field brush
  if (penBrush->en_RenderType==CEntity::RT_FIELDBRUSH) {
    // skip it
    return;
  }

  // prepare the brush entity for rendering if not yet prepared
  PrepareBrush(penBrush); 
  penBrush->en_pbrBrush->br_ulFlags|=BRF_DRAWFIRSTMIP;

  // here, get only the first brush mip
  CBrushMip *pbmRelevant = pbrBrush->GetFirstMip();
  // if it is the one of that sector
  if (pbmRelevant==pbmBrushMip) {
    // add that sector to active sectors
    AddActiveSector(*pbsc);
    UpdateVisTweaks(pbsc);
  }
}

/* Add to rendering all zoning brush sectors near a given box in absolute space. */
void CRenderer::AddZoningSectorsAroundBox(const FLOATaabbox3D &boxNear)
{
  _pfRenderProfile.StartTimer(CRenderProfile::PTI_ADDZONINGSECTORS);

  // get center and radius of the bounding sphere
  FLOAT fSphereRadius = boxNear.Size().Length()/2.0f;
  FLOAT3D vSphereCenter = boxNear.Center();

  re_dViewSphereR = re_prProjection->NearClipDistanceR()*1.5f;
  re_vdViewSphere = vSphereCenter;

  // for all entities in world
  FOREACHINDYNAMICCONTAINER(re_pwoWorld->wo_cenEntities, CEntity, iten) {
    // if the brush is field brush, and field brushes are not rendered
    if (iten->en_RenderType==CEntity::RT_FIELDBRUSH 
        && !_wrpWorldRenderPrefs.IsFieldBrushesOn()) {
      // skip it
      continue;
    }
    // if it is not zoning brush
    if ((iten->en_RenderType!=CEntity::RT_BRUSH && iten->en_RenderType!=CEntity::RT_FIELDBRUSH)
      ||!(iten->en_ulFlags&ENF_ZONING)) {
      // skip it
      continue;
    }

    CEntity *penBrush = &*iten;
    // get its brush
    CBrush3D &brBrush = *penBrush->en_pbrBrush;

    /* !!!! this always prepares all brushes in world,
    should be moved inside loop, but the mip factor must be calculated
    in some other ways.
    */
    // prepare the brush entity for rendering if not yet prepared
    PrepareBrush(penBrush); 

    // get relevant mip factor for that brush and current rendering prefs
    CBrushMip *pbm = brBrush.GetBrushMipByDistance(
      _wrpWorldRenderPrefs.GetCurrentMipBrushingFactor(brBrush.br_prProjection->MipFactor()));
    // if brush mip exists for that mip factor
    if (pbm!=NULL) {
      // for each sector
      FOREACHINDYNAMICARRAY(pbm->bm_abscSectors, CBrushSector, itbsc) {
        // if the sector's bounding box has contact with given bounding box, 
        // and the sector is not hidden
        if(itbsc->bsc_boxBoundingBox.HasContactWith(boxNear) 
         &&!((itbsc->bsc_ulFlags&BSCF_HIDDEN) && !re_bRenderingShadows)) {
          // if the sphere is inside the sector
          if (itbsc->bsc_bspBSPTree.TestSphere(
             vSphereCenter, fSphereRadius)>=0) {

            // add that sector to active sectors
            AddActiveSector(itbsc.Current());
            UpdateVisTweaks(itbsc);
          }
        }
      }
    }
  }
  _pfRenderProfile.StopTimer(CRenderProfile::PTI_ADDZONINGSECTORS);
}

void CMirror::Clear(void)
{
  mi_cspoPolygons.Clear();
}

// add given polygon to this mirror
void CMirror::AddPolygon(CRenderer &re, CScreenPolygon &spo)
{
  CBrushPolygon *pbpo = spo.spo_pbpoBrushPolygon;
  // if not already added
  if (!mi_cspoPolygons.IsMember(&spo)) {
    // add it
    mi_cspoPolygons.Add(&spo);
    CBrushSector &bsc = *pbpo->bpo_pbscSector;
    // for each edge of the polygon
    FOREACHINSTATICARRAY(pbpo->bpo_abpePolygonEdges, CBrushPolygonEdge, itpe) {
      // get transformed end vertices
      CBrushVertex &bvx0 = *itpe->bpe_pbedEdge->bed_pbvxVertex0;
      CBrushVertex &bvx1 = *itpe->bpe_pbedEdge->bed_pbvxVertex1;
      INDEX ivx0 = bsc.bsc_abvxVertices.Index(&bvx0);
      INDEX ivx1 = bsc.bsc_abvxVertices.Index(&bvx1);
      FLOAT3D &tv0 = re.re_avvxViewVertices[bsc.bsc_ivvx0+ivx0].vvx_vView;
      FLOAT3D &tv1 = re.re_avvxViewVertices[bsc.bsc_ivvx0+ivx1].vvx_vView;
      // check both vertices and update closest vertex
      if (tv0(3)>mi_vClosest(3)) {
        mi_vClosest = tv0;
      }
      if (tv1(3)>mi_vClosest(3)) {
        mi_vClosest = tv1;
      }
    }
  }
}

// calculate all needed data from screen polygons
void CMirror::FinishAdding(void)
{
  // clear all data
  mi_boxOnScreen = PIXaabbox2D();
  mi_fpixArea = 0;
  mi_fpixMaxPolygonArea = 0;

  // for each polygon
  FOREACHINDYNAMICCONTAINER(mi_cspoPolygons, CScreenPolygon, itspo) {
    CScreenPolygon &spo = *itspo;

    mi_boxOnScreen|=PIX2D(spo.spo_pixMinI, spo.spo_pixMinJ);
    mi_boxOnScreen|=PIX2D(spo.spo_pixMaxI, spo.spo_pixMaxJ);
    mi_fpixArea += spo.spo_pixTotalArea;
    mi_fpixMaxPolygonArea = Max(mi_fpixMaxPolygonArea, FLOAT(spo.spo_pixTotalArea));
  }
}

/* Add a mirror/portal. */
void CRenderer::AddMirror(CScreenPolygon &spo)
{
  // if far sentinel
  if (spo.spo_pbpoBrushPolygon==NULL) {
    // do nothing
    return;
  }

  // get its index
  CBrushPolygon &bpo = *spo.spo_pbpoBrushPolygon;
  INDEX iMirrorType = bpo.bpo_bppProperties.bpp_ubMirrorType;

  // if no mirror
  if (iMirrorType == 0) {
    // do nothing
    return;
  }

  // if this is last renderer (no more recursion)
  if (re_iIndex>=MAX_RENDERERS-1) {
    // do nothing
    return;
  }

  // if mirrors are disabled
  if (!_wrpWorldRenderPrefs.wrp_bMirrorsOn) {
    // do nothing
    return;
  }

  // for each mirror
  for(INDEX i=0; i<re_amiMirrors.Count(); i++) {
    CMirror &mi = re_amiMirrors[i];
    // if it has same index
    if (mi.mi_iMirrorType==iMirrorType) {
      // add the polygon to the mirror
      mi.AddPolygon(*this, spo);
      return;
    }
  }
  // if no index is found, create new one
  CMirror &mi = re_amiMirrors.Push();
  mi.Clear();
  mi.mi_iMirrorType = iMirrorType;
  CEntity &en = *bpo.bpo_pbscSector->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity;
  CPlacement3D plLerped = en.GetLerpedPlacement();
  FLOATmatrix3D m;
  MakeRotationMatrixFast(m, plLerped.pl_OrientationAngle);
  mi.mi_vClosest = FLOAT3D(0,0,-100000);
  mi.mi_plPlane = 
    bpo.bpo_pbplPlane->bpl_plRelative*m + plLerped.pl_PositionVector;
  BOOL bSuccess = en.GetMirror(iMirrorType, mi.mi_mp);
  // if mirror is valid
  if (bSuccess) {
    // add the polygon to it
    mi.AddPolygon(*this, spo);
  // if mirror is not valid
  } else {
    // remove the mirror
    mi.mi_iMirrorType = -1;
  }

}
