/* ** 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: Geometry.cpp ///////////////////////////////////////////////////////////////////////////// // Author: Steven Johnson, Aug 2002 // Desc: /////////////////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #define DEFINE_GEOMETRY_NAMES // USER INCLUDES ////////////////////////////////////////////////////////////////////////////////// #include "Common/Geometry.h" #include "Common/INI.h" #include "Common/RandomValue.h" #include "Common/Xfer.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// //============================================================================= /*static*/ void GeometryInfo::parseGeometryType( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) { ((GeometryInfo*)store)->m_type = (GeometryType)INI::scanIndexList(ini->getNextToken(), GeometryNames); ((GeometryInfo*)store)->calcBoundingStuff(); } //============================================================================= /*static*/ void GeometryInfo::parseGeometryIsSmall( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) { ((GeometryInfo*)store)->m_isSmall = INI::scanBool(ini->getNextToken()); ((GeometryInfo*)store)->calcBoundingStuff(); } //============================================================================= /*static*/ void GeometryInfo::parseGeometryHeight( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) { ((GeometryInfo*)store)->m_height = INI::scanReal(ini->getNextToken()); ((GeometryInfo*)store)->calcBoundingStuff(); } //============================================================================= /*static*/ void GeometryInfo::parseGeometryMajorRadius( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) { ((GeometryInfo*)store)->m_majorRadius = INI::scanReal(ini->getNextToken()); ((GeometryInfo*)store)->calcBoundingStuff(); } //============================================================================= /*static*/ void GeometryInfo::parseGeometryMinorRadius( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) { ((GeometryInfo*)store)->m_minorRadius = INI::scanReal(ini->getNextToken()); ((GeometryInfo*)store)->calcBoundingStuff(); } //----------------------------------------------------------------------------- void GeometryInfo::set(GeometryType type, Bool isSmall, Real height, Real majorRadius, Real minorRadius) { m_type = type; m_isSmall = isSmall; switch(m_type) { case GEOMETRY_SPHERE: m_majorRadius = majorRadius; m_minorRadius = majorRadius; m_height = majorRadius; break; case GEOMETRY_CYLINDER: m_majorRadius = majorRadius; m_minorRadius = majorRadius; m_height = height; break; case GEOMETRY_BOX: m_majorRadius = majorRadius; m_minorRadius = minorRadius; m_height = height; break; }; calcBoundingStuff(); } //----------------------------------------------------------------------------- static Real calcDotProduct(const Coord3D& a, const Coord3D& b) { return a.x*b.x + a.y*b.y + a.z*b.z; } //----------------------------------------------------------------------------- static Real calcDistSquared(const Coord3D& a, const Coord3D& b) { return sqr(a.x - b.x) + sqr(a.y - b.y) + sqr(a.z - b.z); } //----------------------------------------------------------------------------- static Real calcPointToLineDistSquared(const Coord3D& pt, const Coord3D& lineStart, const Coord3D& lineEnd) { Coord3D line, lineToPt, closest; line.x = lineEnd.x - lineStart.x; line.y = lineEnd.y - lineStart.y; line.z = lineEnd.z - lineStart.z; lineToPt.x = pt.x - lineStart.x; lineToPt.y = pt.y - lineStart.y; lineToPt.z = pt.z - lineStart.z; Real dot = calcDotProduct(lineToPt, line); if (dot <= 0.0f) { // angle is obtuse return calcDistSquared(pt, lineStart); } Real lineLenSqr = calcDistSquared(lineStart, lineEnd); DEBUG_ASSERTCRASH(lineLenSqr==calcDotProduct(line,line),("hmm")); if (lineLenSqr <= dot) { return calcDistSquared(pt, lineEnd); } Real tmp = dot / lineLenSqr; closest.x = lineStart.x + tmp * line.x; closest.y = lineStart.y + tmp * line.y; closest.z = lineStart.z + tmp * line.z; return calcDistSquared(pt, closest); } //============================================================================= Bool GeometryInfo::isIntersectedByLineSegment(const Coord3D& loc, const Coord3D& from, const Coord3D& to) const { DEBUG_CRASH(("this call does not work properly for nonspheres yet. use with caution.")); /// @todo srj -- treats everything as a sphere for now. fix. Real distSquared = calcPointToLineDistSquared(loc, from, to); return distSquared <= sqr(getBoundingSphereRadius()); } //============================================================================= // given an object with this geom, located at 'pos', and another obj with the given // pos & geom, calc the min and max pitches from this to that. void GeometryInfo::calcPitches(const Coord3D& thisPos, const GeometryInfo& that, const Coord3D& thatPos, Real& minPitch, Real& maxPitch) const { Coord3D thisCenter; getCenterPosition(thisPos, thisCenter); Real dxy = sqrt(sqr(thatPos.x - thisCenter.x) + sqr(thatPos.y - thisCenter.y)); Real dz; /** @todo srj -- this could be better, by calcing it for all the corners, not just top-center and bottom-center... oh well */ dz = (thatPos.z + that.getMaxHeightAbovePosition()) - thisCenter.z; maxPitch = atan2(dz, dxy); dz = (thatPos.z - that.getMaxHeightBelowPosition()) - thisCenter.z; minPitch = atan2(dz, dxy); } //============================================================================= // given an object with this geom, SET how far above the object's canonical position its max z should extend. void GeometryInfo::setMaxHeightAbovePosition(Real z) { switch(m_type) { case GEOMETRY_SPHERE: m_majorRadius = z; break; case GEOMETRY_BOX: case GEOMETRY_CYLINDER: m_height = z; break; }; calcBoundingStuff(); } //============================================================================= // given an object with this geom, how far above the object's canonical position does its max z extend? Real GeometryInfo::getMaxHeightAbovePosition() const { switch(m_type) { case GEOMETRY_SPHERE: return m_majorRadius; case GEOMETRY_BOX: case GEOMETRY_CYLINDER: return m_height; }; return 0.0f; } //============================================================================= // given an object with this geom, how far below the object's canonical position does its max z extend? Real GeometryInfo::getMaxHeightBelowPosition() const { switch(m_type) { case GEOMETRY_SPHERE: return m_majorRadius; case GEOMETRY_BOX: case GEOMETRY_CYLINDER: return 0.0f; }; return 0.0f; } //============================================================================= // given an object with this geom, located at 'pos', where is the "center" of the geometry? Real GeometryInfo::getZDeltaToCenterPosition() const { return (m_type == GEOMETRY_SPHERE) ? 0.0f : (m_height * 0.5f); } //============================================================================= // given an object with this geom, located at 'pos', where is the "center" of the geometry? void GeometryInfo::getCenterPosition(const Coord3D& pos, Coord3D& center) const { center = pos; center.z += getZDeltaToCenterPosition(); } //============================================================================= void GeometryInfo::expandFootprint(Real radius) { m_majorRadius += radius; m_minorRadius += radius; calcBoundingStuff(); } //============================================================================= void GeometryInfo::get2DBounds(const Coord3D& geomCenter, Real angle, Region2D& bounds) const { switch(m_type) { case GEOMETRY_SPHERE: case GEOMETRY_CYLINDER: { bounds.lo.x = geomCenter.x - m_majorRadius; bounds.lo.y = geomCenter.y - m_majorRadius; bounds.hi.x = geomCenter.x + m_majorRadius; bounds.hi.y = geomCenter.y + m_majorRadius; break; } case GEOMETRY_BOX: { Real c = (Real)cos(angle); Real s = (Real)sin(angle); Real exc = m_majorRadius*c; Real eyc = m_minorRadius*c; Real exs = m_majorRadius*s; Real eys = m_minorRadius*s; Real x,y; x = geomCenter.x - exc - eys; y = geomCenter.y + eyc - exs; bounds.lo.x = x; bounds.lo.y = y; bounds.hi.x = x; bounds.hi.y = y; x = geomCenter.x + exc - eys; y = geomCenter.y + eyc + exs; if (bounds.lo.x > x) bounds.lo.x = x; if (bounds.lo.y > y) bounds.lo.y = y; if (bounds.hi.x < x) bounds.hi.x = x; if (bounds.hi.y < y) bounds.hi.y = y; x = geomCenter.x + exc + eys; y = geomCenter.y - eyc + exs; if (bounds.lo.x > x) bounds.lo.x = x; if (bounds.lo.y > y) bounds.lo.y = y; if (bounds.hi.x < x) bounds.hi.x = x; if (bounds.hi.y < y) bounds.hi.y = y; x = geomCenter.x - exc + eys; y = geomCenter.y - eyc - exs; if (bounds.lo.x > x) bounds.lo.x = x; if (bounds.lo.y > y) bounds.lo.y = y; if (bounds.hi.x < x) bounds.hi.x = x; if (bounds.hi.y < y) bounds.hi.y = y; break; } } } //============================================================================= void GeometryInfo::clipPointToFootprint(const Coord3D& geomCenter, Coord3D& ptToClip) const { switch(m_type) { case GEOMETRY_SPHERE: case GEOMETRY_CYLINDER: { Real dx = ptToClip.x - geomCenter.x; Real dy = ptToClip.y - geomCenter.y; Real radius = sqrt(sqr(dx) + sqr(dy)); if (radius > m_majorRadius) { Real ratio = m_majorRadius / radius; ptToClip.x = geomCenter.x + dx * ratio; ptToClip.y = geomCenter.y + dy * ratio; } break; } case GEOMETRY_BOX: { ptToClip.x = clamp(geomCenter.x - m_majorRadius, ptToClip.x, geomCenter.x + m_majorRadius); ptToClip.y = clamp(geomCenter.y - m_minorRadius, ptToClip.y, geomCenter.y + m_minorRadius); break; } }; } //============================================================================= inline Bool isWithin(Real a, Real b, Real c) { return a<=b && b<=c; } //============================================================================= Bool GeometryInfo::isPointInFootprint(const Coord3D& geomCenter, const Coord3D& pt) const { switch(m_type) { case GEOMETRY_SPHERE: case GEOMETRY_CYLINDER: { Real dx = pt.x - geomCenter.x; Real dy = pt.y - geomCenter.y; Real radius = sqrt(sqr(dx) + sqr(dy)); return (radius <= m_majorRadius); break; } case GEOMETRY_BOX: { return isWithin(geomCenter.x - m_majorRadius, pt.x, geomCenter.x + m_majorRadius) && isWithin(geomCenter.y - m_minorRadius, pt.y, geomCenter.y + m_minorRadius); } }; return false; } //============================================================================= void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt) const { switch(m_type) { case GEOMETRY_SPHERE: case GEOMETRY_CYLINDER: { #if 1 // this is a better technique than the more obvious radius-and-angle // one, below, because the latter tends to clump more towards the center. Real maxDistSqr = sqr(m_majorRadius); Real distSqr; do { pt.x = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius); pt.y = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius); pt.z = 0.0f; distSqr = sqr(pt.x) + sqr(pt.y); } while (distSqr > maxDistSqr); #else Real radius = GameLogicRandomValueReal(0.0f, m_boundingCircleRadius); Real angle = GameLogicRandomValueReal(-PI, PI); pt.x = radius * Cos(angle); pt.y = radius * Sin(angle); pt.z = 0.0f; #endif break; } case GEOMETRY_BOX: { pt.x = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius); pt.y = GameLogicRandomValueReal(-m_minorRadius, m_minorRadius); pt.z = 0.0f; break; } }; } //============================================================================= Real GeometryInfo::getFootprintArea() const { switch(m_type) { case GEOMETRY_SPHERE: case GEOMETRY_CYLINDER: { return PI * sqr(m_boundingCircleRadius); } case GEOMETRY_BOX: { return 4.0f * m_majorRadius * m_minorRadius; } }; DEBUG_CRASH(("should never get here")); return 0.0f; } //============================================================================= void GeometryInfo::calcBoundingStuff() { switch(m_type) { case GEOMETRY_SPHERE: { m_boundingSphereRadius = m_majorRadius; m_boundingCircleRadius = m_majorRadius; break; } case GEOMETRY_CYLINDER: { m_boundingCircleRadius = m_majorRadius; m_boundingSphereRadius = m_height*0.5; if (m_boundingSphereRadius < m_majorRadius) m_boundingSphereRadius = m_majorRadius; break; } case GEOMETRY_BOX: { m_boundingCircleRadius = sqrt(sqr(m_majorRadius) + sqr(m_minorRadius)); m_boundingSphereRadius = sqrt(sqr(m_majorRadius) + sqr(m_minorRadius) + sqr(m_height*0.5)); break; } }; } #if defined(_DEBUG) || defined(_INTERNAL) //============================================================================= void GeometryInfo::tweakExtents(ExtentModType extentModType, Real extentModAmount) { switch(extentModType) { case EXTENTMOD_HEIGHT: m_height += extentModAmount; break; case EXTENTMOD_MAJOR: m_majorRadius += extentModAmount; break; case EXTENTMOD_MINOR: m_minorRadius += extentModAmount; break; case EXTENTMOD_TYPE: m_type = (GeometryType)((m_type + ((extentModType == EXTENTMOD_TYPE)?1:0)) % GEOMETRY_NUM_TYPES); break; } m_isSmall = false; calcBoundingStuff(); } #endif #if defined(_DEBUG) || defined(_INTERNAL) //============================================================================= AsciiString GeometryInfo::getDescriptiveString() const { AsciiString msg; msg.format("%d/%d(%g %g %g)", (Int)m_type, (Int)m_isSmall, m_majorRadius, m_minorRadius, m_height); return msg; } #endif // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void GeometryInfo::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void GeometryInfo::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // type xfer->xferUser( &m_type, sizeof( GeometryType ) ); // is small xfer->xferBool( &m_isSmall ); // height xfer->xferReal( &m_height ); // major radius xfer->xferReal( &m_majorRadius ); // minor radius xfer->xferReal( &m_minorRadius ); // bouncing circle radius xfer->xferReal( &m_boundingCircleRadius ); // bounding sphere radius xfer->xferReal( &m_boundingSphereRadius ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void GeometryInfo::loadPostProcess( void ) { } // end loadPostProcess