/* 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 <Engine/StdH.h>
#include <Engine/Brushes/Brush.h>
#include <Engine/Brushes/BrushTransformed.h>
#include <Engine/Brushes/BrushArchive.h>
#include <Engine/Base/Stream.h>
#include <Engine/Light/LightSource.h>
#include <Engine/Light/Gradient.h>
#include <Engine/Entities/ShadingInfo.h>
#include <Engine/Base/ListIterator.inl>
#include <Engine/Graphics/Color.h>
#include <Engine/Templates/StaticArray.cpp>
#include <Engine/World/World.h>
#include <Engine/Entities/Entity.h>

#include <Engine/Light/Shadows_internal.h>

#define VERSION_CURRENT 1

// max allowed size of shadowmap in pixels
#define MAX_SHADOWMAP_SIZE 65536


CBrushShadowLayer::CBrushShadowLayer()
{
  bsl_ulFlags = 0;
  bsl_pbsmShadowMap = NULL;
  bsl_plsLightSource = NULL;
  bsl_pixMinU = 0;
  bsl_pixMinV = 0;
  bsl_pixSizeU = 0;
  bsl_pixSizeV = 0;
  bsl_slSizeInPixels = 0;
  bsl_pubLayer = NULL;
  bsl_colLastAnim = C_BLACK;
}

// destructor
CBrushShadowLayer::~CBrushShadowLayer(void)
{
  DiscardShadows();
}


// discard shadows but keep the layer
void CBrushShadowLayer::DiscardShadows(void)
{
  // if the layer is calculated
  if (bsl_pubLayer!=NULL) {
    // free its memory
    FreeMemory(bsl_pubLayer);
    bsl_pubLayer = NULL;
    bsl_slSizeInPixels = 0;
  }
  bsl_ulFlags&=~(BSLF_CALCULATED|BSLF_ALLDARK|BSLF_ALLLIGHT);
}


/*
 * Discard shadow on the polygon.
 */
void CBrushPolygon::DiscardShadows(void)
{
  bpo_smShadowMap.DiscardAllLayers();
  InitializeShadowMap();
}


/* Initialize shadow map for the polygon. */
void CBrushPolygon::InitializeShadowMap(void)
{
  // reset shadow mapping to be default for its plane
  bpo_mdShadow = CMappingDefinition();
  // init the bounding box of the shadow map as empty
  MEXaabbox2D boxPolygonMap;
  // for each edge in polygon
  FOREACHINSTATICARRAY(bpo_abpePolygonEdges, CBrushPolygonEdge, itbpe) {
    // find coordinates for first vertex
    FLOAT3D v0, v1;
    itbpe->GetVertexCoordinatesRelative(v0, v1);
    // find mapping coordinates for first vertex
    MEX2D vTexture;
    bpo_mdShadow.GetTextureCoordinates(
      bpo_pbplPlane->bpl_pwplWorking->wpl_mvRelative, v0, vTexture);
    // add the vertex to the box
    boxPolygonMap |= vTexture;
  }

  // extract mexel dimensions from the bounding box
  MEX2D vmexShadowMin = boxPolygonMap.Min();
  MEX mexMinU = vmexShadowMin(1);
  MEX mexMinV = vmexShadowMin(2);
  MEX2D vmexShadowSize = boxPolygonMap.Size();
  MEX mexSizeU = vmexShadowSize(1);
  MEX mexSizeV = vmexShadowSize(2);

  // mip level is initially minimum mip level that generates needed precision for the polygon
  // (size=(2^ub)*0.5m) (-1 is for *0.5m)
  INDEX iMipLevel = (MAX_MEX_LOG2+bpo_bppProperties.bpp_sbShadowClusterSize-1);

  // expand shadow map for the sake of dark corners
  if( bpo_ulFlags&BPOF_DARKCORNERS) {
    mexSizeU += 2<<iMipLevel;
    mexSizeV += 2<<iMipLevel;
  }
  // round the dimensions up to power of 2
  INDEX iSizeULog2 = (INDEX)ceil(Log2(mexSizeU));
  INDEX iSizeVLog2 = (INDEX)ceil(Log2(mexSizeV));
  mexSizeU = 1<<iSizeULog2;
  mexSizeV = 1<<iSizeVLog2;

  // calculate dimensions in pixels and eventually reduce shadowmap size
  PIX pixSizeU  = mexSizeU>>iMipLevel;
  PIX pixSizeV  = mexSizeV>>iMipLevel;
  INDEX iMipAdj = ClampTextureSize( MAX_SHADOWMAP_SIZE, _pGfx->gl_pixMaxTextureDimension, pixSizeU, pixSizeV);
  pixSizeU   = ClampDn( pixSizeU>>iMipAdj, 1L);
  pixSizeV   = ClampDn( pixSizeV>>iMipAdj, 1L);
  iMipLevel += iMipAdj;

  // move shadow map offset for the sake of dark corners
  if( bpo_ulFlags&BPOF_DARKCORNERS) {
    mexMinU -= 1<<iMipLevel;
    mexMinV -= 1<<iMipLevel;
  }
  // recalculate dimensions and offsets in mex back from the dimensions in pixels
  mexSizeU = (pixSizeU<<iMipLevel);
  mexSizeV = (pixSizeV<<iMipLevel);
  MEX mexOffsetU = -mexMinU;
  MEX mexOffsetV = -mexMinV;
  // remember size of polygon (not necessarily 2^n) (min is always (0,0))
  bpo_smShadowMap.sm_pixPolygonSizeU = Min( (PIX)((vmexShadowSize(1)>>iMipLevel)+3), pixSizeU);
  bpo_smShadowMap.sm_pixPolygonSizeV = Min( (PIX)((vmexShadowSize(2)>>iMipLevel)+3), pixSizeV);
  // safety check
  ASSERT( bpo_smShadowMap.sm_pixPolygonSizeU <= _pGfx->gl_pixMaxTextureDimension &&
          bpo_smShadowMap.sm_pixPolygonSizeV <= _pGfx->gl_pixMaxTextureDimension &&
         (bpo_smShadowMap.sm_pixPolygonSizeU*bpo_smShadowMap.sm_pixPolygonSizeV) <= MAX_SHADOWMAP_SIZE);

  // initialize the shadow map
  bpo_smShadowMap.Initialize(iMipLevel, mexOffsetU, mexOffsetV, mexSizeU, mexSizeV);
  // discard polygon mask if calculated
  if (bpo_smShadowMap.bsm_pubPolygonMask != NULL) {
    FreeMemory(bpo_smShadowMap.bsm_pubPolygonMask);
    bpo_smShadowMap.bsm_pubPolygonMask = NULL;
  }

  // discard all cached shading infos for models
  DiscardShadingInfos();
}

// get shadow/light percentage at given coordinates in shadow layer
FLOAT CBrushShadowLayer::GetLightStrength(PIX pixU, PIX pixV, FLOAT fLRRatio, FLOAT fUDRatio)
{
  // if full dark layer
  if (bsl_ulFlags&BSLF_ALLDARK) {
    // full dark
    return 0.0f;
  }
  // if there is no layer mask, full light layer or the coordinates are out of the layer
  if (bsl_pubLayer==NULL || (bsl_ulFlags&BSLF_ALLLIGHT) ||
    (pixU<bsl_pixMinU) || (pixV<bsl_pixMinV) ||
    (pixU>=bsl_pixMinU+bsl_pixSizeU) || (pixV>=bsl_pixMinV+bsl_pixSizeV)) {
    // full light
    return 1.0f;
  }

  // get the coordinates of the four pixels
  PIX pixU0 = pixU-bsl_pixMinU; PIX pixU1 = Min(pixU0+1, bsl_pixSizeU-1);
  PIX pixV0 = pixV-bsl_pixMinV; PIX pixV1 = Min(pixV0+1, bsl_pixSizeV-1);
  ULONG ulOffsetUL = pixU0+pixV0*bsl_pixSizeU;
  ULONG ulOffsetUR = pixU1+pixV0*bsl_pixSizeU;
  ULONG ulOffsetDL = pixU0+pixV1*bsl_pixSizeU;
  ULONG ulOffsetDR = pixU1+pixV1*bsl_pixSizeU;
  // get light at the four pixels
  FLOAT fUL=0.0f, fUR=0.0f, fDL=0.0f, fDR=0.0f;
  if (bsl_pubLayer[ulOffsetUL/8]&(1<<(ulOffsetUL%8))) { fUL = 1.0f; };
  if (bsl_pubLayer[ulOffsetUR/8]&(1<<(ulOffsetUR%8))) { fUR = 1.0f; };
  if (bsl_pubLayer[ulOffsetDL/8]&(1<<(ulOffsetDL%8))) { fDL = 1.0f; };
  if (bsl_pubLayer[ulOffsetDR/8]&(1<<(ulOffsetDR%8))) { fDR = 1.0f; };

  // return interpolated value
  return Lerp( Lerp(fUL, fUR, fLRRatio), Lerp(fDL, fDR, fLRRatio), fUDRatio);
}

void CBrushShadowMap::ReadLayers_t( CTStream *pstrm)  // throw char *
{
  BOOL bUncalculated = FALSE;
  // if the old version of layers information is really saved here
  if (pstrm->PeekID_t()==CChunkID("SHLY")) {  // shadow layers
    pstrm->ExpectID_t("SHLY");

    // read number of layers
    INDEX ctLayers;
    *pstrm>>ctLayers;
    // for each shadow layer
    for(INDEX iLayer=0; iLayer<ctLayers; iLayer++) {
      // create a new layer
      CBrushShadowLayer *pbsl = new CBrushShadowLayer;
      pbsl->bsl_colLastAnim = 0x12345678;
      // attach it to the shadow map
      bsm_lhLayers.AddTail(pbsl->bsl_lnInShadowMap);
      // make the layer point to its shadow map
      pbsl->bsl_pbsmShadowMap = this;
      // make the light pointer dummy (it is set while loading lights)
      pbsl->bsl_plsLightSource = NULL;

      // read the layer data
      *pstrm>>pbsl->bsl_ulFlags;    // flags
      // if it is new version
      if (pbsl->bsl_ulFlags&BSLF_RECTANGLE) {
        SLONG slLayerSize;
        *pstrm>>slLayerSize;
        if (slLayerSize != 0) {
          pbsl->bsl_pubLayer = (UBYTE *)AllocMemory(slLayerSize);
          pstrm->Read_t(pbsl->bsl_pubLayer, slLayerSize); // the bit packed layer mask
        } else {
          bUncalculated = TRUE;
          pbsl->bsl_pubLayer = NULL;
        }
        // read layer rectangle
        *pstrm>>pbsl->bsl_pixMinU;
        *pstrm>>pbsl->bsl_pixMinV;
        *pstrm>>pbsl->bsl_pixSizeU;
        *pstrm>>pbsl->bsl_pixSizeV;
      // if it is old version
      } else {
        // skip it
        SLONG slLayerSize;
        *pstrm>>slLayerSize;
        if (slLayerSize != 0) {
          pstrm->Seek_t(slLayerSize, CTStream::SD_CUR);
          pbsl->bsl_pubLayer = NULL;
          bUncalculated = TRUE;
        } else {
          bUncalculated = TRUE;
          pbsl->bsl_pubLayer = NULL;
        }
        // destroy it
        pbsl->bsl_lnInShadowMap.Remove();
        delete pbsl;
      }
    }
  // if the new version of layers information is really saved here
  } else if (pstrm->PeekID_t()==CChunkID("SHLA")) {  // shadow layers
    pstrm->ExpectID_t("SHLA");
    // read polygon size
    *pstrm>>sm_pixPolygonSizeU;
    *pstrm>>sm_pixPolygonSizeV;

    // read number of layers
    INDEX ctLayers;
    *pstrm>>ctLayers;
    // for each shadow layer
    for(INDEX iLayer=0; iLayer<ctLayers; iLayer++) {
      // create a new layer
      CBrushShadowLayer *pbsl = new CBrushShadowLayer;
      pbsl->bsl_colLastAnim = 0x12345678;
      // attach it to the shadow map
      bsm_lhLayers.AddTail(pbsl->bsl_lnInShadowMap);
      // make the layer point to its shadow map
      pbsl->bsl_pbsmShadowMap = this;
      // make the light pointer dummy (it is set while loading lights)
      pbsl->bsl_plsLightSource = NULL;

      // read the layer data
      *pstrm>>pbsl->bsl_ulFlags;    // flags
      SLONG slLayerSize;
      *pstrm>>slLayerSize;
      if (slLayerSize != 0) {
        pstrm->Seek_t(slLayerSize, CTStream::SD_CUR);
      }
      bUncalculated = TRUE;
      pbsl->bsl_pubLayer = NULL;
      pbsl->bsl_ulFlags&=~BSLF_CALCULATED;
      // read layer rectangle
      *pstrm>>pbsl->bsl_pixMinU;
      *pstrm>>pbsl->bsl_pixMinV;
      *pstrm>>pbsl->bsl_pixSizeU;
      *pstrm>>pbsl->bsl_pixSizeV;

    }
  // if the new version of layers information is really saved here
  } else if (pstrm->PeekID_t()==CChunkID("SHAL")) {  // shadow layers
    pstrm->ExpectID_t("SHAL");
    // read version number
    INDEX iVersion;
    *pstrm>>iVersion;
    ASSERT(iVersion==VERSION_CURRENT);
    // read polygon size
    *pstrm>>sm_pixPolygonSizeU;
    *pstrm>>sm_pixPolygonSizeV;

    // read number of layers
    INDEX ctLayers;
    *pstrm>>ctLayers;
    // for each shadow layer
    for(INDEX iLayer=0; iLayer<ctLayers; iLayer++) {
      // create a new layer
      CBrushShadowLayer *pbsl = new CBrushShadowLayer;
      pbsl->bsl_colLastAnim = 0x12345678;
      // attach it to the shadow map
      bsm_lhLayers.AddTail(pbsl->bsl_lnInShadowMap);
      // make the layer point to its shadow map
      pbsl->bsl_pbsmShadowMap = this;
      // make the light pointer dummy (it is set while loading lights)
      pbsl->bsl_plsLightSource = NULL;

      // read the layer data
      *pstrm>>pbsl->bsl_ulFlags;    // flags
      *pstrm>>pbsl->bsl_slSizeInPixels;
      if (pbsl->bsl_slSizeInPixels != 0) {
        SLONG slLayerSize = (pbsl->bsl_slSizeInPixels+7)/8;
        pbsl->bsl_pubLayer = (UBYTE *)AllocMemory(slLayerSize);
        pstrm->Read_t(pbsl->bsl_pubLayer, slLayerSize); // the bit packed layer mask
      } else {
        bUncalculated = TRUE;
        pbsl->bsl_pubLayer = NULL;
      }
      // read layer rectangle
      *pstrm>>pbsl->bsl_pixMinU;
      *pstrm>>pbsl->bsl_pixMinV;
      *pstrm>>pbsl->bsl_pixSizeU;
      *pstrm>>pbsl->bsl_pixSizeV;

      // fixup for old levels before alllight and alldark flags
      if ((pbsl->bsl_ulFlags&BSLF_CALCULATED)
        && (pbsl->bsl_pubLayer==NULL)
        &&!(pbsl->bsl_ulFlags&BSLF_ALLLIGHT)
        &&!(pbsl->bsl_ulFlags&BSLF_ALLDARK)) {
        pbsl->bsl_ulFlags|=BSLF_ALLLIGHT;
      }
    }
  }

  // if some layers are uncalculated
  if (bUncalculated) {
    extern CWorld *_pwoCurrentLoading;  // world that is currently loading
    // add the shadow map for calculation
    _pwoCurrentLoading->wo_baBrushes.ba_lhUncalculatedShadowMaps
      .AddTail(bsm_lnInUncalculatedShadowMaps);
  }
}

void CBrushShadowMap::WriteLayers_t( CTStream *pstrm) // throw char *
{
  pstrm->WriteID_t("SHAL"); // shadow layers
  // write version number
  *pstrm<<INDEX(VERSION_CURRENT);

  // write polygon size
  *pstrm<<sm_pixPolygonSizeU;
  *pstrm<<sm_pixPolygonSizeV;

  // write number of layers
  INDEX ctLayers = 0;
  {FOREACHINLIST(CBrushShadowLayer, bsl_lnInShadowMap, bsm_lhLayers, itbsl) {
    if (itbsl->bsl_plsLightSource->ls_ulFlags&LSF_NONPERSISTENT) {
      continue;
    }
    ctLayers++;
  }}
  *pstrm<<ctLayers;
  // for each shadow layer
  FOREACHINLIST(CBrushShadowLayer, bsl_lnInShadowMap, bsm_lhLayers, itbsl) {
    CBrushShadowLayer &bsl = *itbsl;
    if (itbsl->bsl_plsLightSource->ls_ulFlags&LSF_NONPERSISTENT) {
      continue;
    }
    // write the layer data
    *pstrm<<bsl.bsl_ulFlags;    // flags
    if (bsl.bsl_pubLayer == NULL ) {
      *pstrm<<SLONG(0);
    } else {
      *pstrm<<bsl.bsl_slSizeInPixels;
      SLONG slLayerSize = (bsl.bsl_slSizeInPixels+7)/8;
      pstrm->Write_t(bsl.bsl_pubLayer, slLayerSize); // the bit packed layer mask
    }
    // write layer rectangle
    *pstrm<<bsl.bsl_pixMinU;
    *pstrm<<bsl.bsl_pixMinV;
    *pstrm<<bsl.bsl_pixSizeU;
    *pstrm<<bsl.bsl_pixSizeV;
  }
}

// constructor
CBrushShadowMap::CBrushShadowMap(void)
{
  bsm_pubPolygonMask = NULL;  // no polygon mask is calculated initially
  sm_pixPolygonSizeU = -1;    // polygon size must be calculated
  sm_pixPolygonSizeV = -1;
}

// discard all layers on this shadow map
void CBrushShadowMap::DiscardAllLayers(void)
{
  // for each shadow layer
  FORDELETELIST(CBrushShadowLayer, bsl_lnInShadowMap, bsm_lhLayers, itbsl) {
    // delete it
    delete &*itbsl;
  }
  // uncache the shadow map
  Uncache();
}


// discard shadows on all layers on this shadow map
void CBrushShadowMap::DiscardShadows(void)
{
  // for each shadow layer
  FORDELETELIST(CBrushShadowLayer, bsl_lnInShadowMap, bsm_lhLayers, itbsl) {
    // discard shadows on it
    itbsl->DiscardShadows();
  }
}


// remove shadow layers without valid light source
void CBrushShadowMap::RemoveDummyLayers(void)
{
  // for each shadow layer
  FORDELETELIST(CBrushShadowLayer, bsl_lnInShadowMap, bsm_lhLayers, itbsl) {
    // if dummy
    if (itbsl->bsl_plsLightSource==NULL) {
      // remove it
      delete &*itbsl;
    }
  }
}

// destructor
CBrushShadowMap::~CBrushShadowMap(void)
{
  // discard all layers before deleting the object itself
  DiscardAllLayers();
  // discard polygon mask if calculated
  if (bsm_pubPolygonMask != NULL) {
    FreeMemory(bsm_pubPolygonMask);
    bsm_pubPolygonMask = NULL;
  }
}

// queue the shadow map for calculation
void CBrushShadowMap::QueueForCalculation(void)
{
  // if not already queued
  if (!bsm_lnInUncalculatedShadowMaps.IsLinked()) {
    // find the world of the polygon
    CBrushPolygon *pbpo = GetBrushPolygon();
    CBrushSector *pbsc = pbpo->bpo_pbscSector;
    CBrushMip *pbm = pbsc->bsc_pbmBrushMip;
    CBrush3D *pbr = pbm->bm_pbrBrush;
    CWorld *pwo = pbr->br_penEntity->GetWorld();
    // queue the shadow map in the world
    pwo->wo_baBrushes.ba_lhUncalculatedShadowMaps.AddTail(bsm_lnInUncalculatedShadowMaps);
  }
}


// calculate the rectangle where a light influences the shadow map
void CBrushShadowMap::FindLightRectangle(CLightSource &ls, class CLightRectangle &lr)
{
  // get needed data
  CBrushPolygon &bpoPolygon = *GetBrushPolygon();
  const FLOATplane3D &plPlane = bpoPolygon.bpo_pbplPlane->bpl_plAbsolute;
  INDEX iMipLevel = sm_iFirstMipLevel;
  FLOAT3D vLight;
  PIX pixMinU, pixMinV, pixMaxU, pixMaxV;

  // if the light is directional
  if( ls.ls_ulFlags&LSF_DIRECTIONAL)
  {
    pixMinU = 0;
    pixMinV = 0;
    // rectangle is around entire shadowmap
    // pixMaxU = PIX(sm_mexWidth >>iMipLevel);
    // pixMaxV = PIX(sm_mexHeight>>iMipLevel);
    // rectangle is around entire polygon
    pixMaxU = Min( sm_pixPolygonSizeU+16L, sm_mexWidth >>iMipLevel);
    pixMaxV = Min( sm_pixPolygonSizeV+16L, sm_mexHeight>>iMipLevel);
  }
  // if the light is point
  else
  {
    // light position is at the light entity
    vLight = ls.ls_penEntity->GetPlacement().pl_PositionVector;
    // find the point where the light is closest to the polygon
    FLOAT3D vHotPoint = plPlane.ProjectPoint(vLight);
    lr.lr_fLightPlaneDistance = plPlane.PointDistance(vLight);

    CEntity *penWithPolygon = bpoPolygon.bpo_pbscSector->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity;
    ASSERT(penWithPolygon!=NULL);
    const FLOATmatrix3D &mPolygonRotation = penWithPolygon->en_mRotation;
    const FLOAT3D &vPolygonTranslation = penWithPolygon->GetPlacement().pl_PositionVector;

    vHotPoint = (vHotPoint-vPolygonTranslation)*!mPolygonRotation;

    Vector<MEX, 2> vmexHotPoint;
    bpoPolygon.bpo_mdShadow.GetTextureCoordinates(
      bpoPolygon.bpo_pbplPlane->bpl_pwplWorking->wpl_mvRelative, vHotPoint, vmexHotPoint);

    if( !(bpoPolygon.bpo_ulFlags&BPOF_DARKCORNERS)) {
      lr.lr_fpixHotU = FLOAT(vmexHotPoint(1)+sm_mexOffsetX+(1<<iMipLevel))/(1L<<iMipLevel);
      lr.lr_fpixHotV = FLOAT(vmexHotPoint(2)+sm_mexOffsetY+(1<<iMipLevel))/(1L<<iMipLevel);
    } else {
      lr.lr_fpixHotU = FLOAT(vmexHotPoint(1)+sm_mexOffsetX)/(1L<<iMipLevel);
      lr.lr_fpixHotV = FLOAT(vmexHotPoint(2)+sm_mexOffsetY)/(1L<<iMipLevel);
    }
    // calculate maximum radius of light on the polygon
    MEX mexFallOff = MEX( sqrt(ls.ls_rFallOff*ls.ls_rFallOff -
                               lr.lr_fLightPlaneDistance*lr.lr_fLightPlaneDistance)*1024.0f);
    // find rectangle coordinates from that
    pixMinU = ((vmexHotPoint(1)+sm_mexOffsetX-mexFallOff)>>iMipLevel);
    pixMinV = ((vmexHotPoint(2)+sm_mexOffsetY-mexFallOff)>>iMipLevel);
    pixMaxU = ((vmexHotPoint(1)+sm_mexOffsetX+mexFallOff)>>iMipLevel)+1;
    pixMaxV = ((vmexHotPoint(2)+sm_mexOffsetY+mexFallOff)>>iMipLevel)+1;
    // clamp the rectangle to the size of shadow map
    // pixMinU = Min( Max(pixMinU, 0L), sm_mexWidth >>iMipLevel);
    // pixMinV = Min( Max(pixMinV, 0L), sm_mexHeight>>iMipLevel);
    // pixMaxU = Min( Max(pixMaxU, 0L), sm_mexWidth >>iMipLevel);
    // pixMaxV = Min( Max(pixMaxV, 0L), sm_mexHeight>>iMipLevel);
    // clamp the rectangle to the size of polygon
    pixMinU = Min( Max(pixMinU, 0L), Min(sm_pixPolygonSizeU+16L, sm_mexWidth >>iMipLevel));
    pixMinV = Min( Max(pixMinV, 0L), Min(sm_pixPolygonSizeV+16L, sm_mexHeight>>iMipLevel));
    pixMaxU = Min( Max(pixMaxU, 0L), Min(sm_pixPolygonSizeU+16L, sm_mexWidth >>iMipLevel));
    pixMaxV = Min( Max(pixMaxV, 0L), Min(sm_pixPolygonSizeV+16L, sm_mexHeight>>iMipLevel));
  }
  // all done
  lr.lr_pixMinU = pixMinU;
  lr.lr_pixMinV = pixMinV;
  lr.lr_pixSizeU = pixMaxU-pixMinU;
  lr.lr_pixSizeV = pixMaxV-pixMinV;
  ASSERT(lr.lr_pixSizeU>=0);
  ASSERT(lr.lr_pixSizeV>=0);
}


// check if all layers are up to date
void CBrushShadowMap::CheckLayersUpToDate(void)
{
  // do nothing if the shadow map is not cached at all or hasn't got any animating lights
  if( ((sm_pulDynamicShadowMap==NULL || (sm_ulFlags&SMF_DYNAMICBLACK))
   && !(sm_ulFlags&SMF_ANIMATINGLIGHTS))
   ||   sm_pulCachedShadowMap==NULL) return;

  // for each layer
  FOREACHINLIST( CBrushShadowLayer, bsl_lnInShadowMap, bsm_lhLayers, itbsl)
  { // ignore if the layer is all dark
    CBrushShadowLayer &bsl = *itbsl;
    if( bsl.bsl_ulFlags&BSLF_ALLDARK) continue;
    // light source must be valid
    CLightSource &ls = *bsl.bsl_plsLightSource;
    ASSERT( &ls!=NULL);
    if( &ls==NULL) continue;
    // if the layer is not up to date
    if( bsl.bsl_colLastAnim != ls.GetLightColor()) {
      // invalidate entire shadow map
      Invalidate( ls.ls_ulFlags&LSF_DYNAMIC);
      if( !(ls.ls_ulFlags&LSF_DYNAMIC)) return;
    }
  }
}


// test if there is any dynamic layer
BOOL CBrushShadowMap::HasDynamicLayers(void)
{
  // for each layer
  FOREACHINLIST( CBrushShadowLayer, bsl_lnInShadowMap, bsm_lhLayers, itbsl)
  { // light source must be valid
    CLightSource &ls = *itbsl->bsl_plsLightSource;
    ASSERT( &ls!=NULL);
    if( &ls==NULL) continue;
    // if the layer is dynamic, it has
    if( ls.ls_ulFlags&LSF_DYNAMIC) return TRUE;
  }
  // hasn't
  return FALSE;
}



// returns TRUE if shadowmap is all flat along with colFlat variable set to that color
BOOL CBrushShadowMap::IsShadowFlat( COLOR &colFlat)
{
  // fail if flat shadows are not allowed
  extern INDEX shd_bAllowFlats;
  extern INDEX shd_iForceFlats;
  shd_iForceFlats = Clamp( shd_iForceFlats, 0L, 2L);
  if( !shd_bAllowFlats && shd_iForceFlats<1) return FALSE;

  COLOR col;
  UBYTE ubR,ubG,ubB, ubR1,ubG1,ubB1;
  SLONG slR=0,slG=0,slB=0;
  INDEX ctPointLights=0;
  CBrushPolygon *pbpo = GetBrushPolygon();

  // if the shadowmap is not using the shading mode
  if (pbpo->bpo_bppProperties.bpp_ubShadowBlend != BPT_BLEND_SHADE) {
    // it must not be flat
    return FALSE;
  }

  // initial color is sector color
  col = AdjustColor( pbpo->bpo_pbscSector->bsc_colAmbient, _slShdHueShift, _slShdSaturation);
  ColorToRGB( col, ubR,ubG,ubB);
  slR += ubR;  slG += ubG;  slB += ubB; 

  // if gradient layer is present
  const ULONG ulGradientType = pbpo->bpo_bppProperties.bpp_ubGradientType;
  if( ulGradientType>0)
  { 
    CGradientParameters gp;
    CEntity *pen = pbpo->bpo_pbscSector->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity;
    if( pen!=NULL && pen->GetGradient( ulGradientType, gp)) {
      // shadowmap cannot be flat
      if( shd_iForceFlats<1) return FALSE;
      // unless it has been forced
      ColorToRGB( gp.gp_col0, ubR, ubG, ubB);
      ColorToRGB( gp.gp_col1, ubR1,ubG1,ubB1);
      const SLONG slAvgR = ((ULONG)ubR + ubR1) /2;
      const SLONG slAvgG = ((ULONG)ubG + ubG1) /2;
      const SLONG slAvgB = ((ULONG)ubB + ubB1) /2;
      if( gp.gp_bDark) { slR -= slAvgR;  slG -= slAvgR;  slB -= slAvgR; }
      else             { slR += slAvgR;  slG += slAvgR;  slB += slAvgR; }
    }
  }

  // for each layer
  BOOL bDirLightApplied = FALSE;
  FOREACHINLIST( CBrushShadowLayer, bsl_lnInShadowMap, bsm_lhLayers, itbsl)
  { 
    // skip dynamic layers
    CBrushShadowLayer &bsl = *itbsl;
    CLightSource &ls = *bsl.bsl_plsLightSource;
    if( ls.ls_ulFlags&LSF_DYNAMIC) continue;

    // if light is directional 
    if( ls.ls_ulFlags&LSF_DIRECTIONAL)
    {
      // fail if calculated and not all dark or all light
      if( (bsl.bsl_ulFlags&BSLF_CALCULATED) && !(bsl.bsl_ulFlags&(BSLF_ALLDARK|BSLF_ALLLIGHT))) {
        // but only if flats have not been forced!
        if( shd_iForceFlats<1) return FALSE;
      }

      // if polygon allows directional light ambient component
      if( pbpo->bpo_ulFlags&BPOF_HASDIRECTIONALAMBIENT) {
        // mix in ambient color
        col = AdjustColor( ls.GetLightAmbient(), _slShdHueShift, _slShdSaturation);
        ColorToRGB( col, ubR,ubG,ubB);
        slR += ubR;  slG += ubG;  slB += ubB; 
      }
      // done with this layer if it's all dark, or all light but without directional component
      if( (bsl.bsl_ulFlags&BSLF_ALLDARK) || !(pbpo->bpo_ulFlags&BPOF_HASDIRECTIONALLIGHT)) continue;

      // layer is all light, so calculate intensity
      col = ls.GetLightColor();
      if( !(pbpo->bpo_ulFlags&BPOF_NOPLANEDIFFUSION)) {
        FLOAT3D vLightDirection;
        AnglesToDirectionVector( ls.ls_penEntity->GetPlacement().pl_OrientationAngle, vLightDirection);
        const FLOAT fIntensity = -((pbpo->bpo_pbplPlane->bpl_plAbsolute)%vLightDirection);
        // done if polygon is turn away from light source (we already added ambient component)
        if( fIntensity<0.01f) continue;
        ULONG ulIntensity = NormFloatToByte(fIntensity);
        ulIntensity = (ulIntensity<<CT_RSHIFT) | (ulIntensity<<CT_GSHIFT) | (ulIntensity<<CT_BSHIFT);
        col = MulColors( col, ulIntensity);
      }
      // determine and add light color
      col = AdjustColor( col, _slShdHueShift, _slShdSaturation);
      ColorToRGB( col, ubR,ubG,ubB);
      slR += ubR;  slG += ubG;  slB += ubB; 
      bDirLightApplied = TRUE;
    }

    // if light is point
    else
    {
      // just fail if layer isn't all dark
      if( !(bsl.bsl_ulFlags&BSLF_ALLDARK)) {
        // and flat shadows aren't forced
        if( shd_iForceFlats<1) return FALSE;
      }
    }
  }

  // fake directional light if needed and allowed
  if( shd_iForceFlats>0 && !bDirLightApplied) {
    FLOAT3D vLightDir;
    vLightDir(1) = -3.0f;
    vLightDir(2) = -2.0f;
    vLightDir(3) = -1.0f;
    vLightDir.Normalize();
    const FLOAT fIntensity = -((pbpo->bpo_pbplPlane->bpl_plAbsolute)%vLightDir);
    if( fIntensity>0.01f) {
      const UBYTE ubGray = NormFloatToByte(fIntensity*0.49f);
      slR += ubGray;  slG += ubGray;  slB += ubGray; 
    }
  }
  // done - phew, layer is flat
  slR = Clamp( slR, 0L, 255L);
  slG = Clamp( slG, 0L, 255L);
  slB = Clamp( slB, 0L, 255L);
  colFlat = RGBToColor(slR,slG,slB);
  return TRUE;
}



// get amount of memory used by this object
SLONG CBrushShadowMap::GetUsedMemory(void)
{
  // basic size of class
  SLONG slUsedMemory = sizeof(CBrushShadowMap);

  // add polyhon mask (if any)
  if( bsm_pubPolygonMask!=NULL) {
    // loop and add mip-maps
    SLONG slPolyMaskSize = 0;
    PIX pixPolySizeU = sm_pixPolygonSizeU;
    PIX pixPolySizeV = sm_pixPolygonSizeV;
    while( pixPolySizeU>0 && pixPolySizeV>0) {
      slPolyMaskSize += pixPolySizeU * pixPolySizeV;
      pixPolySizeU  >>= 1;
      pixPolySizeV  >>= 1;
    }
    // sum it up
    slUsedMemory += (slPolyMaskSize+8) /8; // bit mask!
  }

  // loop thru layers and add 'em too
  FOREACHINLIST( CBrushShadowLayer, bsl_lnInShadowMap, bsm_lhLayers, itbsl) { // count shadow layers
    CBrushShadowLayer &bsl = *itbsl;
    slUsedMemory += sizeof(CBrushShadowLayer);
    if( bsl.bsl_pubLayer!=NULL) slUsedMemory += bsl.bsl_pixSizeU * bsl.bsl_pixSizeV /8; 
  }

  // done
  return slUsedMemory;
}
