/* Copyright (c) 2002-2012 Croteam Ltd. All rights reserved. */ #include "stdh.h" #include <Engine/Rendering/Render_internal.h> #include <Engine/Brushes/Brush.h> #include <Engine/Brushes/BrushTransformed.h> #include <Engine/Light/LightSource.h> #include <Engine/Base/ListIterator.inl> #include <Engine/Graphics/Color.h> #include <Engine/World/World.h> #include <Engine/Entities/Entity.h> #include <Engine/Templates/StaticArray.cpp> #include <Engine/Graphics/GfxLibrary.h> #include <Engine/Math/Clipping.inl> #include <Engine/Light/Shadows_internal.h> #include <Engine/World/WorldEditingProfile.h> //#include <Engine/Graphics/ImageInfo.h> //#include <Engine/Base/ErrorReporting.h> extern INDEX _ctShadowLayers=0; extern INDEX _ctShadowClusters=0; // class used for making shadow layers (used only locally) class CLayerMaker { // implementation: public: // information about current polygon CBrushPolygon *lm_pbpoPolygon; // the polygon CBrushShadowMap *lm_pbsmShadowMap; // the shadow map on the polygon FLOAT lm_fMipFactor; // the mip factor of this polygon CWorld *lm_pwoWorld; // world for casting rays in CBrushShadowLayer *lm_pbslLayer;// current layer // dimensions of currently processed shadow map MEX lm_mexSizeU; // size in mex MEX lm_mexSizeV; MEX lm_mexOffsetU; // offsets in mex MEX lm_mexOffsetV; INDEX lm_iMipLevel; // mip level PIX lm_pixSizeU; // size in pixels PIX lm_pixSizeV; struct MipmapTable lm_mmtPolygonMask; // mip map table of the polygon mask PIX lm_pixLayerMinU; // layer rectangle inside shadow map PIX lm_pixLayerMinV; PIX lm_pixLayerSizeU; PIX lm_pixLayerSizeV; FLOAT lm_fpixHotU; FLOAT lm_fpixHotV; FLOAT lm_fLightPlaneDistance; struct MipmapTable lm_mmtLayer; // mip map table of the layer UBYTE *lm_pubPolygonMask; // bit-packed mask of where the current polygon is UBYTE *lm_pubLayer; // bit-packed mask of where the current light lights the polygon FLOAT3D lm_vLight; // position of the light source // 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 ANGLE3D lm_aMappingOrientation; // orientation of the texture map in 3D ANGLE3D lm_aInverseMappingOrientation; FLOATmatrix3D lm_mToInverseMapping; // matrix for parallel lights CLightSource *lm_plsLight; // current light // remember general data void CalculateData(void); // make bit-packed mask of where the polygon is in the shadow map void MakePolygonMask(void); // make shadow mask for the light ULONG MakeShadowMask(CBrushShadowLayer *pbsl); ULONG MakeOneShadowMaskMip(INDEX iMip); // flip shadow mask around V axis (for parallel lights) void FlipShadowMask(INDEX iMip); /* Spread the shadow towards pixels outside of polygon. */ void SpreadShadowMaskOutwards(void); /* Spread the shadow towards pixels inside of polygon. */ void SpreadShadowMaskInwards(void); public: // interface: /* Constructor. */ CLayerMaker(void); /* Cast shadows for all layers of a given polygon. */ BOOL CreateLayers(CBrushPolygon &bpo, CWorld &woWorld, BOOL bDoDirectionalLights); }; /* Make mip-maps of the shadow mask. */ static void MakeMipmapsForMask(UBYTE *pubMask, PIX pixSizeU, PIX pixSizeV, SLONG slTotalSize) { // remember pointer after first mip map UBYTE *pubSecond = pubMask+pixSizeU*pixSizeV; // start at the first mip map UBYTE *pubThis = pubMask; PIX pixThisSizeU = pixSizeU; PIX pixThisSizeV = pixSizeV; UBYTE *pubNext; PIX pixNextSizeU; PIX pixNextSizeV; // repeat for(;;) { // calculate size and position of next mip map pubNext = pubThis+pixThisSizeU*pixThisSizeV; pixNextSizeU = pixThisSizeU/2; pixNextSizeV = pixThisSizeV/2; // if the next mip map is too small if (pixNextSizeU<1 || pixNextSizeV<1) { // stop break; } // for each pixel in the next mip map UBYTE *pub = pubNext; for (PIX pixNextV=0; pixNextV<pixNextSizeV; pixNextV++) { for (PIX pixNextU=0; pixNextU<pixNextSizeU; pixNextU++) { // calculate the pixel from four pixels in this mip-map UBYTE *pubUL = pubThis+pixNextU*2+pixNextV*2*pixThisSizeU; UBYTE *pubUR = pubUL+1; UBYTE *pubDL = pubUL+pixThisSizeU; UBYTE *pubDR = pubDL+1; ASSERT(pubDR<pubNext); ULONG ulTotal = ULONG(*pubUL)+ULONG(*pubUR)+ULONG(*pubDL)+ULONG(*pubDR); *pub++ = ulTotal/4; } } // make next mip-map current pubThis = pubNext; pixThisSizeU = pixNextSizeU; pixThisSizeV = pixNextSizeV; } // must have passed exactly all mip-maps ASSERT(pubNext==pubMask+slTotalSize); // for each pixel in all mip-maps after first UBYTE *pubEnd = pubNext; for (UBYTE *pub=pubSecond; pub<pubEnd; pub++) { // normalize the pixel to 0-255 if (*pub<=128) { *pub = 0; } else { *pub = 255; } } } // convert byte packed array to bits (can perform inplace conversion) // NOTE: needs +8 bytes safety wall at the end static void ConvertBytesToBits(UBYTE *pubBytesSrc, UBYTE *pubBitsDst, INDEX ctBytes) { // for each byte of bits for (INDEX i=0; i<(ctBytes+7)/8; i++) { // compose it from 8 bytes (note that bytes are either 0 or 255) *pubBitsDst++ = (pubBytesSrc[0]&0x01)| (pubBytesSrc[1]&0x02)| (pubBytesSrc[2]&0x04)| (pubBytesSrc[3]&0x08)| (pubBytesSrc[4]&0x10)| (pubBytesSrc[5]&0x20)| (pubBytesSrc[6]&0x40)| (pubBytesSrc[7]&0x80); pubBytesSrc+=8; } } // convert bit packed array to bytes // NOTE: needs +8 bytes safety wall at the end static void ConvertBitsToBytes(UBYTE *pubBitsSrc, UBYTE *pubBytesDst, INDEX ctBytes) { // for each byte of bits for (INDEX i=0; i<(ctBytes+7)/8; i++) { // decompose it to 8 bytes pubBytesDst[0] = (*pubBitsSrc)&0x01; pubBytesDst[1] = (*pubBitsSrc)&0x02; pubBytesDst[2] = (*pubBitsSrc)&0x04; pubBytesDst[3] = (*pubBitsSrc)&0x08; pubBytesDst[4] = (*pubBitsSrc)&0x10; pubBytesDst[5] = (*pubBitsSrc)&0x20; pubBytesDst[6] = (*pubBitsSrc)&0x40; pubBytesDst[7] = (*pubBitsSrc)&0x80; pubBitsSrc++; pubBytesDst+=8; } } // remember general data void CLayerMaker::CalculateData(void) { lm_mexSizeU = lm_pbsmShadowMap->sm_mexWidth; lm_mexSizeV = lm_pbsmShadowMap->sm_mexHeight; lm_mexOffsetU = lm_pbsmShadowMap->sm_mexOffsetX; lm_mexOffsetV = lm_pbsmShadowMap->sm_mexOffsetY; lm_iMipLevel = lm_pbsmShadowMap->sm_iFirstMipLevel; lm_pixSizeU = lm_mexSizeU>>lm_iMipLevel; lm_pixSizeV = lm_mexSizeV>>lm_iMipLevel; // find mip-mapping information for the polygon mask MakeMipmapTable( lm_pixSizeU, lm_pixSizeV, lm_mmtPolygonMask); 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; // make 3 orthogonal vectors that define mapping orientation FLOAT3D vX = lm_vStepU; FLOAT3D vY = -lm_vStepV; FLOAT3D vZ = vX*vY; // make a rotation matrix from those vectors vX.Normalize(); vY.Normalize(); vZ.Normalize(); FLOATmatrix3D mOrientation; mOrientation(1,1) = vX(1); mOrientation(1,2) = vY(1); mOrientation(1,3) = vZ(1); mOrientation(2,1) = vX(2); mOrientation(2,2) = vY(2); mOrientation(2,3) = vZ(2); mOrientation(3,1) = vX(3); mOrientation(3,2) = vY(3); mOrientation(3,3) = vZ(3); FLOATmatrix3D mInvOrientation; mInvOrientation(1,1) = -vX(1); mInvOrientation(1,2) = vY(1); mInvOrientation(1,3) = -vZ(1); mInvOrientation(2,1) = -vX(2); mInvOrientation(2,2) = vY(2); mInvOrientation(2,3) = -vZ(2); mInvOrientation(3,1) = -vX(3); mInvOrientation(3,2) = vY(3); mInvOrientation(3,3) = -vZ(3); // make orientation angles from the matrix DecomposeRotationMatrixNoSnap(lm_aMappingOrientation, mOrientation); DecomposeRotationMatrixNoSnap(lm_aInverseMappingOrientation, mInvOrientation); // remember matrix for parallel lights lm_mToInverseMapping = !mInvOrientation; } // test if a point is inside a brush polygon inline BOOL TestPointInPolygon(CBrushPolygon &bpo, const FLOAT3D &v) { // find major axes of the polygon plane INDEX iMajorAxis1 = bpo.bpo_pbplPlane->bpl_iPlaneMajorAxis1; INDEX iMajorAxis2 = bpo.bpo_pbplPlane->bpl_iPlaneMajorAxis2; // if the point is not inside the bounding box of polygon (projected to the major plane) if (v(iMajorAxis1)<bpo.bpo_boxBoundingBox.Min()(iMajorAxis1) ||v(iMajorAxis1)>bpo.bpo_boxBoundingBox.Max()(iMajorAxis1) ||v(iMajorAxis2)<bpo.bpo_boxBoundingBox.Min()(iMajorAxis2) ||v(iMajorAxis2)>bpo.bpo_boxBoundingBox.Max()(iMajorAxis2) ) { // it is not inside the polygon return FALSE; } // create an intersector CIntersector isIntersector(v(iMajorAxis1), v(iMajorAxis2)); // for all edges in the polygon FOREACHINSTATICARRAY(bpo.bpo_abpePolygonEdges, CBrushPolygonEdge, itbpe) { // get edge vertices (edge direction is irrelevant here!) const FLOAT3D &vVertex0 = itbpe->bpe_pbedEdge->bed_pbvxVertex0->bvx_vAbsolute; const FLOAT3D &vVertex1 = itbpe->bpe_pbedEdge->bed_pbvxVertex1->bvx_vAbsolute; // pass the edge to the intersector isIntersector.AddEdge( vVertex0(iMajorAxis1), vVertex0(iMajorAxis2), vVertex1(iMajorAxis1), vVertex1(iMajorAxis2)); } // ask the intersector if the point is inside the polygon return isIntersector.IsIntersecting(); } ///////////////////////////////////////////////////////////////////// // CLayerMaker /* * Constructor. */ CLayerMaker::CLayerMaker(void) { } /* Spread the shadow towards pixels outside of polygon. */ void CLayerMaker::SpreadShadowMaskOutwards(void) { // for each mip map of the layer for(INDEX iMipmap=0; iMipmap<lm_mmtLayer.mmt_ctMipmaps; iMipmap++) { PIX pixLayerMinU = lm_pixLayerMinU>>iMipmap; PIX pixLayerMinV = lm_pixLayerMinV>>iMipmap; PIX pixLayerSizeU = lm_pixLayerSizeU>>iMipmap; PIX pixLayerSizeV = lm_pixLayerSizeV>>iMipmap; PIX pixSizeU = lm_pixSizeU>>iMipmap; PIX pixSizeV = lm_pixSizeV>>iMipmap; PIX pixSizeULog2 = FastLog2(lm_pixSizeU)-iMipmap; UBYTE *pubLayer = lm_pubLayer+lm_mmtLayer.mmt_aslOffsets[iMipmap]; UBYTE *pubPolygonMask = lm_pubPolygonMask+lm_mmtPolygonMask.mmt_aslOffsets[iMipmap]; SLONG slOffsetLayer = 0; // for each pixel in the layer shadow mask for (PIX pixLayerV=0; pixLayerV<pixLayerSizeV; pixLayerV++) { for (PIX pixLayerU=0; pixLayerU<pixLayerSizeU; pixLayerU++) { PIX pixMapU = pixLayerU+pixLayerMinU; PIX pixMapV = pixLayerV+pixLayerMinV; PIX slOffsetMap = pixMapU+(pixMapV<<pixSizeULog2); // if the pixel is not inside the polygon if (!pubPolygonMask[slOffsetMap]) { // find number of all of its neighbours that are in the polygon // and number of all of them that are not in the shadow INDEX ctInPolygon = 0; INDEX ctLighted = 0; #define ADDNEIGHBOUR(du, dv) \ if ((pixLayerU+(du)>=0) \ &&(pixLayerU+(du)<pixLayerSizeU) \ &&(pixLayerV+(dv)>=0) \ &&(pixLayerV+(dv)<pixLayerSizeV) \ &&(pubPolygonMask[slOffsetMap+(du)+((dv)<<pixSizeULog2)])) { \ ctInPolygon++; \ ctLighted+=pubLayer[slOffsetLayer+(du)+(dv)*pixLayerSizeU]&0x01; \ } ADDNEIGHBOUR(-2, -2); ADDNEIGHBOUR(-1, -2); ADDNEIGHBOUR(+0, -2); ADDNEIGHBOUR(+1, -2); ADDNEIGHBOUR(+2, -2); ADDNEIGHBOUR(-2, -1); ADDNEIGHBOUR(-1, -1); ADDNEIGHBOUR(+0, -1); ADDNEIGHBOUR(+1, -1); ADDNEIGHBOUR(+2, -1); ADDNEIGHBOUR(-2, +0); ADDNEIGHBOUR(-1, +0); // ADDNEIGHBOUR(+0, +0); ADDNEIGHBOUR(+1, +0); ADDNEIGHBOUR(+2, +0); ADDNEIGHBOUR(-2, +1); ADDNEIGHBOUR(-1, +1); ADDNEIGHBOUR(+0, +1); ADDNEIGHBOUR(+1, +1); ADDNEIGHBOUR(+2, +1); ADDNEIGHBOUR(-2, +2); ADDNEIGHBOUR(-1, +2); ADDNEIGHBOUR(+0, +2); ADDNEIGHBOUR(+1, +2); ADDNEIGHBOUR(+2, +2); // if there are some neighbours inside, and most of them are lighted if ((ctInPolygon>0) && (ctLighted>ctInPolygon/2)) { // make this one lighted pubLayer[slOffsetLayer] = 255; // otherwise } else { // make this one shadowed pubLayer[slOffsetLayer] = 0; } // if the pixel is inside the polygon } else { NOTHING; } slOffsetLayer++; } } } } /* Spread the shadow towards pixels inside of polygon. */ void CLayerMaker::SpreadShadowMaskInwards(void) { // for each mip map of the layer for(INDEX iMipmap=0; iMipmap<lm_mmtLayer.mmt_ctMipmaps; iMipmap++) { PIX pixLayerMinU = lm_pixLayerMinU>>iMipmap; PIX pixLayerMinV = lm_pixLayerMinV>>iMipmap; PIX pixLayerSizeU = lm_pixLayerSizeU>>iMipmap; PIX pixLayerSizeV = lm_pixLayerSizeV>>iMipmap; PIX pixSizeU = lm_pixSizeU>>iMipmap; PIX pixSizeV = lm_pixSizeV>>iMipmap; PIX pixSizeULog2 = FastLog2(lm_pixSizeU)-iMipmap; UBYTE *pubLayer = lm_pubLayer+lm_mmtLayer.mmt_aslOffsets[iMipmap]; UBYTE *pubPolygonMask = lm_pubPolygonMask+lm_mmtPolygonMask.mmt_aslOffsets[iMipmap]; SLONG slOffsetLayer = 0; // for each pixel in the layer shadow mask for (PIX pixLayerV=0; pixLayerV<pixLayerSizeV; pixLayerV++) { for (PIX pixLayerU=0; pixLayerU<pixLayerSizeU; pixLayerU++) { PIX pixMapU = pixLayerU+pixLayerMinU; PIX pixMapV = pixLayerV+pixLayerMinV; PIX slOffsetMap = pixMapU+(pixMapV<<pixSizeULog2); // if the pixel is inside the polygon if (pubPolygonMask[slOffsetMap]) { // find number of all of its neighbours that are out of the polygon // and number of all of them that are not in the shadow INDEX ctOutPolygon = 0; INDEX ctLighted = 0; #undef ADDNEIGHBOUR #define ADDNEIGHBOUR(du, dv) \ if ((pixLayerU+(du)>=0) \ &&(pixLayerU+(du)<pixLayerSizeU) \ &&(pixLayerV+(dv)>=0) \ &&(pixLayerV+(dv)<pixLayerSizeV)) \ {\ if(!pubPolygonMask[slOffsetMap+(du)+((dv)<<pixSizeULog2)]) { \ ctOutPolygon++; \ ctLighted+=pubLayer[slOffsetLayer+(du)+(dv)*pixLayerSizeU]&0x01; \ }\ } /* ADDNEIGHBOUR(-2, -2); ADDNEIGHBOUR(+2, -2); ADDNEIGHBOUR(-2, +2); ADDNEIGHBOUR(+2, +2); */ ADDNEIGHBOUR(-1, -2); ADDNEIGHBOUR(+0, -2); ADDNEIGHBOUR(+1, -2); ADDNEIGHBOUR(-2, -1); ADDNEIGHBOUR(+2, -1); ADDNEIGHBOUR(-2, +0); ADDNEIGHBOUR(+2, +0); ADDNEIGHBOUR(-2, +1); ADDNEIGHBOUR(+2, +1); ADDNEIGHBOUR(-1, +2); ADDNEIGHBOUR(+0, +2); ADDNEIGHBOUR(+1, +2); ADDNEIGHBOUR(-1, -1); ADDNEIGHBOUR(+0, -1); ADDNEIGHBOUR(+1, -1); ADDNEIGHBOUR(-1, +0); // ADDNEIGHBOUR(+0, +0); ADDNEIGHBOUR(+1, +0); ADDNEIGHBOUR(-1, +1); ADDNEIGHBOUR(+0, +1); ADDNEIGHBOUR(+1, +1); // if there are some neighbours outside if (ctOutPolygon>0) { // if some are not lighted if (ctLighted<ctOutPolygon) { // make this one shadowed pubLayer[slOffsetLayer] = 0; // otherwise } else { // make this one lighted // pubLayer[slOffsetLayer] = 255; } } // if the pixel is not inside the polygon } else { NOTHING; } slOffsetLayer++; } } } } // make bit-packed mask of where the polygon is in the shadow map void CLayerMaker::MakePolygonMask(void) { // allocate memory for the mask lm_pubPolygonMask = (UBYTE *)AllocMemory(lm_mmtPolygonMask.mmt_slTotalSize+8); // if there is packed polygon mask remembered in the shadow map if (lm_pbsmShadowMap->bsm_pubPolygonMask!=NULL) { // convert it from bit-packed into byte-packed mask ConvertBitsToBytes( lm_pbsmShadowMap->bsm_pubPolygonMask, lm_pubPolygonMask, lm_mmtPolygonMask.mmt_slTotalSize); } else { UBYTE *pub = lm_pubPolygonMask; // for each mip-map for (INDEX iMipmap=0; iMipmap<lm_mmtPolygonMask.mmt_ctMipmaps; iMipmap++) { UBYTE *pubForSaving = pub; // start at the first pixel FLOAT3D vRow = lm_vO+(lm_vStepU+lm_vStepV)*(FLOAT(1<<iMipmap)/2.0f); // for each pixel in the shadow map for (PIX pixV=0; pixV<lm_pixSizeV>>iMipmap; pixV++) { FLOAT3D vPoint = vRow; for (PIX pixU=0; pixU<lm_pixSizeU>>iMipmap; pixU++) { // if the point is in the polygon if (TestPointInPolygon(*lm_pbpoPolygon, vPoint)) { // set the pixel *pub = 255; // if the point is not in the polygon } else { // clear the pixel *pub = 0; } // go to the next pixel pub++; vPoint+=lm_vStepU*FLOAT(1<<iMipmap); } vRow+=lm_vStepV*FLOAT(1<<iMipmap); } } // make mip maps of the polygon mask // MakeMipmapsForMask(lm_pubPolygonMask, lm_pixSizeU, lm_pixSizeV, // lm_mmtPolygonMask.mmt_slTotalSize); // convert it from byte-packed into bit-packed mask lm_pbsmShadowMap->bsm_pubPolygonMask = (UBYTE *)AllocMemory((lm_mmtPolygonMask.mmt_slTotalSize+7)/8); ConvertBytesToBits( lm_pubPolygonMask, lm_pbsmShadowMap->bsm_pubPolygonMask, lm_mmtPolygonMask.mmt_slTotalSize); } } // flip shadow mask around V axis (for parallel lights) void CLayerMaker::FlipShadowMask(INDEX iMip) { PIX pixLayerMinU = lm_pixLayerMinU>>iMip; PIX pixLayerMinV = lm_pixLayerMinV>>iMip; PIX pixLayerSizeU = lm_pixLayerSizeU>>iMip; PIX pixLayerSizeV = lm_pixLayerSizeV>>iMip; UBYTE *pubLayer = lm_pubLayer+lm_mmtLayer.mmt_aslOffsets[iMip]; UBYTE *pubRow = pubLayer; // for each row for (PIX pixV=0; pixV<pixLayerSizeV; pixV++) { // flip the row UBYTE *pubLt = pubRow; UBYTE *pubRt = pubRow+pixLayerSizeU-1; for (PIX pixU=0; pixU<pixLayerSizeU/2; pixU++) { Swap(*pubLt, *pubRt); pubLt++; pubRt--; } pubRow+=pixLayerSizeU; } } // make shadow mask for the light ULONG CLayerMaker::MakeShadowMask(CBrushShadowLayer *pbsl) { // if the light doesn't cast shadows, or the polygon does not receive them if (!(pbsl->bsl_plsLightSource->ls_ulFlags&LSF_CASTSHADOWS) || (lm_pbpoPolygon->bpo_ulFlags&BPOF_DOESNOTRECEIVESHADOW) ) { // do nothing return BSLF_ALLLIGHT; } // remember current layer and its light source lm_plsLight = pbsl->bsl_plsLightSource; lm_pbslLayer = pbsl; lm_vLight = lm_plsLight->ls_penEntity->GetPlacement().pl_PositionVector; // find the influenced rectangle CLightRectangle lr; lm_pbsmShadowMap->FindLightRectangle(*lm_plsLight, lr); ASSERT(lr.lr_pixSizeU == lm_pbslLayer->bsl_pixSizeU); ASSERT(lr.lr_pixSizeV == lm_pbslLayer->bsl_pixSizeV); lm_fpixHotU = lr.lr_fpixHotU; lm_fpixHotV = lr.lr_fpixHotV; lm_fLightPlaneDistance = lr.lr_fLightPlaneDistance; lm_pixLayerMinU = lr.lr_pixMinU; lm_pixLayerMinV = lr.lr_pixMinV; lm_pixLayerSizeU= lr.lr_pixSizeU; lm_pixLayerSizeV= lr.lr_pixSizeV; // find mip-mapping information for the rectangle MakeMipmapTable(lm_pixLayerSizeU, lm_pixLayerSizeV, lm_mmtLayer); ASSERT(lr.lr_pixSizeU == lm_pbslLayer->bsl_pixSizeU); ASSERT(lr.lr_pixSizeV == lm_pbslLayer->bsl_pixSizeV); ASSERT(lr.lr_pixSizeU <= lm_pixSizeU); ASSERT(lr.lr_pixSizeV <= lm_pixSizeV); // if there is no influence, do nothing if ((lr.lr_pixSizeU==0) || (lr.lr_pixSizeV==0)) return BSLF_ALLDARK; lm_pbslLayer->bsl_pixMinU = lr.lr_pixMinU; lm_pbslLayer->bsl_pixMinV = lr.lr_pixMinV; lm_pbslLayer->bsl_pixSizeU = lr.lr_pixSizeU; lm_pbslLayer->bsl_pixSizeV = lr.lr_pixSizeV; lm_pbslLayer->bsl_slSizeInPixels = lm_mmtLayer.mmt_slTotalSize; // allocate shadow mask for the light (+8 is safety wall for fast conversions) lm_pubLayer = (UBYTE *)AllocMemory(lm_mmtLayer.mmt_slTotalSize+8); const FLOAT fEpsilon = (1<<lm_iMipLevel)/1024.0f; ULONG ulLighted=BSLF_ALLLIGHT|BSLF_ALLDARK; // if this polygon requires exact shadows if (lm_pbpoPolygon->bpo_ulFlags & BPOF_ACCURATESHADOWS) { // make each mip-map of mask for itself for(INDEX iMip=0; iMip<lm_mmtLayer.mmt_ctMipmaps; iMip++) { ulLighted&=MakeOneShadowMaskMip(iMip); } } else { // make first mip-map of mask ulLighted&=MakeOneShadowMaskMip(0); // make other shadow mask mips from the first one MakeMipmapsForMask( lm_pubLayer, lm_mmtLayer.mmt_pixU, lm_mmtLayer.mmt_pixV, lm_mmtLayer.mmt_slTotalSize); } // spread the shadow mask towards pixels outside of polygon if( !(lm_pbpoPolygon->bpo_ulFlags&BPOF_DARKCORNERS)) { SpreadShadowMaskOutwards(); } else { SpreadShadowMaskInwards(); SpreadShadowMaskOutwards(); } // convert the shadow mask from byte-packed into bit-packed mask ConvertBytesToBits(lm_pubLayer, lm_pubLayer, lm_mmtLayer.mmt_slTotalSize); ShrinkMemory((void **)&lm_pubLayer, (lm_mmtLayer.mmt_slTotalSize+7)/8); pbsl->bsl_pubLayer = lm_pubLayer; // update statistics _ctShadowLayers++; _ctShadowClusters+=lm_mmtLayer.mmt_slTotalSize; return ulLighted; } ULONG CLayerMaker::MakeOneShadowMaskMip(INDEX iMip) { ULONG ulLighted=BSLF_ALLLIGHT|BSLF_ALLDARK; PIX pixLayerMinU = lm_pixLayerMinU>>iMip; PIX pixLayerMinV = lm_pixLayerMinV>>iMip; PIX pixLayerSizeU = lm_pixLayerSizeU>>iMip; PIX pixLayerSizeV = lm_pixLayerSizeV>>iMip; INDEX iMipLevel = lm_iMipLevel+iMip; FLOAT3D vO = lm_vO+(lm_vStepU+lm_vStepV)*(FLOAT(1<<iMip)/2.0f); FLOAT3D vStepU = lm_vStepU*FLOAT(1<<iMip); FLOAT3D vStepV = lm_vStepV*FLOAT(1<<iMip); FLOAT fpixHotU = lm_fpixHotU/FLOAT(1<<iMip); FLOAT fpixHotV = lm_fpixHotV/FLOAT(1<<iMip); UBYTE *pubLayer = lm_pubLayer+lm_mmtLayer.mmt_aslOffsets[iMip]; // if the light is directional if (lm_plsLight->ls_ulFlags&LSF_DIRECTIONAL) { // prepare parallel projection as if viewing from polygon and the shadow map is screen CParallelProjection3D prProjection; prProjection.ScreenBBoxL() = FLOATaabbox2D( FLOAT2D(pixLayerMinU, pixLayerMinV), FLOAT2D(pixLayerMinU+pixLayerSizeU, pixLayerMinV+pixLayerSizeV) ); prProjection.AspectRatioL() = 1.0f; prProjection.NearClipDistanceL() = 0.00f; prProjection.pr_vZoomFactors(1) = prProjection.pr_vZoomFactors(2) = 1024.0f/(1<<iMipLevel); FLOAT3D vDirection; AnglesToDirectionVector( lm_plsLight->ls_penEntity->GetPlacement().pl_OrientationAngle, vDirection); // if polygon is turned away from the light if ((vDirection%(const FLOAT3D &)lm_pbpoPolygon->bpo_pbplPlane->bpl_plAbsolute)>-0.001) { // layer is all dark return BSLF_ALLDARK; } vDirection = vDirection*lm_mToInverseMapping; prProjection.pr_vStepFactors(1) = -vDirection(1)/vDirection(3); prProjection.pr_vStepFactors(2) = -vDirection(2)/vDirection(3); prProjection.pr_vStepFactors(1)*=prProjection.pr_vZoomFactors(1); prProjection.pr_vStepFactors(2)*=prProjection.pr_vZoomFactors(2); CPlacement3D plCenter; plCenter.pl_OrientationAngle = lm_aInverseMappingOrientation; plCenter.pl_PositionVector = vO +vStepU*(FLOAT(pixLayerSizeU)/2-0.5f) // !!!! +vStepV*(FLOAT(pixLayerSizeV)/2);//+5.0f); prProjection.ViewerPlacementL() = plCenter; // render the view to the shadow layer (but ignore the target polygon) CAnyProjection3D apr; apr = prProjection; ULONG ulFlagsBefore = lm_pbpoPolygon->bpo_ulFlags; lm_pbpoPolygon->bpo_ulFlags |= BPOF_INVISIBLE; ulLighted&=RenderShadows(*lm_pwoWorld, *(CEntity*)NULL, apr, lm_pbpoPolygon->bpo_boxBoundingBox, pubLayer, pixLayerSizeU, pixLayerSizeV, lm_plsLight->ls_ubPolygonalMask); lm_pbpoPolygon->bpo_ulFlags = ulFlagsBefore; // flip the shadow mask around v axis (left-right) FlipShadowMask(iMip); // if the light is point } else { // prepare perspective projection as if viewing from light and the shadow map is screen CPerspectiveProjection3D prProjection; if( !(lm_pbpoPolygon->bpo_ulFlags&BPOF_DARKCORNERS)) { prProjection.ScreenBBoxL() = FLOATaabbox2D( FLOAT2D(pixLayerMinU-fpixHotU+1, pixLayerMinV-fpixHotV+1), FLOAT2D(pixLayerMinU+pixLayerSizeU-fpixHotU+1, pixLayerMinV+pixLayerSizeV-fpixHotV+1) ); } else { prProjection.ScreenBBoxL() = FLOATaabbox2D( FLOAT2D(pixLayerMinU-fpixHotU, pixLayerMinV-fpixHotV), FLOAT2D(pixLayerMinU+pixLayerSizeU-fpixHotU, pixLayerMinV+pixLayerSizeV-fpixHotV) ); } prProjection.AspectRatioL() = 1.0f; prProjection.NearClipDistanceL() = lm_plsLight->ls_fNearClipDistance; prProjection.FarClipDistanceL() = lm_fLightPlaneDistance-lm_plsLight->ls_fFarClipDistance; //-fEpsilon/2; !!!! use minimal epsilon for polygon prProjection.ppr_fMetersPerPixel = (1<<iMipLevel)/1024.0f; prProjection.ppr_fViewerDistance = lm_fLightPlaneDistance; CPlacement3D plLight; plLight.pl_OrientationAngle = lm_aMappingOrientation; plLight.pl_PositionVector = lm_vLight; prProjection.ViewerPlacementL() = plLight; CAnyProjection3D apr; apr = prProjection; // ignore the target polygon during rendering ULONG ulFlagsBefore = lm_pbpoPolygon->bpo_ulFlags; lm_pbpoPolygon->bpo_ulFlags |= BPOF_INVISIBLE; // if light is not illumination light if (lm_plsLight->ls_ubPolygonalMask==0) { // just render starting at the light entity position ulLighted&=RenderShadows(*lm_pwoWorld, *lm_plsLight->ls_penEntity, apr, FLOATaabbox3D(), pubLayer, pixLayerSizeU, pixLayerSizeV, lm_plsLight->ls_ubPolygonalMask); // if light is illumination light } else { // add entire box around target polygon and light position to rendering FLOATaabbox3D box = lm_pbpoPolygon->bpo_boxBoundingBox; box|=lm_plsLight->ls_penEntity->GetPlacement().pl_PositionVector; ulLighted&=RenderShadows(*lm_pwoWorld, *(CEntity*)NULL, apr, box, pubLayer, pixLayerSizeU, pixLayerSizeV, lm_plsLight->ls_ubPolygonalMask); } lm_pbpoPolygon->bpo_ulFlags = ulFlagsBefore; } return ulLighted; } /* * Create a shadow map for a given polygon. */ BOOL CLayerMaker::CreateLayers(CBrushPolygon &bpo, CWorld &woWorld, BOOL bDoDirectionalLights) { // __pfWorldEditingProfile.IncrementAveragingCounter(); // __pfWorldEditingProfile.StartTimer(CWorldEditingProfile::PTI_MAKESHADOWS); BOOL bInitialized = FALSE; BOOL bCalculatedSome = FALSE; BOOL bSomeAreUncalculated = FALSE; // remember the world lm_pwoWorld = &woWorld; lm_pbsmShadowMap = &bpo.bpo_smShadowMap; lm_pbpoPolygon = &bpo; // for each layer that should be calculated, but isn't FORDELETELIST(CBrushShadowLayer, bsl_lnInShadowMap, lm_pbsmShadowMap->bsm_lhLayers, itbsl) { // if already calculated, or dynamic if (itbsl->bsl_ulFlags&BSLF_CALCULATED || itbsl->bsl_plsLightSource->ls_ulFlags&LSF_DYNAMIC) { // skip it continue; } // if we are not doing directional lights and it is directional if (!bDoDirectionalLights && (itbsl->bsl_plsLightSource->ls_ulFlags&LSF_DIRECTIONAL)) { // skip it bSomeAreUncalculated = TRUE; continue; } // if not yet initialized if( !bInitialized) { // remember general data CalculateData(); // make bit-packed mask of where the polygon is in the shadow map MakePolygonMask(); bInitialized = TRUE; } CBrushShadowLayer &bsl = *itbsl; // mark the layer is calculated bsl.bsl_ulFlags |= BSLF_CALCULATED; // make shadow mask for the light ULONG ulLighted=MakeShadowMask(itbsl); ASSERT((ulLighted==0) || (ulLighted==BSLF_ALLLIGHT) || (ulLighted==BSLF_ALLDARK)); bsl.bsl_ulFlags &= ~(BSLF_ALLLIGHT|BSLF_ALLDARK); bsl.bsl_ulFlags |= ulLighted; // if the layer is not needed if( ulLighted&(BSLF_ALLLIGHT|BSLF_ALLDARK)) { // free it if( bsl.bsl_pubLayer!=NULL) FreeMemory( bsl.bsl_pubLayer); bsl.bsl_pubLayer = NULL; } bCalculatedSome = TRUE; } // if was intialized if( bInitialized) { // free bit-packed polygon mask FreeMemory( lm_pubPolygonMask); } // if some new layers have been calculated if( bCalculatedSome) { // invalidate mixed layers bpo.bpo_smShadowMap.Invalidate(); } return bSomeAreUncalculated; } /* * Create shadow map for the polygon. */ void CBrushPolygon::MakeShadowMap(CWorld *pwoWorld, BOOL bDoDirectionalLights) { _pfWorldEditingProfile.StartTimer(CWorldEditingProfile::PTI_MAKESHADOWMAP); // create new shadow map CLayerMaker lmMaker; BOOL bSomeAreUncalculated = lmMaker.CreateLayers(*this, *pwoWorld, bDoDirectionalLights); // unqueue the shadow map if (!bSomeAreUncalculated && bpo_smShadowMap.bsm_lnInUncalculatedShadowMaps.IsLinked()) { bpo_smShadowMap.bsm_lnInUncalculatedShadowMaps.Remove(); } _pfWorldEditingProfile.StopTimer(CWorldEditingProfile::PTI_MAKESHADOWMAP); }