/* Copyright (c) 2002-2012 Croteam Ltd. 
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation


This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

#include "stdh.h"

#include <Engine/Brushes/Brush.h>
#include <Engine/Brushes/BrushTransformed.h>
#include <Engine/Light/LightSource.h>
#include <Engine/Light/Gradient.h>
#include <Engine/Base/ListIterator.inl>
#include <Engine/Base/Statistics_internal.h>
#include <Engine/Graphics/Color.h>
#include <Engine/Math/FixInt.h>
#include <Engine/Entities/Entity.h>
#include <Engine/Graphics/GfxLibrary.h>
#include <Engine/Math/Clipping.inl>

#include <Engine/Light/Shadows_internal.h>
#include <Engine/World/WorldEditingProfile.h>

#include <Engine/Templates/StaticArray.cpp>
#include <Engine/Templates/DynamicArray.cpp>

// asm shortcuts
#define O offset
#define Q qword ptr
#define D dword ptr
#define W  word ptr
#define B  byte ptr

#define ASMOPT 1


extern INDEX shd_bFineQuality;
extern INDEX shd_iFiltering;
extern INDEX shd_iDithering;

extern const UBYTE *pubClipByte;
extern UBYTE aubSqrt[  SQRTTABLESIZE];
extern UWORD auw1oSqrt[SQRTTABLESIZE];
extern UWORD auw1oSqrt[SQRTTABLESIZE];
// static FLOAT3D _v00;

// internal class for layer mixing
class CLayerMixer
{
public:
  CBrushShadowMap *lm_pbsmShadowMap;   // shadow map whose layers are mixed
  CBrushPolygon   *lm_pbpoPolygon;     // polygon of the shadow map
  BOOL lm_bDynamic;    // set while doing dynamic light mixing

  // dimensions of currently processed shadow map
  MEX   lm_mexOffsetU;   // offsets in mex
  MEX   lm_mexOffsetV;
  INDEX lm_iFirstLevel;  // mip level of first possible mip-map
  INDEX lm_iMipLevel;    // mip level
  INDEX lm_iMipShift;    // relative mip level (miplevel-firstlevel)
  PIX   lm_pixCanvasSizeU;     // shadowmap canvas size in pixels
  PIX   lm_pixCanvasSizeV;
  PIX   lm_pixPolygonSizeU;    // polygon (used part of shadowmap size in pixels
  PIX   lm_pixPolygonSizeV;
  ULONG*lm_pulShadowMap;       // buffer for final shadow map
  ULONG*lm_pulStaticShadowMap; // precached static shadow map

  // gradients for shadow map walking
  FLOAT3D lm_vO;     // upper left corner of shadow map in 3D
  FLOAT3D lm_vStepU; // step between pixels in same row
  FLOAT3D lm_vStepV; // step between rows
  FLOAT3D lm_vLightDirection; // light direction for directional light sources
  CLightSource *lm_plsLight;  // current light source
  // color components of current light
  COLOR lm_colLight;
  COLOR lm_colAmbient;

  // constructor
  CLayerMixer( CBrushShadowMap *pbsm, INDEX iFirstMip, INDEX iLastMip, BOOL bDynamic);

  // remember general data
  void CalculateData( CBrushShadowMap *pbsm, INDEX iMipmap);
  // mix one mip-map
  void MixOneMipmap( CBrushShadowMap *pbsm, INDEX iMipmap);
  // mix dynamic lights
  void MixOneMipmapDynamic(CBrushShadowMap *pbsm, INDEX iMipmap);

  // find start of a mip-map inside a layer
  void FindLayerMipmap( CBrushShadowLayer *pbsl, UBYTE *&pub, UBYTE &ubMask);

  // add one point layer to the shadow map
  void AddAmbientPoint(void);
  void AddAmbientMaskPoint( UBYTE *pubMask, UBYTE ubMask);
  void AddDiffusionPoint(void);
  void AddDiffusionMaskPoint( UBYTE *pubMask, UBYTE ubMask);
  BOOL PrepareOneLayerPoint( CBrushShadowLayer *pbsl, BOOL bNoMask);
  void AddOneLayerPoint( CBrushShadowLayer *pbsl, UBYTE *pub, UBYTE ubMask=0);

  // add one directional layer to the shadow map
  void AddDirectional(void);
  void AddMaskDirectional( UBYTE *pubMask, UBYTE ubMask);
  void AddOneLayerDirectional( CBrushShadowLayer *pbsl, UBYTE *pub, UBYTE ubMask=0);

  // add one gradient layer to the shadow map
  void AddOneLayerGradient( CGradientParameters &gp);

  // add the intensity to the pixel
  inline void AddToCluster( UBYTE *pub);
  inline void AddAmbientToCluster( UBYTE *pub);
  inline void AddToCluster( UBYTE *pub, FLOAT fIntensity);

  // additional functions
  __forceinline void CopyShadowLayer(void);
  __forceinline void FillShadowLayer( COLOR col);
  // inline FLOAT IntensityAtDistance( FLOAT fDistance, FLOAT fMinDistance);
  // FLOAT lm_fLightMax, lm_fLightStep;
};


// increment a byte without overflowing it
static inline void IncrementByteWithClip( UBYTE &ub, SLONG slAdd)
{
  ub = pubClipByte[(SLONG)ub+slAdd];
}

// increment a color without overflowing it
static inline void IncrementColorWithClip( UBYTE &ubR, UBYTE &ubG, UBYTE &ubB,
                                           SLONG  slR, SLONG  slG, SLONG  slB)
{
  IncrementByteWithClip( ubR, slR);
  IncrementByteWithClip( ubG, slG);
  IncrementByteWithClip( ubB, slB);
}

// add the intensity to the pixel
inline void CLayerMixer::AddToCluster( UBYTE *pub)
{
  IncrementByteWithClip(pub[0], ((UBYTE*)&lm_colLight)[3]);
  IncrementByteWithClip(pub[1], ((UBYTE*)&lm_colLight)[2]);
  IncrementByteWithClip(pub[2], ((UBYTE*)&lm_colLight)[1]);
}
inline void CLayerMixer::AddAmbientToCluster( UBYTE *pub)
{
  IncrementByteWithClip(pub[0], ((UBYTE*)&lm_colAmbient)[3]);
  IncrementByteWithClip(pub[1], ((UBYTE*)&lm_colAmbient)[2]);
  IncrementByteWithClip(pub[2], ((UBYTE*)&lm_colAmbient)[1]);
}
inline void CLayerMixer::AddToCluster( UBYTE *pub, FLOAT fIntensity)
{
  IncrementByteWithClip(pub[0], ((UBYTE*)&lm_colLight)[3] *fIntensity);
  IncrementByteWithClip(pub[1], ((UBYTE*)&lm_colLight)[2] *fIntensity);
  IncrementByteWithClip(pub[2], ((UBYTE*)&lm_colLight)[1] *fIntensity);
}

  
// remember general data
void CLayerMixer::CalculateData( CBrushShadowMap *pbsm, INDEX iMipmap)
{
  _pfWorldEditingProfile.StartTimer(CWorldEditingProfile::PTI_CALCULATEDATA);

  // cache class vars
  lm_pbsmShadowMap = pbsm;
  lm_pbpoPolygon   = pbsm->GetBrushPolygon();

  lm_mexOffsetU  = pbsm->sm_mexOffsetX;
  lm_mexOffsetV  = pbsm->sm_mexOffsetY;
  lm_iFirstLevel = pbsm->sm_iFirstMipLevel;
  lm_iMipLevel   = iMipmap;
  lm_iMipShift   = lm_iMipLevel - lm_iFirstLevel;
  lm_pixCanvasSizeU  = pbsm->sm_mexWidth >>lm_iMipLevel;
  lm_pixCanvasSizeV  = pbsm->sm_mexHeight>>lm_iMipLevel;
  lm_pixPolygonSizeU = Min( lm_pixCanvasSizeU, (PIX)(lm_pbsmShadowMap->sm_pixPolygonSizeU >>lm_iMipShift)+1L);
  lm_pixPolygonSizeV = Min( lm_pixCanvasSizeV, (PIX)(lm_pbsmShadowMap->sm_pixPolygonSizeV >>lm_iMipShift)+1L);

  // determine where this mip-map is relative to the allocated shadow map memory
  PIX pixOffset = pbsm->sm_slMemoryUsed/BYTES_PER_TEXEL
                - GetMipmapOffset( 15, lm_pixCanvasSizeU, lm_pixCanvasSizeV);

  // get right pointers to the shadow mipmap
  if( lm_bDynamic) {
    lm_pulShadowMap       = pbsm->sm_pulDynamicShadowMap + pixOffset;
    lm_pulStaticShadowMap = pbsm->sm_pulCachedShadowMap  + pixOffset;
  } else {
    lm_pulShadowMap       = pbsm->sm_pulCachedShadowMap  + pixOffset;
    lm_pulStaticShadowMap = NULL;
  }

  // prepare 3D positions
  CEntity *penWithPolygon = lm_pbpoPolygon->bpo_pbscSector->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity;
  ASSERT(penWithPolygon!=NULL);
  const FLOATmatrix3D &mPolygonRotation = penWithPolygon->en_mRotation;
  const FLOAT3D &vPolygonTranslation = penWithPolygon->GetPlacement().pl_PositionVector;

  // get first pixel in texture in 3D
  Vector<MEX, 2> vmex0;
  vmex0(1) = -lm_mexOffsetU+(1<<(lm_iMipLevel-1));
  vmex0(2) = -lm_mexOffsetV+(1<<(lm_iMipLevel-1));
  lm_pbpoPolygon->bpo_mdShadow.GetSpaceCoordinates(
    lm_pbpoPolygon->bpo_pbplPlane->bpl_pwplWorking->wpl_mvRelative, vmex0, lm_vO);
  lm_vO = lm_vO*mPolygonRotation+vPolygonTranslation;

  // get steps for walking in texture in 3D
  Vector<MEX, 2> vmexU, vmexV;
  vmexU(1) = (1<<lm_iMipLevel)-lm_mexOffsetU+(1<<(lm_iMipLevel-1));
  vmexU(2) = (0<<lm_iMipLevel)-lm_mexOffsetV+(1<<(lm_iMipLevel-1));
  vmexV(1) = (0<<lm_iMipLevel)-lm_mexOffsetU+(1<<(lm_iMipLevel-1));
  vmexV(2) = (1<<lm_iMipLevel)-lm_mexOffsetV+(1<<(lm_iMipLevel-1));

  lm_pbpoPolygon->bpo_mdShadow.GetSpaceCoordinates(
    lm_pbpoPolygon->bpo_pbplPlane->bpl_pwplWorking->wpl_mvRelative, vmexU, lm_vStepU);
  lm_vStepU = lm_vStepU*mPolygonRotation+vPolygonTranslation;
  lm_pbpoPolygon->bpo_mdShadow.GetSpaceCoordinates(
    lm_pbpoPolygon->bpo_pbplPlane->bpl_pwplWorking->wpl_mvRelative, vmexV, lm_vStepV);
  lm_vStepV = lm_vStepV*mPolygonRotation+vPolygonTranslation;
  lm_vStepU-= lm_vO;
  lm_vStepV-= lm_vO;

  ASSERT( lm_pixPolygonSizeU>0 && lm_pixPolygonSizeV>0);
  _pfWorldEditingProfile.StopTimer(CWorldEditingProfile::PTI_CALCULATEDATA);
}


// find start of a mip-map inside a layer
void CLayerMixer::FindLayerMipmap( CBrushShadowLayer *pbsl, UBYTE *&pub, UBYTE &ubMask)
{
  struct MipmapTable mmtLayer;
  // find mip-mapping information for the layer
  MakeMipmapTable(pbsl->bsl_pixSizeU, pbsl->bsl_pixSizeV, mmtLayer);
  // get pixel offset of the mipmap
  SLONG slPixOffset = mmtLayer.mmt_aslOffsets[lm_iMipLevel-lm_iFirstLevel];
  // convert offset to bits
  pub = pbsl->bsl_pubLayer + (slPixOffset>>3);
  ubMask = 1<<(slPixOffset&7);
}



#define FTOX   0x10000000
#define SHIFTX (28-SQRTTABLESIZELOG2)

// static variables for easier transfers
static const FLOAT3D *_vLight;
static FLOAT _fMinLightDistance, _f1oFallOff;
static INDEX _iPixCt, _iRowCt;
static SLONG _slModulo;
static ULONG _ulLightFlags, _ulPolyFlags;
static SLONG _slL2Row, _slDDL2oDU, _slDDL2oDV, _slDDL2oDUoDV, _slDL2oDURow, _slDL2oDV;
static SLONG _slLightMax, _slHotSpot, _slLightStep;
static ULONG *_pulLayer;


// add one layer point light without diffusion and mask
void CLayerMixer::AddAmbientPoint(void)
{
  // prepare some local variables
  __int64 mmDDL2oDU = _slDDL2oDU;
  __int64 mmDDL2oDV = _slDDL2oDV;
  ULONG ulLightRGB = ByteSwap(lm_colLight);
  _slLightMax<<=7;
  _slLightStep>>=1;

  __asm {
    // prepare interpolants
    movd    mm0,D [_slL2Row]
    movd    mm1,D [_slDL2oDURow]
    psllq   mm1,32
    por     mm1,mm0         // MM1 = slDL2oDURow | slL2Row
    movd    mm0,D [_slDL2oDV]
    movd    mm2,D [_slDDL2oDUoDV]
    psllq   mm2,32
    por     mm2,mm0         // MM2 = slDDL2oDUoDV | slDL2oDV
    // prepare color
    pxor    mm0,mm0
    movd    mm7,D [ulLightRGB]
    punpcklbw mm7,mm0
    psllw   mm7,1
    // loop thru rows
    mov     edi,D [_pulLayer]
    mov     ebx,D [_iRowCt]
rowLoop:
    push    ebx
    movd    ebx,mm1         // EBX = slL2Point
    movq    mm3,mm1
    psrlq   mm3,32          // MM3 = 0 | slDL2oDU
    // loop thru pixels in current row
    mov     ecx,D [_iPixCt]
pixLoop:
    // check if pixel need to be drawn
    cmp     ebx,FTOX
    jge     skipPixel
    // calculate intensities and do actual drawing of shadow pixel ARGB
    movd    mm4,ecx
    mov     eax,ebx
    sar     eax,SHIFTX
    and     eax,(SQRTTABLESIZE-1)
    movzx   eax,B aubSqrt[eax]
    mov     ecx,D [_slLightMax]
    cmp     eax,D [_slHotSpot]
    jle     skipInterpolation
    mov     ecx,255
    sub     ecx,eax
    imul    ecx,D [_slLightStep]
skipInterpolation:
    // calculate rgb pixel to add
    movd    mm6,ecx    
    punpcklwd mm6,mm6
    punpckldq mm6,mm6
    pmulhw  mm6,mm7
    // add dynamic light pixel to underlying pixel
    movd    mm5,D [edi]
    punpcklbw mm5,mm0
    paddw   mm5,mm6
    packuswb mm5,mm0
    movd    D [edi],mm5
    movd    ecx,mm4
skipPixel:
    // advance to next pixel
    add     edi,4
    movd    eax,mm3
    add     ebx,eax
    paddd   mm3,Q [mmDDL2oDU]
    dec     ecx
    jnz     pixLoop
    // advance to the next row
    pop     ebx
    add     edi,D [_slModulo]
    paddd   mm1,mm2
    paddd   mm2,Q [mmDDL2oDV]
    dec     ebx
    jnz     rowLoop
    emms
  }
}

// add one layer point light without diffusion and with mask
void CLayerMixer::AddAmbientMaskPoint( UBYTE *pubMask, UBYTE ubMask)
{

#if ASMOPT == 1

  // prepare some local variables
  __int64 mmDDL2oDU = _slDDL2oDU;
  __int64 mmDDL2oDV = _slDDL2oDV;
  ULONG ulLightRGB = ByteSwap(lm_colLight);
  _slLightMax<<=7;
  _slLightStep>>=1;

  __asm {
    // prepare interpolants
    movd    mm0,D [_slL2Row]
    movd    mm1,D [_slDL2oDURow]
    psllq   mm1,32
    por     mm1,mm0         // MM1 = slDL2oDURow | slL2Row
    movd    mm0,D [_slDL2oDV]
    movd    mm2,D [_slDDL2oDUoDV]
    psllq   mm2,32
    por     mm2,mm0         // MM2 = slDDL2oDUoDV | slDL2oDV
    // prepare color
    pxor    mm0,mm0         // MM0 = 0 | 0 (for unpacking purposes)
    movd    mm7,D [ulLightRGB]
    punpcklbw mm7,mm0
    psllw   mm7,1
    // loop thru rows
    mov     esi,D [pubMask]
    mov     edi,D [_pulLayer]
    movzx   edx,B [ubMask]
    mov     ebx,D [_iRowCt]
rowLoop:
    push    ebx
    movd    ebx,mm1         // EBX = slL2Point
    movq    mm3,mm1
    psrlq   mm3,32          // MM3 = 0 | slDL2oDU
    // loop thru pixels in current row
    mov     ecx,D [_iPixCt]
pixLoop:
    // check if pixel need to be drawn; i.e. draw if( [esi] & ubMask && (slL2Point<FTOX))
    cmp     ebx,FTOX
    jge     skipPixel
    test    dl,B [esi]
    je      skipPixel
    // calculate intensities and do actual drawing of shadow pixel ARGB
    movd    mm4,ecx
    mov     eax,ebx
    sar     eax,SHIFTX
    and     eax,(SQRTTABLESIZE-1)
    movzx   eax,B aubSqrt[eax]
    mov     ecx,D [_slLightMax]
    cmp     eax,D [_slHotSpot]
    jle     skipInterpolation
    mov     ecx,255
    sub     ecx,eax
    imul    ecx,D [_slLightStep]
skipInterpolation:
    // mix underlaying pixels with the calculated one
    movd    mm6,ecx 
    punpcklwd mm6,mm6
    punpckldq mm6,mm6
    pmulhw  mm6,mm7
    // add light pixel to underlying pixel
    movd    mm5,D [edi]
    punpcklbw mm5,mm0
    paddw   mm5,mm6
    packuswb mm5,mm0
    movd    D [edi],mm5
    movd    ecx,mm4
skipPixel:
    // advance to next pixel
    add     edi,4
    movd    eax,mm3
    add     ebx,eax
    paddd   mm3,Q [mmDDL2oDU]
    rol     dl,1
    adc     esi,0
    dec     ecx
    jnz     pixLoop
    // advance to the next row
    pop     ebx
    add     edi,D [_slModulo]
    paddd   mm1,mm2
    paddd   mm2,Q [mmDDL2oDV]
    dec     ebx
    jnz     rowLoop
    emms
  }

#else

  for( PIX pixV=0; pixV<_iRowCt; pixV++)
  {
    SLONG slL2Point = _slL2Row;
    SLONG slDL2oDU  = _slDL2oDURow;
    for( PIX pixU=0; pixU<_iPixCt; pixU++)
    {
      // if the point is not masked
      if( *pubPoint&ubMask && (slL2Point<FTOX)) {
        SLONG slL = (slL2Point>>SHIFTX)&(SQRTTABLESIZE-1);  // and is just for degenerate cases
        SLONG slIntensity = _slLightMax;
        slL = aubSqrt[slL];
        if( slL>_slHotSpot) slIntensity = ((255-slL)*_slLightStep)>>8;
        // add the intensity to the pixel
        AddToCluster( (UBYTE*)_pulLayer, slIntensity/255.0f);
      } 
      // go to the next pixel
      _pulLayer++;
      slL2Point += _slDL2oDU;
      slDL2oDU  += _slDDL2oDU;
      ubMask<<=1;
      if( ubMask==0) {
        pubPoint++;
        ubMask = 1;
      }
    }
    // go to the next row
    _pulLayer    += _slModulo/BYTES_PER_TEXEL;
    _slL2Row     += _slDL2oDV;
    _slDL2oDV    += _slDDL2oDV;
    _slDL2oDURow += _slDDL2oDUoDV;
  }

#endif

}

// add one layer point light with diffusion and without mask
void CLayerMixer::AddDiffusionPoint(void)
{
  // adjust params for diffusion lighting
  SLONG slMax1oL = MAX_SLONG;
  _slLightStep = FloatToInt(_slLightStep * _fMinLightDistance * _f1oFallOff);
  if( _slLightStep!=0) slMax1oL = (256<<8) / _slLightStep +256;

  // prepare some local variables
  __int64 mmDDL2oDU = _slDDL2oDU;
  __int64 mmDDL2oDV = _slDDL2oDV;
  ULONG ulLightRGB = ByteSwap(lm_colLight);
  _slLightMax<<=7;
  _slLightStep>>=1;

  __asm {
    // prepare interpolants
    movd    mm0,D [_slL2Row]
    movd    mm1,D [_slDL2oDURow]
    psllq   mm1,32
    por     mm1,mm0         // MM1 = slDL2oDURow | slL2Row
    movd    mm0,D [_slDL2oDV]
    movd    mm2,D [_slDDL2oDUoDV]
    psllq   mm2,32
    por     mm2,mm0         // MM2 = slDDL2oDUoDV | slDL2oDV
    // prepare color
    pxor    mm0,mm0
    movd    mm7,D [ulLightRGB]
    punpcklbw mm7,mm0
    psllw   mm7,1
    // loop thru rows
    mov     edi,D [_pulLayer]
    mov     ebx,D [_iRowCt]
rowLoop:
    push    ebx
    movd    ebx,mm1         // EBX = slL2Point
    movq    mm3,mm1
    psrlq   mm3,32          // MM3 = 0 | slDL2oDU
    // loop thru pixels in current row
    mov     ecx,D [_iPixCt]
pixLoop:
    // check if pixel need to be drawn
    cmp     ebx,FTOX
    jge     skipPixel
    // calculate intensities and do actual drawing of shadow pixel ARGB
    movd    mm4,ecx
    mov     eax,ebx
    sar     eax,SHIFTX
    and     eax,(SQRTTABLESIZE-1)
    movzx   eax,W auw1oSqrt[eax*2]
    mov     ecx,D [_slLightMax]
    cmp     eax,D [slMax1oL]
    jge     skipInterpolation
    lea     ecx,[eax-256]
    imul    ecx,D [_slLightStep]
skipInterpolation:
    // calculate rgb pixel to add
    movd    mm6,ecx    
    punpcklwd mm6,mm6
    punpckldq mm6,mm6
    pmulhw  mm6,mm7
    // add dynamic light pixel to underlying pixel
    movd    mm5,D [edi]
    punpcklbw mm5,mm0
    paddw   mm5,mm6
    packuswb mm5,mm0
    movd    D [edi],mm5
    movd    ecx,mm4
skipPixel:
    // advance to next pixel
    add     edi,4
    movd    eax,mm3
    add     ebx,eax
    paddd   mm3,Q [mmDDL2oDU]
    dec     ecx
    jnz     pixLoop
    // advance to the next row
    pop     ebx
    add     edi,D [_slModulo]
    paddd   mm1,mm2
    paddd   mm2,Q [mmDDL2oDV]
    dec     ebx
    jnz     rowLoop
    emms
  }
}

// add one layer point light with diffusion and mask
void CLayerMixer::AddDiffusionMaskPoint( UBYTE *pubMask, UBYTE ubMask)
{
  // adjust params for diffusion lighting
  SLONG slMax1oL = MAX_SLONG;
  _slLightStep = FloatToInt(_slLightStep * _fMinLightDistance * _f1oFallOff);
  if( _slLightStep!=0) slMax1oL = (256<<8) / _slLightStep +256;

#if ASMOPT == 1

  // prepare some local variables
  __int64 mmDDL2oDU = _slDDL2oDU;
  __int64 mmDDL2oDV = _slDDL2oDV;
  ULONG ulLightRGB = ByteSwap(lm_colLight);
  _slLightMax<<=7;
  _slLightStep>>=1;

  __asm {
    // prepare interpolants
    movd    mm0,D [_slL2Row]
    movd    mm1,D [_slDL2oDURow]
    psllq   mm1,32
    por     mm1,mm0         // MM1 = slDL2oDURow | slL2Row
    movd    mm0,D [_slDL2oDV]
    movd    mm2,D [_slDDL2oDUoDV]
    psllq   mm2,32
    por     mm2,mm0         // MM2 = slDDL2oDUoDV | slDL2oDV
    // prepare color
    pxor    mm0,mm0         // MM0 = 0 | 0 (for unpacking purposes)
    movd    mm7,D [ulLightRGB]
    punpcklbw mm7,mm0
    psllw   mm7,1
    // loop thru rows
    mov     esi,D [pubMask]
    mov     edi,D [_pulLayer]
    movzx   edx,B [ubMask]
    mov     ebx,D [_iRowCt]
rowLoop:
    push    ebx
    movd    ebx,mm1         // EBX = slL2Point
    movq    mm3,mm1
    psrlq   mm3,32          // MM3 = 0 | slDL2oDU
    // loop thru pixels in current row
    mov     ecx,D [_iPixCt]
pixLoop:
    // check if pixel need to be drawn; i.e. draw if( [esi] & ubMask && (slL2Point<FTOX))
    cmp     ebx,FTOX
    jge     skipPixel
    test    dl,B [esi]
    je      skipPixel
    // calculate intensities and do actual drawing of shadow pixel ARGB
    movd    mm4,ecx
    mov     eax,ebx
    sar     eax,SHIFTX
    and     eax,(SQRTTABLESIZE-1)
    movzx   eax,W auw1oSqrt[eax*2]
    mov     ecx,D [_slLightMax]
    cmp     eax,D [slMax1oL]
    jge     skipInterpolation
    lea     ecx,[eax-256]
    imul    ecx,D [_slLightStep]
skipInterpolation:
    // mix underlaying pixels with the calculated one
    movd    mm6,ecx 
    punpcklwd mm6,mm6
    punpckldq mm6,mm6
    pmulhw  mm6,mm7
    // add light pixel to underlying pixel
    movd    mm5,D [edi]
    punpcklbw mm5,mm0
    paddw   mm5,mm6
    packuswb mm5,mm0
    movd    D [edi],mm5
    movd    ecx,mm4
skipPixel:
    // advance to next pixel
    add     edi,4
    movd    eax,mm3
    add     ebx,eax
    paddd   mm3,Q [mmDDL2oDU]
    rol     dl,1
    adc     esi,0
    dec     ecx
    jnz     pixLoop
    // advance to the next row
    pop     ebx
    add     edi,D [_slModulo]
    paddd   mm1,mm2
    paddd   mm2,Q [mmDDL2oDV]
    dec     ebx
    jnz     rowLoop
    emms
  }

#else


  // for each pixel in the shadow map
  for( PIX pixV=0; pixV<_iRowCt; pixV++)
  {
    SLONG slL2Point = _slL2Row;
    SLONG slDL2oDU  = _slDL2oDURow;
    for( PIX pixU=0; pixU<_iPixCt; pixU++)
    {
      // if the point is not masked
      if( *pubMask&ubMask && (slL2Point<FTOX)) {
        SLONG sl1oL = (slL2Point>>SHIFTX)&(SQRTTABLESIZE-1);  // and is just for degenerate cases
        sl1oL = auw1oSqrt[sl1oL];
        SLONG slIntensity = _slLightMax;
        if( sl1oL<slMax1oL) slIntensity = ((sl1oL-256)*_slLightStep)>>16;
        // add the intensity to the pixel
        AddToCluster( (UBYTE*)_pulLayer, slIntensity/255.0f);
      } 
      // advance to next pixel
      _pulLayer++;
      slL2Point +=  slDL2oDU;
      slDL2oDU  += _slDDL2oDU;
      ubMask<<=1;
      if( ubMask==0) {
        pubMask++;
        ubMask = 1;
      }
    }
    // advance to next row
    _pulLayer    += _slModulo/BYTES_PER_TEXEL;
    _slL2Row     += _slDL2oDV;
    _slDL2oDV    += _slDDL2oDV;
    _slDL2oDURow += _slDDL2oDUoDV;
  }

#endif

}

// prepares point light that creates layer (returns TRUE if there is infulence)
BOOL CLayerMixer::PrepareOneLayerPoint( CBrushShadowLayer *pbsl, BOOL bNoMask)
{
  // determine light infulence dimensions
  _iPixCt = pbsl->bsl_pixSizeU >>lm_iMipShift;
  _iRowCt = pbsl->bsl_pixSizeV >>lm_iMipShift;
  PIX pixMinU = pbsl->bsl_pixMinU >>lm_iMipShift;
  PIX pixMinV = pbsl->bsl_pixMinV >>lm_iMipShift;
  // clamp influence to polygon size
  if( (pixMinU+_iPixCt) > lm_pixPolygonSizeU && bNoMask) _iPixCt = lm_pixPolygonSizeU-pixMinU;
  if( (pixMinV+_iRowCt) > lm_pixPolygonSizeV)            _iRowCt = lm_pixPolygonSizeV-pixMinV;
  _slModulo = (lm_pixCanvasSizeU-_iPixCt) *BYTES_PER_TEXEL;
  _pulLayer = lm_pulShadowMap + (pixMinV*lm_pixCanvasSizeU)+pixMinU;
  ASSERT( pixMinU>=0 && pixMinU<lm_pixCanvasSizeU && pixMinV>=0 && pixMinV<lm_pixCanvasSizeV);

  // get the light source properties of the layer
  lm_plsLight = pbsl->bsl_plsLightSource;
  _vLight = &lm_plsLight->ls_penEntity->GetPlacement().pl_PositionVector;
  _fMinLightDistance = lm_pbpoPolygon->bpo_pbplPlane->bpl_plAbsolute.PointDistance(*_vLight);
  _f1oFallOff   = 1.0f / lm_plsLight->ls_rFallOff;
  _ulLightFlags = lm_plsLight->ls_ulFlags;
  _ulPolyFlags  = lm_pbpoPolygon->bpo_ulFlags;
  lm_colLight   = lm_plsLight->GetLightColor();
  pbsl->bsl_colLastAnim = lm_colLight;

  // if there is no influence, do nothing
  if( (pbsl->bsl_pixSizeU>>lm_iMipShift)==0 || (pbsl->bsl_pixSizeV>>lm_iMipShift)==0
    || _iPixCt<=0 || _iRowCt<=0) return FALSE;

  // adjust for sector ambient
  if( _ulLightFlags&LSF_SUBSTRACTSECTORAMBIENT) {
    COLOR colAmbient = lm_pbpoPolygon->bpo_pbscSector->bsc_colAmbient;
    IncrementByteWithClip( ((UBYTE*)&lm_colLight)[1], -((UBYTE*)&colAmbient)[1]);
    IncrementByteWithClip( ((UBYTE*)&lm_colLight)[2], -((UBYTE*)&colAmbient)[2]);
    IncrementByteWithClip( ((UBYTE*)&lm_colLight)[3], -((UBYTE*)&colAmbient)[3]);
    if( _ulPolyFlags&BPOF_HASDIRECTIONALAMBIENT)
    { // find directional layers for each shadow layer
      FOREACHINLIST( CBrushShadowLayer, bsl_lnInShadowMap, lm_pbsmShadowMap->bsm_lhLayers, itbsl)
      { // loop thru layers
        CBrushShadowLayer &bsl = *itbsl;
        if( bsl.bsl_plsLightSource->ls_ulFlags&LSF_DIRECTIONAL)
        { // skip if no ambient color
          colAmbient = bsl.bsl_plsLightSource->ls_colAmbient & 0xFFFFFF00;
          if( IsBlack(colAmbient)) continue;
          // substract ambient
          IncrementByteWithClip( ((UBYTE*)&lm_colLight)[1], -((UBYTE*)&colAmbient)[1]);
          IncrementByteWithClip( ((UBYTE*)&lm_colLight)[2], -((UBYTE*)&colAmbient)[2]);
          IncrementByteWithClip( ((UBYTE*)&lm_colLight)[3], -((UBYTE*)&colAmbient)[3]);
        }
      }
    }
  }

  // prepare intermediate light interpolants
  FLOAT3D v00 = (lm_vO+lm_vStepU*pixMinU + lm_vStepV*pixMinV) - *_vLight;
  FLOAT fFactor = FTOX * _f1oFallOff*_f1oFallOff;
  FLOAT fL2Row  = v00%v00;
  FLOAT fDDL2oDU    = lm_vStepU%lm_vStepU;
  FLOAT fDDL2oDV    = lm_vStepV%lm_vStepV;
  FLOAT fDDL2oDUoDV = lm_vStepU%lm_vStepV;
  FLOAT fDL2oDURow  = fDDL2oDU + 2*(lm_vStepU%v00);
  FLOAT fDL2oDV     = fDDL2oDV + 2*(lm_vStepV%v00);
  //_v00 = v00;

#if ASMOPT == 1
  __asm {
    fld     D [fDDL2oDU]
    fadd    D [fDDL2oDU]
    fld     D [fDDL2oDV]
    fadd    D [fDDL2oDV]
    fld     D [fDDL2oDUoDV]
    fadd    D [fDDL2oDUoDV]
    // st0=2*fDDL2oDUoDV, st1=2*fDDL2oDV, st2=2*fDDL2oDU
    fld     D [fL2Row]
    fmul    D [fFactor]
    fld     D [fDL2oDURow]
    fmul    D [fFactor]
    fld     D [fDL2oDV]
    fmul    D [fFactor]
    // st0=fDL2oDV*fFactor, st1=fDL2oDURow*fFactor, st2=fL2Row*fFactor,
    // st3=2*fDDL2oDUoDV, st4=2*fDDL2oDV, st5=2*fDDL2oDU
    fld     D [fFactor]
    fmul    st(4),st(0)
    fmul    st(5),st(0)
    fmulp   st(6),st(0)
    fistp   D [_slDL2oDV]
    fistp   D [_slDL2oDURow]
    fistp   D [_slL2Row]
    fistp   D [_slDDL2oDUoDV]
    fistp   D [_slDDL2oDV]
    fistp   D [_slDDL2oDU]
  }
#else
  fDDL2oDU     *= 2;
  fDDL2oDV     *= 2;
  fDDL2oDUoDV  *= 2;
  _slL2Row      = FloatToInt( fL2Row      * fFactor);
  _slDDL2oDU    = FloatToInt( fDDL2oDU    * fFactor);
  _slDDL2oDV    = FloatToInt( fDDL2oDV    * fFactor);
  _slDDL2oDUoDV = FloatToInt( fDDL2oDUoDV * fFactor);
  _slDL2oDURow  = FloatToInt( fDL2oDURow  * fFactor);
  _slDL2oDV     = FloatToInt( fDL2oDV     * fFactor);
#endif

  // prepare final light interpolants
  _slLightMax  = 255;
  _slHotSpot   = FloatToInt( 255.0f * lm_plsLight->ls_rHotSpot * _f1oFallOff);
  _slLightStep = FloatToInt( 65535.0f / (255.0f - _slHotSpot));
  // dark light inverts parameters
  if( _ulLightFlags & LSF_DARKLIGHT) {
    _slLightMax  = -_slLightMax;
    _slLightStep = -_slLightStep;
  }

  // saturate light color
  lm_colLight = AdjustColor( lm_colLight, _slShdHueShift, _slShdSaturation);
  // all done
  return TRUE;
}


// add one layer to the shadow map (pubMask=NULL for no mask)
void CLayerMixer::AddOneLayerPoint( CBrushShadowLayer *pbsl, UBYTE *pubMask, UBYTE ubMask)
{
  // try to prepare layer for this point light
  _pfWorldEditingProfile.StartTimer( CWorldEditingProfile::PTI_ADDONELAYERPOINT);
  if( !PrepareOneLayerPoint( pbsl, pubMask==NULL)) {
    _pfWorldEditingProfile.StopTimer( CWorldEditingProfile::PTI_ADDONELAYERPOINT);
    return;
  }

  // determine diffusion presence and corresponding routine
  BOOL bDiffusion = (_ulLightFlags&LSF_DIFFUSION) && !(_ulPolyFlags&BPOF_NOPLANEDIFFUSION);
  // masked or non-masked?
  if( pubMask==NULL) {
    // non-masked
    if( !lm_bDynamic && bDiffusion) {
      // non-masked diffusion
      AddDiffusionPoint();
    } else {
      // non-masked ambient
      AddAmbientPoint();
    }
  } else {
    // masked
    if( bDiffusion) {
      // masked diffusion
      AddDiffusionMaskPoint( pubMask, ubMask);
    } else {
      AddAmbientMaskPoint( pubMask, ubMask);
    }
  }

  // all done
  _pfWorldEditingProfile.StopTimer(CWorldEditingProfile::PTI_ADDONELAYERPOINT);
}



// apply gradient to layer
void CLayerMixer::AddOneLayerGradient( CGradientParameters &gp)
{
  // convert gradient parameters for plane
  ASSERT( Abs(gp.gp_fH1-gp.gp_fH0)>0.0001f);
  FLOAT f1oDH = 1.0f / (gp.gp_fH1-gp.gp_fH0);
  FLOAT fGr00 = (lm_vO % gp.gp_vGradientDir - gp.gp_fH0) *f1oDH;
  FLOAT fDGroDI = (lm_vStepU % gp.gp_vGradientDir) *f1oDH;
  FLOAT fDGroDJ = (lm_vStepV % gp.gp_vGradientDir) *f1oDH;
  fDGroDI += fDGroDI/lm_pixPolygonSizeU;
  fDGroDJ += fDGroDJ/lm_pixPolygonSizeV;
  SLONG fixDGroDI = FloatToInt(fDGroDI*32767.0f); // 16:15
  SLONG fixDGroDJ = FloatToInt(fDGroDJ*32767.0f); // 16:15
  COLOR col0 = gp.gp_col0;
  COLOR col1 = gp.gp_col1;
  _pulLayer  = lm_pulShadowMap;
  FLOAT fStart = Clamp( fGr00-(fDGroDJ+fDGroDI)*0.5f, 0.0f, 1.0f);

#if ASMOPT == 1

  __int64 mmRowAdv;
  SLONG fixGRow  = (fGr00-(fDGroDJ+fDGroDI)*0.5f)*32767.0f; // 16:15
  SLONG slModulo = (lm_pixCanvasSizeU-lm_pixPolygonSizeU) *BYTES_PER_TEXEL;
  COLOR colStart = LerpColor( col0, col1, fStart);
  INDEX ctCols = lm_pixPolygonSizeU;
  INDEX ctRows = lm_pixPolygonSizeV;
  BOOL  bDarkLight = gp.gp_bDark;
  
  __asm {
    mov     eax,D [colStart]
    mov     ebx,D [col0]
    mov     edx,D [col1]
    bswap   eax
    bswap   ebx
    bswap   edx
    movd    mm1,eax
    movd    mm2,ebx
    movd    mm3,edx
    punpcklbw mm1,mm1
    punpcklbw mm2,mm2
    punpcklbw mm3,mm3
    psrlw   mm1,2
    psrlw   mm2,1
    psrlw   mm3,1
    // eventually adjust for dark light
    cmp     D [bDarkLight],0
    je      skipDark
    pcmpeqd mm0,mm0
    pxor    mm1,mm0
    pxor    mm2,mm0
    pxor    mm3,mm0
    psubw   mm1,mm0
    psubw   mm2,mm0
    psubw   mm3,mm0
skipDark:
    // keep colors
    movq    mm0,mm2
    movq    mm7,mm3
    // find row and column advancers
    psubw   mm3,mm2
    movq    mm2,mm3
    movd    mm5,D [fixDGroDI]
    movd    mm6,D [fixDGroDJ]
    punpcklwd mm5,mm5
    punpcklwd mm6,mm6
    punpckldq mm5,mm5
    punpckldq mm6,mm6
    pmulhw  mm2,mm5           // column color advancer (8:6)
    pmulhw  mm3,mm6           // row color advancer    (8:6)
    movq    Q [mmRowAdv],mm3 

    // prepare starting variables
    movq    mm5,mm0
    movq    mm6,mm7
    psraw   mm5,1   // starting color
    psraw   mm6,1   // ending color
    pxor    mm0,mm0
    mov     esi,D [fixGRow]
    mov     edi,D [_pulLayer]
    mov     edx,D [ctRows]
rowLoop:
    mov     ebx,esi
    movq    mm4,mm1
    mov     ecx,D [ctCols]
pixLoop:
    // add or substract light pixel to underlying pixel
    movq    mm7,mm4
    psraw   mm7,6
    movd    mm3,D [edi]
    punpcklbw mm3,mm0
    paddw   mm3,mm7
    packuswb mm3,mm0
    movd    D [edi],mm3

    // advance to next pixel
    add     ebx,D [fixDGroDI]
    cmp     ebx,0x8000
    ja      pixClamp
    paddw   mm4,mm2
    add     edi,4
    dec     ecx
    jnz     pixLoop
    jmp     pixDone
pixClamp:
    movq    mm4,mm6
    jg      pixNext
    movq    mm4,mm5
pixNext:
    add     edi,4
    dec     ecx
    jnz     pixLoop
pixDone:

    // advance to next row
    add     esi,D [fixDGroDJ]
    cmp     esi,0x8000
    ja      rowClamp
    paddw   mm1,Q [mmRowAdv]
    add     edi,D [slModulo]
    dec     edx
    jnz     rowLoop
    jmp     rowDone
rowClamp:
    movq    mm1,mm6
    jg      rowNext
    movq    mm1,mm5
rowNext:
    add     edi,D [slModulo]
    dec     edx
    jnz     rowLoop
rowDone:
    emms
  }

#else

  // well, make gradient ...
  SLONG slR0=0,slG0=0,slB0=0;
  SLONG slR1=0,slG1=0,slB1=0;
  ColorToRGB( col0, (UBYTE&)slR0,(UBYTE&)slG0,(UBYTE&)slB0);
  ColorToRGB( col1, (UBYTE&)slR1,(UBYTE&)slG1,(UBYTE&)slB1);
  if( gp.gp_bDark) {
    slR0 = -slR0;  slG0 = -slG0;  slB0 = -slB0;
    slR1 = -slR1;  slG1 = -slG1;  slB1 = -slB1;
  }
  fixDGroDI >>= 1; // 16:14
  fixDGroDJ >>= 1; // 16:14
  SWORD fixRrow = Lerp( slR0,slR1,fStart) <<6; // 8:6
  SWORD fixGrow = Lerp( slG0,slG1,fStart) <<6; // 8:6
  SWORD fixBrow = Lerp( slB0,slB1,fStart) <<6; // 8:6
  SWORD fixDRoDI = ((slR1-slR0)*fixDGroDI)>>8;
  SWORD fixDGoDI = ((slG1-slG0)*fixDGroDI)>>8;
  SWORD fixDBoDI = ((slB1-slB0)*fixDGroDI)>>8;
  SWORD fixDRoDJ = ((slR1-slR0)*fixDGroDJ)>>8;
  SWORD fixDGoDJ = ((slG1-slG0)*fixDGroDJ)>>8;
  SWORD fixDBoDJ = ((slB1-slB0)*fixDGroDJ)>>8;

  // loop it, baby
  FLOAT fGrRow = fGr00 - (fDGroDJ+fDGroDI)*0.5f;
  PIX   pixOffset = 0;
  PIX   pixModulo = lm_pixCanvasSizeU-lm_pixPolygonSizeU;
  for( INDEX j=0; j<lm_pixPolygonSizeV; j++)
  { // prepare row
    FLOAT fGrCol  = fGrRow;
    SWORD fixRcol = fixRrow;
    SWORD fixGcol = fixGrow;
    SWORD fixBcol = fixBrow;
    for( INDEX i=0; i<lm_pixPolygonSizeU; i++)
    { // loop pixels
      SLONG slR = Clamp( fixRcol>>6, -255, +255);
      SLONG slG = Clamp( fixGcol>>6, -255, +255);
      SLONG slB = Clamp( fixBcol>>6, -255, +255);
      IncrementByteWithClip( ((UBYTE*)&_pulLayer[pixOffset])[0], slR);
      IncrementByteWithClip( ((UBYTE*)&_pulLayer[pixOffset])[1], slG);
      IncrementByteWithClip( ((UBYTE*)&_pulLayer[pixOffset])[2], slB);
      // advance to next pixel
      fGrCol += fDGroDI;
      pixOffset++;
      if( fGrCol<0) {
        fixRcol = slR0<<6;
        fixGcol = slG0<<6;
        fixBcol = slB0<<6;
      } else if( fGrCol>1) {
        fixRcol = slR1<<6;
        fixGcol = slG1<<6;
        fixBcol = slB1<<6;
      } else {
        fixRcol += fixDRoDI;
        fixGcol += fixDGoDI;
        fixBcol += fixDBoDI;
      }
    }
    // advance to next row
    fGrRow += fDGroDJ;
    pixOffset += pixModulo;
    if( fGrRow<0) {
      fixRrow = slR0<<6;
      fixGrow = slG0<<6;
      fixBrow = slB0<<6;
    } else if( fGrRow>1) {
      fixRrow = slR1<<6;
      fixGrow = slG1<<6;
      fixBrow = slB1<<6;
    } else {
      fixRrow += fixDRoDJ;
      fixGrow += fixDGoDJ;
      fixBrow += fixDBoDJ;
    }
  }

#endif

}



// apply directional light or ambient to layer
void CLayerMixer::AddDirectional(void)
{

#if ASMOPT == 1

  ULONG ulLight = ByteSwap( lm_colLight);
  __asm {
    // prepare pointers and variables
    mov     edi,D [_pulLayer]
    mov     ebx,D [_iRowCt]
    movd    mm6,D [ulLight]
    punpckldq mm6,mm6
rowLoop: 
    mov     ecx,D [_iPixCt]
    shr     ecx,1
    jz      pixRest
pixLoop:
    // mix underlaying pixels with the constant color pixel
    movq    mm5,Q [edi]
    paddusb mm5,mm6
    movq    Q [edi],mm5
    // advance to next pixel
    add     edi,8
    dec     ecx
    jnz     pixLoop
pixRest:
    test    D [_iPixCt],1
    jz      rowNext
    movd    mm5,D [edi]
    paddusb mm5,mm6
    movd    D [edi],mm5
    add     edi,4
rowNext:
    // advance to the next row
    add     edi,D [_slModulo]
    dec     ebx
    jnz     rowLoop
    emms
  }

#else

  // for each pixel in the shadow map
  for( PIX pixV=0; pixV<_iRowCt; pixV++) {
    for( PIX pixU=0; pixU<_iPixCt; pixU++) {
      // add the intensity to the pixel
      AddToCluster( (UBYTE*)_pulLayer);
      _pulLayer++; // go to the next pixel
    } // go to the next row
    _pulLayer += slModulo;
  }

#endif

}

// apply directional light thru mask to layer
void CLayerMixer::AddMaskDirectional( UBYTE *pubMask, UBYTE ubMask)
{

#if ASMOPT == 1

  // prepare some local variables
  ULONG ulLight = ByteSwap( lm_colLight);
  __asm {
    // prepare pointers and variables
    movzx   edx,B [ubMask]
    mov     esi,D [pubMask]
    mov     edi,D [_pulLayer]
    mov     ebx,D [_iRowCt]
    movd    mm6,D [ulLight]
rowLoop:
    mov     ecx,D [_iPixCt]
pixLoop:
    // mix underlaying pixels with the constant light color if not shaded
    test    dl,B [esi]
    jz      skipLight
    movd    mm5,D [edi]
    paddusb mm5,mm6
    movd    D [edi],mm5
skipLight:
    // advance to next pixel
    add     edi,4
    rol     dl,1
    adc     esi,0
    dec     ecx
    jnz     pixLoop
    // advance to the next row
    add     edi,D [_slModulo]
    dec     ebx
    jnz     rowLoop
    emms
  }

#else

  // for each pixel in the shadow map
  for( PIX pixV=0; pixV<_iRowCt; pixV++) {
    for( PIX pixU=0; pixU<_iPixCt; pixU++) {
      // if the point is not masked
      if( *pubMask&ubMask) {
        // add the intensity to the pixel
        AddToCluster( (UBYTE*)_pulLayer);
      } // go to the next pixel
      _pulLayer++;
      ubMask<<=1;
      if( ubMask==0) {
        pubMask ++;
        ubMask = 1;
      }
    } // go to the next row
    _pulLayer += slModulo;
  }

#endif

}


// apply directional light to layer
// (pubMask=NULL for no mask, ubMask = 0xFF for full mask)
void CLayerMixer::AddOneLayerDirectional( CBrushShadowLayer *pbsl, UBYTE *pubMask, UBYTE ubMask)
{
  // only if there is color light (ambient is added at initial fill)
  if( !(lm_pbpoPolygon->bpo_ulFlags&BPOF_HASDIRECTIONALLIGHT)) return;
  _pfWorldEditingProfile.StartTimer(CWorldEditingProfile::PTI_ADDONELAYERDIRECTIONAL);

  // determine light influence dimensions
  _iPixCt = pbsl->bsl_pixSizeU >>lm_iMipShift;
  _iRowCt = pbsl->bsl_pixSizeV >>lm_iMipShift;
  PIX pixMinU = pbsl->bsl_pixMinU >>lm_iMipShift;
  PIX pixMinV = pbsl->bsl_pixMinV >>lm_iMipShift;
  ASSERT( pixMinU==0 && pixMinV==0);
  // clamp influence to polygon size
  if( _iPixCt > lm_pixPolygonSizeU && pubMask==NULL) _iPixCt = lm_pixPolygonSizeU;
  if( _iRowCt > lm_pixPolygonSizeV)                  _iRowCt = lm_pixPolygonSizeV;
  _slModulo = (lm_pixCanvasSizeU-_iPixCt) *BYTES_PER_TEXEL;
  _pulLayer = lm_pulShadowMap;

  // if there is no influence, do nothing
  if( (pbsl->bsl_pixSizeU>>lm_iMipShift)==0 || (pbsl->bsl_pixSizeV>>lm_iMipShift)==0
    || _iPixCt<=0 || _iRowCt<=0) {
    _pfWorldEditingProfile.StopTimer(CWorldEditingProfile::PTI_ADDONELAYERDIRECTIONAL);
    return;
  }

  // get the light source of the layer
  lm_plsLight = pbsl->bsl_plsLightSource;
  const FLOAT3D &vLight = lm_plsLight->ls_penEntity->GetPlacement().pl_PositionVector;
  AnglesToDirectionVector( lm_plsLight->ls_penEntity->GetPlacement().pl_OrientationAngle,
                           lm_vLightDirection);
  // calculate intensity
  FLOAT fIntensity = 1.0f;
  if( !(lm_pbpoPolygon->bpo_ulFlags&BPOF_NOPLANEDIFFUSION)) {
    fIntensity = -((lm_pbpoPolygon->bpo_pbplPlane->bpl_plAbsolute)%lm_vLightDirection);
    fIntensity = ClampDn( fIntensity, 0.0f);
  }
  // calculate light color and ambient
  lm_colLight = lm_plsLight->GetLightColor();
  pbsl->bsl_colLastAnim = lm_colLight;
  ULONG ulIntensity = NormFloatToByte(fIntensity);
  ulIntensity = (ulIntensity<<CT_RSHIFT)|(ulIntensity<<CT_GSHIFT)|(ulIntensity<<CT_BSHIFT);
  lm_colLight = MulColors(   lm_colLight, ulIntensity);
  lm_colLight = AdjustColor( lm_colLight, _slShdHueShift, _slShdSaturation);

  // masked or non-masked?
  if( pubMask==NULL) {
    // non-masked
    AddDirectional();
  } else {
    // masked
    AddMaskDirectional( pubMask, ubMask);
  }

  // all done
  _pfWorldEditingProfile.StopTimer(CWorldEditingProfile::PTI_ADDONELAYERDIRECTIONAL);
}



// clamper helper
static INDEX GetDither(void)
{
  shd_iDithering = Clamp( shd_iDithering, 0L, 5L);
  INDEX iDither  = shd_iDithering;
  if( iDither>2) iDither++;
  return iDither;
}


// mix one mip-map
void CLayerMixer::MixOneMipmap(CBrushShadowMap *pbsm, INDEX iMipmap)
{
  // remember general data
  CalculateData( pbsm, iMipmap);
  const BOOL bDynamicOnly = lm_pbpoPolygon->bpo_ulFlags&BPOF_DYNAMICLIGHTSONLY;

  // fill with sector ambient
  _pfWorldEditingProfile.StartTimer(CWorldEditingProfile::PTI_AMBIENTFILL);

  // eventually add ambient component of all directional layers that might contribute
  COLOR colAmbient = 0x80808000UL; // overide ambient light color for dynamic lights only
  if( !bDynamicOnly) {
    colAmbient = AdjustColor( lm_pbpoPolygon->bpo_pbscSector->bsc_colAmbient, _slShdHueShift, _slShdSaturation);
    if( lm_pbpoPolygon->bpo_ulFlags&BPOF_HASDIRECTIONALAMBIENT) {
      {FOREACHINLIST( CBrushShadowLayer, bsl_lnInShadowMap, lm_pbsmShadowMap->bsm_lhLayers, itbsl) {
        CBrushShadowLayer &bsl = *itbsl;
        CLightSource &ls = *bsl.bsl_plsLightSource;
        ASSERT( &ls!=NULL); if( &ls==NULL) continue; // safety check
        if( !(ls.ls_ulFlags&LSF_DIRECTIONAL)) continue;  // skip non-directional layers
        COLOR col = AdjustColor( ls.GetLightAmbient(), _slShdHueShift, _slShdSaturation);
        colAmbient = AddColors( colAmbient, col);
      }}
    }
  } // set initial color
  __asm {
    cld
    mov     ebx,D [this]
    mov     ecx,D [ebx].lm_pixCanvasSizeU
    imul    ecx,D [ebx].lm_pixCanvasSizeV
    mov     edi,D [ebx].lm_pulShadowMap
    mov     eax,D [colAmbient]
    bswap   eax
    rep     stosd
  }
  _pfWorldEditingProfile.StopTimer(CWorldEditingProfile::PTI_AMBIENTFILL);

  // find gradient layer
  CGradientParameters gpGradient;
  BOOL  bHasGradient   = FALSE;
  ULONG ulGradientType = lm_pbpoPolygon->bpo_bppProperties.bpp_ubGradientType;
  if( ulGradientType>0) {
    CEntity *pen = lm_pbpoPolygon->bpo_pbscSector->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity;
    if( pen!=NULL) bHasGradient = pen->GetGradient( ulGradientType, gpGradient);
  }
  // add gradient if gradient is light
  if( bHasGradient && !gpGradient.gp_bDark) AddOneLayerGradient( gpGradient);

  // for each shadow layer
  lm_pbsmShadowMap->sm_ulFlags &= ~SMF_ANIMATINGLIGHTS;
  {FORDELETELIST( CBrushShadowLayer, bsl_lnInShadowMap, lm_pbsmShadowMap->bsm_lhLayers, itbsl)
  {
    CBrushShadowLayer &bsl = *itbsl;
    CLightSource &ls = *bsl.bsl_plsLightSource;
    ASSERT( &ls!=NULL); if( &ls==NULL) continue; // safety check

    // skip if should not be applied
    if( (bDynamicOnly && !(ls.ls_ulFlags&LSF_NONPERSISTENT)) || ls.ls_ulFlags&LSF_DYNAMIC) continue;

    // set corresponding shadowmap flag if this is an animating light
    if( ls.ls_paoLightAnimation!=NULL) lm_pbsmShadowMap->sm_ulFlags |= SMF_ANIMATINGLIGHTS;

    // if the layer is calculated
    if( bsl.bsl_pubLayer!=NULL)
    {
      UBYTE *pub;
      UBYTE ubMask;
      FindLayerMipmap( itbsl, pub, ubMask);
      // add the layer to the shadow map with masking
      if( ls.ls_ulFlags&LSF_DIRECTIONAL) {
        AddOneLayerDirectional( itbsl, pub, ubMask);
      } else {
        AddOneLayerPoint( itbsl, pub, ubMask);
      }
    }
    // if the layer is all light
    else if( !(bsl.bsl_ulFlags&BSLF_CALCULATED) || (bsl.bsl_ulFlags&BSLF_ALLLIGHT))
    {
      // add the layer to the shadow map without masking
      if( ls.ls_ulFlags&LSF_DIRECTIONAL) {
        AddOneLayerDirectional( itbsl, NULL);
      } else {
        AddOneLayerPoint( itbsl, NULL);
      }
    }
  }}

  // if gradient is dark, substract gradient
  if( bHasGradient && gpGradient.gp_bDark) AddOneLayerGradient( gpGradient);

  // do eventual filtering of shadow layer
  shd_iFiltering = Clamp( shd_iFiltering, 0L, +6L);
  if( shd_iFiltering>0) {
    FilterBitmap( shd_iFiltering, lm_pulShadowMap, lm_pulShadowMap,
                  lm_pixPolygonSizeU, lm_pixPolygonSizeV, lm_pixCanvasSizeU, lm_pixCanvasSizeV);
  }
  // do eventual dithering of shadow layer
  const INDEX iDither = GetDither();
  if( !(_pGfx->gl_ulFlags&GLF_32BITTEXTURES)) shd_bFineQuality = FALSE;
  if( iDither && !(shd_bFineQuality)) {
    DitherBitmap( iDither, lm_pulShadowMap, lm_pulShadowMap,
                  lm_pixPolygonSizeU, lm_pixPolygonSizeV, lm_pixCanvasSizeU, lm_pixCanvasSizeV);
  }
}



// copy from static shadow map to dynamic layer
__forceinline void CLayerMixer::CopyShadowLayer(void)
{
  __asm {
    cld
    mov     ebx,D [this]
    mov     ecx,D [ebx].lm_pixCanvasSizeU
    imul    ecx,D [ebx].lm_pixCanvasSizeV
    mov     esi,D [ebx].lm_pulStaticShadowMap
    mov     edi,D [ebx].lm_pulShadowMap
    rep     movsd
  }
}


// copy from static shadow map to dynamic layer
__forceinline void CLayerMixer::FillShadowLayer( COLOR col)
{
  __asm {
    cld
    mov     ebx,D [this]
    mov     ecx,D [ebx].lm_pixCanvasSizeU
    imul    ecx,D [ebx].lm_pixCanvasSizeV
    mov     edi,D [ebx].lm_pulShadowMap
    mov     eax,D [col]
    bswap   eax   // convert to R,G,B,A memory format!
    rep     stosd
  }
}


// mix dynamic lights
void CLayerMixer::MixOneMipmapDynamic( CBrushShadowMap *pbsm, INDEX iMipmap)
{
  // remember general data
  CalculateData( pbsm, iMipmap);
  // if static shadow map is all flat
  if( pbsm->sm_pulCachedShadowMap==&pbsm->sm_colFlat) {
    // just fill dynamic shadow map with flat color
    FillShadowLayer( pbsm->sm_colFlat);
  } // if not flat
  else {
    // copy static layer
    CopyShadowLayer();
  }

  // for each shadow layer
  {FORDELETELIST( CBrushShadowLayer, bsl_lnInShadowMap, lm_pbsmShadowMap->bsm_lhLayers, itbsl)
  { // the layer's light source must be valid
    CBrushShadowLayer &bsl = *itbsl;
    CLightSource &ls = *bsl.bsl_plsLightSource;
    ASSERT( &ls!=NULL);
    if( !(ls.ls_ulFlags&LSF_DYNAMIC)) continue;
    COLOR colLight = ls.GetLightColor() & ~CT_AMASK;
    if( IsBlack(colLight)) continue;
    // apply one layer
    colLight = AdjustColor( colLight, _slShdHueShift, _slShdSaturation);
    AddOneLayerPoint( itbsl, NULL);
  }}
}


// constructor
CLayerMixer::CLayerMixer( CBrushShadowMap *pbsm, INDEX iFirstMip, INDEX iLastMip, BOOL bDynamic)
{
  lm_bDynamic = bDynamic;
  if( bDynamic) {
    // check dynamic layers for complete blackness
    BOOL bAllBlack = TRUE;
    pbsm->sm_ulFlags &= ~SMF_DYNAMICBLACK;
    {FORDELETELIST( CBrushShadowLayer, bsl_lnInShadowMap, pbsm->bsm_lhLayers, itbsl) {
      CLightSource &ls = *itbsl->bsl_plsLightSource;
      ASSERT( &ls!=NULL);
      if( !(ls.ls_ulFlags&LSF_DYNAMIC)) continue;
      COLOR colLight = ls.GetLightColor() & ~CT_AMASK;
      itbsl->bsl_colLastAnim = colLight;
      if( !IsBlack(colLight)) bAllBlack = FALSE; // must continue because of layer info update (light anim and such stuff)
    }}
    // skip mixing if dynamic layers were all black
    if( bAllBlack) {
      pbsm->sm_ulFlags |= SMF_DYNAMICBLACK;
      return;
    }
    // need to mix in
    for( INDEX iMipmap=iFirstMip; iMipmap<=iLastMip; iMipmap++) MixOneMipmapDynamic( pbsm, iMipmap);
  }
  // mix static layers
  else {
    for( INDEX iMipmap=iFirstMip; iMipmap<=iLastMip; iMipmap++) MixOneMipmap( pbsm, iMipmap);
  }
}


// mix all layers into cached shadow map
void CBrushShadowMap::MixLayers( INDEX iFirstMip, INDEX iLastMip, BOOL bDynamic/*=FALSE*/)
{
  _sfStats.StartTimer( CStatForm::STI_SHADOWUPDATE);
  _pfWorldEditingProfile.StartTimer( CWorldEditingProfile::PTI_MIXLAYERS);
  // mix the layers with a shadow mixer
  CLayerMixer lmMixer( this, iFirstMip, iLastMip, bDynamic);
  _pfWorldEditingProfile.StopTimer( CWorldEditingProfile::PTI_MIXLAYERS);
  _sfStats.StopTimer( CStatForm::STI_SHADOWUPDATE);
}
