/*
** Command & Conquer Generals Zero Hour(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: W3DWaypointBuffer.cpp ////////////////////////////////////////////////
//-----------------------------------------------------------------------------
//
// Electronic Arts Pacific.
//
// Confidential Information
// Copyright (C) 2002 - All Rights Reserved
//
//-----------------------------------------------------------------------------
//
// Project: Command & Conquers: Generals
//
// File name: W3DWaypointBuffer.cpp
//
// Created: Kris Morness, October 2002
//
// Desc: Draw buffer to handle all the waypoints in the scene. Waypoints
// are rendered after terrain, after roads & bridges, and after
// global fog, but before structures, objects, units, trees, etc.
// This way if we have two waypoints at the bottom of a hill but
// going through the hill, the line won't get cut off. However,
// structures and units on top of paths will render above it. Waypoints
// are only shown for selected units while in waypoint plotting mode.
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
#include "W3DDevice/GameClient/W3DWaypointBuffer.h"
#include
#include
#include
#include
#include "Common/GlobalData.h"
#include "Common/RandomValue.h"
#include "Common/ThingFactory.h"
#include "Common/ThingTemplate.h"
#include "GameClient/Drawable.h"
#include "GameClient/GameClient.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/AIUpdate.h"
#include "W3DDevice/GameClient/TerrainTex.h"
#include "W3DDevice/GameClient/HeightMap.h"
#include "WW3D2/Camera.h"
#include "WW3D2/DX8Wrapper.h"
#include "WW3D2/DX8Renderer.h"
#include "WW3D2/Mesh.h"
#include "WW3D2/MeshMdl.h"
#include "WW3D2/Segline.h"
#define MAX_DISPLAY_NODES 512
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//=============================================================================
// W3DWaypointBuffer::W3DWaypointBuffer
//=============================================================================
/** Constructor. Sets m_initialized to true if it finds the w3d models it needs
for the bibs. */
//=============================================================================
W3DWaypointBuffer::W3DWaypointBuffer(void)
{
m_waypointNodeRobj = WW3DAssetManager::Get_Instance()->Create_Render_Obj( "SCMNode" );
m_line = new SegmentedLineClass;
m_texture = WW3DAssetManager::Get_Instance()->Get_Texture( "EXLaser.tga" );
setDefaultLineStyle();
}
//=============================================================================
// W3DWaypointBuffer::~W3DWaypointBuffer
//=============================================================================
/** Destructor. Releases w3d assets. */
//=============================================================================
W3DWaypointBuffer::~W3DWaypointBuffer(void)
{
REF_PTR_RELEASE( m_waypointNodeRobj );
REF_PTR_RELEASE( m_texture );
REF_PTR_RELEASE( m_line );
}
//=============================================================================
// W3DWaypointBuffer::freeBibBuffers
//=============================================================================
/** Frees the index and vertex buffers. */
//=============================================================================
void W3DWaypointBuffer::freeWaypointBuffers()
{
}
void W3DWaypointBuffer::setDefaultLineStyle( void )
{
if( m_texture )
{
m_line->Set_Texture( m_texture );
}
ShaderClass lineShader=ShaderClass::_PresetAdditiveShader;
lineShader.Set_Depth_Compare(ShaderClass::PASS_ALWAYS);
m_line->Set_Shader( lineShader ); //pick the alpha blending mode you want - see shader.h for others.
m_line->Set_Width( 1.5f );
m_line->Set_Color( Vector3( 0.25f, 0.5f, 1.0f ) );
m_line->Set_Texture_Mapping_Mode( SegLineRendererClass::TILED_TEXTURE_MAP ); //this tiles the texture across the line
}
//=============================================================================
// W3DWaypointBuffer::drawWaypoints
//=============================================================================
/** Draws the waypoints. Uses camera to cull */
//=============================================================================
void W3DWaypointBuffer::drawWaypoints(RenderInfoClass &rinfo)
{
if ( ! TheInGameUI )
return;
setDefaultLineStyle();
if( TheInGameUI->isInWaypointMode() )
{
//Create a default light environment with no lights and only full ambient.
//@todo: Fix later by copying default scene light environement from W3DScene.cpp.
LightEnvironmentClass lightEnv;
lightEnv.Reset(Vector3(0,0,0), Vector3(1.0f,1.0f,1.0f));
lightEnv.Pre_Render_Update(rinfo.Camera.Get_Transform());
RenderInfoClass localRinfo(rinfo.Camera);
localRinfo.light_environment=&lightEnv;
Vector3 points[ MAX_DISPLAY_NODES + 1 ]; //Lines have nodes + 1 points.
const DrawableList *selected = TheInGameUI->getAllSelectedDrawables();
Drawable *draw;
for( DrawableListCIt it = selected->begin(); it != selected->end(); ++it )
{
draw = *it;
Object *obj = draw->getObject();
Int numPoints = 1;
if( obj && ! obj->isKindOf( KINDOF_IGNORED_IN_GUI ))//so mobs and stuff sont make a gazillion lines
{
AIUpdateInterface *ai = obj->getAI();
Int goalSize = ai ? ai->friend_getWaypointGoalPathSize() : 0;
Int gpIdx = ai ? ai->friend_getCurrentGoalPathIndex() : 0;
if( ai && gpIdx >= 0 && gpIdx < goalSize )
{
const Coord3D *pos = obj->getPosition();
points[ 0 ].Set( Vector3( pos->x, pos->y, pos->z ) );
for( int i = gpIdx; i < goalSize; i++ )
{
const Coord3D *waypoint = ai->friend_getGoalPathPosition( i );
if( waypoint )
{
//Render line from previous point to current node.
if( numPoints < MAX_DISPLAY_NODES + 1 )
{
points[ numPoints ].Set( Vector3( waypoint->x, waypoint->y, waypoint->z ) );
numPoints++;
}
m_waypointNodeRobj->Set_Position(Vector3(waypoint->x,waypoint->y,waypoint->z));
WW3D::Render(*m_waypointNodeRobj,localRinfo);
}
}
//Now render the lines in one pass!
m_line->Set_Points( numPoints, points );
m_line->Render( localRinfo );
}
}
}
}
else // maybe we want to draw rally points, then?
{
//Create a default light environment with no lights and only full ambient.
//@todo: Fix later by copying default scene light environement from W3DScene.cpp.
LightEnvironmentClass lightEnv;
lightEnv.Reset(Vector3(0,0,0), Vector3(1.0f,1.0f,1.0f));
lightEnv.Pre_Render_Update(rinfo.Camera.Get_Transform());
RenderInfoClass localRinfo(rinfo.Camera);
localRinfo.light_environment=&lightEnv;
Vector3 points[ MAX_DISPLAY_NODES + 1 ]; //Lines have nodes + 1 points.
const DrawableList *selected = TheInGameUI->getAllSelectedDrawables();
Drawable *draw;
for( DrawableListCIt it = selected->begin(); it != selected->end(); ++it )
{
draw = *it;
Object *obj = draw->getObject();
Int numPoints = 0;
if( obj )
{
if ( ! obj->isLocallyControlled())
continue;
// WAIT! before we go browsing the drawable list for buildings that want to draw their rally points
// lets test for that very special case of having a listeningoutpost selected, and some enemy drawable moused-over
if ( obj->isKindOf( KINDOF_REVEALS_ENEMY_PATHS ) )
{
DrawableID enemyID = TheInGameUI->getMousedOverDrawableID();
Drawable *enemyDraw = TheGameClient->findDrawableByID( enemyID );
if ( enemyDraw )
{
Object *enemy = enemyDraw->getObject();
if ( enemy )
{
if ( enemy->getRelationship( obj ) == ENEMIES )
{
Coord3D delta = *obj->getPosition();
delta.sub( enemy->getPosition() );
if ( delta.length() <= obj->getVisionRange() ) // is listening outpost close enough to do this?
{
//////////////////////////////////////////////////////////////////////
AIUpdateInterface *ai = enemy->getAI();
Int goalSize = ai ? ai->friend_getWaypointGoalPathSize() : 0;
Int gpIdx = ai ? ai->friend_getCurrentGoalPathIndex() : 0;
if( ai )
{
Bool lineExists = FALSE;
const Coord3D *pos = enemy->getPosition();
points[ numPoints++ ].Set( Vector3( pos->x, pos->y, pos->z ) );
if ( gpIdx >= 0 && gpIdx < goalSize )// Ooh, the enemy is in waypoint mode
{
for( int i = gpIdx; i < goalSize; i++ )
{
const Coord3D *waypoint = ai->friend_getGoalPathPosition( i );
if( waypoint )
{
//Render line from previous point to current node.
if( numPoints < MAX_DISPLAY_NODES + 1 )
{
points[ numPoints++ ].Set( Vector3( waypoint->x, waypoint->y, waypoint->z ) );
}
m_waypointNodeRobj->Set_Position(Vector3(waypoint->x,waypoint->y,waypoint->z));
WW3D::Render(*m_waypointNodeRobj,localRinfo);
lineExists = TRUE;
}
}
}
else // then enemy may be moving to a goal position
{
const Coord3D *destinationPoint = ai->getGoalPosition();
if ( destinationPoint->length() > 1.0f )
{
points[ numPoints++ ].Set( Vector3( destinationPoint->x, destinationPoint->y, destinationPoint->z ) );
m_waypointNodeRobj->Set_Position(Vector3(destinationPoint->x,destinationPoint->y,destinationPoint->z));
WW3D::Render(*m_waypointNodeRobj,localRinfo);
lineExists = TRUE;
}
}
if ( lineExists )
{
//Now render the lines in one pass!
m_line->Set_Color( Vector3( 0.95f, 0.5f, 0.0f ) );
m_line->Set_Width( 3.0f );
m_line->Set_Points( numPoints, points );
m_line->Render( localRinfo );
}
}
//////////////////////////////////////////////////////////////////////
}
}
}
}
break;// dont even bother with the rest, since this one listening outpost satisfies the single path-line limit
}
ExitInterface *exitInterface = obj->getObjectExitInterface();
if( exitInterface )
{
Coord3D exitPoint;
if ( ! exitInterface->getExitPosition(exitPoint))
exitPoint = *obj->getPosition();
points[ numPoints ].Set( Vector3( exitPoint.x, exitPoint.y, exitPoint.z ) );
numPoints++;
Bool boxWrap = TRUE;
Coord3D naturalRallyPoint;
if (exitInterface->getNaturalRallyPoint(naturalRallyPoint, FALSE))//FALSE means "without the extra offset"
{
if( !naturalRallyPoint.equals( exitPoint ) )
{
points[ numPoints ].Set( Vector3( naturalRallyPoint.x, naturalRallyPoint.y, naturalRallyPoint.z ) );
numPoints++;
}
else
{
//Helipad rally point -- so don't use box wrapping.
boxWrap = FALSE;
}
}
else
continue; //next drawable
const Coord3D *rallyPoint = exitInterface->getRallyPoint();
if( rallyPoint )
{
if( boxWrap )
{
//test to se whether the rally point flanks the side of the natural rally point
//to do this we find the two corners of the geometry extents that are nearest the natural rally point
// these define the natural rally point edge
// an intermediate point should be inserted at the corner nearest the rally point
const GeometryInfo& geom = obj->getGeometryInfo();
const Coord3D *ctr = obj->getPosition();
Coord3D NRPDelta;
NRPDelta.x = naturalRallyPoint.x - exitPoint.x;
NRPDelta.y = naturalRallyPoint.y - exitPoint.y;
NRPDelta.z = 0.0f;
//This is a quick idiot test to se whether the rally line needs to wrap the box at all
Coord3D wayOutPoint = NRPDelta;
wayOutPoint.normalize();
wayOutPoint.scale( 99999.9f );
Real wayOutLength = wayOutPoint.length();
wayOutPoint.add(&naturalRallyPoint);
//if the rallypoint is closer to the wayoutpoint than it is to the natural rally point then we definitely do not wrap
Coord3D rallyToWayOutDelta = wayOutPoint;
rallyToWayOutDelta.sub(rallyPoint);
if ( (100.0f + rallyToWayOutDelta.length()) > wayOutLength)
{
//if we passed the above idiot test, now lets be sure by testing the dotproduct of the rp against the wayoutpoint
wayOutPoint.normalize();// a normal shooting straight out the door
//next comes the delta between the NRP and the RP
Coord3D NRPToRPDelta = naturalRallyPoint;
NRPToRPDelta.sub(rallyPoint);
NRPToRPDelta.normalize();
Real dot = NRPToRPDelta.x * wayOutPoint.x + NRPToRPDelta.y * wayOutPoint.y;
if (dot > 0)
{
Real angle = obj->getOrientation();
Real c = (Real)cos(angle);
Real s = (Real)sin(angle);
Coord3D NRPToCtrDelta;
NRPToCtrDelta.x = naturalRallyPoint.x - ctr->x;
NRPToCtrDelta.y = naturalRallyPoint.y - ctr->y;
NRPToCtrDelta.x = 0.0f;
Real exc = geom.getMajorRadius() * c;
Real eyc = geom.getMinorRadius() * c;
Real exs = geom.getMajorRadius() * s;
Real eys = geom.getMinorRadius() * s;
Coord2D corners[ 4 ];
corners[0].x = ctr->x - exc - eys;
corners[0].y = ctr->y + eyc - exs;
corners[1].x = ctr->x + exc - eys;
corners[1].y = ctr->y + eyc + exs;
corners[2].x = ctr->x + exc + eys;
corners[2].y = ctr->y - eyc + exs;
corners[3].x = ctr->x - exc + eys;
corners[3].y = ctr->y - eyc - exs;
Coord2D *pNearElbow = NULL;//find the closest corner to the rallyPoint same end as door
Coord2D *pFarElbow = NULL; //find the closest corner to the rallypoint away from door
Coord2D *nearCandidate = NULL;
Coord3D cornerToRPDelta, cornerToExitDelta;
cornerToRPDelta.z = 0.0f;
cornerToExitDelta.z = 0.0f;
Real elbowDistanceNear = 99999.9f;
Real elbowDistanceFar = 99999.9f;
for (UnsignedInt cornerIndex = 0; cornerIndex < 4; ++ cornerIndex)
{
nearCandidate = &corners[cornerIndex];//for quicker array access
cornerToExitDelta.x = exitPoint.x - nearCandidate->x;
cornerToExitDelta.y = exitPoint.y - nearCandidate->y;
cornerToExitDelta.normalize();
dot = cornerToExitDelta.x * wayOutPoint.x + cornerToExitDelta.y * wayOutPoint.y;
if ( dot < 0.0f )
{
cornerToRPDelta.x = rallyPoint->x - nearCandidate->x;
cornerToRPDelta.y = rallyPoint->y - nearCandidate->y;
if (cornerToRPDelta.length() < elbowDistanceNear)
{
elbowDistanceNear = cornerToRPDelta.length();
pNearElbow = nearCandidate;
}
}
else
{
cornerToRPDelta.x = rallyPoint->x - nearCandidate->x;
cornerToRPDelta.y = rallyPoint->y - nearCandidate->y;
if (cornerToRPDelta.length() < elbowDistanceFar)
{
elbowDistanceFar = cornerToRPDelta.length();
pFarElbow = nearCandidate;
}
}
}
if (pNearElbow)//did we find a nearest corner?
{
m_waypointNodeRobj->Set_Position(Vector3(pNearElbow->x,pNearElbow->y,ctr->z));
WW3D::Render(*m_waypointNodeRobj,localRinfo); //The little hockey puck
points[ numPoints ].Set( Vector3( pNearElbow->x, pNearElbow->y, ctr->z ) );
numPoints++;
//and for that matter did we find a far side coner?
if (pFarElbow)//did we find a nearest corner?
{
// but let's test the dot of the first elbow against the rally point to find out
//whethet the rally point wraps around this one, too
Coord3D firstElbowDelta;
firstElbowDelta.x = naturalRallyPoint.x - pNearElbow->x;
firstElbowDelta.y = naturalRallyPoint.y - pNearElbow->y;
firstElbowDelta.z = 0.0f;
firstElbowDelta.normalize();
Coord3D firstToRPDelta;
firstToRPDelta.x = pNearElbow->x - rallyPoint->x;
firstToRPDelta.y = pNearElbow->y - rallyPoint->y;
firstToRPDelta.z = 0.0f;
firstToRPDelta.normalize();
dot = firstToRPDelta.x * firstElbowDelta.x + firstToRPDelta.y * firstElbowDelta.y;
if (dot < 0)// we have a second elbow
{
m_waypointNodeRobj->Set_Position(Vector3(pFarElbow->x,pFarElbow->y,ctr->z));
WW3D::Render(*m_waypointNodeRobj,localRinfo); //The little hockey puck
points[ numPoints ].Set( Vector3( pFarElbow->x, pFarElbow->y, ctr->z ) );
numPoints++;
}
}
}
}
}
}
// Finally draw the line out to the RallyPoint
points[ numPoints ].Set( Vector3( rallyPoint->x, rallyPoint->y, rallyPoint->z ) );
numPoints++;
}
else
continue;
m_waypointNodeRobj->Set_Position(Vector3(naturalRallyPoint.x,naturalRallyPoint.y,naturalRallyPoint.z));
WW3D::Render(*m_waypointNodeRobj,localRinfo); //The little hockey puck
m_line->Set_Points( numPoints, points );
m_line->Render( localRinfo );
}// end if exit interface
}
}
}
}