/* ** Command & Conquer Generals(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** 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, see . */ ;//////////////////////////////////////////////////////////////////////////////// ;// // ;// (c) 2001-2003 Electronic Arts Inc. // ;// // ;//////////////////////////////////////////////////////////////////////////////// // FILE: W3DTextureShadow.cpp /////////////////////////////////////////////////////////// // // Texture based shadow representation. // // Author: Mark Wilczynski, February 2002 // // /////////////////////////////////////////////////////////////////////////////// // USER INCLUDES ////////////////////////////////////////////////////////////// #include "always.h" #include "GameClient/View.h" #include "WW3D2/Camera.h" #include "WW3D2/Light.h" #include "WW3D2/DX8Wrapper.h" #include "WW3D2/HLod.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" #include "WW3D2/assetmgr.h" #include "WW3D2/texproject.h" #include "WW3D2/dx8renderer.h" #include "Lib/BaseType.h" #include "W3DDevice/GameClient/W3DGranny.h" #include "W3DDevice/GameClient/Heightmap.h" #include "D3dx8math.h" #include "common/GlobalData.h" #include "W3DDevice/GameClient/W3DProjectedShadow.h" #include "WW3D2/statistics.h" #include "Common/Debug.h" #include "GameLogic/Object.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/TerrainLogic.h" #include "GameClient/drawable.h" #include "W3DDevice/GameClient/Module/W3DModelDraw.h" #include "W3DDevice/GameClient/W3DShadow.h" #include "W3DDevice/GameClient/Heightmap.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif /** @todo: We're going to have a pool of a couple rendertargets to use in rare cases when dynamic shadows need to be generated. Maybe we can even get away with a single one that gets used immediatly to render, then recycled. For most of the objects, we need to have a static texture that is reused for all instances on the level. Need to add support for loading textures from disk instead of generating them in code. Could allow for a single non-distinct blob to be used for everything. Instead of projecting onto arbitrary geometry, could allow for terrain only. Maybe project onto a deformed terrain patch that molds to trays/bibs. */ #define DEFAULT_RENDER_TARGET_WIDTH 512 #define DEFAULT_RENDER_TARGET_HEIGHT 512 W3DProjectedShadowManager *TheW3DProjectedShadowManager=NULL; //global singleton ProjectedShadowManager *TheProjectedShadowManager; //global singleton with simpler interface. extern const FrustumClass *shadowCameraFrustum; //defined in W3DShadow. ///@todo: Externs from volumetric shadow renderer - these need to be moved into W3DBufferManager extern LPDIRECT3DVERTEXBUFFER8 shadowVertexBufferD3D; ///removeShadow(this);} ///freeAllTextures(); } // end Reset Bool W3DProjectedShadowManager::init( void ) { m_W3DShadowTextureManager = NEW W3DShadowTextureManager; m_shadowCamera = NEW_REF( CameraClass, () ); m_shadowContext= NEW SpecialRenderInfoClass(*m_shadowCamera,SpecialRenderInfoClass::RENDER_SHADOW); m_shadowContext->light_environment = &m_shadowLightEnv; return TRUE; } Bool W3DProjectedShadowManager::ReAcquireResources(void) { //grab assets which don't survive a device reset and need //to be present for duration of game. ///@todo: We should allocate our render target pool here. DEBUG_ASSERTCRASH(m_dynamicRenderTarget == NULL, ("Acquire of existing shadow render target")); m_renderTargetHasAlpha=TRUE; if ((m_dynamicRenderTarget=DX8Wrapper::Create_Render_Target (DEFAULT_RENDER_TARGET_WIDTH, DEFAULT_RENDER_TARGET_HEIGHT, true)) == NULL) { m_renderTargetHasAlpha=FALSE; //failed to get a render target with alpha. //try again without. m_dynamicRenderTarget=DX8Wrapper::Create_Render_Target (DEFAULT_RENDER_TARGET_WIDTH, DEFAULT_RENDER_TARGET_HEIGHT); } LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); DEBUG_ASSERTCRASH(m_pDev, ("Trying to ReAquireResources on W3DProjectedShadowManager without device")); DEBUG_ASSERTCRASH(shadowDecalIndexBufferD3D == NULL && shadowDecalIndexBufferD3D == NULL, ("ReAquireResources not released in W3DProjectedShadowManager")); if (FAILED(m_pDev->CreateIndexBuffer ( SHADOW_DECAL_INDEX_SIZE*sizeof(WORD), D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &shadowDecalIndexBufferD3D ))) return FALSE; if (shadowDecalVertexBufferD3D == NULL) { // Create vertex buffer if (FAILED(m_pDev->CreateVertexBuffer ( SHADOW_DECAL_VERTEX_SIZE*sizeof(SHADOW_DECAL_VERTEX), D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC, 0, D3DPOOL_DEFAULT, &shadowDecalVertexBufferD3D ))) return FALSE; } return TRUE; } void W3DProjectedShadowManager::ReleaseResources(void) { invalidateCachedLightPositions(); //textures need to be updated REF_PTR_RELEASE(m_dynamicRenderTarget); //need to create a new render target if (shadowDecalIndexBufferD3D) shadowDecalIndexBufferD3D->Release(); if (shadowDecalVertexBufferD3D) shadowDecalVertexBufferD3D->Release(); shadowDecalIndexBufferD3D=NULL; shadowDecalVertexBufferD3D=NULL; } void W3DProjectedShadowManager::invalidateCachedLightPositions(void) { m_W3DShadowTextureManager->invalidateCachedLightPositions(); } void W3DProjectedShadowManager::updateRenderTargetTextures(void) { ///@todo: Don't update texture for shadows that can't be seen!! W3DProjectedShadow *shadow; if (!m_shadowList) return; //there are no shadows to render. if (!TheGlobalData->m_useShadowDecals) return; if (m_numProjectionShadows) for( shadow = m_shadowList; shadow; shadow = shadow->m_next ) { //decals don't need any updates on a per-frame basis since //the image never changes. if (shadow->m_type != SHADOW_DECAL) shadow->update(); } } ///Renders shadow on part of terrain covered by world-space bounding box. Int W3DProjectedShadowManager::renderProjectedTerrainShadow(W3DProjectedShadow *shadow, AABoxClass &box) { static Matrix4 mWorld(true); //initialize to identity matrix struct SHADOW_VOLUME_VERTEX //vertex structure passed to D3D { float x,y,z; }; Int i,j,k; UnsignedByte alpha[4]; float UA[4], VA[4]; Bool flipForBlend; #define SHADOW_VOLUME_FVF D3DFVF_XYZ if (TheTerrainRenderObject) { WorldHeightMap *hmap=TheTerrainRenderObject->getMap(); //Find size of heightmap sub-rectangle affected by shadow Real cx=box.Center.X; Real cy=box.Center.Y; Real dx=box.Extent.X; Real dy=box.Extent.Y; Real mapScaleInv=1.0f/MAP_XY_FACTOR; SHADOW_VOLUME_VERTEX* pvVertices; UnsignedShort *pvIndices; LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); if (!m_pDev) return 0; //Get terrain cell index for area with shadow Int startX=REAL_TO_INT_FLOOR(((cx - dx)*mapScaleInv)); Int endX=REAL_TO_INT_CEIL(((cx + dx)*mapScaleInv)); Int startY=REAL_TO_INT_FLOOR(((cy - dy)*mapScaleInv)); Int endY=REAL_TO_INT_CEIL(((cy + dy)*mapScaleInv)); //clip bounds to extents of heightmap startX = __max(startX,0); endX = __min(endX,hmap->getXExtent()-1); startY = __max(startY,0); endY = __min(endY,hmap->getYExtent()-1); Int vertsPerRow=endX - startX+1; //number of cells +1 Int vertsPerColumn=endY-startY+1; //number of cells +1 if (vertsPerRow == 1 || vertsPerColumn == 1) return 0; //nothing to render Int numVerts = vertsPerRow *vertsPerColumn; //number of terrain vertices if (nShadowVertsInBuf > (SHADOW_VERTEX_SIZE-numVerts)) //check if room for model verts { //flush the buffer by drawing the contents and re-locking again if (shadowVertexBufferD3D->Lock(0,numVerts*sizeof(SHADOW_VOLUME_VERTEX),(unsigned char**)&pvVertices,D3DLOCK_DISCARD) != D3D_OK) return 0; nShadowVertsInBuf=0; nShadowStartBatchVertex=0; } else { if (shadowVertexBufferD3D->Lock(nShadowVertsInBuf*sizeof(SHADOW_VOLUME_VERTEX),numVerts*sizeof(SHADOW_VOLUME_VERTEX), (unsigned char**)&pvVertices,D3DLOCK_NOOVERWRITE) != D3D_OK) return 0; } if(pvVertices) { //insert each cell's bottom/left edge vertex for (j=startY; j <= endY; j++) { float ycoord = (float)j * MAP_XY_FACTOR; for (i=startX; i <= endX; i++) { pvVertices->x=(float)i*MAP_XY_FACTOR; pvVertices->y=ycoord; pvVertices->z=(float)hmap->getHeight(i,j)*MAP_HEIGHT_SCALE; pvVertices++; } } } shadowVertexBufferD3D->Unlock(); Int numIndex=(endX - startX) * (endY-startY)*6; //6 indices per terrain cell (2 triangles). if (nShadowIndicesInBuf > (SHADOW_INDEX_SIZE-numIndex)) //check if room for model verts { //flush the buffer by drawing the contents and re-locking again if (shadowIndexBufferD3D->Lock(0,numIndex*sizeof(short),(unsigned char**)&pvIndices,D3DLOCK_DISCARD) != D3D_OK) return 0; nShadowIndicesInBuf=0; nShadowStartBatchIndex=0; } else { if (shadowIndexBufferD3D->Lock(nShadowIndicesInBuf*sizeof(short),numIndex*sizeof(short), (unsigned char**)&pvIndices,D3DLOCK_NOOVERWRITE) != D3D_OK) return 0; } if(pvIndices) { //fill each cell's vertex indices Int rowStart; for (j=startY,rowStart=0; jgetAlphaUVData(k, j, UA, VA, alpha, &flipForBlend, false); /* if (flipForBlend) { pvIndices[0]=i; pvIndices[1]=i+1; pvIndices[2]=i+vertsPerRow; pvIndices[3]=i+vertsPerRow; pvIndices[4]=i+1; pvIndices[5]=i+vertsPerRow+1; } else { pvIndices[0]=i+vertsPerRow; pvIndices[4]=pvIndices[1]=i; pvIndices[3]=pvIndices[2]=i+vertsPerRow+1; pvIndices[5]=i+1; }*/ ///@todo: fix the winding order in heightmap to be in strip order like above! if (flipForBlend) { pvIndices[0]=i+1; pvIndices[1]=i+vertsPerRow; pvIndices[2]=i; pvIndices[3]=i+1; pvIndices[4]=i+1+vertsPerRow; pvIndices[5]=i+vertsPerRow; } else { pvIndices[0]=i; pvIndices[1]=i+1+vertsPerRow; pvIndices[2]=i+vertsPerRow; pvIndices[3]=i; pvIndices[4]=i+1; pvIndices[5]=i+1+vertsPerRow; } pvIndices += 6; } } } shadowIndexBufferD3D->Unlock(); m_pDev->SetIndices(shadowIndexBufferD3D,nShadowStartBatchVertex); m_pDev->SetTransform(D3DTS_WORLD,(_D3DMATRIX *)&mWorld); m_pDev->SetStreamSource(0,shadowVertexBufferD3D,sizeof(SHADOW_VOLUME_VERTEX)); m_pDev->SetVertexShader(SHADOW_VOLUME_FVF); Int numPolys = (endX - startX)*(endY - startY)*2; //2 triangles per cell m_pDev->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE); //should reject background pixels m_pDev->SetRenderState( D3DRS_STENCILENABLE, TRUE ); m_pDev->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_ALWAYS ); m_pDev->SetRenderState( D3DRS_STENCILREF, 0x1 ); m_pDev->SetRenderState( D3DRS_STENCILMASK, 0xffffffff ); m_pDev->SetRenderState( D3DRS_STENCILWRITEMASK,0xffffffff ); m_pDev->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); m_pDev->SetRenderState( D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); m_pDev->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_INCR ); // m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); //useful to see bounds m_pDev->SetRenderState( D3DRS_LIGHTING, FALSE); m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR); m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO ); if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) { Debug_Statistics::Record_DX8_Polys_And_Vertices(numPolys,numVerts,ShaderClass::_PresetOpaqueShader); m_pDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,numVerts,nShadowStartBatchIndex,numPolys); } m_pDev->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); //should reject background pixels m_pDev->SetRenderState( D3DRS_STENCILENABLE, FALSE ); // m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); m_pDev->SetRenderState( D3DRS_LIGHTING, TRUE); nShadowVertsInBuf += numVerts; nShadowStartBatchVertex=nShadowVertsInBuf; nShadowIndicesInBuf += numIndex; nShadowStartBatchIndex=nShadowIndicesInBuf; return 1; } return 0; } #if 0 TextureClass *snow=NULL; TextureClass *grass=NULL; TextureClass *ground=NULL; #define V_COUNT (4*4) //4 vertices per cell #define I_COUNT (4*6) //6 indices per cell #define TILE_HEIGHT 10.1f #define TILE_DIFFUSE 0x00b4b0a5 enum BlendDirection { B_A, //visible on all sides B_R, //visible on right B_L, //visible on left B_T, //visible on top B_B, //visble on bottom B_TL, //visible on top/left B_BR, //visible on bottom/right B_TR, //visible on top/right B_BL //visilbe on bottom/left }; //Vertex alpha values for each blend direction assuming tile vertices //start at top left corner and continue counter-clockwise DWORD BDToVA[9][4]= { {0xff000000,0xff000000,0xff000000,0xff000000}, {0,0,0xff000000,0xff000000}, {0xff000000,0xff000000,0,0}, {0xff000000,0,0,0xff000000}, {0,0xff000000,0xff000000,0}, {0xff000000,0,0,0}, {0,0,0xff000000,0}, {0,0,0,0xff000000}, {0,0xff000000,0,0} }; static void RenderVBTile(TextureClass *text, Real ox, Real oy, Real ou, Real ov, BlendDirection bd=B_A) { DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,DX8_FVF_XYZNDUV2,4); DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC_DX8,6); DynamicVBAccessClass::WriteLockClass lock(&vb_access); VertexFormatXYZNDUV2* vb= lock.Get_Formatted_Vertex_Array(); DynamicIBAccessClass::WriteLockClass lockib(&ib_access); if (!vb) return; UnsignedShort *ib=lockib.Get_Index_Array(); vb->x=ox; vb->y=oy; vb->z=TILE_HEIGHT; vb->diffuse=TILE_DIFFUSE|BDToVA[bd][0]; vb->u1=ou; vb->v1=ov; vb++; vb->x=ox; vb->y=oy-10.0f; vb->z=TILE_HEIGHT; vb->diffuse=TILE_DIFFUSE|BDToVA[bd][1]; vb->u1=ou; vb->v1=ov+0.25f; vb++; vb->x=ox+10.0f; vb->y=oy-10.0f; vb->z=TILE_HEIGHT; vb->diffuse=TILE_DIFFUSE|BDToVA[bd][2]; vb->u1=ou+0.25f; vb->v1=ov+0.25f; vb++; vb->x=ox+10.0f; vb->y=oy; vb->z=TILE_HEIGHT; vb->diffuse=TILE_DIFFUSE|BDToVA[bd][3]; vb->u1=ou+0.25f; vb->v1=ov; vb++; ib[0]=0; ib[1]=1; ib[2]=3; ib[3]=3; ib[4]=1; ib[5]=2; if (bd == B_TR || bd == B_BL) { //need to flip triangles so alpha gradient doesn't follow diagonal edge ib[2]=2; ib[4]=0; } DX8Wrapper::Set_Index_Buffer(ib_access,0); DX8Wrapper::Set_Vertex_Buffer(vb_access); DX8Wrapper::Set_Texture(0, text); DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, TRUE ); ShaderClass::Invalidate(); //invalidate to force shader to reset since we directly changed states DX8Wrapper::Draw_Triangles( 0,2, 0, 4); //draw a quad, 2 triangles, 4 verts } //Debug code used to draw some dummy polygons. void TestBlendRender(RenderInfoClass & rinfo) { static Int doInit=1; if (doInit) { doInit = 0; snow = WW3DAssetManager::Get_Instance()->Get_Texture("TXSnow04a.tga"); grass = WW3DAssetManager::Get_Instance()->Get_Texture("TMGras23a.tga"); ground = WW3DAssetManager::Get_Instance()->Get_Texture("TXAsph01a.tga"); } VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); DX8Wrapper::Set_Material(vmat); REF_PTR_RELEASE(vmat); DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); Matrix3D tm(1); //identity DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); //grass RenderVBTile(grass,580.0f,480.0f,0.0f,0.0f); RenderVBTile(grass,590.0f,480.0f,0.25f,0.0f); RenderVBTile(grass,580.0f,470.0f,0.0f,0.25f); RenderVBTile(grass,590.0f,470.0f,0.25f,0.25f); RenderVBTile(grass,580.0f,460.0f,0.0f,0.5f); RenderVBTile(grass,590.0f,460.0f,0.25f,0.5f,B_L); RenderVBTile(grass,580.0f,450.0f,0.0f,0.75f); RenderVBTile(grass,590.0f,450.0f,0.25f,0.75f,B_L); RenderVBTile(grass,580.0f,440.0f,0.0f,0.0f); RenderVBTile(grass,590.0f,440.0f,0.25f,0.0f,B_L); RenderVBTile(grass,610.0f,460.0f,0.0f,0.5f, B_B); RenderVBTile(grass,610.0f,450.0f,0.0f,0.75f); RenderVBTile(grass,610.0f,440.0f,0.0f,0.0f); //snow RenderVBTile(snow,590.0f,480.0f,0.0f,0.0f, B_R); RenderVBTile(snow,600.0f,480.0f,0.25f,0.0f); RenderVBTile(snow,610.0f,480.0f,0.5f,0.0f); RenderVBTile(snow,590.0f,470.0f,0.0f,0.25f, B_R); RenderVBTile(snow,600.0f,470.0f,0.25f,0.25f); RenderVBTile(snow,610.0f,470.0f,0.5f,0.25f); RenderVBTile(snow,590.0f,460.0f,0.0f,0.5f, B_TR); RenderVBTile(snow,600.0f,460.0f,0.25f,0.5f,B_T); RenderVBTile(snow,610.0f,460.0f,0.5f,0.5f,B_T); } #endif void W3DProjectedShadowManager::flushDecals(W3DShadowTexture *texture, ShadowType type) { static Matrix4 mWorld(true); //initialize to identity matrix if (nShadowDecalVertsInBatch == 0 && nShadowDecalPolysInBatch == 0) { //nothing to render return; } LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); if (!m_pDev) return; //no D3D Device to render VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); DX8Wrapper::Set_Material(vmat); REF_PTR_RELEASE(vmat); DX8Wrapper::Set_Texture(0,texture->getTexture()); // DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); //good for debugging, draws without alpha switch (type) { case SHADOW_DECAL: DX8Wrapper::Set_Shader(ShaderClass::_PresetMultiplicativeShader); break; case SHADOW_ALPHA_DECAL: DX8Wrapper::Set_Shader(ShaderClass::_PresetAlphaShader); break; case SHADOW_ADDITIVE_DECAL: DX8Wrapper::Set_Shader(ShaderClass::_PresetAdditiveShader); break; } // DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0x60); // DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_GREATEREQUAL); //_PresetAlphaSpriteShader DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices //Alpha Blended Shadows // m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); // m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); /* UnsignedInt color=TheW3DShadowManager->getShadowColor(); m_pDev->SetRenderState( D3DRS_TEXTUREFACTOR, 0xff000000 | color); m_pDev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); m_pDev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); m_pDev->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR); m_pDev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); m_pDev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); m_pDev->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); */ m_pDev->SetIndices(shadowDecalIndexBufferD3D,nShadowDecalStartBatchVertex); m_pDev->SetTransform(D3DTS_WORLD,(_D3DMATRIX *)&mWorld); m_pDev->SetStreamSource(0,shadowDecalVertexBufferD3D,sizeof(SHADOW_DECAL_VERTEX)); m_pDev->SetVertexShader(SHADOW_DECAL_FVF); //Hard Shadows using stencil /* m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ZERO); m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); m_pDev->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE); //should reject background pixels m_pDev->SetRenderState( D3DRS_STENCILENABLE, TRUE ); */ /* m_pDev->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_ALWAYS ); m_pDev->SetRenderState( D3DRS_STENCILREF, 0x1 ); m_pDev->SetRenderState( D3DRS_STENCILMASK, 0xffffffff ); m_pDev->SetRenderState( D3DRS_STENCILWRITEMASK,0xffffffff ); m_pDev->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); m_pDev->SetRenderState( D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); m_pDev->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_INCR ); */ //m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); //useful to see bounds if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) { Debug_Statistics::Record_DX8_Polys_And_Vertices(nShadowDecalPolysInBatch,nShadowDecalVertsInBatch,ShaderClass::_PresetOpaqueShader); m_pDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,nShadowDecalVertsInBatch,nShadowDecalStartBatchIndex,nShadowDecalPolysInBatch); } // m_pDev->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); //should reject background pixels // m_pDev->SetRenderState( D3DRS_STENCILENABLE, FALSE ); //m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); //Restore multiplicative sprite shader // m_pDev->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_SRCCOLOR); //restore W3D state // m_pDev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO); /* m_pDev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); m_pDev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); m_pDev->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_CURRENT); m_pDev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); m_pDev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); m_pDev->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_CURRENT); */ nShadowDecalStartBatchVertex=nShadowDecalVertsInBuf; nShadowDecalStartBatchIndex=nShadowDecalIndicesInBuf; nShadowDecalPolysInBatch=0; //reset number of polys in texture batch nShadowDecalVertsInBatch=0; } /* void testShadowDecal(void) { Shadow::ShadowTypeInfo decalInfo; decalInfo.allowUpdates = FALSE; //shadow image will never update decalInfo.allowWorldAlign = TRUE; //shadow image will wrap around world objects decalInfo.m_type = SHADOW_ALPHA_DECAL; strcpy(decalInfo.m_ShadowName,"exwave256"); decalInfo.m_sizeX = 1280.0f; decalInfo.m_sizeY = 1280.0f; decalInfo.m_offsetX = 0; decalInfo.m_offsetY = 0; Shadow *shadow=TheProjectedShadowManager->addDecal(&decalInfo); shadow->setPosition(600,600,600); shadow->setAngle(0.0f); shadow->setColor(0xffff0000); } */ #define BRIDGE_OFFSET_FACTOR 1.5f /**Decals have a low poly count so its better to render large numbers at once. This system will queue them up until the buffers fill up. It will then flush the buffer (draw decals) and be ready for new decals. This is an optimized system that only uses the render objects bounding box to determine shadow visibility. */ void W3DProjectedShadowManager::queueDecal(W3DProjectedShadow *shadow) { int i,j,k; Vector3 hmapVertex,objPos; AABoxClass box; Matrix3D objXform(1); Real cx,cy,dx,dy; Real mapScaleInv=1.0f/MAP_XY_FACTOR; static Vector3 objCenter(0,0,0); Vector3 uVector,vVector; Real uOffset,vOffset,vecLength; Int borderSize; RenderObjClass *robj=shadow->m_robj; Real layerHeight=0; if (TheTerrainRenderObject) { LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); if (!m_pDev) return; //no D3D Device to render WorldHeightMap *hmap=TheTerrainRenderObject->getMap(); borderSize=hmap->getBorderSize(); if (robj) { objPos=robj->Get_Position(); objXform=robj->Get_Transform(); if (robj->Get_User_Data()) { Drawable *draw=((DrawableInfo *)robj->Get_User_Data())->m_drawable; const Object *object=draw->getObject(); PathfindLayerEnum objectLayer; if (object && (objectLayer=object->getLayer()) != LAYER_GROUND) { //check if object that this decal belongs to is not on the ground (bridge?) layerHeight=BRIDGE_OFFSET_FACTOR+TheTerrainLogic->getLayerHeight(objPos.X,objPos.Y,objectLayer); } } } else { //no render object so use shadow's local position and default orientation objPos.Set(shadow->m_x,shadow->m_y,shadow->m_z); objXform.Rotate_Z(shadow->m_localAngle); } //Find size of heightmap sub-rectangle affected by shadow //If user supplied size values, ignore bounding box objPos.Z=0.0f; //we don't care about object height since shadows project top-down uVector=objXform.Get_X_Vector(); uVector.Z=0.0f; vecLength=uVector.Length(); if (vecLength != 0.0f) //prevent divide by zero { uVector *= 1.0f/vecLength; vVector = uVector; vVector.Rotate_Z(-1.0f,0.0f); //rotate u vector by -90 degress to get v vector. } else { vVector=objXform.Get_Y_Vector(); vVector.Z=0.0f; vecLength=vVector.Length(); if (vecLength != 0.0f) //prevent divide by zero vVector *= 1.0f/vecLength; else vVector.Set(0.0f,-1.0f,0.0f); //Point uvector in default direction uVector = vVector; uVector.Rotate_Z(1.0f,0.0f); //rotate v vector by 90 degress to get u vector. } //Compute bounding box of projection Vector3 boxCorners[4]; //top-left, top-right, bottom-right, bottom-left dx = shadow->m_decalSizeX; dy = shadow->m_decalSizeY; Vector3 left_x=-dx * (uVector * (0.5f + shadow->m_decalOffsetU)); Vector3 right_x = dx * (uVector * (0.5f - shadow->m_decalOffsetU)); Vector3 top_y = -dy * (vVector * (0.5f + shadow->m_decalOffsetV)); Vector3 bottom_y = dy * (vVector * (0.5f - shadow->m_decalOffsetV)); ///@todo: Optimize this bounding box calculation to use transformed extents //Also skip bounding box calculation if object has not moved. boxCorners[0] = left_x + top_y; boxCorners[1] = right_x + top_y; boxCorners[2] = right_x + bottom_y; boxCorners[3] = left_x + bottom_y; Real min_x,max_x,min_y,max_y; max_x=min_x=boxCorners[0].X; max_y=min_y=boxCorners[0].Y; for (Int bi=1; bi<4; bi++) { max_x = __max(max_x,boxCorners[bi].X); min_x = __min(min_x,boxCorners[bi].X); max_y = __max(max_y,boxCorners[bi].Y); min_y = __min(min_y,boxCorners[bi].Y); } uVector *= shadow->m_oowDecalSizeX; vVector *= shadow->m_oowDecalSizeY; uOffset = shadow->m_decalOffsetU + 0.5f; vOffset = shadow->m_decalOffsetV + 0.5f; /* { //This version will stretch to fit orientation of object ///@todo: Most of the values below can be cached in shadow object shadow->m_robj->Get_Obj_Space_Bounding_Box(box); decalSizeX=box.Extent.X*2.0f; //use local space bounding box to determine shadow size decalSizeY=box.Extent.Y*2.0f; // box=shadow->m_robj->Get_Bounding_Box(); //get world-space bounding box objPos = box.Center; box.Init(objCenter,Vector3(decalSizeX*0.5f,decalSizeY*0.5f,1.0f)); box.Transform(objXform); //transform box from object space to world space box.Translate(objPos=objXform.Rotate_Vector(objPos)); objPos += shadow->m_robj->Get_Position(); } */ // Experimental code to try and get a better fitting bounding box around shadow /* Experimental code to try and get a better fitting bounding box around shadow { //use the object's bounding box to determine shadow extent ///@todo: Most of the values below can be cached in shadow object uVector=objXform.Get_X_Vector(); uVector.Z=0; uVector.Normalize(); vVector=objXform.Get_Y_Vector(); //invert direction since v axis runs right relative to u. vVector.Z=0; vVector.Normalize(); shadow->m_robj->Get_Obj_Space_Bounding_Box(box); decalSizeX = box.Extent.X * 2.0f; decalSizeY = box.Extent.Y * 2.0f; Real newExtentX = fabs(uVector * box.Extent) + fabs(uVector * box.Center); //get new extent for object orientation Real newExtentY = fabs(vVector * box.Extent) + fabs(vVector * box.Center); //get new extent for object orientation objPos += uVector * box.Center.X; objPos += vVector * box.Center.Y; // objPos.Y = objPos.Y + vVector * box.Center; objPos.Z = 0.0f; //set new oriented bounding box extents box.Extent.Set(newExtentX, newExtentY, 0); box.Center.Set(objPos.X,objPos.Y,objPos.Z); } */ cx=box.Center.X; cy=box.Center.Y; dx=box.Extent.X; dy=box.Extent.Y; //Get terrain cell index for area with shadow Int startX=REAL_TO_INT_FLOOR(((objPos.X+min_x)*mapScaleInv)) + borderSize; Int endX=REAL_TO_INT_CEIL(((objPos.X+max_x)*mapScaleInv)) + borderSize; Int startY=REAL_TO_INT_FLOOR(((objPos.Y+min_y)*mapScaleInv)) + borderSize; Int endY=REAL_TO_INT_CEIL(((objPos.Y+max_y)*mapScaleInv)) + borderSize; startX = __max(startX,drawStartX); startX = __min(startX,drawEdgeX); startY = __max(startY,drawStartY); startY = __min(startY,drawEdgeY); endX = __max(endX,drawStartX); endX = __min(endX,drawEdgeX); endY = __max(endY,drawStartY); endY = __min(endY,drawEdgeY); //Check if decal too large to fit inside 65536 index buffer. //try clipping each direction to < 104 since that's more than //enough to cover typical map. Int numExtraX=(endX - startX+1)-104; if (numExtraX > 0) { //figure out how much to clip out at each edge of decal Int numStartExtraX=REAL_TO_INT_FLOOR((float)numExtraX/2.0f); Int numEdgeExtraX=numExtraX-numStartExtraX; startX+=numStartExtraX; endX-=numEdgeExtraX; } Int numExtraY=(endY - startY+1)-104; if (numExtraY > 0) { Int numStartExtraY=REAL_TO_INT_FLOOR((float)numExtraY/2.0f); Int numEdgeExtraY=numExtraY-numStartExtraY; startY+=numStartExtraY; endY-=numEdgeExtraY; } Int vertsPerRow=endX - startX+1; //number of cells +1 Int vertsPerColumn=endY-startY+1; //number of cells +1 if (vertsPerRow <= 1 || vertsPerColumn <= 1) return; //nothing to render Int numVerts = vertsPerRow *vertsPerColumn; //number of terrain vertices Int numIndex=(endX - startX) * (endY-startY)*6; //6 indices per terrain cell (2 triangles). SHADOW_DECAL_VERTEX* pvVertices; UnsignedShort *pvIndices; if (nShadowDecalVertsInBuf > (SHADOW_DECAL_VERTEX_SIZE-numVerts)) //check if room for model verts { //flush the buffer by drawing the contents and re-locking again flushDecals(shadow->m_shadowTexture[0], shadow->m_type); if (shadowDecalVertexBufferD3D->Lock(0,numVerts*sizeof(SHADOW_DECAL_VERTEX),(unsigned char**)&pvVertices,D3DLOCK_DISCARD) != D3D_OK) return; nShadowDecalStartBatchVertex=0; nShadowDecalPolysInBatch=0; //reset number of polys in texture batch nShadowDecalVertsInBatch=0; nShadowDecalVertsInBuf=0; } else { if (shadowDecalVertexBufferD3D->Lock(nShadowDecalVertsInBuf*sizeof(SHADOW_DECAL_VERTEX),numVerts*sizeof(SHADOW_DECAL_VERTEX), (unsigned char**)&pvVertices,D3DLOCK_NOOVERWRITE) != D3D_OK) return; } //code to deal with rotated shadows based on sun direction, fix this later. For now shadow rotates with object rotation. //shadow->m_shadowTexture[0]->getDecalUVAxis(&uVector,&vVector); //uVector *= 20.0f/dx;//shadow->m_decalRadius; //scale texture to fit object //vVector *= 20.0f/dy;//1.2f;//shadow->m_decalRadius; //scale texture to fit object /* uVector=objXform.Get_X_Vector(); uVector.Normalize(); uVector /= decalSizeX + (1.0f+4.0f/64.0f); //texture has 1 pixel transparent border so we strtech up to make sure solid pixels reach extent.. vVector=objXform.Get_Y_Vector() * -1.0f; //invert direction since v axis runs right relative to u. vVector.Normalize(); vVector /= decalSizeY + (1.0f+4.0f/64.0f); */ DEBUG_ASSERTCRASH(numVerts == ((endY-startY+1)*(endX-startX+1)), ("queueDecal VB size mismatch")); if(pvVertices) { if (layerHeight) for (j=startY; j <= endY; j++) { hmapVertex.Y=(float)(j-borderSize) * MAP_XY_FACTOR; for (i=startX; i <= endX; i++) { hmapVertex.X=(float)(i-borderSize)*MAP_XY_FACTOR; hmapVertex.Z=__max((float)hmap->getHeight(i,j)*MAP_HEIGHT_SCALE,layerHeight); pvVertices->x=hmapVertex.X; pvVertices->y=hmapVertex.Y; pvVertices->z=hmapVertex.Z; pvVertices->diffuse=shadow->m_diffuse; pvVertices->u=Vector3::Dot_Product(uVector, (hmapVertex-objPos))+uOffset; pvVertices->v=Vector3::Dot_Product(vVector, (hmapVertex-objPos))+vOffset; pvVertices++; } } else //insert each cell's bottom/left edge vertex for (j=startY; j <= endY; j++) { hmapVertex.Y=(float)(j-borderSize) * MAP_XY_FACTOR; for (i=startX; i <= endX; i++) { hmapVertex.X=(float)(i-borderSize)*MAP_XY_FACTOR; hmapVertex.Z=(float)hmap->getHeight(i,j)*MAP_HEIGHT_SCALE+0.01f * MAP_XY_FACTOR; pvVertices->x=hmapVertex.X; pvVertices->y=hmapVertex.Y; pvVertices->z=hmapVertex.Z; pvVertices->diffuse=shadow->m_diffuse; pvVertices->u=Vector3::Dot_Product(uVector, (hmapVertex-objPos))+uOffset; pvVertices->v=Vector3::Dot_Product(vVector, (hmapVertex-objPos))+vOffset; pvVertices++; } } } shadowDecalVertexBufferD3D->Unlock(); if (nShadowDecalIndicesInBuf > (SHADOW_DECAL_INDEX_SIZE-numIndex)) //check if room for model verts { //flush the buffer by drawing the contents and re-locking again flushDecals(shadow->m_shadowTexture[0], shadow->m_type); if (shadowDecalIndexBufferD3D->Lock(0,numIndex*sizeof(short),(unsigned char**)&pvIndices,D3DLOCK_DISCARD) != D3D_OK) return; nShadowDecalStartBatchIndex=0; nShadowDecalPolysInBatch=0; //reset number of polys in texture batch nShadowDecalVertsInBatch=0; nShadowDecalIndicesInBuf=0; } else { if (shadowDecalIndexBufferD3D->Lock(nShadowDecalIndicesInBuf*sizeof(short),numIndex*sizeof(short), (unsigned char**)&pvIndices,D3DLOCK_NOOVERWRITE) != D3D_OK) return; } if(pvIndices) { //fill each cell's vertex indices Int rowStart; for (j=startY,rowStart=0; jgetFlipState(k,j)) { pvIndices[0]=i+1+nShadowDecalVertsInBatch; pvIndices[1]=i+vertsPerRow+nShadowDecalVertsInBatch; pvIndices[2]=i+nShadowDecalVertsInBatch; pvIndices[3]=i+1+nShadowDecalVertsInBatch; pvIndices[4]=i+1+vertsPerRow+nShadowDecalVertsInBatch; pvIndices[5]=i+vertsPerRow+nShadowDecalVertsInBatch; } else { pvIndices[0]=i+nShadowDecalVertsInBatch; pvIndices[1]=i+1+vertsPerRow+nShadowDecalVertsInBatch; pvIndices[2]=i+vertsPerRow+nShadowDecalVertsInBatch; pvIndices[3]=i+nShadowDecalVertsInBatch; pvIndices[4]=i+1+nShadowDecalVertsInBatch; pvIndices[5]=i+1+vertsPerRow+nShadowDecalVertsInBatch; } pvIndices += 6; } } } shadowDecalIndexBufferD3D->Unlock(); Int numPolys = (endX - startX)*(endY - startY)*2; //2 triangles per cell nShadowDecalPolysInBatch += numPolys; nShadowDecalVertsInBuf += numVerts; nShadowDecalVertsInBatch += numVerts; // nShadowDecalStartBatchVertex=nShadowDecalVertsInBuf; nShadowDecalIndicesInBuf += numIndex; // nShadowDecalStartBatchIndex=nShadowDecalIndicesInBuf; return; } } /**Simpler/faster decal system that always uses 2 triangles that are roughly oriented to terrain. Since they are not projected onto terrain, there may be clipping artifacts in certain situations. TODO: Too much clipping. Need to check terrain heights at all 4 corners and adjust tilt to match*/ ///@todo: We should have a pre-made static filled index buffer since we always send down 2 triangles. void W3DProjectedShadowManager::queueSimpleDecal(W3DProjectedShadow *shadow) { Vector3 objPos; Matrix3D objXform; Vector3 uVector,vVector; Coord3D normal; if (TheTerrainRenderObject) { LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); if (!m_pDev) return; //no D3D Device to render objPos=shadow->m_robj->Get_Position(); objXform=shadow->m_robj->Get_Transform(); Real groundHeight=TheTerrainRenderObject->getHeightMapHeight(objPos.X, objPos.Y, &normal); Vector3 groundNormal(normal.x,normal.y,normal.z); //Find new tu vector parallel to terrain by projecting existing x_vector onto //terrain normal and subtracting the result. uVector=objXform.Get_X_Vector(); Real uVectorAlongNormal = Vector3::Dot_Product(uVector,groundNormal); uVector -= uVectorAlongNormal * groundNormal; uVector.Normalize(); //Find new tv vector parallel to terrain by crossing new tu vector with terrain normal. Vector3::Cross_Product(uVector,groundNormal,&vVector); Int numVerts = 4; //number of decal vertices Int numIndex=6; //(2 triangles). SHADOW_DECAL_VERTEX* pvVertices; UnsignedShort *pvIndices; if (nShadowDecalVertsInBuf > (SHADOW_DECAL_VERTEX_SIZE-numVerts)) //check if room for model verts { //flush the buffer by drawing the contents and re-locking again flushDecals(shadow->m_shadowTexture[0], shadow->m_type); if (shadowDecalVertexBufferD3D->Lock(0,numVerts*sizeof(SHADOW_DECAL_VERTEX),(unsigned char**)&pvVertices,D3DLOCK_DISCARD) != D3D_OK) return; nShadowDecalStartBatchVertex=0; nShadowDecalPolysInBatch=0; //reset number of polys in texture batch nShadowDecalVertsInBatch=0; nShadowDecalVertsInBuf=0; } else { if (shadowDecalVertexBufferD3D->Lock(nShadowDecalVertsInBuf*sizeof(SHADOW_DECAL_VERTEX),numVerts*sizeof(SHADOW_DECAL_VERTEX), (unsigned char**)&pvVertices,D3DLOCK_NOOVERWRITE) != D3D_OK) return; } objPos.Z=groundHeight; //force decal to ground level objPos += groundNormal * 1.0f; //offset decal slightly above terrain to reduce z-fighting. Vector3 vertex; if(pvVertices) { //Top-left vertex = objPos + vVector * shadow->m_decalSizeY * -0.5f - uVector * shadow->m_decalSizeX * 0.5f; pvVertices->x=vertex.X; pvVertices->y=vertex.Y; pvVertices->z=vertex.Z; pvVertices->u=0.0f; pvVertices->v=0.0f; pvVertices++; //Bottom-left vertex += vVector * shadow->m_decalSizeY; pvVertices->x=vertex.X; pvVertices->y=vertex.Y; pvVertices->z=vertex.Z; pvVertices->u=0.0f; pvVertices->v=1.0f; pvVertices++; //Bottom-right vertex += uVector * shadow->m_decalSizeX; pvVertices->x=vertex.X; pvVertices->y=vertex.Y; pvVertices->z=vertex.Z; pvVertices->u=1.0f; pvVertices->v=1.0f; pvVertices++; //Top-right vertex -= vVector * shadow->m_decalSizeY; pvVertices->x=vertex.X; pvVertices->y=vertex.Y; pvVertices->z=vertex.Z; pvVertices->u=1.0f; pvVertices->v=0.0f; pvVertices++; } shadowDecalVertexBufferD3D->Unlock(); if (nShadowDecalIndicesInBuf > (SHADOW_DECAL_INDEX_SIZE-numIndex)) //check if room for model verts { //flush the buffer by drawing the contents and re-locking again flushDecals(shadow->m_shadowTexture[0],shadow->m_type); if (shadowDecalIndexBufferD3D->Lock(0,numIndex*sizeof(short),(unsigned char**)&pvIndices,D3DLOCK_DISCARD) != D3D_OK) return; nShadowDecalStartBatchIndex=0; nShadowDecalPolysInBatch=0; //reset number of polys in texture batch nShadowDecalVertsInBatch=0; nShadowDecalIndicesInBuf=0; } else { if (shadowDecalIndexBufferD3D->Lock(nShadowDecalIndicesInBuf*sizeof(short),numIndex*sizeof(short), (unsigned char**)&pvIndices,D3DLOCK_NOOVERWRITE) != D3D_OK) return; } if(pvIndices) { pvIndices[0]=nShadowDecalVertsInBatch; pvIndices[1]=nShadowDecalVertsInBatch+1; pvIndices[2]=nShadowDecalVertsInBatch+2; pvIndices[3]=nShadowDecalVertsInBatch; pvIndices[4]=nShadowDecalVertsInBatch+2; pvIndices[5]=nShadowDecalVertsInBatch+3; pvIndices += 6; } shadowDecalIndexBufferD3D->Unlock(); Int numPolys = 2; //2 triangles per decal nShadowDecalPolysInBatch += numPolys; nShadowDecalVertsInBuf += numVerts; nShadowDecalVertsInBatch += numVerts; nShadowDecalIndicesInBuf += numIndex; return; } } Int W3DProjectedShadowManager::renderShadows(RenderInfoClass & rinfo) { ///@todo: implement this method. W3DProjectedShadow *shadow; static AABoxClass aaBox; static SphereClass sphere; Int projectionCount=0; if (!m_shadowList && !m_decalList) return projectionCount; //there are no shadows to render. //Find extents of visible terrain if (TheTerrainRenderObject) { WorldHeightMap *hmap=TheTerrainRenderObject->getMap(); drawEdgeY=hmap->getDrawOrgY()+hmap->getDrawHeight()-1; drawEdgeX=hmap->getDrawOrgX()+hmap->getDrawWidth()-1; if (drawEdgeX > (hmap->getXExtent()-1)) drawEdgeX = hmap->getXExtent()-1; if (drawEdgeY > (hmap->getYExtent()-1)) drawEdgeY = hmap->getYExtent()-1; drawStartX=hmap->getDrawOrgX(); drawStartY=hmap->getDrawOrgY(); } else return projectionCount; //According to Nvidia there's a D3D bug that happens if you don't start with a //new dynamic VB each frame - so we force a DISCARD by overflowing the counter. nShadowDecalVertsInBuf = 0xffff; nShadowDecalIndicesInBuf = 0xffff; if (TheGlobalData->m_useShadowDecals) { // Render the object TheDX8MeshRenderer.Set_Camera(&rinfo.Camera); //keep track of active decal texture so we can render all decals at once. W3DShadowTexture *lastShadowDecalTexture=NULL; ShadowType lastShadowType = SHADOW_NONE; for( shadow = m_shadowList; shadow; shadow = shadow->m_next ) { if (shadow->m_isEnabled && !shadow->m_isInvisibleEnabled) { if (shadow->m_type & SHADOW_DECAL) { if (lastShadowDecalTexture == NULL) lastShadowDecalTexture=m_shadowList->m_shadowTexture[0]; if (lastShadowType == SHADOW_NONE) lastShadowType = m_shadowList->m_type; if (shadow->m_shadowTexture[0] != lastShadowDecalTexture || shadow->m_type != lastShadowType) { flushDecals(lastShadowDecalTexture,lastShadowType); //switched to a new texture, need to render polys using last texture. lastShadowDecalTexture=shadow->m_shadowTexture[0]; lastShadowType=shadow->m_type; } ///@todo: may need to fix this if shadows are large enough to be seen while object is not visible if (shadow->m_robj->Is_Really_Visible()) { //queueSimpleDecal(shadow); queueDecal(shadow); //only draw shadow if casting object is visible projectionCount++; } continue; } //First test if shadow is visible on screen sphere=shadow->m_shadowTexture[0]->getBoundingSphere(); sphere.Center += shadow->m_robj->Get_Position(); CollisionMath::OverlapType result=CollisionMath::Overlap_Test(*shadowCameraFrustum,sphere); if (result == CollisionMath::OVERLAPPED) { //do a more accurate test against bounding box. aaBox=shadow->m_shadowTexture[0]->getBoundingBox(); aaBox.Translate(shadow->m_robj->Get_Position()); //translate bounding box to world space. if (CollisionMath::Overlap_Test(*shadowCameraFrustum,aaBox) == CollisionMath::OUTSIDE) continue; } else if (result == CollisionMath::OUTSIDE) continue; //Shadow is visible on screen. Figure out which visible objects it may affect. //Check if bounding sphere was inside so bounding box never initialized if (result == CollisionMath::INSIDE) { aaBox=shadow->m_shadowTexture[0]->getBoundingBox(); aaBox.Translate(shadow->m_robj->Get_Position()); //translate bounding box to world space. } if (shadow->m_type == SHADOW_PROJECTION) { //build inverse camera/view transforms needed for projection shadow->updateProjectionParameters(rinfo.Camera.Get_Transform()); TexProjectClass *projector=shadow->getShadowProjector(); //terrain is always visible and affected by all shadows so must render projector->Peek_Material_Pass()->Install_Materials(); DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices if (renderProjectedTerrainShadow(shadow, aaBox)) projectionCount++; projector->Peek_Material_Pass()->UnInstall_Materials(); SimpleObjectIterator *iter; Object *obj; iter = ThePartitionManager->iterateObjectsInRange((const Coord3D*)&sphere.Center,sphere.Radius, FROM_CENTER_3D); MemoryPoolObjectHolder hold( iter ); AABoxIntersectionTestClass boxtest(aaBox,COLLISION_TYPE_ALL); for( obj = iter->first(); obj; obj = iter->next() ) { Drawable *draw = obj->getDrawable(); for (DrawModule ** dm = draw->getDrawModules(); *dm; ++dm) { const ObjectDrawInterface* di = (*dm)->getObjectDrawInterface(); if (di) { W3DModelDraw *w3dDraw= (W3DModelDraw *)di; RenderObjClass *robj=NULL; ///@todo: don't apply shadows to translcuent objects unless they are MOBILE - hack to get tanks to work. if ((robj=w3dDraw->getRenderObject()) != 0 && (!robj->Is_Alpha() || !obj->isKindOf(KINDOF_IMMOBILE)) && robj != shadow->m_robj && robj->Is_Really_Visible()) { //do a more accurate test against W3D render bounding boxes. if (robj->Intersect_AABox(boxtest)) { //Shadow reached a visible object so it needs to be rendered with shadow applied. rinfo.Push_Material_Pass(projector->Peek_Material_Pass()); rinfo.Push_Override_Flags(RenderInfoClass::RINFO_OVERRIDE_ADDITIONAL_PASSES_ONLY); robj->Render(rinfo); //WW3D::Render(*robj,rinfo); rinfo.Pop_Override_Flags(); rinfo.Pop_Material_Pass(); projectionCount++; //keep track of number of shadow projections } }//robj }//di } // end for drawmodule } // end for obj } }//shadow is enabled } flushDecals(lastShadowDecalTexture,lastShadowType); //make sure there are not any unrendered decals left over. TheDX8MeshRenderer.Flush(); //draw all the shadow receiving objects }//rendering shadows if (m_decalList) { //keep track of active decal texture so we can render all decals at once. W3DShadowTexture *lastShadowDecalTexture=NULL; ShadowType lastShadowType = SHADOW_NONE; for( shadow = m_decalList; shadow; shadow = shadow->m_next ) { if (shadow->m_isEnabled && !shadow->m_isInvisibleEnabled) { if (lastShadowDecalTexture == NULL) lastShadowDecalTexture=m_decalList->m_shadowTexture[0]; if (lastShadowType == SHADOW_NONE) lastShadowType = m_decalList->m_type; if (shadow->m_shadowTexture[0] != lastShadowDecalTexture || shadow->m_type != lastShadowType) { flushDecals(lastShadowDecalTexture,lastShadowType); //switched to a new texture, need to render polys using last texture. lastShadowDecalTexture=shadow->m_shadowTexture[0]; lastShadowType=shadow->m_type; } ///@todo: may need to fix this if shadows are large enough to be seen while object is not visible if (!(shadow->m_robj && !shadow->m_robj->Is_Really_Visible())) { //queueSimpleDecal(shadow); queueDecal(shadow); //only draw shadow if casting object is visible projectionCount++; } }//shadow is enabled } flushDecals(lastShadowDecalTexture,lastShadowType); //make sure there are not any unrendered decals left over. } return projectionCount; } /** Generic function which can be used to create arbitrary decals that don't have to be used for shadows. Some examples: Scorch marks, blood, stains, selection/status indicators, etc.*/ Shadow* W3DProjectedShadowManager::addDecal(Shadow::ShadowTypeInfo *shadowInfo) { W3DShadowTexture *st=NULL; ShadowType shadowType=SHADOW_NONE; /// type of projection Bool allowWorldAlign=FALSE; /// wrap shadow around world geometry - else align perpendicular to local z-axis. Real decalSizeX=0.0f; Real decalSizeY=0.0f; Bool allowSunDirection=FALSE; Char texture_name[64]; Int nameLen; if (!shadowInfo) return NULL; //right now we require hardware render-to-texture support //simple decal using the premade texture specified. //can be always perpendicular to model's z-axis or projected //onto world geometry. nameLen=strlen(shadowInfo->m_ShadowName); strncpy(texture_name,shadowInfo->m_ShadowName,nameLen); strcpy(texture_name+nameLen,".tga"); //append texture extension //Check if we previously added a decal using this texture st=m_W3DShadowTextureManager->getTexture(texture_name); if (st == NULL) { //Adding a new decal texture TextureClass *w3dTexture=WW3DAssetManager::Get_Instance()->Get_Texture(texture_name); w3dTexture->Set_U_Addr_Mode(TextureClass::TEXTURE_ADDRESS_CLAMP); w3dTexture->Set_V_Addr_Mode(TextureClass::TEXTURE_ADDRESS_CLAMP); w3dTexture->Set_Mip_Mapping(TextureClass::FILTER_TYPE_NONE); DEBUG_ASSERTCRASH(w3dTexture != NULL, ("Could not load decal texture: %s\n",texture_name)); if (!w3dTexture) return NULL; st = NEW W3DShadowTexture; // poolify SET_REF_OWNER( st ); st->Set_Name(texture_name); m_W3DShadowTextureManager->addTexture( st ); st->setTexture(w3dTexture); } shadowType=shadowInfo->m_type; allowWorldAlign=shadowInfo->allowWorldAlign; allowSunDirection=shadowInfo->m_type & SHADOW_DIRECTIONAL_PROJECTION; decalSizeX=shadowInfo->m_sizeX; decalSizeY=shadowInfo->m_sizeY; W3DProjectedShadow *shadow = NEW W3DProjectedShadow; // poolify // sanity if( shadow == NULL ) return NULL; shadow->setRenderObject(NULL); shadow->setTexture(0,st); ///@todo: Fix projected shadows to allow multiple lights shadow->m_type = shadowType; /// type of projection shadow->m_allowWorldAlign=allowWorldAlign; /// wrap shadow around world geometry - else align perpendicular to local z-axis. shadow->m_oowDecalSizeX = 1.0f/decalSizeX; //one over width shadow->m_oowDecalSizeY = 1.0f/decalSizeY; //one over height //Prestore some values used during projection to optimize out division. shadow->m_decalSizeX = decalSizeX; //width shadow->m_decalSizeY = decalSizeY; //height shadow->m_decalOffsetU=0; shadow->m_decalOffsetV=0; shadow->m_flags = allowSunDirection; shadow->init(); // add to our shadow list through the shadow next links, insert next to other shadows using same texture W3DProjectedShadow *nextShadow=NULL,*prevShadow=NULL; for( nextShadow = m_decalList; nextShadow; prevShadow=nextShadow,nextShadow = nextShadow->m_next ) { if (nextShadow->m_shadowTexture[0]==st) { //found start of other shadows using same texture, insert new shadow here. shadow->m_next=nextShadow; if (prevShadow) { prevShadow->m_next=shadow; } else m_decalList=shadow; break; } } if (nextShadow==NULL) { //shadow with new texture. Add to top of list. shadow->m_next = m_decalList; m_decalList = shadow; } switch (shadow->m_type) { case SHADOW_DECAL: case SHADOW_ALPHA_DECAL: case SHADOW_ADDITIVE_DECAL: m_numDecalShadows++; break; case SHADOW_PROJECTION: m_numProjectionShadows++; default: break; } return shadow; } /** Generic function which can be used to create arbitrary decals that follow the renderObject but don't have to be used for shadows. Some examples: Scorch marks, blood, stains, selection/status indicators, etc.*/ Shadow* W3DProjectedShadowManager::addDecal(RenderObjClass *robj, Shadow::ShadowTypeInfo *shadowInfo) { W3DShadowTexture *st=NULL; ShadowType shadowType=SHADOW_NONE; /// type of projection Bool allowWorldAlign=FALSE; /// wrap shadow around world geometry - else align perpendicular to local z-axis. Real decalSizeX=0.0f; Real decalSizeY=0.0f; Real decalOffsetX=0.0f; Real decalOffsetY=0.0f; Bool allowSunDirection=FALSE; Char texture_name[64]; Int nameLen; if (!robj || !shadowInfo) return NULL; //right now we require hardware render-to-texture support //simple decal using the premade texture specified. //can be always perpendicular to model's z-axis or projected //onto world geometry. nameLen=strlen(shadowInfo->m_ShadowName); strncpy(texture_name,shadowInfo->m_ShadowName,nameLen); strcpy(texture_name+nameLen,".tga"); //append texture extension //Check if we previously added a decal using this texture st=m_W3DShadowTextureManager->getTexture(texture_name); if (st == NULL) { //Adding a new decal texture TextureClass *w3dTexture=WW3DAssetManager::Get_Instance()->Get_Texture(texture_name); w3dTexture->Set_U_Addr_Mode(TextureClass::TEXTURE_ADDRESS_CLAMP); w3dTexture->Set_V_Addr_Mode(TextureClass::TEXTURE_ADDRESS_CLAMP); w3dTexture->Set_Mip_Mapping(TextureClass::FILTER_TYPE_NONE); DEBUG_ASSERTCRASH(w3dTexture != NULL, ("Could not load decal texture: %s\n",texture_name)); if (!w3dTexture) return NULL; st = NEW W3DShadowTexture; SET_REF_OWNER( st ); st->Set_Name(texture_name); m_W3DShadowTextureManager->addTexture( st ); st->setTexture(w3dTexture); } shadowType=shadowInfo->m_type; allowWorldAlign=shadowInfo->allowWorldAlign; allowSunDirection=shadowInfo->m_type & SHADOW_DIRECTIONAL_PROJECTION; decalSizeX=shadowInfo->m_sizeX; decalSizeY=shadowInfo->m_sizeY; decalOffsetX=shadowInfo->m_offsetX; decalOffsetY=shadowInfo->m_offsetY; W3DProjectedShadow *shadow = NEW W3DProjectedShadow; // sanity if( shadow == NULL ) return NULL; shadow->setRenderObject(robj); shadow->setTexture(0,st); ///@todo: Fix projected shadows to allow multiple lights shadow->m_type = shadowType; /// type of projection shadow->m_allowWorldAlign=allowWorldAlign; /// wrap shadow around world geometry - else align perpendicular to local z-axis. AABoxClass box; robj->Get_Obj_Space_Bounding_Box(box); //Check if app is overriding any of the default texture stretch factors. if (!decalSizeX) decalSizeX=box.Extent.X*2.0f;//use bounding box to determine size shadow->m_oowDecalSizeX = 1.0f/decalSizeX; //one over width if (!decalSizeY) decalSizeY=box.Extent.Y*2.0f;//world space distance to stretch full texture shadow->m_oowDecalSizeY = 1.0f/decalSizeY; //one over height if (decalOffsetX) decalOffsetX=decalOffsetX*shadow->m_oowDecalSizeX; if (decalOffsetY) decalOffsetY=decalOffsetY*shadow->m_oowDecalSizeY; //Prestore some values used during projection to optimize out division. shadow->m_decalSizeX = decalSizeX; //width shadow->m_decalSizeY = decalSizeY; //height shadow->m_decalOffsetU= decalOffsetX; shadow->m_decalOffsetV= decalOffsetY; shadow->m_flags = allowSunDirection; shadow->init(); // add to our shadow list through the shadow next links, insert next to other shadows using same texture W3DProjectedShadow *nextShadow=NULL,*prevShadow=NULL; for( nextShadow = m_decalList; nextShadow; prevShadow=nextShadow,nextShadow = nextShadow->m_next ) { if (nextShadow->m_shadowTexture[0]==st) { //found start of other shadows using same texture, insert new shadow here. shadow->m_next=nextShadow; if (prevShadow) { prevShadow->m_next=shadow; } else m_decalList=shadow; break; } } if (nextShadow==NULL) { //shadow with new texture. Add to top of list. shadow->m_next = m_decalList; m_decalList = shadow; } switch (shadow->m_type) { case SHADOW_DECAL: case SHADOW_ALPHA_DECAL: case SHADOW_ADDITIVE_DECAL: m_numDecalShadows++; break; case SHADOW_PROJECTION: m_numProjectionShadows++; default: break; } return shadow; } W3DProjectedShadow* W3DProjectedShadowManager::addShadow(RenderObjClass *robj, Shadow::ShadowTypeInfo *shadowInfo, Drawable *draw) { W3DShadowTexture *st=NULL; static char defaultDecalName[]={"shadow.tga"}; ShadowType shadowType=SHADOW_NONE; /// type of projection Bool allowWorldAlign=FALSE; /// wrap shadow around world geometry - else align perpendicular to local z-axis. Real decalSizeX=0.0f; Real decalSizeY=0.0f; Real decalOffsetX=0.0f; Real decalOffsetY=0.0f; Bool allowSunDirection=FALSE; Char texture_name[64]; Int nameLen; if (!m_dynamicRenderTarget || !robj || !TheGlobalData->m_useShadowDecals) return NULL; //right now we require hardware render-to-texture support if (shadowInfo) { //determine what kind of shadow is needed if (shadowInfo->m_type==SHADOW_DECAL) { //simple decal using the premade texture specified. //can be always perpendicular to model's z-axis or projected //onto world geometry. nameLen=strlen(shadowInfo->m_ShadowName); if (nameLen <= 1) //no texture name given, use same as object { strcpy(texture_name,defaultDecalName); } else { strncpy(texture_name,shadowInfo->m_ShadowName,nameLen); strcpy(texture_name+nameLen,".tga"); //append texture extension } st=m_W3DShadowTextureManager->getTexture(texture_name); if (st == NULL) { //need to add this texture without creating it from a real renderobject TextureClass *w3dTexture=WW3DAssetManager::Get_Instance()->Get_Texture(texture_name); w3dTexture->Set_U_Addr_Mode(TextureClass::TEXTURE_ADDRESS_CLAMP); w3dTexture->Set_V_Addr_Mode(TextureClass::TEXTURE_ADDRESS_CLAMP); w3dTexture->Set_Mip_Mapping(TextureClass::FILTER_TYPE_NONE); DEBUG_ASSERTCRASH(w3dTexture != NULL, ("Could not load decal texture")); if (!w3dTexture) return NULL; st = NEW W3DShadowTexture; // poolify SET_REF_OWNER( st ); st->Set_Name(texture_name); m_W3DShadowTextureManager->addTexture( st ); st->setTexture(w3dTexture); } shadowType=SHADOW_DECAL; allowSunDirection=shadowInfo->m_type & SHADOW_DIRECTIONAL_PROJECTION; decalSizeX=shadowInfo->m_sizeX; decalSizeY=shadowInfo->m_sizeY; decalOffsetX=shadowInfo->m_offsetX; decalOffsetY=shadowInfo->m_offsetY; } else if (shadowInfo->m_type==SHADOW_PROJECTION) { //projection of object geometry into a texture. //can be applied on a plane horizontal to model's z-axis or //projected onto world geometry. if (shadowInfo->m_ShadowName[0] != '\0') { //the shadow will be based on another render object //to allow multiple models to share same shadow - for //example, all trees could use same shadow even if slightly //different color, etc. strcpy(texture_name,shadowInfo->m_ShadowName); } else strcpy(texture_name,robj->Get_Name()); //not texture name give, assume model name. st=m_W3DShadowTextureManager->getTexture(texture_name); if (st == NULL) { //texture doesn't exist, use current render object to create it m_W3DShadowTextureManager->createTexture(robj,texture_name); //try loading again st=m_W3DShadowTextureManager->getTexture(texture_name); DEBUG_ASSERTCRASH(st != NULL, ("Could not create shadow texture")); if (st==NULL) return NULL; //could not create the shadow texture } shadowType=SHADOW_PROJECTION; }//SHADOW_PROJECTION } else { //no shadow info, assume user wants a projected shadow strcpy(texture_name,robj->Get_Name()); st=m_W3DShadowTextureManager->getTexture(texture_name); if (st==NULL) { //did not find a cached copy of the shadow geometry, create a new one m_W3DShadowTextureManager->createTexture(robj,texture_name); //try loading again st=m_W3DShadowTextureManager->getTexture(texture_name); if (st==NULL) return NULL; //could not create the shadow texture } shadowType=SHADOW_PROJECTION; } W3DProjectedShadow *shadow = NEW W3DProjectedShadow; // sanity if( shadow == NULL ) return NULL; shadow->setRenderObject(robj); shadow->setTexture(0,st); ///@todo: Fix projected shadows to allow multiple lights shadow->m_type = shadowType; /// type of projection shadow->m_allowWorldAlign=allowWorldAlign; /// wrap shadow around world geometry - else align perpendicular to local z-axis. AABoxClass box; robj->Get_Obj_Space_Bounding_Box(box); //Check if app is overriding any of the default texture stretch factors. if (decalSizeX) decalSizeX=1.0f/decalSizeX; //world space distance to stretch full texture scale else decalSizeX=1.0f/(box.Extent.X*2.0f);//use bounding box to determine size if (decalSizeY) decalSizeY=-1.0f/decalSizeY; else decalSizeY=-1.0f/(box.Extent.Y*2.0f);//world space distance to stretch full texture if (decalOffsetX) decalOffsetX=-decalOffsetX*decalSizeX; else decalOffsetX=0.0f;//-box.Center.X*decalSizeX; if (decalOffsetY) decalOffsetY=-decalOffsetY*decalSizeY; else decalOffsetY=0.0f;//-box.Center.Y*decalSizeY; //Prestore some values used during projection to optimize out division. shadow->m_oowDecalSizeX = decalSizeX; //one over width shadow->m_oowDecalSizeY = decalSizeY; //one over height shadow->m_decalSizeX = 1.0f/decalSizeX; //width shadow->m_decalSizeY = 1.0f/decalSizeY; //height shadow->m_decalOffsetU= decalOffsetX; shadow->m_decalOffsetV= decalOffsetY; shadow->m_flags = allowSunDirection; shadow->init(); // add to our shadow list through the shadow next links, insert next to other shadows using same texture W3DProjectedShadow *nextShadow=NULL,*prevShadow=NULL; for( nextShadow = m_shadowList; nextShadow; prevShadow=nextShadow,nextShadow = nextShadow->m_next ) { if (nextShadow->m_shadowTexture[0]==st) { //found start of other shadows using same texture, insert new shadow here. shadow->m_next=nextShadow; if (prevShadow) { prevShadow->m_next=shadow; } else m_shadowList=shadow; break; } } if (nextShadow==NULL) { //shadow with new texture. Add to top of list. shadow->m_next = m_shadowList; m_shadowList = shadow; } switch (shadow->m_type) { case SHADOW_DECAL: m_numDecalShadows++; break; case SHADOW_PROJECTION: m_numProjectionShadows++; default: break; } return shadow; } void W3DProjectedShadowManager::removeShadow (W3DProjectedShadow *shadow) { W3DProjectedShadow *prev_shadow=NULL; W3DProjectedShadow *next_shadow=NULL; if (shadow->m_type & (SHADOW_ALPHA_DECAL|SHADOW_ADDITIVE_DECAL)) { for( next_shadow = m_decalList; next_shadow; prev_shadow=next_shadow, next_shadow = next_shadow->m_next ) { if (next_shadow == shadow) { if (prev_shadow) prev_shadow->m_next=shadow->m_next; else m_decalList=shadow->m_next; switch (shadow->m_type) { case SHADOW_DECAL: m_numDecalShadows--; break; case SHADOW_PROJECTION: m_numProjectionShadows--; default: break; } delete shadow; return; } } // end for } //search for this shadow for( next_shadow = m_shadowList; next_shadow; prev_shadow=next_shadow, next_shadow = next_shadow->m_next ) { if (next_shadow == shadow) { if (prev_shadow) prev_shadow->m_next=shadow->m_next; else m_shadowList=shadow->m_next; switch (shadow->m_type) { case SHADOW_DECAL: m_numDecalShadows--; break; case SHADOW_PROJECTION: m_numProjectionShadows--; default: break; } delete shadow; return; } } // end for } void W3DProjectedShadowManager::removeAllShadows(void) { W3DProjectedShadow *cur_shadow=NULL; W3DProjectedShadow *next_shadow=m_shadowList; m_shadowList = NULL; m_numDecalShadows = 0; m_numProjectionShadows = 0; //search for this shadow for( cur_shadow = next_shadow; cur_shadow; cur_shadow = next_shadow ) { next_shadow = cur_shadow->m_next; cur_shadow->m_next = NULL; delete cur_shadow; } // end for next_shadow=m_decalList; cur_shadow=NULL; m_decalList=NULL; for( cur_shadow = next_shadow; cur_shadow; cur_shadow = next_shadow ) { next_shadow = cur_shadow->m_next; cur_shadow->m_next = NULL; delete cur_shadow; } // end for } #if defined(_DEBUG) || defined(_INTERNAL) void W3DProjectedShadow::getRenderCost(RenderCost & rc) const { if (TheGlobalData->m_useShadowDecals && m_isEnabled && !m_isInvisibleEnabled) rc.addShadowDrawCalls(1); } #endif W3DProjectedShadow::W3DProjectedShadow(void) { m_diffuse=0xffffffff; m_shadowProjector=NULL; m_lastObjPosition.Set(0,0,0); m_type = SHADOW_NONE; /// type of projection m_allowWorldAlign = FALSE; /// wrap shadow around world geometry - else align perpendicular to local z-axis. m_isEnabled = TRUE; m_isInvisibleEnabled = FALSE; for (Int i=0; iSet_Intensity(0.4f,true); m_shadowProjector->Set_Texture(m_shadowTexture[0]->getTexture()); } } #define DECAL_TEXELS_PER_WORLD_UNIT (64.0f/20.0f) //64 texels per 2 terrain cells (20 units) void W3DProjectedShadow::updateTexture(Vector3 &lightPos) { SpecialRenderInfoClass *context; //default uv coordinates before rotation starting at top/left going clockwise static Vector2 uvData[4]={Vector2(-0.5,-0.5f),Vector2(-0.5,0.5f),Vector2(0.5f,0.5f),Vector2(-0.5f,0.5f)}; //force light always 2000 units from object - for some reason projection fails if //light is too far. ///@todo: See why infinite light sources don't project shadows correctly. if (m_type == SHADOW_PROJECTION) { //projected shadows use custom runtime generated textures based on object geometry Vector3 objPos=m_robj->Get_Position(); if (objPos == Vector3(0,0,0)) return; //render object does not have a valid position (never rendered). Vector3 objToLight=lightPos - objPos; objToLight.Normalize(); objToLight = objPos + objToLight * 2000.0f; m_shadowProjector->Compute_Perspective_Projection(m_robj,objToLight); m_shadowProjector->Set_Render_Target(TheW3DProjectedShadowManager->getRenderTarget()); //Set ambient to 0, so we get a black shadow on solid backgroud context=TheW3DProjectedShadowManager->getRenderContext(); context->light_environment->Reset(m_robj->Get_Position(), Vector3(0,0,0)); m_shadowProjector->Compute_Texture(m_robj,context); //Need to copy generated texture into permanent texture. SurfaceClass *oldSurface=m_shadowTexture[0]->getTexture()->Get_Surface_Level(); SurfaceClass *newSurface=TheW3DProjectedShadowManager->getRenderTarget()->Get_Surface_Level(); //Copy shadow from temporary video-memory surface into a permanent texture oldSurface->Copy(0,0,0,0,DEFAULT_RENDER_TARGET_WIDTH,DEFAULT_RENDER_TARGET_HEIGHT,newSurface); REF_PTR_RELEASE(newSurface); REF_PTR_RELEASE(oldSurface); m_shadowTexture[0]->updateBounds(TheW3DShadowManager->getLightPosWorld(0),m_robj); //update local shadow bounds } else if (m_type == SHADOW_DECAL) { //decal shadows use artist supplied textures. We just need to tweak the uv coordinates to match //the light direction. Vector3 objPos=m_robj->Get_Position(); Vector3 objectToLight; if (m_flags & SHADOW_DIRECTIONAL_PROJECTION) { objectToLight=lightPos-objPos; //we're ignoring sun's distance from horizon, so drop vertical component objectToLight.Z=0; objectToLight.Normalize(); } else objectToLight.Set(1.0f,0.0f,0.0f); SurfaceClass::SurfaceDescription surface_desc; m_shadowTexture[0]->getTexture()->Get_Level_Description(surface_desc); //default shadow texture points along world -x axis (west). Rotate uv coordinates to fit actual light direction Vector3 uVec = objectToLight * DECAL_TEXELS_PER_WORLD_UNIT / (float)surface_desc.Width; objectToLight.Rotate_Z(-1.0f,0.0f); //rotate u vector by -90 degress to get v vector. Vector3 vVec = objectToLight * DECAL_TEXELS_PER_WORLD_UNIT / (float)surface_desc.Height; m_shadowTexture[0]->setDecalUVAxis(uVec, vVec); ///@todo: tweak decal bounding volumes to something sensible AABoxClass box; SphereClass sphere; m_robj->Get_Obj_Space_Bounding_Box(box); //shadow uses same bounding box as object m_robj->Get_Obj_Space_Bounding_Sphere(sphere); m_shadowTexture[0]->setBoundingSphere(sphere); m_shadowTexture[0]->setBoundingBox(box); } m_shadowTexture[0]->setLightPosHistory(lightPos); //store position of light at time of texture update. } void W3DProjectedShadow::updateProjectionParameters(const Matrix3D &cameraXform) { m_shadowProjector->Pre_Render_Update(cameraXform); } void W3DProjectedShadow::update(void) { if (m_shadowTexture[0]->getLightPosHistory() != TheW3DShadowManager->getLightPosWorld(0)) { //light has moved since last time this shadow was calculated. Need update updateTexture(TheW3DShadowManager->getLightPosWorld(0)); } if (m_lastObjPosition != m_robj->Get_Position()) { //object has moved. Texture stays the same but projection matrix needs updating. //force light always 2000 units from object - for some reason projection fails if //light is too far. ///@todo: See why infinite light sources don't project shadows correctly. if (m_type == SHADOW_PROJECTION) { Vector3 objToLight=TheW3DShadowManager->getLightPosWorld(0) - m_robj->Get_Position(); objToLight.Normalize(); objToLight = m_robj->Get_Position() + objToLight * 2000.0f; m_shadowProjector->Compute_Perspective_Projection(m_robj,objToLight); } setObjPosHistory(m_robj->Get_Position()); } } Int W3DShadowTexture::init(RenderObjClass *robj) { ///@todo: implement this function SurfaceClass::SurfaceDescription surface_desc; TheW3DProjectedShadowManager->getRenderTarget()->Get_Level_Description(surface_desc); TextureClass *new_texture = MSGNEW("TextureClass") TextureClass(surface_desc.Width,surface_desc.Height,surface_desc.Format,TextureClass::MIP_LEVELS_1); setTexture(new_texture); return TRUE; } void W3DShadowTexture::updateBounds(Vector3 &lightPos, RenderObjClass *robj) { AABoxClass &box=m_areaEffectBox; ///@todo: fix for multiple lights Vector3 objPos; Vector3 Corners[8]; Vector3 lightRay; Real floorZ; Real vectorScale,vectorScaleTemp, vectorScaleMax,length; //calculate local bounding box of shadow projection objPos=robj->Get_Position(); box=robj->Get_Bounding_Box(); floorZ = objPos.Z - 2.0f; //lower slightly so shadows go under ground. //project each box corner to base of object to determine rough extent of shadow //Get vertices of top of bounding box Corners[0]=box.Center+box.Extent; //top right corner Corners[1]=Corners[0]; Corners[1].X -= 2.0f*box.Extent.X; //top left corner Corners[2]=Corners[1]; Corners[2].Y -= 2.0f*box.Extent.Y; //bottom left corner Corners[3]=Corners[2]; Corners[3].X += 2.0f*box.Extent.X; //bottom right corner //Project top volume corners onto ground plane lightRay = Corners[0] - lightPos; //vector light to corner length= 1.0f/lightRay.Length(); lightRay *= length; vectorScaleMax=vectorScale=(Real)fabs((Corners[0].Z-floorZ)/lightRay.Z); //length of vector from top corner to ground. Corners[4]=Corners[0]+lightRay*vectorScale; vectorScaleMax *= length; lightRay = Corners[1] - lightPos; //vector light to corner length= 1.0f/lightRay.Length(); lightRay *= length; vectorScaleTemp=(Real)fabs((Corners[1].Z-floorZ)/lightRay.Z); //length of vector from top corner to ground. Corners[5]=Corners[1]+lightRay*vectorScaleTemp; vectorScaleTemp *= length; if (vectorScaleTemp > vectorScaleMax) vectorScaleMax=vectorScaleTemp; //keep track of maximum required extrusion length. lightRay = Corners[2] - lightPos; //vector light to corner length= 1.0f/lightRay.Length(); lightRay *= length; vectorScale=(Real)fabs((Corners[2].Z-floorZ)/lightRay.Z); //length of vector from top corner to ground. Corners[6]=Corners[2]+lightRay*vectorScale; vectorScale *= length; if (vectorScale > vectorScaleMax) vectorScaleMax=vectorScale; //keep track of maximum required extrusion length. lightRay = Corners[3] - lightPos; //vector light to corner length= 1.0f/lightRay.Length(); lightRay *= length; vectorScaleTemp=(Real)fabs((Corners[3].Z-floorZ)/lightRay.Z); //length of vector from top corner to ground. Corners[7]=Corners[3]+lightRay*vectorScaleTemp; vectorScaleTemp *= length; if (vectorScaleTemp > vectorScaleMax) vectorScaleMax=vectorScaleTemp; //keep track of maximum required extrusion length. box.Init(Corners, 8); //generate a new bounding box to fit the shadow projection m_areaEffectSphere.Init(box.Center,box.Extent.Length()); m_areaEffectSphere.Center -= objPos; //translate sphere to object space. box.Translate(-objPos); //translate box to object space. } W3DShadowTextureManager::W3DShadowTextureManager(void) { // Create the hash tables texturePtrTable = NEW HashTableClass( 2048 ); missingTextureTable = NEW HashTableClass( 2048 ); } W3DShadowTextureManager::~W3DShadowTextureManager(void) { freeAllTextures(); delete texturePtrTable; texturePtrTable = NULL; delete missingTextureTable; missingTextureTable = NULL; } /** Release all loaded textures */ void W3DShadowTextureManager::freeAllTextures(void) { // Make an iterator, and release all ptrs W3DShadowTextureManagerIterator it( *this ); for( it.First(); !it.Is_Done(); it.Next() ) { W3DShadowTexture *text = it.getCurrentTexture(); text->Release_Ref(); } // Then clear the table texturePtrTable->Reset(); } /** Find texture in cache */ W3DShadowTexture * W3DShadowTextureManager::peekTexture(const char * name) { return (W3DShadowTexture*)texturePtrTable->Find( name ); } /** Get texture from cache and increment its reference count */ W3DShadowTexture * W3DShadowTextureManager::getTexture(const char * name) { W3DShadowTexture * text = peekTexture( name ); if ( text != NULL ) { text->Add_Ref(); } return text; } /** Add texture to cache */ Bool W3DShadowTextureManager::addTexture(W3DShadowTexture *newTexture) { WWASSERT (newTexture != NULL); // Increment the refcount on the new texture and add it to our table. newTexture->Add_Ref (); texturePtrTable->Add( newTexture ); return true; } void W3DShadowTextureManager::invalidateCachedLightPositions(void) { // step through each of our shadow textures and update previous light position. Vector3 idVec(0,0,0); W3DShadowTextureManagerIterator it( *this ); for( it.First(); !it.Is_Done(); it.Next() ) { W3DShadowTexture *text = it.getCurrentTexture(); text->setLightPosHistory(idVec); } } /* ** An entry for a table of textures not found, so we can quickly determine their loss */ class MissingTextureClass : public HashableClass { public: MissingTextureClass( const char * name ) : Name( name ) {} virtual ~MissingTextureClass( void ) {} virtual const char * Get_Key( void ) { return Name; } private: StringClass Name; }; /* ** Missing Textures ** ** The idea here, allow the system to register which textures are determined to be missing ** so that if they are asked for again, we can quickly return NULL, without searching again. */ void W3DShadowTextureManager::registerMissing( const char * name ) { missingTextureTable->Add( NEW MissingTextureClass( name ) ); } Bool W3DShadowTextureManager::isMissing( const char * name ) { return ( missingTextureTable->Find( name ) != NULL ); } /** Create shadow geometry from a reference W3D RenderObject*/ int W3DShadowTextureManager::createTexture(RenderObjClass *robj, const char *name) { Bool res=FALSE; W3DShadowTexture * newTexture = NEW W3DShadowTexture; if (newTexture == NULL) { goto Error; } SET_REF_OWNER( newTexture ); newTexture->Set_Name(name); res=newTexture->init(robj); if (res != TRUE) { // load failed! newTexture->Release_Ref(); goto Error; } else if (peekTexture(newTexture->Get_Name()) != NULL) { // duplicate exists! newTexture->Release_Ref(); // Release the one we just loaded goto Error; } else { addTexture( newTexture ); newTexture->Release_Ref(); } return 0; Error: return 1; }