/*
** 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: W3DRadar.cpp /////////////////////////////////////////////////////////////////////////////
// Author: Colin Day, January 2002
// Desc: W3D radar implementation, this has the necessary device dependent drawing
// necessary for the radar
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "Common/AudioEventRTS.h"
#include "Common/Debug.h"
#include "Common/GlobalData.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "GameLogic/TerrainLogic.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/StealthUpdate.h"
#include "GameClient/Color.h"
#include "GameClient/Display.h"
#include "GameClient/GameClient.h"
#include "GameClient/GameWindow.h"
#include "GameClient/Image.h"
#include "GameClient/Line2D.h"
#include "GameClient/TerrainVisual.h"
#include "W3DDevice/Common/W3DRadar.h"
#include "W3DDevice/GameClient/HeightMap.h"
#include "W3DDevice/GameClient/W3DShroud.h"
#include "WW3D2/Texture.h"
#include "WW3D2/DX8Caps.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// PRIVATE DATA ///////////////////////////////////////////////////////////////////////////////////
enum { OVERLAY_REFRESH_RATE = 6 }; ///< over updates once this many frames
//-------------------------------------------------------------------------------------------------
/** Is the point legal, that is, inside the resolution of the radar cells */
//-------------------------------------------------------------------------------------------------
inline Bool legalRadarPoint( Int px, Int py )
{
if( px < 0 || py < 0 || px >= RADAR_CELL_WIDTH || py >= RADAR_CELL_HEIGHT )
return FALSE;
return TRUE;
}
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
static WW3DFormat findFormat(const WW3DFormat formats[])
{
for( Int i = 0; formats[ i ] != WW3D_FORMAT_UNKNOWN; i++ )
{
if( DX8Caps::Support_Texture_Format( formats[ i ] ) )
{
return formats[ i ];
} // end if
} // end for i
DEBUG_CRASH(("WW3DRadar: No appropriate texture format\n") );
return WW3D_FORMAT_UNKNOWN;
}
//-------------------------------------------------------------------------------------------------
/** Find the texture format we're going to use for the radar. The texture format must
* be supported by the hardware. The "more preferred" formats appear at the top of
* the format tables in order from most preferred to least preferred */
//-------------------------------------------------------------------------------------------------
void W3DRadar::initializeTextureFormats( void )
{
const WW3DFormat terrainFormats[] =
{
WW3D_FORMAT_R8G8B8,
WW3D_FORMAT_X8R8G8B8,
WW3D_FORMAT_R5G6B5,
WW3D_FORMAT_X1R5G5B5,
WW3D_FORMAT_UNKNOWN // keep this one last
};
const WW3DFormat overlayFormats[] =
{
WW3D_FORMAT_A8R8G8B8,
WW3D_FORMAT_A4R4G4B4,
WW3D_FORMAT_UNKNOWN // keep this one last
};
const WW3DFormat shroudFormats[] =
{
WW3D_FORMAT_A8R8G8B8,
WW3D_FORMAT_A4R4G4B4,
WW3D_FORMAT_UNKNOWN // keep this one last
};
// find a format for the terrain texture
m_terrainTextureFormat = findFormat(terrainFormats);
// find a format for the overlay texture
m_overlayTextureFormat = findFormat(overlayFormats);
// find a format for the shroud texture
m_shroudTextureFormat = findFormat(shroudFormats);
} // end initializeTextureFormats
//-------------------------------------------------------------------------------------------------
/** Delete resources used specifically in this W3D radar implemetation */
//-------------------------------------------------------------------------------------------------
void W3DRadar::deleteResources( void )
{
//
// delete terrain resources used
//
if( m_terrainTexture )
m_terrainTexture->Release_Ref();
m_terrainTexture = NULL;
if( m_terrainImage )
m_terrainImage->deleteInstance();
m_terrainImage = NULL;
//
// delete overlay resources used
//
if( m_overlayTexture )
m_overlayTexture->Release_Ref();
m_overlayTexture = NULL;
if( m_overlayImage )
m_overlayImage->deleteInstance();
m_overlayImage = NULL;
//
// delete shroud resources used
//
if( m_shroudTexture )
m_shroudTexture->Release_Ref();
m_shroudTexture = NULL;
if( m_shroudImage )
m_shroudImage->deleteInstance();
m_shroudImage = NULL;
} // end deleteResources
//-------------------------------------------------------------------------------------------------
/** Reconstruct the view box given the current camera settings */
//-------------------------------------------------------------------------------------------------
void W3DRadar::reconstructViewBox( void )
{
Coord3D world[ 4 ];
ICoord2D radar[ 4 ];
Int i;
// get the 4 points of the view corners in the 3D world at the average Z height in the map
TheTacticalView->getScreenCornerWorldPointsAtZ( &world[ 0 ],
&world[ 1 ],
&world[ 2 ],
&world[ 3 ],
getTerrainAverageZ() );
// convert each of the 4 points in the world to radar cell positions
for( i = 0; i < 4; i++ )
{
// first convert to radar cells
radar[ i ].x = world[ i ].x / (m_mapExtent.width() / RADAR_CELL_WIDTH);
radar[ i ].y = world[ i ].y / (m_mapExtent.height() / RADAR_CELL_HEIGHT);
//
// store these points in the view box array which contains a first position
// of (0,0) and then offsets for each additional entry point
//
if( i == 0 )
{
m_viewBox[ i ].x = 0;
m_viewBox[ i ].y = 0;
} // end if
else
{
m_viewBox[ i ].x = radar[ i ].x - radar[ i - 1 ].x;
m_viewBox[ i ].y = radar[ i ].y - radar[ i - 1 ].y;
} // end else
} // end for i
//
// save the camera settings for this view box, we will need to make it again only
// if some of these change
//
m_viewAngle = TheTacticalView->getAngle();
Coord3D pos;
TheTacticalView->getPosition( &pos );
m_viewZoom = TheTacticalView->getZoom();
m_reconstructViewBox = FALSE;
} // end reconstructViewBox
//-------------------------------------------------------------------------------------------------
/** Convert radar position to actual pixel coord */
//-------------------------------------------------------------------------------------------------
void W3DRadar::radarToPixel( const ICoord2D *radar, ICoord2D *pixel,
Int radarUpperLeftX, Int radarUpperLeftY,
Int radarWidth, Int radarHeight )
{
// sanity
if( radar == NULL || pixel == NULL )
return;
pixel->x = (radar->x * radarWidth / RADAR_CELL_WIDTH) + radarUpperLeftX;
// note the "inverted" y here to orient the way our world looks with +x=right and -y=down
pixel->y = ((RADAR_CELL_HEIGHT - 1 - radar->y) * radarHeight / RADAR_CELL_HEIGHT) + radarUpperLeftY;
} // end radarToPixel
//-------------------------------------------------------------------------------------------------
/** Draw a hero icon at a position, given radar box upper left location and dimensions. */
//-------------------------------------------------------------------------------------------------
void W3DRadar::drawHeroIcon( Int pixelX, Int pixelY, Int width, Int height, const Coord3D *pos )
{
// get the hero icon image
static const Image *image = (Image *)TheMappedImageCollection->findImageByName("HeroReticle");
if (image != NULL)
{
// convert world to radar coords
ICoord2D ulRadar;
ulRadar.x = pos->x / (m_mapExtent.width() / RADAR_CELL_WIDTH);
ulRadar.y = pos->y / (m_mapExtent.height() / RADAR_CELL_HEIGHT);
// convert radar to screen coords
ICoord2D offsetScreen;
radarToPixel( &ulRadar, &offsetScreen, pixelX, pixelY, width, height );
// shift from an upper left to a center focus for the icon
int iconWidth = image->getImageWidth();
int iconHeight = image->getImageHeight();
offsetScreen.x -= (iconWidth / 2) - 1;
offsetScreen.y -= iconHeight / 2;
// draw the icon
TheDisplay->drawImage( image, offsetScreen.x , offsetScreen.y, offsetScreen.x + iconWidth, offsetScreen.y + iconHeight );
}
}
//-------------------------------------------------------------------------------------------------
/** Draw a "box" into the texture passed in that represents the viewable area for
* the tactical display into the game world */
//-------------------------------------------------------------------------------------------------
void W3DRadar::drawViewBox( Int pixelX, Int pixelY, Int width, Int height )
{
ICoord2D ulScreen;
ICoord2D ulRadar;
Coord3D ulWorld;
ICoord2D ulStart = { 0, 0 };
ICoord2D start, end;
ICoord2D clipStart, clipEnd;
Real lineWidth = 1.0f;
Color topColor = GameMakeColor( 225, 225, 0, 255 );
Color bottomColor = GameMakeColor( 158, 158, 0, 255 );
//
// setup the clipping region ... note that this clipping region is not over just the
// radar image area ... it's in the WHOLE window available for the radar
//
IRegion2D clipRegion;
ICoord2D radarWindowSize, radarWindowScreenPos;
m_radarWindow->winGetSize( &radarWindowSize.x, &radarWindowSize.y );
m_radarWindow->winGetScreenPosition( &radarWindowScreenPos.x, &radarWindowScreenPos.y );
clipRegion.lo.x = radarWindowScreenPos.x;
clipRegion.lo.y = radarWindowScreenPos.y;
clipRegion.hi.x = radarWindowScreenPos.x + radarWindowSize.x;
clipRegion.hi.y = radarWindowScreenPos.y + radarWindowSize.y;
// convert top left of screen into world position
TheTacticalView->getOrigin( &ulScreen.x, &ulScreen.y );
TheTacticalView->screenToWorldAtZ( &ulScreen, &ulWorld, getTerrainAverageZ() );
// convert world to radar coords
ulRadar.x = ulWorld.x / (m_mapExtent.width() / RADAR_CELL_WIDTH);
ulRadar.y = ulWorld.y / (m_mapExtent.height() / RADAR_CELL_HEIGHT);
//
// convert radar point to actual pixel coords on the screen, shifted
// into position on the radar for where the radar is drawn and the size of the
// area that the radar is drawn in
//
radarToPixel( &ulRadar, &ulStart, pixelX, pixelY, width, height );
//
// using our view box offset array, convert each of those radar cell offset points
// into screen pixels and draw the box. The view box array is setup with the
// first index containing (0,0) (the point we just converted in theory), with cell
// offsets to each of the other corners in the following order
// (upper left, upper right, lower right, lower left)
//
ICoord2D radar;
// top line
start = ulStart;
radar.x = ulRadar.x + m_viewBox[ 1 ].x;
radar.y = ulRadar.y + m_viewBox[ 1 ].y;
radarToPixel( &radar, &end, pixelX, pixelY, width, height );
if( ClipLine2D( &start, &end, &clipStart, &clipEnd, &clipRegion ) )
TheDisplay->drawLine( clipStart.x, clipStart.y, clipEnd.x, clipEnd.y,
lineWidth, topColor );
// right line
start = end;
radar.x += m_viewBox[ 2 ].x;
radar.y += m_viewBox[ 2 ].y;
radarToPixel( &radar, &end, pixelX, pixelY, width, height );
if( ClipLine2D( &start, &end, &clipStart, &clipEnd, &clipRegion ) )
TheDisplay->drawLine( clipStart.x, clipStart.y, clipEnd.x, clipEnd.y,
lineWidth, topColor, bottomColor );
// bottom line
start = end;
radar.x += m_viewBox[ 3 ].x;
radar.y += m_viewBox[ 3 ].y;
radarToPixel( &radar, &end, pixelX, pixelY, width, height );
if( ClipLine2D( &start, &end, &clipStart, &clipEnd, &clipRegion ) )
TheDisplay->drawLine( clipStart.x, clipStart.y, clipEnd.x, clipEnd.y,
lineWidth, bottomColor );
// left line
start = end;
end = ulStart;
if( ClipLine2D( &start, &end, &clipStart, &clipEnd, &clipRegion ) )
TheDisplay->drawLine( clipStart.x, clipStart.y, clipEnd.x, clipEnd.y,
lineWidth, bottomColor, topColor );
} // end drawViewBox
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void W3DRadar::drawSingleBeaconEvent( Int pixelX, Int pixelY, Int width, Int height, Int index )
{
RadarEvent *event = &(m_event[index]);
ICoord2D tri[ 3 ];
ICoord2D start, end;
Real angle, addAngle;
Color startColor, endColor;
Real lineWidth = 1.0f;
UnsignedInt currentFrame = TheGameLogic->getFrame();
UnsignedInt frameDiff; // frames the event has been alive for
Real maxEventSize = width / 10.0f; // max size of the event marker
Int minEventSize = 6; // min size of the event marker
Int eventSize; // current size of a marker to draw
const Real TIME_FROM_FULL_SIZE_TO_SMALL_SIZE = LOGICFRAMES_PER_SECOND * 1.5;
Real totalAnglesToSpin = 2.0f * PI; ///< spin around this many angles going from big to small
UnsignedByte r, g, b, a;
// setup screen clipping region
IRegion2D clipRegion;
clipRegion.lo.x = pixelX;
clipRegion.lo.y = pixelY;
clipRegion.hi.x = pixelX + width;
clipRegion.hi.y = pixelY + height;
// get the difference in frame from the current frame to the frame we were created on
frameDiff = currentFrame - event->createFrame;
// compute the size of the event marker, it is largest when it starts and smallest at the end
eventSize = REAL_TO_INT( maxEventSize * ( 1.0f - frameDiff / TIME_FROM_FULL_SIZE_TO_SMALL_SIZE) );;
// we never let the event size get too small
if( eventSize < minEventSize )
eventSize = minEventSize;
// compute how much "angle" we will add to each point to make it rotate as it's getting small
addAngle = -totalAnglesToSpin * (frameDiff / TIME_FROM_FULL_SIZE_TO_SMALL_SIZE);
// create a triangle around the event
angle = 0.0f - addAngle;
tri[ 0 ].x = REAL_TO_INT( (DOUBLE_TO_REAL( Cos( angle ) ) * eventSize) + event->radarLoc.x );
tri[ 0 ].y = REAL_TO_INT( (DOUBLE_TO_REAL( Sin( angle ) ) * eventSize) + event->radarLoc.y );
angle = 2.0f * PI / 3.0f - addAngle;
tri[ 1 ].x = REAL_TO_INT( (DOUBLE_TO_REAL( Cos( angle ) ) * eventSize) + event->radarLoc.x );
tri[ 1 ].y = REAL_TO_INT( (DOUBLE_TO_REAL( Sin( angle ) ) * eventSize) + event->radarLoc.y );
angle = -2.0f * PI / 3.0f - addAngle;
tri[ 2 ].x = REAL_TO_INT( (DOUBLE_TO_REAL( Cos( angle ) ) * eventSize) + event->radarLoc.x );
tri[ 2 ].y = REAL_TO_INT( (DOUBLE_TO_REAL( Sin( angle ) ) * eventSize) + event->radarLoc.y );
// translate radar coords to screen coords
radarToPixel( &tri[ 0 ], &tri[ 0 ], pixelX, pixelY, width, height );
radarToPixel( &tri[ 1 ], &tri[ 1 ], pixelX, pixelY, width, height );
radarToPixel( &tri[ 2 ], &tri[ 2 ], pixelX, pixelY, width, height );
//
// make the colors we're going to use, when we're at our smallest size we will start to
// fade the alpha away to transparent so that at our lifetime frame we are completely gone
//
// color 1 ------------------
r = event->color1.red;
g = event->color1.green;
b = event->color1.blue;
a = event->color1.alpha;
if( currentFrame > event->fadeFrame )
{
a = REAL_TO_UNSIGNEDBYTE( (Real)a * (1.0f - (Real)(currentFrame - event->fadeFrame) /
(Real)(event->dieFrame - event->fadeFrame) ) );
} // end if
startColor = GameMakeColor( r, g, b, a );
// color 2 ------------------
r = event->color2.red;
g = event->color2.green;
b = event->color2.blue;
a = event->color2.alpha;
if( currentFrame > event->fadeFrame )
{
a = REAL_TO_UNSIGNEDBYTE( (Real)a * (1.0f - (Real)(currentFrame - event->fadeFrame) /
(Real)(event->dieFrame - event->fadeFrame) ) );
} // end if
endColor = GameMakeColor( r, g, b, a );
// draw the lines
if( ClipLine2D( &tri[ 0 ], &tri[ 1 ], &start, &end, &clipRegion ) )
TheDisplay->drawLine( start.x, start.y, end.x, end.y, lineWidth, startColor, endColor );
if( ClipLine2D( &tri[ 1 ], &tri[ 2 ], &start, &end, &clipRegion ) )
TheDisplay->drawLine( start.x, start.y, end.x, end.y, lineWidth, startColor, endColor );
if( ClipLine2D( &tri[ 2 ], &tri[ 0 ], &start, &end, &clipRegion ) )
TheDisplay->drawLine( start.x, start.y, end.x, end.y, lineWidth, startColor, endColor );
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void W3DRadar::drawSingleGenericEvent( Int pixelX, Int pixelY, Int width, Int height, Int index )
{
RadarEvent *event = &(m_event[index]);
ICoord2D tri[ 3 ];
ICoord2D start, end;
Real angle, addAngle;
Color startColor, endColor;
Real lineWidth = 1.0f;
UnsignedInt currentFrame = TheGameLogic->getFrame();
UnsignedInt frameDiff; // frames the event has been alive for
Real maxEventSize = width / 2.0f; // max size of the event marker
Int minEventSize = 6; // min size of the event marker
Int eventSize; // current size of a marker to draw
const Real TIME_FROM_FULL_SIZE_TO_SMALL_SIZE = LOGICFRAMES_PER_SECOND * 1.5;
Real totalAnglesToSpin = 2.0f * PI; ///< spin around this many angles going from big to small
UnsignedByte r, g, b, a;
// setup screen clipping region
IRegion2D clipRegion;
clipRegion.lo.x = pixelX;
clipRegion.lo.y = pixelY;
clipRegion.hi.x = pixelX + width;
clipRegion.hi.y = pixelY + height;
// get the difference in frame from the current frame to the frame we were created on
frameDiff = currentFrame - event->createFrame;
// compute the size of the event marker, it is largest when it starts and smallest at the end
eventSize = REAL_TO_INT( maxEventSize * ( 1.0f - frameDiff / TIME_FROM_FULL_SIZE_TO_SMALL_SIZE) );;
// we never let the event size get too small
if( eventSize < minEventSize )
eventSize = minEventSize;
// compute how much "angle" we will add to each point to make it rotate as it's getting small
addAngle = totalAnglesToSpin * (frameDiff / TIME_FROM_FULL_SIZE_TO_SMALL_SIZE);
// create a triangle around the event
angle = 0.0f - addAngle;
tri[ 0 ].x = REAL_TO_INT( (DOUBLE_TO_REAL( Cos( angle ) ) * eventSize) + event->radarLoc.x );
tri[ 0 ].y = REAL_TO_INT( (DOUBLE_TO_REAL( Sin( angle ) ) * eventSize) + event->radarLoc.y );
angle = 2.0f * PI / 3.0f - addAngle;
tri[ 1 ].x = REAL_TO_INT( (DOUBLE_TO_REAL( Cos( angle ) ) * eventSize) + event->radarLoc.x );
tri[ 1 ].y = REAL_TO_INT( (DOUBLE_TO_REAL( Sin( angle ) ) * eventSize) + event->radarLoc.y );
angle = -2.0f * PI / 3.0f - addAngle;
tri[ 2 ].x = REAL_TO_INT( (DOUBLE_TO_REAL( Cos( angle ) ) * eventSize) + event->radarLoc.x );
tri[ 2 ].y = REAL_TO_INT( (DOUBLE_TO_REAL( Sin( angle ) ) * eventSize) + event->radarLoc.y );
// translate radar coords to screen coords
radarToPixel( &tri[ 0 ], &tri[ 0 ], pixelX, pixelY, width, height );
radarToPixel( &tri[ 1 ], &tri[ 1 ], pixelX, pixelY, width, height );
radarToPixel( &tri[ 2 ], &tri[ 2 ], pixelX, pixelY, width, height );
//
// make the colors we're going to use, when we're at our smallest size we will start to
// fade the alpha away to transparent so that at our lifetime frame we are completely gone
//
// color 1 ------------------
r = event->color1.red;
g = event->color1.green;
b = event->color1.blue;
a = event->color1.alpha;
if( currentFrame > event->fadeFrame )
{
a = REAL_TO_UNSIGNEDBYTE( (Real)a * (1.0f - (Real)(currentFrame - event->fadeFrame) /
(Real)(event->dieFrame - event->fadeFrame) ) );
} // end if
startColor = GameMakeColor( r, g, b, a );
// color 2 ------------------
r = event->color2.red;
g = event->color2.green;
b = event->color2.blue;
a = event->color2.alpha;
if( currentFrame > event->fadeFrame )
{
a = REAL_TO_UNSIGNEDBYTE( (Real)a * (1.0f - (Real)(currentFrame - event->fadeFrame) /
(Real)(event->dieFrame - event->fadeFrame) ) );
} // end if
endColor = GameMakeColor( r, g, b, a );
// draw the lines
if( ClipLine2D( &tri[ 0 ], &tri[ 1 ], &start, &end, &clipRegion ) )
TheDisplay->drawLine( start.x, start.y, end.x, end.y, lineWidth, startColor, endColor );
if( ClipLine2D( &tri[ 1 ], &tri[ 2 ], &start, &end, &clipRegion ) )
TheDisplay->drawLine( start.x, start.y, end.x, end.y, lineWidth, startColor, endColor );
if( ClipLine2D( &tri[ 2 ], &tri[ 0 ], &start, &end, &clipRegion ) )
TheDisplay->drawLine( start.x, start.y, end.x, end.y, lineWidth, startColor, endColor );
}
//-------------------------------------------------------------------------------------------------
/** Draw all the radar events */
//-------------------------------------------------------------------------------------------------
void W3DRadar::drawEvents( Int pixelX, Int pixelY, Int width, Int height )
{
Int i;
for( i = 0; i < MAX_RADAR_EVENTS; i++ )
{
// only 'active' events actually have something to draw
if( m_event[ i ].active == TRUE && m_event[ i ].type != RADAR_EVENT_FAKE )
{
// if we haven't played the sound for this event, do it now that we can see it
if( m_event[ i ].soundPlayed == FALSE && m_event[i].type != RADAR_EVENT_BEACON_PULSE )
{
static AudioEventRTS eventSound("RadarEvent");
TheAudio->addAudioEvent( &eventSound );
} // end if
m_event[ i ].soundPlayed = TRUE;
if ( m_event[ i ].type == RADAR_EVENT_BEACON_PULSE )
drawSingleBeaconEvent( pixelX, pixelY, width, height, i );
else
drawSingleGenericEvent( pixelX, pixelY, width, height, i );
} // end if
} // end for i
} // end drawEvents
//-------------------------------------------------------------------------------------------------
/** Draw all the radar icons */
//-------------------------------------------------------------------------------------------------
void W3DRadar::drawIcons( Int pixelX, Int pixelY, Int width, Int height )
{
// draw the hero icons
std::list::const_iterator iter = m_cachedHeroPosList.begin();
while (iter != m_cachedHeroPosList.end())
{
drawHeroIcon( pixelX, pixelY, width, height, (*iter) );
++iter;
}
}
//-------------------------------------------------------------------------------------------------
/** Render an object list into the texture passed in */
//-------------------------------------------------------------------------------------------------
void W3DRadar::renderObjectList( const RadarObject *listHead, TextureClass *texture, Bool calcHero )
{
// sanity
if( listHead == NULL || texture == NULL )
return;
// get surface for texture to render into
SurfaceClass *surface = texture->Get_Surface_Level();
// loop through all objects and draw
ICoord2D radarPoint;
Player *player = ThePlayerList->getLocalPlayer();
Int playerIndex=0;
if (player)
playerIndex=player->getPlayerIndex();
if( calcHero )
{
// clear all entries from the cached hero object list
m_cachedHeroPosList.clear();
}
for( const RadarObject *rObj = listHead; rObj; rObj = rObj->friend_getNext() )
{
if (rObj->isTemporarilyHidden())
continue;
// get object
const Object *obj = rObj->friend_getObject();
// cache hero object positions for drawing in icon layer
if( calcHero && obj->isHero() )
{
m_cachedHeroPosList.push_back(obj->getPosition());
}
Bool skip = FALSE;
// check for shrouded status
if (obj->getShroudedStatus(playerIndex) > OBJECTSHROUD_PARTIAL_CLEAR)
skip = TRUE; //object is fogged or shrouded, don't render it.
//
// objects with a local only unit priority will only appear on the radar if they
// are controlled by the local player, or if the local player is an observer (cause
// they are godlike and can see everything)
//
if( obj->getRadarPriority() == RADAR_PRIORITY_LOCAL_UNIT_ONLY &&
obj->getControllingPlayer() != ThePlayerList->getLocalPlayer() &&
ThePlayerList->getLocalPlayer()->isPlayerActive() )
skip = TRUE;
// get object position
const Coord3D *pos = obj->getPosition();
// compute object position as a radar blip
radarPoint.x = pos->x / (m_mapExtent.width() / RADAR_CELL_WIDTH);
radarPoint.y = pos->y / (m_mapExtent.height() / RADAR_CELL_HEIGHT);
if ( skip )
continue;
// get the color we're going to draw in
Color c = rObj->getColor();
// adjust the alpha for stealth units so they "fade/blink" on the radar for the controller
// if( obj->getRadarPriority() == RADAR_PRIORITY_LOCAL_UNIT_ONLY )
// ML-- What the heck is this? local-only and neutral-observier-viewed units are stealthy?? Since when?
// Now it twinkles for any stealthed object, whether locally controlled or neutral-observier-viewed
if( obj->testStatus( OBJECT_STATUS_STEALTHED ) )
{
static NameKeyType key_StealthUpdate = NAMEKEY( "StealthUpdate" );
StealthUpdate* stealth = (StealthUpdate*)obj->findUpdateModule( key_StealthUpdate );
if( !stealth )
continue;
if ( ThePlayerList->getLocalPlayer()->getRelationship(obj->getTeam()) == ENEMIES )
if( !obj->testStatus( OBJECT_STATUS_DETECTED ) && !stealth->isDisguised() )
skip = TRUE;
UnsignedByte r, g, b, a;
GameGetColorComponents( c, &r, &g, &b, &a );
const UnsignedInt framesForTransition = LOGICFRAMES_PER_SECOND;
const UnsignedByte minAlpha = 32;
if (skip)
continue;
Real alphaScale = INT_TO_REAL(TheGameLogic->getFrame() % framesForTransition) / (framesForTransition / 2.0f);
if( alphaScale > 0.0f )
a = REAL_TO_UNSIGNEDBYTE( ((alphaScale - 1.0f) * (255.0f - minAlpha)) + minAlpha );
else
a = REAL_TO_UNSIGNEDBYTE( (alphaScale * (255.0f - minAlpha)) + minAlpha );
c = GameMakeColor( r, g, b, a );
} // end if
// draw the blip, but make sure the points are legal
if( legalRadarPoint( radarPoint.x, radarPoint.y ) )
surface->DrawPixel( radarPoint.x, radarPoint.y, c );
radarPoint.y++;
if( legalRadarPoint( radarPoint.x, radarPoint.y ) )
surface->DrawPixel( radarPoint.x, radarPoint.y, c );
radarPoint.x++;
if( legalRadarPoint( radarPoint.x, radarPoint.y ) )
surface->DrawPixel( radarPoint.x, radarPoint.y, c );
radarPoint.y--;
if( legalRadarPoint( radarPoint.x, radarPoint.y ) )
surface->DrawPixel( radarPoint.x, radarPoint.y, c );
} // end for
REF_PTR_RELEASE(surface);
} // end renderObjectList
//-------------------------------------------------------------------------------------------------
/** Shade the color passed in using the height parameter to lighten and darken it. Colors
* will be interpolated using the value "height" across the range from loZ to hiZ. The
* midZ is the "middle" point, height values above it will be lightened, while
* lower ones are darkened. */
//-------------------------------------------------------------------------------------------------
void W3DRadar::interpolateColorForHeight( RGBColor *color,
Real height,
Real hiZ,
Real midZ,
Real loZ )
{
const Real howBright = 0.95f; // bigger is brighter (0.0 to 1.0)
const Real howDark = 0.60f; // bigger is darker (0.0 to 1.0)
// sanity on map height (flat maps bomb)
if (hiZ == midZ)
hiZ = midZ+0.1f;
if (midZ == loZ)
loZ = midZ-0.1f;
if (hiZ == loZ)
hiZ = loZ+0.2f;
Real t;
RGBColor colorTarget;
// if "over" the middle height, interpolate lighter
if( height >= midZ )
{
// how far are we from the middleZ towards the hi Z
t = (height - midZ) / (hiZ - midZ);
// compute what our "lightest" color possible we want to use is
colorTarget.red = color->red + (1.0f - color->red) * howBright;
colorTarget.green = color->green + (1.0f - color->green) * howBright;
colorTarget.blue = color->blue + (1.0f - color->blue) * howBright;
} // end if
else // interpolate darker
{
// how far are we from the middleZ towards the low Z
t = (midZ - height) / (midZ - loZ);
// compute what the "darkest" color possible we want to use is
colorTarget.red = color->red + (0.0f - color->red) * howDark;
colorTarget.green = color->green + (0.0f - color->green) * howDark;
colorTarget.blue = color->blue + (0.0f - color->blue) * howDark;
} // end else
// interpolate toward the target color
color->red = color->red + (colorTarget.red - color->red) * t;
color->green = color->green + (colorTarget.green - color->green) * t;
color->blue = color->blue + (colorTarget.blue - color->blue) * t;
// keep the color real
if( color->red < 0.0f )
color->red = 0.0f;
if( color->red > 1.0f )
color->red = 1.0f;
if( color->green < 0.0f )
color->green = 0.0f;
if( color->green > 1.0f )
color->green = 1.0f;
if( color->blue < 0.0f )
color->blue = 0.0f;
if( color->blue > 1.0f )
color->blue = 1.0f;
} // end interpolateColorForHeight
///////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS /////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
W3DRadar::W3DRadar( void )
{
m_terrainTextureFormat = WW3D_FORMAT_UNKNOWN;
m_terrainImage = NULL;
m_terrainTexture = NULL;
m_overlayTextureFormat = WW3D_FORMAT_UNKNOWN;
m_overlayImage = NULL;
m_overlayTexture = NULL;
m_shroudTextureFormat = WW3D_FORMAT_UNKNOWN;
m_shroudImage = NULL;
m_shroudTexture = NULL;
m_textureWidth = RADAR_CELL_WIDTH;
m_textureHeight = RADAR_CELL_HEIGHT;
m_reconstructViewBox = TRUE;
m_viewAngle = 0.0f;
m_viewZoom = 0.0f;
for( Int i = 0; i < 4; i++ )
{
m_viewBox[ i ].x = 0;
m_viewBox[ i ].y = 0;
} // end for
} // end W3DRadar
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
W3DRadar::~W3DRadar( void )
{
// delete resources used for the W3D radar
deleteResources();
} // end ~W3DRadar
//-------------------------------------------------------------------------------------------------
/** Radar initialization */
//-------------------------------------------------------------------------------------------------
void W3DRadar::init( void )
{
ICoord2D size;
Region2D uv;
// extending functionality
Radar::init();
// gather specific texture format information
initializeTextureFormats();
// allocate our terrain texture
// poolify
m_terrainTexture = MSGNEW("TextureClass") TextureClass( m_textureWidth, m_textureHeight,
m_terrainTextureFormat, TextureClass::MIP_LEVELS_1 );
DEBUG_ASSERTCRASH( m_terrainTexture, ("W3DRadar: Unable to allocate terrain texture\n") );
// allocate our overlay texture
m_overlayTexture = MSGNEW("TextureClass") TextureClass( m_textureWidth, m_textureHeight,
m_overlayTextureFormat, TextureClass::MIP_LEVELS_1 );
DEBUG_ASSERTCRASH( m_overlayTexture, ("W3DRadar: Unable to allocate overlay texture\n") );
// set filter type for the overlay texture, try it and see if you like it, I don't ;)
// m_overlayTexture->Set_Min_Filter( TextureClass::FILTER_TYPE_NONE );
// m_overlayTexture->Set_Mag_Filter( TextureClass::FILTER_TYPE_NONE );
// allocate our shroud texture
m_shroudTexture = MSGNEW("TextureClass") TextureClass( m_textureWidth, m_textureHeight,
m_shroudTextureFormat, TextureClass::MIP_LEVELS_1 );
DEBUG_ASSERTCRASH( m_shroudTexture, ("W3DRadar: Unable to allocate shroud texture\n") );
m_shroudTexture->Set_Min_Filter( TextureClass::FILTER_TYPE_DEFAULT );
m_shroudTexture->Set_Mag_Filter( TextureClass::FILTER_TYPE_DEFAULT );
//
// create images used for rendering and set them up with the textures
//
//
// the terrain image, note the UV coords change it from (0,0) in the upper left
// to (0,0) in the lower left cause that's how we are initially oriented in the
// world (positive X to the right and positive Y up)
//
m_terrainImage = newInstance(Image);
uv.lo.x = 0.0f;
uv.lo.y = 1.0f;
uv.hi.x = 1.0f;
uv.hi.y = 0.0f;
m_terrainImage->setStatus( IMAGE_STATUS_RAW_TEXTURE );
m_terrainImage->setRawTextureData( m_terrainTexture );
m_terrainImage->setUV( &uv );
m_terrainImage->setTextureWidth( m_textureWidth );
m_terrainImage->setTextureHeight( m_textureHeight );
size.x = m_textureWidth;
size.y = m_textureHeight;
m_terrainImage->setImageSize( &size );
// the overlay image
m_overlayImage = newInstance(Image);
uv.lo.x = 0.0f;
uv.lo.y = 1.0f;
uv.hi.x = 1.0f;
uv.hi.y = 0.0f;
m_overlayImage->setStatus( IMAGE_STATUS_RAW_TEXTURE );
m_overlayImage->setRawTextureData( m_overlayTexture );
m_overlayImage->setUV( &uv );
m_overlayImage->setTextureWidth( m_textureWidth );
m_overlayImage->setTextureHeight( m_textureHeight );
size.x = m_textureWidth;
size.y = m_textureHeight;
m_overlayImage->setImageSize( &size );
// the shroud image
m_shroudImage = newInstance(Image);
uv.lo.x = 0.0f;
uv.lo.y = 1.0f;
uv.hi.x = 1.0f;
uv.hi.y = 0.0f;
m_shroudImage->setStatus( IMAGE_STATUS_RAW_TEXTURE );
m_shroudImage->setRawTextureData( m_shroudTexture );
m_shroudImage->setUV( &uv );
m_shroudImage->setTextureWidth( m_textureWidth );
m_shroudImage->setTextureHeight( m_textureHeight );
size.x = m_textureWidth;
size.y = m_textureHeight;
m_shroudImage->setImageSize( &size );
} // end init
//-------------------------------------------------------------------------------------------------
/** Reset the radar to the initial empty state ready for new data */
//-------------------------------------------------------------------------------------------------
void W3DRadar::reset( void )
{
// extending functionality, call base class
Radar::reset();
// clear our texture data, but do not delete the resources
SurfaceClass *surface;
surface = m_terrainTexture->Get_Surface_Level();
if( surface )
{
surface->Clear();
REF_PTR_RELEASE(surface);
}
surface = m_overlayTexture->Get_Surface_Level();
if( surface )
{
surface->Clear();
REF_PTR_RELEASE(surface);
}
// don't call Clear(); that wips to transparent. do this instead.
//gs Dude, it's called CLEARshroud. It needs to clear the shroud.
clearShroud();
} // end reset
//-------------------------------------------------------------------------------------------------
/** Update */
//-------------------------------------------------------------------------------------------------
void W3DRadar::update( void )
{
// extend base class
Radar::update();
} // end update
//-------------------------------------------------------------------------------------------------
/** Reset the radar for the new map data being given to it */
//-------------------------------------------------------------------------------------------------
void W3DRadar::newMap( TerrainLogic *terrain )
{
//
// extending functionality, call the base class ... this will cause a reset of the
// system which will clear out our textures but not free them
//
Radar::newMap( terrain );
// sanity
if( terrain == NULL )
return;
// build terrain texture
buildTerrainTexture( terrain );
} // end newMap
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void W3DRadar::buildTerrainTexture( TerrainLogic *terrain )
{
SurfaceClass *surface;
RGBColor waterColor;
// we will want to reconstruct our new view box now
m_reconstructViewBox = TRUE;
// setup our water color
waterColor.red = 0.55f;
waterColor.green = 0.55f;
waterColor.blue = 1.0f;
// get the terrain surface to draw in
surface = m_terrainTexture->Get_Surface_Level();
DEBUG_ASSERTCRASH( surface, ("W3DRadar: Can't get surface for terrain texture\n") );
// build the terrain
RGBColor sampleColor;
RGBColor color;
Int i, j, samples;
Int x, y, z;
ICoord2D radarPoint;
Coord3D worldPoint;
Bridge *bridge;
for( y = 0; y < m_textureHeight; y++ )
{
for( x = 0; x < m_textureWidth; x++ )
{
// what point are we inspecting
radarPoint.x = x;
radarPoint.y = y;
radarToWorld( &radarPoint, &worldPoint );
// get height of the terrain at this sample point
z = terrain->getGroundHeight( worldPoint.x, worldPoint.y );
// check to see if this point is part of a working bridge
Bool workingBridge = FALSE;
bridge = TheTerrainLogic->findBridgeAt( &worldPoint );
if( bridge != NULL )
{
Object *obj = TheGameLogic->findObjectByID( bridge->peekBridgeInfo()->bridgeObjectID );
if( obj )
{
BodyModuleInterface *body = obj->getBodyModule();
if( body->getDamageState() != BODY_RUBBLE )
workingBridge = TRUE;
} // end if
} // end if
// create a color based on the Z height of the map
Real waterZ;
if( workingBridge == FALSE && terrain->isUnderwater( worldPoint.x, worldPoint.y, &waterZ ) )
{
const Int waterSamplesAway = 1; // how many "tiles" from the center tile we will sample away
// to average a color for the tile color
sampleColor.red = sampleColor.green = sampleColor.blue = 0.0f;
samples = 0;
for( j = y - waterSamplesAway; j <= y + waterSamplesAway; j++ )
{
if( j >= 0 && j < m_textureHeight )
{
for( i = x - waterSamplesAway; i <= x + waterSamplesAway; i++ )
{
if( i >= 0 && i < m_textureWidth )
{
// the the world point we are concerned with
radarPoint.x = i;
radarPoint.y = j;
radarToWorld( &radarPoint, &worldPoint );
// get Z at this sample height
Real underwaterZ = terrain->getGroundHeight( worldPoint.x, worldPoint.y );
// get color for this Z and add to our sample color
if( terrain->isUnderwater( worldPoint.x, worldPoint.y ) )
{
// this is our "color" for water
color = waterColor;
// interpolate the water color for height in the water table
interpolateColorForHeight( &color, underwaterZ, waterZ,
waterZ,
m_mapExtent.lo.z );
// add color to our samples
sampleColor.red += color.red;
sampleColor.green += color.green;
sampleColor.blue += color.blue;
samples++;
} // end if
} // end if
} // end for i
} // end if
} // end for j
// prevent divide by zeros
if( samples == 0 )
samples = 1;
// set the color to an average of the colors read
color.red = sampleColor.red / (Real)samples;
color.green = sampleColor.green / (Real)samples;
color.blue = sampleColor.blue / (Real)samples;
} // end if
else // regular terrain ...
{
const Int samplesAway = 1; // how many "tiles" from the center tile we will sample away
// to average a color for the tile color
sampleColor.red = sampleColor.green = sampleColor.blue = 0.0f;
samples = 0;
for( j = y - samplesAway; j <= y + samplesAway; j++ )
{
if( j >= 0 && j < m_textureHeight )
{
for( i = x - samplesAway; i <= x + samplesAway; i++ )
{
if( i >= 0 && i < m_textureWidth )
{
// the the world point we are concerned with
radarPoint.x = i;
radarPoint.y = j;
radarToWorld( &radarPoint, &worldPoint );
// get the color we're going to use here
if( workingBridge )
{
AsciiString bridgeTName = bridge->getBridgeTemplateName();
TerrainRoadType *bridgeTemplate = TheTerrainRoads->findBridge( bridgeTName );
// sanity
DEBUG_ASSERTCRASH( bridgeTemplate, ("W3DRadar::buildTerrainTexture - Can't find bridge template for '%s'\n", bridgeTName.str()) );
// use bridge color
if ( bridgeTemplate )
color = bridgeTemplate->getRadarColor();
else
color.setFromInt(0xffffffff);
//
// we won't use the height of the terrain at this sample point, we will
// instead use the height for the entire bridge
//
Real bridgeHeight = (bridge->peekBridgeInfo()->fromLeft.z +
bridge->peekBridgeInfo()->fromRight.z +
bridge->peekBridgeInfo()->toLeft.z +
bridge->peekBridgeInfo()->toRight.z) / 4.0f;
// interpolate the color, but use the bridge height, not the terrain height
interpolateColorForHeight( &color, bridgeHeight,
getTerrainAverageZ(),
m_mapExtent.hi.z, m_mapExtent.lo.z );
} // end if
else
{
// get the color at this point
TheTerrainVisual->getTerrainColorAt( worldPoint.x, worldPoint.y, &color );
// interpolate the color for height
interpolateColorForHeight( &color, z, getTerrainAverageZ(),
m_mapExtent.hi.z, m_mapExtent.lo.z );
} // end else
// add color to our samples
sampleColor.red += color.red;
sampleColor.green += color.green;
sampleColor.blue += color.blue;
samples++;
} // end if
} // end for i
} // end if
} // end for j
// prevent divide by zeros
if( samples == 0 )
samples = 1;
// set the color to an average of the colors read
color.red = sampleColor.red / (Real)samples;
color.green = sampleColor.green / (Real)samples;
color.blue = sampleColor.blue / (Real)samples;
} // end else
//
// draw the pixel for the terrain at this point, note that because of the orientation
// of our world we draw it with positive y in the "up" direction
//
// FYI: I tried making this faster by pulling out all the code inside DrawPixel
// and locking only once ... but it made absolutely *no* performance difference,
// the sampling and interpolation algorithm for generating pretty looking terrain
// and water for the radar is just, well, expensive.
//
surface->DrawPixel( x, y, GameMakeColor( color.red * 255,
color.green * 255,
color.blue * 255,
255 ) );
} // end for x
} // end for y
// all done with the surface
REF_PTR_RELEASE(surface);
} // end buildTerrainTexture
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void W3DRadar::clearShroud()
{
#if defined(_DEBUG) || defined(_INTERNAL)
if (!TheGlobalData->m_shroudOn)
return;
#endif
SurfaceClass *surface = m_shroudTexture->Get_Surface_Level();
// fill to clear, shroud will make black. Don't want to make something black that logic can't clear
unsigned int color = GameMakeColor( 0, 0, 0, 0 );
for( Int y = 0; y < m_textureHeight; y++ )
{
surface->DrawHLine(y, 0, m_textureWidth-1, color);
}
REF_PTR_RELEASE(surface);
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void W3DRadar::setShroudLevel(Int shroudX, Int shroudY, CellShroudStatus setting)
{
#if defined(_DEBUG) || defined(_INTERNAL)
if (!TheGlobalData->m_shroudOn)
return;
#endif
W3DShroud* shroud = TheTerrainRenderObject ? TheTerrainRenderObject->getShroud() : NULL;
if (!shroud)
return;
SurfaceClass* surface = m_shroudTexture->Get_Surface_Level();
DEBUG_ASSERTCRASH( surface, ("W3DRadar: Can't get surface for Shroud texture\n") );
Int mapMinX = shroudX * shroud->getCellWidth();
Int mapMinY = shroudY * shroud->getCellHeight();
Int mapMaxX = (shroudX+1) * shroud->getCellWidth();
Int mapMaxY = (shroudY+1) * shroud->getCellHeight();
ICoord2D radarPoint;
Coord3D worldPoint;
worldPoint.x = mapMinX;
worldPoint.y = mapMinY;
worldToRadar( &worldPoint, &radarPoint );
Int radarMinX = radarPoint.x;
Int radarMinY = radarPoint.y;
worldPoint.x = mapMaxX;
worldPoint.y = mapMaxY;
worldToRadar( &worldPoint, &radarPoint );
Int radarMaxX = radarPoint.x;
Int radarMaxY = radarPoint.y;
/*
Int radarMinX = REAL_TO_INT_FLOOR(mapMinX / getXSample());
Int radarMinY = REAL_TO_INT_FLOOR(mapMinY / getYSample());
Int radarMaxX = REAL_TO_INT_CEIL(mapMaxX / getXSample());
Int radarMaxY = REAL_TO_INT_CEIL(mapMaxY / getYSample());
*/
/// @todo srj -- this really needs to smooth the display!
//Logic is saying shroud. We can add alpha levels here in client if needed.
// W3DShroud is a 0-255 alpha byte. Logic shroud is a double reference count.
Int alpha;
if( setting == CELLSHROUD_SHROUDED )
alpha = 255;
else if( setting == CELLSHROUD_FOGGED )
alpha = 127;///< @todo placeholder to get feedback on logic work while graphic side being decided
else
alpha = 0;
for( Int y = radarMinY; y <= radarMaxY; y++ )
{
for( Int x = radarMinX; x <= radarMaxX; x++ )
{
if( legalRadarPoint( x, y ) )
surface->DrawPixel( x, y, GameMakeColor( 0, 0, 0, alpha ) );
}
}
REF_PTR_RELEASE(surface);
}
//-------------------------------------------------------------------------------------------------
/** Actually draw the radar at the screen coordinates provided
* NOTE about how drawing works: The radar images are computed at samples across the
* map and are built into a "square" texture area. At the time of drawing and computing
* radar<->world coords we consider the "ratio" of width to height of the map dimensions
* so that when we draw we preserve the aspect ratio of the map and don't squish it in
* any direction that would cause the map to be distorted. Extra blank space is drawn
* around the radar images to keep the whole radar area covered when the map displayed
* is "long" or "tall" */
//-------------------------------------------------------------------------------------------------
void W3DRadar::draw( Int pixelX, Int pixelY, Int width, Int height )
{
// if the local player does not have a radar then we can't draw anything
Player *player = ThePlayerList->getLocalPlayer();
if( !player->hasRadar() && !TheRadar->isRadarForced() )
return;
//
// given a upper left corner at pixelX|Y and a width and height to draw into, figure out
// where we should start and end the image so that the final drawn image has the
// same ratio as the map and isn't stretched or distorted
//
ICoord2D ul, lr;
findDrawPositions( pixelX, pixelY, width, height, &ul, &lr );
Int scaledWidth = lr.x - ul.x;
Int scaledHeight = lr.y - ul.y;
// draw black border areas where we need map
Color fillColor = GameMakeColor( 0, 0, 0, 255 );
Color lineColor = GameMakeColor( 50, 50, 50, 255 );
if( m_mapExtent.width()/width >= m_mapExtent.height()/height )
{
// draw horizontal bars at top and bottom
TheDisplay->drawFillRect( pixelX, pixelY, width, ul.y - pixelY - 1, fillColor );
TheDisplay->drawFillRect( pixelX, lr.y + 1, width, pixelY + height - lr.y - 1, fillColor);
TheDisplay->drawLine(pixelX, ul.y, pixelX + width, ul.y, 1, lineColor);
TheDisplay->drawLine(pixelX, lr.y + 1, pixelX + width, lr.y + 1, 1, lineColor);
} // end if
else
{
// draw vertical bars to the left and right
TheDisplay->drawFillRect( pixelX, pixelY, ul.x - pixelX - 1, height, fillColor );
TheDisplay->drawFillRect( lr.x + 1, pixelY, width - (lr.x - pixelX) - 1, height, fillColor );
TheDisplay->drawLine(ul.x, pixelY, ul.x, pixelY + height, 1, lineColor);
TheDisplay->drawLine(lr.x + 1, pixelY, lr.x + 1, pixelY + height, 1, lineColor);
} // end else
// draw the terrain texture
TheDisplay->drawImage( m_terrainImage, ul.x, ul.y, lr.x, lr.y );
// refresh the overlay texture once every so many frames
if( TheGameClient->getFrame() % OVERLAY_REFRESH_RATE == 0 )
{
// reset the overlay texture
SurfaceClass *surface = m_overlayTexture->Get_Surface_Level();
surface->Clear();
REF_PTR_RELEASE(surface);
// rebuild the object overlay
renderObjectList( getObjectList(), m_overlayTexture );
renderObjectList( getLocalObjectList(), m_overlayTexture, TRUE );
} // end if
// draw the overlay image
TheDisplay->drawImage( m_overlayImage, ul.x, ul.y, lr.x, lr.y );
// draw the shroud image
#if defined(_DEBUG) || defined(_INTERNAL)
if( TheGlobalData->m_shroudOn )
#else
if (true)
#endif
{
TheDisplay->drawImage( m_shroudImage, ul.x, ul.y, lr.x, lr.y );
}
// draw any icons
drawIcons( ul.x, ul.y, scaledWidth, scaledHeight );
// draw any radar events
drawEvents( ul.x, ul.y, scaledWidth, scaledHeight );
// see if we need to reconstruct the view box
if( TheTacticalView->getZoom() != m_viewZoom )
m_reconstructViewBox = TRUE;
if( TheTacticalView->getAngle() != m_viewAngle )
m_reconstructViewBox = TRUE;
if( m_reconstructViewBox == TRUE )
reconstructViewBox();
// draw the view region on top of the radar reconstructing if necessary
drawViewBox( ul.x, ul.y, scaledWidth, scaledHeight );
} // end draw
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void W3DRadar::refreshTerrain( TerrainLogic *terrain )
{
// extend base class
Radar::refreshTerrain( terrain );
// rebuild the entire terrain texture
buildTerrainTexture( terrain );
} // end refreshTerrain