/* ** 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: W3DTankDraw.cpp ////////////////////////////////////////////////////////////////////////// // Draw turreted tanks // Michael S. Booth, October 2001 /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include #include #include "Common/Thing.h" #include "Common/ThingFactory.h" #include "Common/GameAudio.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" #include "GameLogic/Weapon.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Module/PhysicsUpdate.h" #include "GameLogic/Module/BodyModule.h" #include "GameLogic/ScriptEngine.h" #include "GameLogic/Module/AIUpdate.h" #include "GameClient/Drawable.h" #include "GameClient/ParticleSys.h" #include "W3DDevice/GameClient/W3DGameClient.h" #include "W3DDevice/GameClient/Module/W3DTankDraw.h" #include "WW3D2/matinfo.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif class Matrix3D; //------------------------------------------------------------------------------------------------- W3DTankDrawModuleData::W3DTankDrawModuleData() : m_treadDebrisNameLeft("TrackDebrisDirtLeft"), m_treadDebrisNameRight("TrackDebrisDirtRight"), m_treadAnimationRate(0.0f), m_treadPivotSpeedFraction(0.6f), m_treadDriveSpeedFraction(0.3f) { } //------------------------------------------------------------------------------------------------- W3DTankDrawModuleData::~W3DTankDrawModuleData() { } //------------------------------------------------------------------------------------------------- void W3DTankDrawModuleData::buildFieldParse(MultiIniFieldParse& p) { W3DModelDrawModuleData::buildFieldParse(p); static const FieldParse dataFieldParse[] = { { "TreadDebrisLeft", INI::parseAsciiString, NULL, offsetof(W3DTankDrawModuleData, m_treadDebrisNameLeft) }, { "TreadDebrisRight", INI::parseAsciiString, NULL, offsetof(W3DTankDrawModuleData, m_treadDebrisNameRight) }, { "TreadAnimationRate", INI::parseVelocityReal, NULL, offsetof(W3DTankDrawModuleData, m_treadAnimationRate) }, { "TreadPivotSpeedFraction", INI::parseReal, NULL, offsetof(W3DTankDrawModuleData, m_treadPivotSpeedFraction) }, { "TreadDriveSpeedFraction", INI::parseReal, NULL, offsetof(W3DTankDrawModuleData, m_treadDriveSpeedFraction) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- W3DTankDraw::W3DTankDraw( Thing *thing, const ModuleData* moduleData ) : W3DModelDraw( thing, moduleData ),m_prevRenderObj(NULL), m_treadDebrisLeft(NULL), m_treadDebrisRight(NULL) { m_treadDebrisLeft = NULL; m_treadDebrisRight = NULL; for (Int i=0; iattachToObject(NULL); m_treadDebrisLeft->destroy(); m_treadDebrisLeft = NULL; } if (m_treadDebrisRight) { m_treadDebrisRight->attachToObject(NULL); m_treadDebrisRight->destroy(); m_treadDebrisRight = NULL; } } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void W3DTankDraw::createEmitters( void ) { if (!m_treadDebrisLeft) { const ParticleSystemTemplate *sysTemplate; sysTemplate = TheParticleSystemManager->findTemplate(getW3DTankDrawModuleData()->m_treadDebrisNameLeft); if (sysTemplate) { m_treadDebrisLeft = TheParticleSystemManager->createParticleSystem( sysTemplate ); m_treadDebrisLeft->attachToDrawable(getDrawable()); // important: mark it as do-not-save, since we'll just re-create it when we reload. m_treadDebrisLeft->setSaveable(FALSE); // they come into being stopped. m_treadDebrisLeft->stop(); } } if (!m_treadDebrisRight) { const ParticleSystemTemplate *sysTemplate; sysTemplate = TheParticleSystemManager->findTemplate(getW3DTankDrawModuleData()->m_treadDebrisNameRight); if (sysTemplate) { m_treadDebrisRight = TheParticleSystemManager->createParticleSystem( sysTemplate ); m_treadDebrisRight->attachToDrawable(getDrawable()); // important: mark it as do-not-save, since we'll just re-create it when we reload. m_treadDebrisRight->setSaveable(FALSE); // they come into being stopped. m_treadDebrisRight->stop(); } } } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- W3DTankDraw::~W3DTankDraw() { for (Int i=0; iisDrawableEffectivelyHidden()) return; if (m_treadDebrisLeft) m_treadDebrisLeft->start(); if (m_treadDebrisRight) m_treadDebrisRight->start(); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- /** * Stop creating debris from the tank treads */ void W3DTankDraw::stopMoveDebris( void ) { if (m_treadDebrisLeft) m_treadDebrisLeft->stop(); if (m_treadDebrisRight) m_treadDebrisRight->stop(); } //------------------------------------------------------------------------------------------------- void W3DTankDraw::setHidden(Bool h) { W3DModelDraw::setHidden(h); if (h) { stopMoveDebris(); } } //------------------------------------------------------------------------------------------------- void W3DTankDraw::setFullyObscuredByShroud(Bool fullyObscured) { if (fullyObscured != getFullyObscuredByShroud()) { if (fullyObscured) stopMoveDebris(); } W3DModelDraw::setFullyObscuredByShroud(fullyObscured); } /**Update uv coordinates on each tread object to simulate movement*/ void W3DTankDraw::updateTreadPositions(Real uvDelta) { Real offset_u; TreadObjectInfo *pTread=m_treads; for (Int i=0; im_type == TREAD_LEFT) //this tread needs to scroll forwards offset_u = pTread->m_materialSettings.customUVOffset.X + uvDelta; else if (pTread->m_type == TREAD_RIGHT) //this tread needs to scroll backwards offset_u = pTread->m_materialSettings.customUVOffset.X - uvDelta; // ensure coordinates of offset are in [0, 1] range: offset_u = offset_u - WWMath::Floor(offset_u); pTread->m_materialSettings.customUVOffset.Set(offset_u,0); pTread++; } } /**Grab pointers to the sub-meshes for each tread*/ void W3DTankDraw::updateTreadObjects(void) { RenderObjClass *robj=getRenderObject(); //clear all previous tread pointers for (Int i=0; im_treadAnimationRate && robj) { for (Int i=0; i < robj->Get_Num_Sub_Objects() && m_treadCount < MAX_TREADS_PER_TANK; i++) { RenderObjClass *subObj=robj->Get_Sub_Object(i); const char *meshName; //Check if subobject name starts with "TREADS". if (subObj && subObj->Class_ID() == RenderObjClass::CLASSID_MESH && subObj->Get_Name() && ( (meshName=strchr(subObj->Get_Name(),'.') ) != 0 && *(meshName++)) &&_strnicmp(meshName,"TREADS", 6) == 0) { //check if sub-object has the correct material to do texture scrolling. MaterialInfoClass *mat=subObj->Get_Material_Info(); if (mat) { for (Int j=0; jVertex_Material_Count(); j++) { VertexMaterialClass *vmaterial=mat->Peek_Vertex_Material(j); LinearOffsetTextureMapperClass *mapper=(LinearOffsetTextureMapperClass *)vmaterial->Peek_Mapper(); if (mapper && mapper->Mapper_ID() == TextureMapperClass::MAPPER_ID_LINEAR_OFFSET) { mapper->Set_UV_Offset_Delta(Vector2(0,0)); //disable automatic scrolling subObj->Add_Ref(); //increase reference since we're storing the pointer m_treads[m_treadCount].m_robj=subObj; m_treads[m_treadCount].m_type = TREAD_MIDDLE; //default type subObj->Set_User_Data(&m_treads[m_treadCount].m_materialSettings); //tell W3D about custom material settings m_treads[m_treadCount].m_materialSettings.customUVOffset=Vector2(0,0); switch (meshName[6]) //check next character after 'TREADS' { case 'L': case 'l': m_treads[m_treadCount].m_type = TREAD_LEFT; break; case 'R': case 'r': m_treads[m_treadCount].m_type = TREAD_RIGHT; break; } m_treadCount++; } } REF_PTR_RELEASE(mat); } } REF_PTR_RELEASE(subObj); } } m_prevRenderObj = robj; } //------------------------------------------------------------------------------------------------- void W3DTankDraw::onRenderObjRecreated(void) { updateTreadObjects(); } //------------------------------------------------------------------------------------------------- /** Map behavior states into W3D animations. */ //------------------------------------------------------------------------------------------------- void W3DTankDraw::doDrawModule(const Matrix3D* transformMtx) { const Real DEBRIS_THRESHOLD = 0.00001f; Bool frozen = TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished(); frozen = frozen || TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript(); if (frozen) return; if (getRenderObject()==NULL) return; if (getRenderObject() != m_prevRenderObj) { updateTreadObjects(); } // get object from logic Object *obj = getDrawable()->getObject(); if (obj == NULL) return; // get object physics state PhysicsBehavior *physics = obj->getPhysics(); if (physics == NULL) return; const Coord3D *vel = physics->getVelocity(); // if tank is moving, kick up dust and debris Real velMag = vel->x*vel->x + vel->y*vel->y; // only care about moving on the ground if (velMag > DEBRIS_THRESHOLD && !getDrawable()->isDrawableEffectivelyHidden() && !getFullyObscuredByShroud()) startMoveDebris(); else stopMoveDebris(); // kick debris higher the faster we move Coord3D velMult; velMag = (Real)sqrt( velMag ); velMult.x = 0.5f * velMag + 0.1f; if (velMult.x > 1.0f) velMult.x = 1.0f; velMult.y = velMult.x; velMult.z = velMag + 0.1f; if (velMult.z > 1.0f) velMult.z = 1.0f; m_treadDebrisLeft->setVelocityMultiplier( &velMult ); m_treadDebrisRight->setVelocityMultiplier( &velMult ); m_treadDebrisLeft->setBurstCountMultiplier( velMult.z ); m_treadDebrisRight->setBurstCountMultiplier( velMult.z ); //Update movement of treads if (m_treadCount) { PhysicsTurningType turn=physics->getTurning(); Real offset_u; Real treadScrollSpeed=getW3DTankDrawModuleData()->m_treadAnimationRate; TreadObjectInfo *pTread=m_treads; Real maxSpeed=obj->getAIUpdateInterface()->getCurLocomotorSpeed(); //For optimization sake, we only do complex tread scrolling when tank //is mostly stationary and turning if (turn != TURN_NONE && physics->getVelocityMagnitude()/maxSpeed < getW3DTankDrawModuleData()->m_treadPivotSpeedFraction) { //Check if we have turned enough since last draw to require animation Coord3D dir; obj->getUnitDirectionVector2D(dir); Real angleToGoal = dir.x * m_lastDirection.x + dir.y * m_lastDirection.y; if (fabs(1.0f-angleToGoal) > 0.00001f) //check if difference in angle cosines is greater than some cutoff. { if (turn == TURN_NEGATIVE) //turning right updateTreadPositions(-treadScrollSpeed); else //turning left updateTreadPositions(treadScrollSpeed); } m_lastDirection=dir; //update for next frame } else if (physics->isMotive() && physics->getVelocityMagnitude()/maxSpeed >= getW3DTankDrawModuleData()->m_treadDriveSpeedFraction) { //do simple scrolling based only on speed when tank is moving straight at high speed. //we stop scrolling when tank slows down to reduce the appearance of sliding //tread scrolling speed was not directly tied into tank velocity because it looked odd //under certain situations when tank moved sideways. for (Int i=0; im_materialSettings.customUVOffset.X - treadScrollSpeed; // ensure coordinates of offset are in [0, 1] range: offset_u = offset_u - WWMath::Floor(offset_u); pTread->m_materialSettings.customUVOffset.Set(offset_u,0); pTread++; } } } W3DModelDraw::doDrawModule(transformMtx); } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void W3DTankDraw::crc( Xfer *xfer ) { // extend base class W3DModelDraw::crc( xfer ); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void W3DTankDraw::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class W3DModelDraw::xfer( xfer ); // John A and Mark W say there is no data to save here } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void W3DTankDraw::loadPostProcess( void ) { // extend base class W3DModelDraw::loadPostProcess(); // toss any existing ones and re-create 'em (since this module expects 'em to always be around) tossEmitters(); createEmitters(); } // end loadPostProcess