/* ** 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: W3DLaserDraw.cpp ///////////////////////////////////////////////////////////////////////// // Author: Colin Day, May 2001 // Desc: W3DLaserDraw // Updated: Kris Morness July 2002 -- made it data driven and added new features to make it flexible. /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include #include "Common/Thing.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" #include "GameClient/Color.h" #include "GameClient/Drawable.h" #include "GameClient/GameClient.h" #include "GameClient/RayEffect.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Object.h" #include "GameLogic/TerrainLogic.h" #include "GameLogic/Module/LaserUpdate.h" #include "W3DDevice/GameClient/Module/W3DLaserDraw.h" #include "W3DDevice/GameClient/W3DDisplay.h" #include "W3DDevice/GameClient/W3DScene.h" #include "WW3D2/RInfo.h" #include "WW3D2/Camera.h" #include "WW3D2/Segline.h" #include "WWMath/Vector3.h" #include "WW3D2/AssetMgr.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- W3DLaserDrawModuleData::W3DLaserDrawModuleData() { m_innerBeamWidth = 0.0f; //The total width of beam m_outerBeamWidth = 1.0f; //The total width of beam m_numBeams = 1; //Number of overlapping cylinders that make the beam. 1 beam will just use inner data. m_maxIntensityFrames = 0; //Laser stays at max intensity for specified time in ms. m_fadeFrames = 0; //Laser will fade and delete. m_scrollRate = 0.0f; m_tile = false; m_segments = 1; m_arcHeight = 0.0f; m_segmentOverlapRatio = 0.0f; m_tilingScalar = 1.0f; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- W3DLaserDrawModuleData::~W3DLaserDrawModuleData() { } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void W3DLaserDrawModuleData::buildFieldParse(MultiIniFieldParse& p) { ModuleData::buildFieldParse(p); static const FieldParse dataFieldParse[] = { { "NumBeams", INI::parseUnsignedInt, NULL, offsetof( W3DLaserDrawModuleData, m_numBeams ) }, { "InnerBeamWidth", INI::parseReal, NULL, offsetof( W3DLaserDrawModuleData, m_innerBeamWidth ) }, { "OuterBeamWidth", INI::parseReal, NULL, offsetof( W3DLaserDrawModuleData, m_outerBeamWidth ) }, { "InnerColor", INI::parseColorInt, NULL, offsetof( W3DLaserDrawModuleData, m_innerColor ) }, { "OuterColor", INI::parseColorInt, NULL, offsetof( W3DLaserDrawModuleData, m_outerColor ) }, { "MaxIntensityLifetime", INI::parseDurationUnsignedInt, NULL, offsetof( W3DLaserDrawModuleData, m_maxIntensityFrames ) }, { "FadeLifetime", INI::parseDurationUnsignedInt, NULL, offsetof( W3DLaserDrawModuleData, m_fadeFrames ) }, { "Texture", INI::parseAsciiString, NULL, offsetof( W3DLaserDrawModuleData, m_textureName ) }, { "ScrollRate", INI::parseReal, NULL, offsetof( W3DLaserDrawModuleData, m_scrollRate ) }, { "Tile", INI::parseBool, NULL, offsetof( W3DLaserDrawModuleData, m_tile ) }, { "Segments", INI::parseUnsignedInt, NULL, offsetof( W3DLaserDrawModuleData, m_segments ) }, { "ArcHeight", INI::parseReal, NULL, offsetof( W3DLaserDrawModuleData, m_arcHeight ) }, { "SegmentOverlapRatio", INI::parseReal, NULL, offsetof( W3DLaserDrawModuleData, m_segmentOverlapRatio ) }, { "TilingScalar", INI::parseReal, NULL, offsetof( W3DLaserDrawModuleData, m_tilingScalar ) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- W3DLaserDraw::W3DLaserDraw( Thing *thing, const ModuleData* moduleData ) : DrawModule( thing, moduleData ), m_line3D(NULL), m_texture(NULL), m_textureAspectRatio(1.0f), m_selfDirty(TRUE) { Vector3 dummyPos1( 0.0f, 0.0f, 0.0f ); Vector3 dummyPos2( 1.0f, 1.0f, 1.0f ); Int i; const W3DLaserDrawModuleData *data = getW3DLaserDrawModuleData(); m_texture = WW3DAssetManager::Get_Instance()->Get_Texture( data->m_textureName.str() ); if (m_texture) { if (!m_texture->Is_Initialized()) m_texture->Init(); //make sure texture is actually loaded before accessing surface. SurfaceClass::SurfaceDescription surfaceDesc; m_texture->Get_Level_Description(surfaceDesc); m_textureAspectRatio = (Real)surfaceDesc.Width/(Real)surfaceDesc.Height; } //Get the color components for calculation purposes. Real innerRed, innerGreen, innerBlue, innerAlpha, outerRed, outerGreen, outerBlue, outerAlpha; GameGetColorComponentsReal( data->m_innerColor, &innerRed, &innerGreen, &innerBlue, &innerAlpha ); GameGetColorComponentsReal( data->m_outerColor, &outerRed, &outerGreen, &outerBlue, &outerAlpha ); //Make sure our beams range between 1 and the maximum cap. #ifdef I_WANT_TO_BE_FIRED // srj sez: this data is const for a reason. casting away the constness because we don't like the values // isn't an acceptable solution. if you need to constrain the values, do so at parsing time, when // it's still legal to modify these values. (In point of fact, there's not even really any reason to limit // the numBeams or segments anymore.) data->m_numBeams = __min( __max( 1, data->m_numBeams ), MAX_LASER_LINES ); data->m_segments = __min( __max( 1, data->m_segments ), MAX_SEGMENTS ); data->m_tilingScalar = __max( 0.01f, data->m_tilingScalar ); #endif //Allocate an array of lines equal to the number of beams * segments m_line3D = NEW SegmentedLineClass *[ data->m_numBeams * data->m_segments ]; for( int segment = 0; segment < data->m_segments; segment++ ) { //We don't care about segment positioning yet until we actually set the position // create all the lines we need at the right transparency level for( i = data->m_numBeams - 1; i >= 0; i-- ) { int index = segment * data->m_numBeams + i; Real red, green, blue, alpha, width; if( data->m_numBeams == 1 ) { width = data->m_innerBeamWidth; alpha = innerAlpha; red = innerRed * innerAlpha; green = innerGreen * innerAlpha; blue = innerBlue * innerAlpha; } else { //Calculate the scale between min and max values //0 means use min value, 1 means use max value //0.2 means min value + 20% of the diff between min and max Real scale = i / ( data->m_numBeams - 1.0f); width = data->m_innerBeamWidth + scale * (data->m_outerBeamWidth - data->m_innerBeamWidth); alpha = innerAlpha + scale * (outerAlpha - innerAlpha); red = innerRed + scale * (outerRed - innerRed) * innerAlpha; green = innerGreen + scale * (outerGreen - innerGreen) * innerAlpha; blue = innerBlue + scale * (outerBlue - innerBlue) * innerAlpha; } m_line3D[ index ] = NEW SegmentedLineClass; SegmentedLineClass *line = m_line3D[ index ]; if( line ) { line->Set_Texture( m_texture ); line->Set_Shader( ShaderClass::_PresetAdditiveShader ); //pick the alpha blending mode you want - see shader.h for others. line->Set_Width( width ); line->Set_Color( Vector3( red, green, blue ) ); line->Set_UV_Offset_Rate( Vector2(0.0f, data->m_scrollRate) ); //amount to scroll texture on each draw if( m_texture ) { line->Set_Texture_Mapping_Mode(SegLineRendererClass::TILED_TEXTURE_MAP); //this tiles the texture across the line } // add to scene W3DDisplay::m_3DScene->Add_Render_Object( line ); //add it to our scene so it gets rendered with other objects. // hide the render object until the first time we come to draw it and // set the correct position line->Set_Visible( 0 ); } } // end for i } //end segment loop } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- W3DLaserDraw::~W3DLaserDraw( void ) { const W3DLaserDrawModuleData *data = getW3DLaserDrawModuleData(); for( int i = 0; i < data->m_numBeams * data->m_segments; i++ ) { // remove line from scene W3DDisplay::m_3DScene->Remove_Render_Object( m_line3D[ i ] ); // delete line REF_PTR_RELEASE( m_line3D[ i ] ); } // end for i delete [] m_line3D; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Real W3DLaserDraw::getLaserTemplateWidth() const { const W3DLaserDrawModuleData *data = getW3DLaserDrawModuleData(); return data->m_outerBeamWidth * 0.5f; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void W3DLaserDraw::doDrawModule(const Matrix3D* transformMtx) { //UnsignedInt currentFrame = TheGameClient->getFrame(); const W3DLaserDrawModuleData *data = getW3DLaserDrawModuleData(); //Get the updatemodule that drives it... Drawable *draw = getDrawable(); static NameKeyType key_LaserUpdate = NAMEKEY( "LaserUpdate" ); LaserUpdate *update = (LaserUpdate*)draw->findClientUpdateModule( key_LaserUpdate ); if( !update ) { DEBUG_ASSERTCRASH( 0, ("W3DLaserDraw::doDrawModule() expects its owner drawable %s to have a ClientUpdate = LaserUpdate module.", draw->getTemplate()->getName().str() )); return; } //If the update has moved the laser, it requires a reset of the laser. if (update->isDirty() || m_selfDirty) { update->setDirty(false); m_selfDirty = false; Vector3 laserPoints[ 2 ]; for( int segment = 0; segment < data->m_segments; segment++ ) { if( data->m_arcHeight > 0.0f && data->m_segments > 1 ) { //CALCULATE A CURVED LINE BASED ON TOTAL LENGTH AND DESIRED HEIGHT INCREASE //To do this we will use a portion of the cos wave ranging between -0.25PI //and +0.25PI. 0PI is 1.0 and 0.25PI is 0.70 -- resulting in a somewhat //gentle curve depending on the line height and length. We also have to make //the line *level* for this phase of the calculations. //Get the desired direct line Coord3D lineStart, lineEnd, lineVector; lineStart.set( update->getStartPos() ); lineEnd.set( update->getEndPos() ); //This is critical -- in the case we have sloped lines (at the end, we'll fix it) // lineEnd.z = lineStart.z; //Get the length of the line lineVector.set( &lineEnd ); lineVector.sub( &lineStart ); Real lineLength = lineVector.length(); //Get the middle point (we'll use this to determine how far we are from //that to calculate our height -- middle point is the highest). Coord3D lineMiddle; lineMiddle.set( &lineStart ); lineMiddle.add( &lineEnd ); lineMiddle.scale( 0.5 ); //The half length is used to scale with the distance from middle to //get our cos( 0 to 0.25 PI) cos value Real halfLength = lineLength * 0.5f; //Now calculate which segment we will use. Real startSegmentRatio = segment / ((Real)data->m_segments); Real endSegmentRatio = (segment + 1.0f) / ((Real)data->m_segments); //Offset the segment ever-so-slightly to minimize overlap -- only apply //to segments that are not the start/end point if( segment > 0 ) { startSegmentRatio -= data->m_segmentOverlapRatio; } if( segment < data->m_segments - 1 ) { endSegmentRatio += data->m_segmentOverlapRatio; } //Calculate our start segment position on the *ground*. Coord3D segmentStart, segmentEnd, vector; vector.set( &lineVector ); vector.scale( startSegmentRatio ); segmentStart.set( &lineStart ); segmentStart.add( &vector ); //Calculate our end segment position on the *ground*. vector.set( &lineVector ); vector.scale( endSegmentRatio ); segmentEnd.set( &lineStart ); segmentEnd.add( &vector ); //-------------------------------------------------------------------------------- //Now at this point, we have our segment line in the level positions that we want. //Calculate the raised height for the start/end segment positions using cosine. //-------------------------------------------------------------------------------- //Calculate the distance from midpoint for the start positions. vector.set( &lineMiddle ); vector.sub( &segmentStart ); Real dist = vector.length(); Real scaledRadians = dist / halfLength * PI * 0.5f; Real height = cos( scaledRadians ); height *= data->m_arcHeight; segmentStart.z += height; //Now do the same thing for the end position. vector.set( &lineMiddle ); vector.sub( &segmentEnd ); dist = vector.length(); scaledRadians = dist / halfLength * PI * 0.5f; height = cos( scaledRadians ); height *= data->m_arcHeight; segmentEnd.z += height; //This makes the laser skim the ground rather than penetrate it! laserPoints[ 0 ].Set( segmentStart.x, segmentStart.y, MAX( segmentStart.z, 2.0f + TheTerrainLogic->getGroundHeight(segmentStart.x, segmentStart.y) ) ); laserPoints[ 1 ].Set( segmentEnd.x, segmentEnd.y, MAX( segmentEnd.z, 2.0f + TheTerrainLogic->getGroundHeight(segmentEnd.x, segmentEnd.y) ) ); } else { //No arc -- way simpler! laserPoints[ 0 ].Set( update->getStartPos()->x, update->getStartPos()->y, update->getStartPos()->z ); laserPoints[ 1 ].Set( update->getEndPos()->x, update->getEndPos()->y, update->getEndPos()->z ); } //Get the color components for calculation purposes. Real innerRed, innerGreen, innerBlue, innerAlpha, outerRed, outerGreen, outerBlue, outerAlpha; GameGetColorComponentsReal( data->m_innerColor, &innerRed, &innerGreen, &innerBlue, &innerAlpha ); GameGetColorComponentsReal( data->m_outerColor, &outerRed, &outerGreen, &outerBlue, &outerAlpha ); for( Int i = data->m_numBeams - 1; i >= 0; i-- ) { Real alpha, width; int index = segment * data->m_numBeams + i; if( data->m_numBeams == 1 ) { width = data->m_innerBeamWidth * update->getWidthScale(); alpha = innerAlpha; } else { //Calculate the scale between min and max values //0 means use min value, 1 means use max value //0.2 means min value + 20% of the diff between min and max Real scale = i / ( data->m_numBeams - 1.0f); Real ultimateScale = update->getWidthScale(); width = (data->m_innerBeamWidth + scale * (data->m_outerBeamWidth - data->m_innerBeamWidth)); width *= ultimateScale; alpha = innerAlpha + scale * (outerAlpha - innerAlpha); } //Calculate the number of times to tile the line based on the height of the texture used. if( m_texture && data->m_tile ) { //Calculate the length of the line. Vector3 lineVector; Vector3::Subtract( laserPoints[1], laserPoints[0], &lineVector ); Real length = lineVector.Length(); //Adjust tile factor so texture is NOT stretched but tiled equally in both width and length. Real tileFactor = length/width*m_textureAspectRatio*data->m_tilingScalar; //Set the tile factor m_line3D[ index ]->Set_Texture_Tile_Factor( tileFactor ); //number of times to tile texture across each segment } m_line3D[ index ]->Set_Width( width ); m_line3D[ index ]->Set_Points( 2, &laserPoints[0] ); } } } return; } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void W3DLaserDraw::crc( Xfer *xfer ) { // extend base class DrawModule::crc( xfer ); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void W3DLaserDraw::xfer( Xfer *xfer ) { // version const XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class DrawModule::xfer( xfer ); // Kris says there is no data to save for these, go ask him. // m_selfDirty is not saved, is runtime only } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void W3DLaserDraw::loadPostProcess( void ) { // extend base class DrawModule::loadPostProcess(); m_selfDirty = true; // so we update the first time after reload } // end loadPostProcess