/*
** 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: W3DScene.cpp /////////////////////////////////////////////////////////
//
// Scene manger for display using W3DDispaly. A scene manager can customize
// the rendering process, culling, material passes ...
//
// Author: Colin Day, April 2001
//
///////////////////////////////////////////////////////////////////////////////
// SYSTEM INCLUDES ////////////////////////////////////////////////////////////
#include
// USER INCLUDES //////////////////////////////////////////////////////////////
#include "Lib/BaseType.h"
#include "Common/GlobalData.h"
#include "Common/PerfTimer.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "GameLogic/Object.h"
#include "GameLogic/GameLogic.h"
#include "GameClient/Drawable.h"
#include "GameClient/ParticleSys.h"
#include "GameClient/Color.h"
#include "GameClient/View.h"
#include "W3DDevice/GameClient/HeightMap.h"
#include "W3DDevice/GameClient/W3DScene.h"
#include "W3DDevice/GameClient/W3DDynamicLight.h"
#include "W3DDevice/GameClient/W3DGranny.h"
#include "W3DDevice/GameClient/W3DShadow.h"
#include "W3DDevice/GameClient/W3DStatusCircle.h"
#include "W3DDevice/GameClient/W3DCustomScene.h"
#include "W3DDevice/GameClient/W3DShroud.h"
#include "WW3D2/camera.h"
#include "WW3D2/dx8renderer.h"
#include "WW3D2/sortingrenderer.h"
#include "WW3D2/dx8wrapper.h"
#include "WW3D2/Light.h"
#include "WW3D2/matpass.h"
#include "WW3D2/shader.h"
#include "WW3D2/DX8Caps.h"
#include "WW3D2/colorspace.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
///////////////////////////////////////////////////////////////////////////////
// DEFINITIONS ////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///@todo: Remove these globals since we no longer need W3D to call them for us.
extern void DoTrees(RenderInfoClass & rinfo);
extern void DoShadows(RenderInfoClass & rinfo, Bool stencilPass);
extern void DoParticles(RenderInfoClass & rinfo);
// No texturing, no zbuffer reading/writing, primary gradient, no
// blending, no fogging - mostly for use in solid-colored opaque objects.
#define SC_PLAYER_COLOR ( SHADE_CNST(ShaderClass::PASS_ALWAYS, ShaderClass::DEPTH_WRITE_DISABLE, ShaderClass::COLOR_WRITE_ENABLE, \
ShaderClass::SRCBLEND_ONE, ShaderClass::DSTBLEND_ZERO, ShaderClass::FOG_DISABLE, ShaderClass::GRADIENT_MODULATE, ShaderClass::SECONDARY_GRADIENT_DISABLE, \
ShaderClass::TEXTURING_DISABLE, ShaderClass::ALPHATEST_DISABLE, ShaderClass::CULL_MODE_ENABLE, \
ShaderClass::DETAILCOLOR_DISABLE, ShaderClass::DETAILALPHA_DISABLE) )
static ShaderClass PlayerColorShader(SC_PLAYER_COLOR);
//=============================================================================
// RTS3DScene::RTS3DScene
//=============================================================================
/** */
//=============================================================================
RTS3DScene::RTS3DScene()
{
setName("RTS3DScene");
m_drawTerrainOnly = false;
m_numGlobalLights=0;
for (Int i=0; im_shroudOn)
m_shroudMaterialPass = NEW_REF(W3DShroudMaterialPassClass,());
else
m_shroudMaterialPass = NULL;
#else
m_shroudMaterialPass = NEW_REF(W3DShroudMaterialPassClass,());
#endif
m_maskMaterialPass = NEW_REF(W3DMaskMaterialPassClass,());
m_customPassMode = SCENE_PASS_DEFAULT;
m_heatVisionMaterialPass = NEW_REF(MaterialPassClass,());
m_heatVisionOnlyPass = NEW_REF(MaterialPassClass,());
VertexMaterialClass *heatVisionMtl = NEW_REF(VertexMaterialClass,());
heatVisionMtl->Set_Lighting(true);
heatVisionMtl->Set_Ambient(0,0,0);
heatVisionMtl->Set_Diffuse(0.02f,0.01f,0.00f);
heatVisionMtl->Set_Emissive(0.5f,0.2f,0.0f);
m_heatVisionMaterialPass->Set_Material(heatVisionMtl);
ShaderClass heatVisionShader=ShaderClass::_PresetAdditiveSolidShader;
heatVisionShader.Set_Depth_Compare(ShaderClass::PASS_EQUAL);
m_heatVisionMaterialPass->Set_Shader(heatVisionShader);
heatVisionMtl->Release_Ref();
heatVisionShader.Set_Depth_Compare(ShaderClass::PASS_LEQUAL);
heatVisionShader.Set_Depth_Mask(ShaderClass::DEPTH_WRITE_DISABLE);
m_heatVisionOnlyPass->Set_Material(heatVisionMtl);
m_heatVisionOnlyPass->Set_Shader(heatVisionShader);
//Allocate memory to hold queue of visible renderobjects that need to be drawn last
//because they are forced translucent.
m_translucentObjectsCount = 0;
if (TheGlobalData && TheGlobalData->m_maxVisibleTranslucentObjects)
m_translucentObjectsBuffer = NEW RenderObjClass* [TheGlobalData->m_maxVisibleTranslucentObjects];
else
m_translucentObjectsBuffer = NULL;
m_numPotentialOccluders=0;
m_numPotentialOccludees=0;
m_numNonOccluderOrOccludee=0;
m_occludedObjectsCount=0;
m_potentialOccluders=NULL;
m_potentialOccludees=NULL;
m_nonOccludersOrOccludees=NULL;
//Modify the shader to make occlusion transparent
ShaderClass shader = PlayerColorShader;
shader.Set_Src_Blend_Func(ShaderClass::SRCBLEND_SRC_ALPHA);
shader.Set_Dst_Blend_Func(ShaderClass::DSTBLEND_ONE_MINUS_SRC_ALPHA);
m_potentialOccluders = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccluderObjects];
m_potentialOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccludeeObjects];
m_nonOccludersOrOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleNonOccluderOrOccludeeObjects];
#ifdef USE_NON_STENCIL_OCCLUSION
for (i=0; iSet_Lighting(true);
vmtl->Set_Ambient(0,0,0); //we're only using emissive so kill all other lights.
vmtl->Set_Diffuse(0,0,0);
m_occludedMaterialPass[i]->Set_Material(vmtl);
m_occludedMaterialPass[i]->Set_Shader(shader);
vmtl->Release_Ref(); //material pass is holding the pointer so release ref.
}
#else
for (i=0; iGet_Position();
//Find which objects are actually occluded
RenderObjClass **occludee=m_potentialOccludees;
LineSegClass lineseg;
CastResultStruct result;
Bool hit=FALSE;
Vector3 newEndPoint;
result.ComputeContactPoint=false;
RayCollisionTestClass raytest(lineseg,&result,COLLISION_TYPE_ALL,false,false);
raytest.CollisionType=COLLISION_TYPE_ALL;
m_occludedObjectsCount=0;
for (Int i=0; iGet_Position());
RenderObjClass **occluder=m_potentialOccluders;
//Check this object against all other possible blocking objects
for (Int j=0; jGet_Bounding_Sphere();
// make a vector from the ray origin to the sphere center
Vector3 sphere_vector(sphere->Center - raytest.Ray.Get_P0());
// get the dot product between the sphere_vector and the ray vector
Real Alpha = Vector3::Dot_Product(sphere_vector, raytest.Ray.Get_Dir());
Real Beta = sphere->Radius * sphere->Radius - (Vector3::Dot_Product(sphere_vector, sphere_vector) - Alpha * Alpha);
if(Beta < 0.0f)
continue; //no intersection
//Do a more accurate test against object geometry
if (robj->Cast_Ray(raytest))
{
//Found an object closer than last closest object
//Adjust our results and refine search
raytest.CollidedRenderObj = robj;
hit=TRUE;
//reset the result space for next test
result.StartBad = false; result.Fraction = 1.0f;
break;
}
}
if (hit)
{ //ocludee was blocked by something so flag it for custom rendering
DrawableInfo *drawInfo=(DrawableInfo *)(*occludee)->Get_User_Data();
drawInfo->m_flags |= DrawableInfo::ERF_IS_OCCLUDED;
m_potentialOccludees[m_occludedObjectsCount++] = *occludee;
}
}
}
//=============================================================================
// RTS3DScene::castRay
//=============================================================================
/** Does a ray intersection test against objects in our scene.
By default, it only tests objects determined to be visible to the user.
Setting testAll forces it to test all objects in the scene.
CollisionType is used as a mask to ignore certain types of objects.
*/
//=============================================================================
Bool RTS3DScene::castRay(RayCollisionTestClass & raytest, Bool testAll, Int collisionType)
{
// this shouldn't be necessary here, and would be an undesirable performance hit.
// if you ever add or modify code here, it MIGHT become necessary... so do so with caution. (srj)
//#ifdef DIRTY_CONDITION_FLAGS
// StDrawableDirtyStuffLocker lockDirtyStuff;
//#endif
//temporary results for each object tested
CastResultStruct result;
RayCollisionTestClass tempRayTest(raytest.Ray,&result);
Vector3 newEndPoint;
Bool hit=FALSE;
tempRayTest.CollisionType = COLLISION_TYPE_ALL;
//check if a mesh is translucent before colliding with it. Skips headlights, etc.
tempRayTest.CheckTranslucent = true;
RefRenderObjListIterator it(&RenderList);
// select the first object
it.First();
while (!it.Is_Done())
{
// get the render object
RenderObjClass * robj = it.Peek_Obj();
it.Next();
// only intersect if it was visible or if we must test all
if(robj->Get_Collision_Type() & collisionType && (testAll || robj->Is_Really_Visible()))
{
// Do a quick ray-sphere test (Graphics Gems I, p388)
const SphereClass *sphere = &robj->Get_Bounding_Sphere();
// make a vector from the ray origin to the sphere center
Vector3 sphere_vector(sphere->Center - tempRayTest.Ray.Get_P0());
// get the dot product between the sphere_vector and the ray vector
Real Alpha = Vector3::Dot_Product(sphere_vector, tempRayTest.Ray.Get_Dir());
Real Beta = sphere->Radius * sphere->Radius - (Vector3::Dot_Product(sphere_vector, sphere_vector) - Alpha * Alpha);
if(Beta < 0.0f)
continue; //no intersection
//Do a more accurate test against object geometry
if (robj->Cast_Ray(tempRayTest))
{
//Found an object closer than last closest object
//Adjust our results and refine search
raytest.CollidedRenderObj = robj;
hit=TRUE;
//Refine search by making ray shorter
tempRayTest.Ray.Compute_Point(tempRayTest.Result->Fraction,&newEndPoint);
tempRayTest.Ray.Set(raytest.Ray.Get_P0(),newEndPoint);
//intersection point is at the end of shortened ray, so adjust fraction to 1.0
tempRayTest.Result->Fraction=1.0f;
}
}
}
//Store results of ray intersection test including a clipped ray that ends on object.
raytest.Ray=tempRayTest.Ray;
return hit;
}
//=============================================================================
// RTS3DScene::Visibility_Check
//=============================================================================
/** Custom visibility check method for the RTS3DScene, we can put optimized
* culling methods in here */
//=============================================================================
void RTS3DScene::Visibility_Check(CameraClass * camera)
{
#ifdef DIRTY_CONDITION_FLAGS
StDrawableDirtyStuffLocker lockDirtyStuff;
#endif
RefRenderObjListIterator it(&RenderList);
DrawableInfo *drawInfo = NULL;
Drawable *draw = NULL;
RenderObjClass * robj;
m_numPotentialOccluders=0;
m_numPotentialOccludees=0;
m_translucentObjectsCount=0;
m_numNonOccluderOrOccludee=0;
Int currentFrame=0;
if (TheGameLogic) currentFrame = TheGameLogic->getFrame();
if (currentFrame <= TheGlobalData->m_defaultOcclusionDelay)
currentFrame = TheGlobalData->m_defaultOcclusionDelay+1; //make sure occlusion is enabled when game starts (frame 0).
if (ShaderClass::Is_Backface_Culling_Inverted())
{ //we are rendering reflections
///@todo: Have better flag to detect reflection pass
// Loop over all top-level RenderObjects in this scene. If the bounding sphere is not in front
// of all the frustum planes, it is invisible.
for (it.First(); !it.Is_Done(); it.Next()) {
robj = it.Peek_Obj();
draw=NULL;
drawInfo = (DrawableInfo *)robj->Get_User_Data();
if (drawInfo)
draw=drawInfo->m_drawable;
if( draw )
{
if (robj->Is_Force_Visible()) {
robj->Set_Visible(true);
} else {
robj->Set_Visible(draw->getDrawsInMirror() && !camera->Cull_Sphere(robj->Get_Bounding_Sphere()));
}
}
else
{ //perform normal culling on non-drawables
if (robj->Is_Force_Visible()) {
robj->Set_Visible(true);
} else {
robj->Set_Visible(!camera->Cull_Sphere(robj->Get_Bounding_Sphere()));
}
}
}
}
else
{
// Loop over all top-level RenderObjects in this scene. If the bounding sphere is not in front
// of all the frustum planes, it is invisible.
for (it.First(); !it.Is_Done(); it.Next()) {
robj = it.Peek_Obj();
if (robj->Is_Force_Visible()) {
robj->Set_Visible(true);
} else if (robj->Is_Hidden()) {
robj->Set_Visible(false);
} else {
bool isVisible=!camera->Cull_Sphere(robj->Get_Bounding_Sphere());
if (isVisible)
{ //need to keep track of occluders and ocludees for subsequent code.
drawInfo = (DrawableInfo *)robj->Get_User_Data();
if (drawInfo && (draw=drawInfo->m_drawable) != NULL)
{
if (draw->isDrawableEffectivelyHidden() || draw->getFullyObscuredByShroud())
{ robj->Set_Visible(false);
continue;
}
//assume normal rendering.
drawInfo->m_flags = DrawableInfo::ERF_IS_NORMAL; //clear any rendering flags that may be in effect.
if (draw->getEffectiveOpacity() != 1.0f && m_translucentObjectsCount < TheGlobalData->m_maxVisibleTranslucentObjects)
{ drawInfo->m_flags = DrawableInfo::ERF_IS_TRANSLUCENT; //object is translucent
m_translucentObjectsBuffer[m_translucentObjectsCount++] = robj;
}
else
if (TheGlobalData->m_enableBehindBuildingMarkers && TheGameLogic->getShowBehindBuildingMarkers())
{
//visible drawable. Check if it's either an occluder or occludee
if (draw->isKindOf(KINDOF_STRUCTURE) && m_numPotentialOccluders < TheGlobalData->m_maxVisibleOccluderObjects)
{ //object which could occlude other objects that need to be visible.
m_potentialOccluders[m_numPotentialOccluders++]=robj;
drawInfo->m_flags |= DrawableInfo::ERF_POTENTIAL_OCCLUDER;
}
else
if (draw->getObject() &&
(draw->isKindOf(KINDOF_SCORE) || draw->isKindOf(KINDOF_SCORE_CREATE) || draw->isKindOf(KINDOF_SCORE_DESTROY) || draw->isKindOf(KINDOF_MP_COUNT_FOR_VICTORY)) &&
(draw->getObject()->getSafeOcclusionFrame()) <= currentFrame && m_numPotentialOccludees < TheGlobalData->m_maxVisibleOccludeeObjects)
{ //object which could be occluded but still needs to be visible.
m_potentialOccludees[m_numPotentialOccludees++]=robj;
drawInfo->m_flags |= DrawableInfo::ERF_POTENTIAL_OCCLUDEE;
}
else
if (drawInfo->m_flags == DrawableInfo::ERF_IS_NORMAL && m_numNonOccluderOrOccludee < TheGlobalData->m_maxVisibleNonOccluderOrOccludeeObjects)
{ //regular object with no custom effects but still needs to be delayed to get the occlusion feature to work correctly.
m_nonOccludersOrOccludees[m_numNonOccluderOrOccludee++]=robj;
drawInfo->m_flags |= DrawableInfo::ERF_IS_NON_OCCLUDER_OR_OCCLUDEE;
}
}
}
}
robj->Set_Visible(isVisible);
}
///@todo: We're not using LOD yet so I disabled this code. MW
// Also, should check how multiple passes (reflections) get along
// with the LOD manager - we're rendering double the load it thinks we are.
// Prepare visible objects for LOD:
// if (robj->Is_Really_Visible()) {
// robj->Prepare_LOD(*camera);
// }
}
}
Visibility_Checked = true;
}
//============================================================================
// RTS3DScene::renderSingleDrawable
//=============================================================================
/** Renders a single drawable entity. */
//=============================================================================
void RTS3DScene::renderSpecificDrawables(RenderInfoClass &rinfo, Int numDrawable, Drawable **theDrawables)
{
#ifdef DIRTY_CONDITION_FLAGS
StDrawableDirtyStuffLocker lockDirtyStuff;
#endif
Int localPlayerIndex = ThePlayerList ? ThePlayerList->getLocalPlayer()->getPlayerIndex() : 0;
RefRenderObjListIterator it(&UpdateList);
// loop through all render objects in the list:
for (it.First(&RenderList); !it.Is_Done();)
{
RenderObjClass *robj;
// get the render object
robj = it.Peek_Obj();
it.Next(); //advance to next object in case this one gets deleted during renderOneObject().
DrawableInfo *drawInfo = (DrawableInfo *)robj->Get_User_Data();
Drawable *draw=NULL;
if (drawInfo)
draw = drawInfo->m_drawable;
if (!draw) continue;
Bool match = false;
for (Int i = 0; iClass_ID() == RenderObjClass::CLASSID_IMAGE3D )
{ robj->Render(rinfo); //notify decals system that this track is visible
return; //decals are not lit by this system yet so skip rest of lighting
}
LightEnvironmentClass lightEnv;
SphereClass sph = robj->Get_Bounding_Sphere();
drawInfo = (DrawableInfo *)robj->Get_User_Data();
if (drawInfo)
{ draw = drawInfo->m_drawable;
//If we have a drawInfo but not drawable, we must be dealing with
//a ghost object which is always fogged.
if (!draw)
ss = OBJECTSHROUD_FOGGED;
}
// all this ambient business no longer handles the tinting and flashing stuff,
// but it does still light the drawable explicitly, and can be fudged like this
// infantry test does, here...
// the tint has been delegated to the getTint() stuff, below... MLorenzen
Vector3 ambient = Get_Ambient_Light();
if (draw && (drawableHidden=draw->isDrawableEffectivelyHidden()) != TRUE)
{
#ifdef NOT_IN_USE
const Vector3* drawAmbient = draw->getAmbientLight();
if (drawAmbient)
ambient.Add(ambient, *drawAmbient, &ambient);
#endif
obj = draw->getObject();
if (obj) {
ss = obj->getShroudedStatus(localPlayerIndex);
// For objects like planes, that pop out of the shroud, fire, then head back,
// we keep drawing them for 2 seconds after they return to the fogged area,
// so the player can see them and missiles chasing them. jba.
if (ss == OBJECTSHROUD_CLEAR) {
draw->setShroudClearFrame(TheGameLogic->getFrame());
} else if (ss >= OBJECTSHROUD_FOGGED && draw->getShroudClearFrame()!=0) {
UnsignedInt limit = 2*LOGICFRAMES_PER_SECOND;
if (obj->isEffectivelyDead()) {
limit += 3*LOGICFRAMES_PER_SECOND;
}
if (TheGameLogic->getFrame() < limit + draw->getShroudClearFrame()) {
// It's been less than 2 seconds since we could see them clear, so keep showing them.
ss = OBJECTSHROUD_PARTIAL_CLEAR;
}
}
if (!robj->Peek_Scene())
return; //this object was removed by the getShroudedStatus() call.
}
else
{ //drawable with no object so no way to know if it's shrouded.
ss = OBJECTSHROUD_CLEAR; //assume not shrouded/fogged.
//Check to see if there is another unrelated object which controls the shroud status
//(Hack for prison camps which contain enemy prisoner drawables)
if (drawInfo->m_shroudStatusObjectID != INVALID_ID)
{ Object *shroudObject=TheGameLogic->findObjectByID(drawInfo->m_shroudStatusObjectID);
if (shroudObject && shroudObject->getShroudedStatus(localPlayerIndex) >= OBJECTSHROUD_FOGGED)
ss = OBJECTSHROUD_SHROUDED; //we will assume that drawables without objects are 'particle' like and therefore don't need drawing if fogged/shrouded.
}
}
if (draw->isKindOf(KINDOF_INFANTRY))
{ ambient = m_infantryAmbient;
sceneLights = m_infantryLight;
}
lightEnv.Reset(sph.Center, ambient);
// HANDLE THE SPECIAL DRAWABLE-LEVEL COLORING SETTINGS FIRST
const Vector3 *tintColor = NULL;
const Vector3 *selectionColor = NULL;
tintColor = draw->getTintColor();
selectionColor = draw->getSelectionColor();
if ( tintColor || selectionColor )
{
Vector3 sumTint, temp, restore;
sumTint.Set(0,0,0);
if (tintColor)
Vector3::Add(sumTint, *tintColor, &sumTint);
if (selectionColor)
Vector3::Add(sumTint, *selectionColor, &sumTint);
for (Int globalLightIndex = 0; globalLightIndex < m_numGlobalLights; globalLightIndex++)
{
sceneLights[globalLightIndex]->Get_Diffuse( &temp );
restore = temp;
Vector3::Add(temp, sumTint, &temp);
sceneLights[globalLightIndex]->Set_Diffuse( temp );
lightEnv.Add_Light(*sceneLights[globalLightIndex]);
sceneLights[globalLightIndex]->Set_Diffuse( restore );
} // next light
temp = lightEnv.Get_Equivalent_Ambient();
Vector3::Add(sumTint, temp, &temp );
lightEnv.Set_Output_Ambient( temp );
}
else // no funny coloring going on, so just add the lights normally
{
for (Int globalLightIndex = 0; globalLightIndex < m_numGlobalLights; globalLightIndex++)
{
lightEnv.Add_Light(*sceneLights[globalLightIndex]);
}
}
//Apply custom render pass for any drawables with heatvision enabled
if (draw->getHeatVisionOpacity() != 0 )
{
rinfo.materialPassEmissiveOverride = draw->getHeatVisionOpacity();
if (draw->getStealthLook() == STEALTHLOOK_VISIBLE_DETECTED )
{
// THIS WILL EXPLICITLY SKIP THE FIRST PASS SO THAT HEATVISION ONLY WILL RENDER
rinfo.Push_Override_Flags(RenderInfoClass::RINFO_OVERRIDE_ADDITIONAL_PASSES_ONLY);
rinfo.Push_Material_Pass(m_heatVisionOnlyPass);
doExtraFlagsPop=TRUE;
}
else
{
//THIS CALLS FOR THE HEATVISION TO RENDER
rinfo.Push_Material_Pass(m_heatVisionMaterialPass);
}
doExtraMaterialPop=TRUE;
}
}
else
{ //either no drawable or it is hidden
if (drawableHidden)
return; //don't bother with anything else
//Render object without a drawable. Must be either some fluff/debug object or a ghostObject.
if (ss == OBJECTSHROUD_FOGGED)
{ //Must be ghost object because we don't fog normal things. Fogged objects always have a predefined
//lighting environment applied which emulates the look of fog.
rinfo.light_environment = &m_foggedLightEnv;
robj->Render(rinfo);
rinfo.light_environment = NULL;
return;
}
else
{ lightEnv.Reset(sph.Center, ambient);
for (Int globalLightIndex = 0; globalLightIndex < m_numGlobalLights; globalLightIndex++)
lightEnv.Add_Light(*m_globalLight[globalLightIndex]);
}
}
if (!drawableHidden)
{
//standard scene lights
RefRenderObjListIterator it2(&LightList);
for (it2.First(); !it2.Is_Done(); it2.Next())
{
LightClass *pLight = (LightClass*)it2.Peek_Obj();
SphereClass lSph = pLight->Get_Bounding_Sphere();
Bool cull = (pLight->Get_Type() == LightClass::POINT && !Spheres_Intersect(sph, lSph));
if (!cull) {
lightEnv.Add_Light(*pLight);
}
}
// dynamic lights
RefRenderObjListIterator dynaLightIt(&m_dynamicLightList);
for (dynaLightIt.First(); !dynaLightIt.Is_Done(); dynaLightIt.Next())
{
W3DDynamicLight* pDyna = (W3DDynamicLight*)dynaLightIt.Peek_Obj();
if (!pDyna->isEnabled()) {
continue;
}
SphereClass lSph = pDyna->Get_Bounding_Sphere();
if (pDyna->Get_Type() == LightClass::POINT && !Spheres_Intersect(sph, lSph)) {
continue;
}
lightEnv.Add_Light(*(LightClass*)dynaLightIt.Peek_Obj());
}
lightEnv.Pre_Render_Update(rinfo.Camera.Get_Transform());
rinfo.light_environment = &lightEnv;
if (drawInfo)
{
#if defined(_DEBUG) || defined(_INTERNAL)
if (!TheGlobalData->m_shroudOn)
ss = OBJECTSHROUD_CLEAR;
#endif
if (m_customPassMode == SCENE_PASS_DEFAULT)
{
if (ss <= OBJECTSHROUD_CLEAR)
robj->Render(rinfo);
else
{
rinfo.Push_Material_Pass(m_shroudMaterialPass);
robj->Render(rinfo);
rinfo.Pop_Material_Pass();
}
}
else
if (m_maskMaterialPass)
{ rinfo.Push_Material_Pass(m_maskMaterialPass);
rinfo.Push_Override_Flags(RenderInfoClass::RINFO_OVERRIDE_ADDITIONAL_PASSES_ONLY);
robj->Render(rinfo);
rinfo.Pop_Override_Flags();
rinfo.Pop_Material_Pass();
}
}//drawInfo exists so rendering a drawable.
else
{
robj->Render(rinfo);
}
}//drawable or robj is not hidden
rinfo.light_environment = NULL;
if (doExtraMaterialPop) //check if there is an extra material on the stack from the heatvision effect.
rinfo.Pop_Material_Pass();
if (doExtraFlagsPop)
rinfo.Pop_Override_Flags(); //flags used to disable base pass and only render custom heat vision pass.
}
//DECLARE_PERF_TIMER(translucentRender)
/**Draw everything that was submitted from this scene*/
void RTS3DScene::Flush(RenderInfoClass & rinfo)
{
//don't draw shadows in this mode because they interfere with destination alpha or are invisible (wireframe)
if (m_customPassMode == SCENE_PASS_DEFAULT && Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE)
DoShadows(rinfo, false); //draw all non-stencil shadows (decals) since they fall under other objects.
TheDX8MeshRenderer.Flush(); //draw all non-translucent objects.
//draw all non-translucent objects which were separated because they are hidden and need custom rendering.
#ifdef USE_NON_STENCIL_OCCLUSION
flushOccludedObjects(rinfo);
#else
if (DX8Wrapper::Has_Stencil())
flushOccludedObjectsIntoStencil(rinfo);
#endif
// Draw the trees last so they alpha blend onto everything correctly.
DoTrees(rinfo);
//don't draw shadows in this mode because they interfere with destination alpha
if (m_customPassMode == SCENE_PASS_DEFAULT && Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE)
DoShadows(rinfo, true); //draw all stencil shadows
WW3D::Render_And_Clear_Static_Sort_Lists(rinfo); //draws things like water
if (m_customPassMode == SCENE_PASS_DEFAULT && Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE)
flushTranslucentObjects(rinfo); //draw all translucent meshes which don't need per-poly sorting.
{
//USE_PERF_TIMER(translucentRender)
//don't draw transparent in this mode because they interfere with destination alpha
if (m_customPassMode == SCENE_PASS_DEFAULT && Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE)
DoParticles(rinfo); //queue up particles for rendering.
SortingRendererClass::Flush(); //draw sorted translucent polys like particles.
}
TheDX8MeshRenderer.Clear_Pending_Delete_Lists();
}
/**Generate a predefined light environment(s) that will be applied to many objects. Useful for things like totally fogged
objects and most generaic map objects that are not lit by dynamic lights.*/
void RTS3DScene::updateFixedLightEnvironments(RenderInfoClass & rinfo)
{
//Figure out how dimly lit fogged objects should be compared to fully lit.
Real foggedLightFrac = (Real)TheGlobalData->m_fogAlpha/(Real)TheGlobalData->m_clearAlpha;
Vector3 oldDiffuse;
Real infantryLightScale;
if( TheGlobalData->m_scriptOverrideInfantryLightScale != -1.0f )
infantryLightScale = TheGlobalData->m_scriptOverrideInfantryLightScale;
else
infantryLightScale = TheGlobalData->m_infantryLightScale[TheGlobalData->m_timeOfDay];
//Generate the default light environment
m_defaultLightEnv.Reset(Vector3(0,0,0), Get_Ambient_Light());
m_foggedLightEnv.Reset(Vector3(0,0,0), Get_Ambient_Light()*foggedLightFrac);
for (Int globalLightIndex = 0; globalLightIndex < m_numGlobalLights; globalLightIndex++)
{ m_defaultLightEnv.Add_Light(*m_globalLight[globalLightIndex]);
//copy default lighting for infantry so we can tweak it.
*m_infantryLight[globalLightIndex]=*m_globalLight[globalLightIndex];
m_globalLight[globalLightIndex]->Get_Diffuse(&oldDiffuse);
m_infantryLight[globalLightIndex]->Set_Diffuse(oldDiffuse*infantryLightScale);
m_globalLight[globalLightIndex]->Get_Ambient(&oldDiffuse);
m_infantryLight[globalLightIndex]->Set_Ambient(oldDiffuse*infantryLightScale);
m_infantryLight[globalLightIndex]->Set_Transform(m_globalLight[globalLightIndex]->Get_Transform());
//copy the normal light for fog so we can modify it
m_scratchLight->Set_Transform(m_globalLight[globalLightIndex]->Get_Transform());
//modify light with attenuated value to adjust for fog.
m_globalLight[globalLightIndex]->Get_Diffuse(&oldDiffuse);
m_scratchLight->Set_Diffuse(oldDiffuse*foggedLightFrac);
m_globalLight[globalLightIndex]->Get_Ambient(&oldDiffuse);
m_scratchLight->Set_Ambient(oldDiffuse*foggedLightFrac);
m_foggedLightEnv.Add_Light(*m_scratchLight);
}
m_defaultLightEnv.Pre_Render_Update(rinfo.Camera.Get_Transform());
m_foggedLightEnv.Pre_Render_Update(rinfo.Camera.Get_Transform());
m_infantryAmbient = Get_Ambient_Light();// * infantryLightScale; //for now don't adjust ambient so that we don't lose directional lighting.
}
/**Generate custom rendering passes for each potential player color. This is currently only used
to render occluded objects using the color of the player*/
void RTS3DScene::updatePlayerColorPasses(void)
{
#ifdef USE_NON_STENCIL_OCCLUSION
Vector3 hsv,rgb;
if (TheGlobalData->m_enableBehindBuildingMarkers && TheGameLogic->getShowBehindBuildingMarkers())
{
Int numPlayers=ThePlayerList->getPlayerCount();
for (Int i=0; igetNthPlayer(i);
Int playerIndex=player->getPlayerIndex();
Real red,green,blue,alpha;
GameGetColorComponentsReal(player->getPlayerColor(),&red,&green,&blue,&alpha);
RGB_To_HSV(hsv,Vector3(red,green,blue));
hsv.Z*=TheGlobalData->m_occludedLuminanceScale;
HSV_To_RGB(rgb,hsv);
VertexMaterialClass *vmat=m_occludedMaterialPass[playerIndex]->Peek_Material();
vmat->Set_Emissive(rgb);
}
}
#endif
}
#define ZBias 0.0001f
//DECLARE_PERF_TIMER(NonTerrainRender)
void RTS3DScene::Render(RenderInfoClass & rinfo)
{
//USE_PERF_TIMER(NonTerrainRender)
DX8Wrapper::Set_Fog(FogEnabled, FogColor, FogStart, FogEnd);
//Override the behind building selection if it's not available on current hardware (needs stencil).
TheWritableGlobalData->m_enableBehindBuildingMarkers = TheWritableGlobalData->m_enableBehindBuildingMarkers && DX8Wrapper::Has_Stencil();
if (Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE)
{
if (m_customPassMode == SCENE_PASS_DEFAULT)
{ //Regular rendering pass with no effects
updatePlayerColorPasses();///@todo: this probably doesn't need to be done each frame.
updateFixedLightEnvironments(rinfo);
Customized_Render(rinfo);
Flush(rinfo);
}
else
if (m_customPassMode == SCENE_PASS_ALPHA_MASK)
{
//a projected alpha texture which will later be used to determine where
//wireframe should be visible.
///@todo: Clearing to black may not be needed if the scene already did the clear.
DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_ALPHA);
DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 0);
//Since all objects will be rendered with same material, disable resetting until all are done.
m_maskMaterialPass->setAllowUninstall(FALSE);
Customized_Render(rinfo); //render mask into alpha channel and fill z-buffer with depth values.
Flush(rinfo);
m_maskMaterialPass->setAllowUninstall(TRUE);
m_maskMaterialPass->UnInstall_Materials();
DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED);
ShaderClass::Invalidate();
}
}
else
{
Bool old_enable=WW3D::Is_Texturing_Enabled();
if (Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_CLEAR_LINE)
{ //render scene with solid black color but have destination alpha store
//a projected alpha texture which will later be used to determine where
//wireframe should be visible.
///@todo: Clearing to black may not be needed if the scene already did the clear.
DX8Wrapper::Clear(true, false, Vector3(0.0f,0.0f,0.0f),1.0f); // Clear color but not z
DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_ALPHA);
DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 0);
//We're only filling the z-buffer so ignore normal textures and state changes to speed things up.
m_customPassMode = SCENE_PASS_ALPHA_MASK;
m_maskMaterialPass->setAllowUninstall(FALSE);
Customized_Render(rinfo); //render mask into alpha channel and fill z-buffer with depth values.
Flush(rinfo);
m_maskMaterialPass->setAllowUninstall(TRUE);
m_maskMaterialPass->UnInstall_Materials();
DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED);
WW3D::Enable_Coloring(0xff008000);
WW3D::Enable_Texturing(false);
DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
//Move maximum z-buffer value in a little to shift all z-values closer
//and thus forcing line to appear on top of previous pass.
Real nearZ,farZ;
rinfo.Camera.Get_Zbuffer_Range(nearZ, farZ);
rinfo.Camera.Set_Zbuffer_Range(nearZ, farZ-ZBias);
rinfo.Camera.Apply();
// DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 4);
Customized_Render(rinfo); //render wireframe where z-test passes
Flush(rinfo);
DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_SOLID);
rinfo.Camera.Set_Zbuffer_Range(nearZ, farZ);
rinfo.Camera.Apply();
// DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 0);
WW3D::Enable_Texturing(old_enable);
WW3D::Enable_Coloring(0);
ShaderClass::Invalidate();
}
else
{ //old W3D custom rendering code.
//Disable writes to color buffer to save memory bandwidth - we only need Z.
DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,0);
DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 0);
Customized_Render(rinfo);
Flush(rinfo);
//Re-enable writes to color buffer.
DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED);
switch (Get_Extra_Pass_Polygon_Mode()) {
case EXTRA_PASS_LINE:
WW3D::Enable_Texturing(false);
DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 7);
Customized_Render(rinfo);
break;
case EXTRA_PASS_CLEAR_LINE:
DX8Wrapper::Clear(true, false, Vector3(0.0f,0.0f,0.0f), 0.0f); // Clear color but not z
WW3D::Enable_Texturing(false);
WW3D::Enable_Coloring(0xff008000);
DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 7);
Customized_Render(rinfo);
break;
}
Flush(rinfo);
DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_SOLID);
DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 0);
WW3D::Enable_Texturing(old_enable);
WW3D::Enable_Coloring(0);
ShaderClass::Invalidate();
}
}
}
//=============================================================================
// RTS3DScene::Customized_Renderer
//=============================================================================
/** Custom render method for the RTS3DScene, custom render properties for our
* particular game go here */
//=============================================================================
void RTS3DScene::Customized_Render( RenderInfoClass &rinfo )
{
#ifdef DIRTY_CONDITION_FLAGS
StDrawableDirtyStuffLocker lockDirtyStuff;
#endif
RenderObjClass *terrainObject=NULL,*robj;
m_translucentObjectsCount = 0; //start of new frame so no translucent objects
m_occludedObjectsCount = 0;
Int localPlayerIndex = ThePlayerList ? ThePlayerList->getLocalPlayer()->getPlayerIndex() : 0;
#define USE_LIGHT_ENV 1
if (!Visibility_Checked) {
// set the visibility bit in all render objects in all layers.
Visibility_Check(&rinfo.Camera);
#ifdef USE_NON_STENCIL_OCCLUSION
flagOccludedObjects(&rinfo.Camera);
#endif
}
Visibility_Checked = false;
RefRenderObjListIterator it(&UpdateList);
// allow all objects in the update list to do their "every frame" processing
for (it.First(); !it.Is_Done(); it.Next()) {
RenderObjClass * robj = it.Peek_Obj();
if (robj->Class_ID() == RenderObjClass::CLASSID_TILEMAP)
terrainObject=robj; //found terrain object, store for later.
if (!ShaderClass::Is_Backface_Culling_Inverted()) {
// If we are doing water mirror, we draw with backface culling inverted. In this case,
// we only want to call On_Frame_Update if we aren't drawing water, as otherwise
// we get 2 frame updates per frame, and it screws up the particle emitters.
it.Peek_Obj()->On_Frame_Update();
}
}
//terrain needs to be rendered first
if (terrainObject) // Don't check visibility - terrain is always visible. jba.
{
robj=terrainObject;
rinfo.light_environment = NULL; // Terrain is self lit.
rinfo.Camera.Set_User_Data(this); //pass the scene to terrain via user data.
if (m_customPassMode == SCENE_PASS_DEFAULT && m_shroudMaterialPass)
{
rinfo.Push_Material_Pass(m_shroudMaterialPass);
robj->Render(rinfo);
rinfo.Pop_Material_Pass();
}
else
if (m_customPassMode == SCENE_PASS_ALPHA_MASK && m_maskMaterialPass)
{
rinfo.Push_Material_Pass(m_maskMaterialPass);
robj->Render(rinfo);
rinfo.Pop_Material_Pass();
}
else
robj->Render(rinfo);
}
if (m_drawTerrainOnly) {
return;
}
#ifdef EXTENDED_STATS
if (DX8Wrapper::stats.m_disableObjects) {
return;
}
#endif
// loop through all render objects in the list:
for (it.First(&RenderList); !it.Is_Done();)
{
// get the render object
robj = it.Peek_Obj();
it.Next(); //advance to next object in case this one gets deleted during renderOneObject().
if (robj->Class_ID() == RenderObjClass::CLASSID_TILEMAP)
continue; //we already rendered terrain
if (robj->Is_Really_Visible()) {
DrawableInfo *drawInfo = (DrawableInfo *)robj->Get_User_Data();
Drawable *draw=NULL;
if (drawInfo)
draw = drawInfo->m_drawable;
#ifdef USE_NON_STENCIL_OCCLUSION
if (!(draw && drawInfo->m_flags & DrawableInfo::ERF_DELAYED_RENDER)) //model rendering is delayed for some reason until end of normal scene
#else
if (!(draw && drawInfo->m_flags & (DrawableInfo::ERF_DELAYED_RENDER|DrawableInfo::ERF_POTENTIAL_OCCLUDER|DrawableInfo::ERF_IS_NON_OCCLUDER_OR_OCCLUDEE))) //in this mode we delay almost all objects in order to do correct sorting with stencil.
#endif
renderOneObject(rinfo, robj, localPlayerIndex);
}
}
#ifdef INCLUDE_GRANNY_IN_BUILD
if (TheGrannyRenderObjSystem)
{ //we only want to update granny animations once per frame, so only queue
//them up if this is not a mirror pass.
if (!ShaderClass::Is_Backface_Culling_Inverted())
TheGrannyRenderObjSystem->queueUpdate();
TheGrannyRenderObjSystem->Flush();
}
#endif
//Tell shadow manager to render shadows at the end of this frame
//Don't draw shadows if there is no terrain present.
if (TheW3DShadowManager && terrainObject && !ShaderClass::Is_Backface_Culling_Inverted() &&
Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE)
TheW3DShadowManager->queueShadows(TRUE);
// only render particles once per frame
if (terrainObject != NULL && TheParticleSystemManager != NULL &&
Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE)
{ TheParticleSystemManager->queueParticleRender();
}
} // end Customized_Renderer
/**Convert a player index to a color index, we use this because color indices are
assigned in left-right binary flipped fashion so as not to occupy lower bits unless
necessary. This is used because lower bits are free for use by stencil shadow
rendering*/
#define NUMBER_PLAYER_COLOR_BITS 4 //need 4 bits to encode 8 players because indices start at 1 (not 0).
Int playerIndexToColorIndex(Int playerIndex)
{
Int tmp=playerIndex;
Int result=0;
Int flippedPosition;
//Player index is stored in 4 bits
for (Int i=0; i i)
{ //shifting left
result |= (tmp & (1<>(i-flippedPosition);
}
}
return result;
}
/**Utility function used to render a full screen quad with the specified color and
stencil mask*/
void renderStenciledPlayerColor( UnsignedInt color, UnsignedInt stencilRef, Bool clear=FALSE)
{
struct _TRANSLITVERTEX {
Vector4 p;
DWORD color; // diffuse color
} v[4];
Int xpos, ypos, width, height;
TheTacticalView->getOrigin(&xpos,&ypos);
width=TheTacticalView->getWidth();
height=TheTacticalView->getHeight();
v[0].p.Set(xpos+width, ypos+height, 0.0f, 1.0f );
v[1].p.Set(xpos+width, 0, 0.0f, 1.0f );
v[2].p.Set(xpos, ypos+height, 0.0f, 1.0f );
v[3].p.Set(xpos, 0, 0.0f, 1.0f );
v[0].color = color;
v[1].color = color;
v[2].color = color;
v[3].color = color;
DX8Wrapper::Set_Shader(PlayerColorShader);
VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE);
DX8Wrapper::Set_Material(vmat);
REF_PTR_RELEASE(vmat);
DX8Wrapper::Apply_Render_State_Changes(); //force update all renderstates
LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8();
if (!m_pDev)
return; //need device to render anything.
//draw polygons like this is very inefficient but for only 2 triangles, it's
//not worth bothering with index/vertex buffers.
m_pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE);
// Set stencil states
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, TRUE );
DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE );
DWORD oldColorWriteEnable=0x12345678;
if (clear)
{ //we want to clear the stencil buffer to some known value whereever a player index is stored
Int occludedMask=TheW3DShadowManager->getStencilShadowMask();
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, 0x80808080 );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILMASK, occludedMask ); //isolate bits containing occluder|playerIndex
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILWRITEMASK,0xffffffff );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_LESS ); //only draw to pixels that match the reference value
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, D3DSTENCILOP_REPLACE );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE ); //pixels which had occluded player colors, get MSB set.
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_ZERO ); //pixels which had no occluded player colors are cleared.
DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC, D3DCMP_NEVER ); //fail all access to the frame buffer to improve memory bandwidth
//disable writes to color buffer
if (DX8Caps::Get_Default_Caps().PrimitiveMiscCaps & D3DPMISCCAPS_COLORWRITEENABLE)
{ DX8Wrapper::_Get_D3D_Device8()->GetRenderState(D3DRS_COLORWRITEENABLE, &oldColorWriteEnable);
DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,0);
}
else
{ //device does not support disabling writes to color buffer so fake it through alpha blending
DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, TRUE);
DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_ZERO );
DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_ONE );
}
}
else
{ DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, stencilRef );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILMASK, 0xffffffff );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILWRITEMASK,0xffffffff );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_EQUAL );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP );
//Make occluded pixels transparent
DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, TRUE);
DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );
}
if (DX8Wrapper::_Is_Triangle_Draw_Enabled())
m_pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANSLITVERTEX));
// turn off the stencil buffer
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, FALSE );
DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, FALSE); //restore shader state
DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_ONE );
DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_ZERO );
DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC, D3DCMP_ALWAYS);
if (oldColorWriteEnable != 0x12345678)
DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,oldColorWriteEnable);
} // end renderStencilShadows
#define MAX_VISIBLE_OCCLUDED_PLAYER_OBJECTS 512 //maximum number of occluded objects permitted per player
void RTS3DScene::flushOccludedObjectsIntoStencil(RenderInfoClass & rinfo)
{
RenderObjClass *robj;
Drawable *draw;
RenderObjClass *playerObjects[MAX_PLAYER_COUNT][MAX_VISIBLE_OCCLUDED_PLAYER_OBJECTS];
RenderObjClass **lastPlayerObject[MAX_PLAYER_COUNT];
Int playerColorIndex[MAX_PLAYER_COUNT];
Int visiblePlayerColors[MAX_PLAYER_COUNT]; ///setStencilShadowMask(0);
Int localPlayerIndex = ThePlayerList ? ThePlayerList->getLocalPlayer()->getPlayerIndex() : 0;
if (m_numPotentialOccludees && m_numPotentialOccluders)
{
//bucket sort all possibly occluded objects by player index/color.
for (Int i=0; iGet_User_Data())->m_drawable;
Object *object=draw->getObject();
Int index=object->getControllingPlayer()->getPlayerIndex();
if ((lastPlayerObject[index]-&playerObjects[index][0]) >= MAX_VISIBLE_OCCLUDED_PLAYER_OBJECTS)
{
DEBUG_ASSERTCRASH(FALSE,("Exceeded Maximum Number of potentially occluded models"));
continue;
}
*lastPlayerObject[index] = robj;
lastPlayerObject[index]++; //increment to next object
}
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, TRUE );
DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILMASK, 0xffffffff);
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILWRITEMASK, 0xffffffff);
//Always store player index into stencil unless it is occluded by another
//player's potentially occluded objects.
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_ALWAYS );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE );
//Find out which player indices are actually used and remap them to
//a color index. Render all objects using the same color index at once.
//We render potential occludees first because this allows them to z-sort correctly
//when they are behind an occluder.
for (i=0; iGet_User_Data())->m_drawable;
Object *object=draw->getObject();
Int color=object->getControllingPlayer()->getPlayerColor();
RGB_To_HSV(hsv,Vector3(((color>>16)&0xff)/255.0f,((color>>8)&0xff)/255.0f,(color &0xff)/255.0f));
hsv.Z*=TheGlobalData->m_occludedLuminanceScale;
HSV_To_RGB(rgb,hsv);
visiblePlayerColors[numVisiblePlayerColors++]=DX8Wrapper::Convert_Color(rgb,0.5f);
}
Int thisPlayerColorIndex=playerColorIndex[i];
//Store this object's color index into bits 3-6 of stencil buffer
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, thisPlayerColorIndex<<3);
//Render all of this player's objects for which we care when they are occluded.
RenderObjClass **renderList=&playerObjects[i][0];
for (Int j=0; jsetStencilShadowMask(usedPlayerColorBits);
if (numVisiblePlayerColors >= 8 && TheGlobalData->m_useShadowVolumes)
{ //for cases where we have 8 or more visible players, we're only left with 3 bits to store
//stencil shadows. That's probably not enough since it will only allow 7 overlapping shadows.
//So we clear the stencil buffer, leaving only the MSB set on any occluded player pixels so that
//shadow code knows not to overwrite these pixels.
renderStenciledPlayerColor(0,0, TRUE);
TheW3DShadowManager->setStencilShadowMask(0x80808080); //msb indicates occluded player pixels so ignore it when filling screen with shadow
}
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, FALSE );
}
else
if (m_numNonOccluderOrOccludee || m_numPotentialOccluders || m_numPotentialOccludees)
{ //no occluded objects so don't need to render anything special. Just draw the queued up
//objects like normal because they were skipped in the main scene traversal.
RenderObjClass **occludeeList=m_potentialOccludees;
for (i=0; iGet_Ambient_Light(),0.0f));
}
/*Version which does not require stencil buffer*/
void RTS3DScene::flushOccludedObjects(RenderInfoClass & rinfo)
{
RenderObjClass *robj;
Drawable *draw;
TheW3DShadowManager->setStencilShadowMask(0);
if (m_occludedObjectsCount)
{
Int localPlayerIndex = ThePlayerList ? ThePlayerList->getLocalPlayer()->getPlayerIndex() : 0;
if (DX8Wrapper::Has_Stencil()) //just in case we have shadows, disable them over occluded pixels.
{
//Set all stencil pixels of potentially occluded objects to 128.
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, TRUE );
DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, 128 );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILMASK, 0xffffffff );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILWRITEMASK,0xffffffff );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE );
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_ALWAYS );
}
//First draw all the solid colored models
///@todo: Optimize this so that the extra passes don't actually install the material since it's all the same.
rinfo.Push_Override_Flags(RenderInfoClass::RINFO_OVERRIDE_ADDITIONAL_PASSES_ONLY); //disable textures
for (Int i=0; iGet_User_Data())->m_drawable;
Object *object=draw->getObject();
Int index=object->getControllingPlayer()->getPlayerIndex();
rinfo.Push_Material_Pass(m_occludedMaterialPass[index]);
robj->Render(rinfo);
rinfo.Pop_Material_Pass();
}
rinfo.Pop_Override_Flags();
TheDX8MeshRenderer.Flush();
//Now draw the normal models so they cover up the colored models on any pixels that
//Now draw the normal models so they cover up the colored models on any pixels that
//Normal models will clear stencil value from 128 back to 0 where the object pixels are
//not occluded but will leave 128 in stencil where still occluded.
if (DX8Wrapper::Has_Stencil())
DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILREF, 0 );
for (i=0; isetStencilShadowMask(0x80808080); //upper MSB always contains flag indicating occluded player color.
}
//Reset scene ambient because we sometimes mess around with it to make objects
//glow, etc. when processing drawables. This is a good place to do it because this
//function gets called right after we flush regular render objects.
DX8Wrapper::Set_DX8_Render_State(D3DRS_AMBIENT,DX8Wrapper::Convert_Color(this->Get_Ambient_Light(),0.0f));
}
void RTS3DScene::flushTranslucentObjects(RenderInfoClass & rinfo)
{
RenderObjClass *robj;
Drawable *draw;
if (m_translucentObjectsCount)
{
Int localPlayerIndex = ThePlayerList ? ThePlayerList->getLocalPlayer()->getPlayerIndex() : 0;
for (Int i=0; iGet_User_Data())->m_drawable;
rinfo.alphaOverride = draw->getEffectiveOpacity();
renderOneObject(rinfo, robj, localPlayerIndex);//WW3D::Render(*robj,rinfo);
}
//Flush all the submitted translucent objects.
TheDX8MeshRenderer.Flush();
WW3D::Render_And_Clear_Static_Sort_Lists(rinfo); //draws things like water
rinfo.alphaOverride = 1.0f; //disable forced alpha
m_translucentObjectsCount = 0;
}
//Reset scene ambient because we sometimes mess around with it to make objects
//glow, etc. when processing drawables. This is a good place to do it because this
//function gets called right after we flush regular render objects.
DX8Wrapper::Set_DX8_Render_State(D3DRS_AMBIENT,DX8Wrapper::Convert_Color(this->Get_Ambient_Light(),0.0f));
}
//=============================================================================
// RTS3DScene::createLightsIterator
//=============================================================================
/** Returns an iterator of the lights in the scene. */
//=============================================================================
RefRenderObjListIterator * RTS3DScene::createLightsIterator(void)
{
RefRenderObjListIterator * it = NEW RefRenderObjListIterator(&LightList); // poolify
return it;
}
//=============================================================================
// RTS3DScene::destroyLightsIterator
//=============================================================================
/** Destroys the iterator returned by createLightsIterator. */
//=============================================================================
void RTS3DScene::destroyLightsIterator(RefRenderObjListIterator * it)
{
delete it;
}
//=============================================================================
// RTS3DScene::addDynamicLight
//=============================================================================
/** Adds a dynamic light. */
//=============================================================================
void RTS3DScene::addDynamicLight(W3DDynamicLight * obj)
{
m_dynamicLightList.Add(obj);
UpdateList.Add(obj);
}
//=============================================================================
// RTS3DScene::addDynamicLight
//=============================================================================
/** Adds a dynamic light. */
//=============================================================================
W3DDynamicLight * RTS3DScene::getADynamicLight(void)
{
RefRenderObjListIterator dynaLightIt(&m_dynamicLightList);
W3DDynamicLight *pLight;
for (dynaLightIt.First(); !dynaLightIt.Is_Done(); dynaLightIt.Next())
{
pLight = (W3DDynamicLight*)dynaLightIt.Peek_Obj();
if (!pLight->isEnabled()) {
pLight->setEnabled(true);
return(pLight);
}
}
pLight = NEW_REF(W3DDynamicLight, ());
addDynamicLight( pLight );
pLight->Release_Ref();
pLight->setEnabled(true);
return(pLight);
}
//=============================================================================
// RTS3DScene::removeDynamicLight
//=============================================================================
/** Removes a dynamic light. */
//=============================================================================
void RTS3DScene::removeDynamicLight(W3DDynamicLight * obj)
{
m_dynamicLightList.Remove(obj);
}
//=============================================================================
// RTS3DScene::doRender
//=============================================================================
/** Render the scene */
//=============================================================================
void RTS3DScene::doRender( CameraClass * cam )
{
m_camera = cam;
DRAW();
m_camera = NULL;
} // end Customized_Render
//=============================================================================
// RTS3DScene::draw
//=============================================================================
/** Customized render for the 2d scene management */
//=============================================================================
void RTS3DScene::draw( )
{
if (m_camera == NULL) {
DEBUG_CRASH(("Null m_camera in RTS3DScene::draw"));
return;
}
WW3D::Render( this, m_camera );
} // end Customized_Render
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//=============================================================================
// RTS2DScene::RTS2DScene
//=============================================================================
/** */
//=============================================================================
RTS2DScene::RTS2DScene()
{
setName("RTS2DScene");
m_status = NEW_REF( W3DStatusCircle, () );
Add_Render_Object( m_status );
} // end RTS2DScene
//=============================================================================
// RTS2DScene::~RTS2DScene
//=============================================================================
/** */
//=============================================================================
RTS2DScene::~RTS2DScene()
{
this->Remove_Render_Object(m_status);
REF_PTR_RELEASE(m_status);
} // end ~RTS2DScene
//=============================================================================
// RTS2DScene::Custimized_Render
//=============================================================================
/** Customized render for the 2d scene management */
//=============================================================================
void RTS2DScene::Customized_Render( RenderInfoClass &rinfo )
{
// call simple scene class renderer
SimpleSceneClass::Customized_Render( rinfo );
} // end Customized_Render
//=============================================================================
// RTS2DScene::doRender
//=============================================================================
/** Render the scene */
//=============================================================================
void RTS2DScene::doRender( CameraClass * cam )
{
m_camera = cam;
DRAW();
m_camera = NULL;
} // end Customized_Render
//=============================================================================
// RTS2DScene::draw
//=============================================================================
/** Customized render for the 2d scene management */
//=============================================================================
void RTS2DScene::draw( )
{
if (m_camera == NULL) {
DEBUG_CRASH(("Null m_camera in RTS2DScene::draw"));
return;
}
WW3D::Render( this, m_camera );
} // end Customized_Render
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//=============================================================================
// RTS3DInterfaceScene::RTS3DInterfaceScene
//=============================================================================
/** */
//=============================================================================
RTS3DInterfaceScene::RTS3DInterfaceScene()
{
} // end RTS3DInterfaceScene
//=============================================================================
// RTS3DInterfaceScene::~RTS3DInterfaceScene
//=============================================================================
/** */
//=============================================================================
RTS3DInterfaceScene::~RTS3DInterfaceScene()
{
} // end ~RTS3DInterfaceScene
//=============================================================================
// RTS3DInterfaceScene::Custimized_Render
//=============================================================================
/** Customized render for the 3d interface scene management */
//=============================================================================
void RTS3DInterfaceScene::Customized_Render( RenderInfoClass &rinfo )
{
// call simple scene class renderer
SimpleSceneClass::Customized_Render( rinfo );
} // end Customized_Render