From 291a8540f7c67253aef4626b5d21684a1155b301 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 19 Apr 2026 12:09:08 +0200 Subject: [PATCH 1/5] feat(view): Save position and view direction of player camera in MSG_SET_REPLAY_CAMERA --- Core/GameEngine/Include/GameClient/View.h | 9 +-- .../Include/W3DDevice/GameClient/W3DView.h | 8 ++- .../Source/W3DDevice/GameClient/W3DView.cpp | 62 +++++++++++++++---- Core/Libraries/Include/Lib/BaseType.h | 45 ++++++++++++++ .../Source/WWVegas/WWMath/matrix3d.cpp | 42 +++++++------ .../Source/WWVegas/WWMath/matrix3d.h | 4 ++ .../Source/Common/MessageStream.cpp | 5 +- .../GameClient/MessageStream/LookAtXlat.cpp | 3 + .../GameLogic/System/GameLogicDispatch.cpp | 21 ++++++- .../Libraries/Source/WWVegas/WW3D2/camera.cpp | 15 +++++ .../Libraries/Source/WWVegas/WW3D2/camera.h | 5 ++ 11 files changed, 173 insertions(+), 46 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/View.h b/Core/GameEngine/Include/GameClient/View.h index 0f5ddb4cbfe..c4d982cd60a 100644 --- a/Core/GameEngine/Include/GameClient/View.h +++ b/Core/GameEngine/Include/GameClient/View.h @@ -192,7 +192,9 @@ class View : public Snapshot const Coord3D &getPosition() const { return m_pos; } ///< Returns position camera is looking at Coord2D getPosition2D() const { Coord2D c = { m_pos.x, m_pos.y }; return c; } ///< Returns position camera is looking at - virtual const Coord3D& get3DCameraPosition() const = 0; ///< Returns the actual camera position + virtual Coord3D get3DCameraPosition() const { Coord3D c={0,0,0}; return c; } ///< Returns the actual camera position + virtual Coord3D get3DCameraDirection() const { Coord3D c={0,0,0}; return c; } ///< Returns the actual camera view direction + virtual void set3DCameraLookAt(const Coord3D &pos, const Coord3D &dir, Real roll) {} ///< Set the actual camera position and view direction virtual Real getZoom() { return m_zoom; } virtual void setZoom(Real z) { m_zoom = z; } @@ -395,11 +397,6 @@ class ViewDummy : public View return 0; } virtual void forceRedraw() override {} - virtual const Coord3D& get3DCameraPosition() const override - { - static Coord3D zero = {0,0,0}; - return zero; - } virtual WorldToScreenReturn worldToScreenTriReturn(const Coord3D *w, ICoord2D *s ) override { return WTS_INVALID; diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h index bd4f7eccea3..02eb5a9dd30 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h @@ -220,7 +220,9 @@ class W3DView : public View, public SubsystemInterface CameraClass *get3DCamera() const { return m_3DCamera; } - virtual const Coord3D& get3DCameraPosition() const override; + virtual Coord3D get3DCameraPosition() const override; ///< Returns the actual camera position + virtual Coord3D get3DCameraDirection() const override; ///< Returns the actual camera view direction + virtual void set3DCameraLookAt(const Coord3D &pos, const Coord3D &dir, Real roll) override; ///< Set the actual camera position and view direction virtual void setCameraLock(ObjectID id) override; virtual void setSnapMode( CameraLockType lockType, Real lockDist ) override; @@ -295,7 +297,9 @@ class W3DView : public View, public SubsystemInterface Real getDesiredZoom(Real x, Real y) const; Real getMaxHeight(Real x, Real y) const; Real getMaxZoom(Real x, Real y) const; - void setCameraTransform(); ///< set the transform matrix of m_3DCamera, based on m_pos & m_angle + void updateCameraTransform(); ///< update the transform matrix of m_3DCamera, based on m_pos & m_angle + void updateCameraClipPlanes(); + void setCameraTransform(const Matrix3D &transform); void buildCameraPosition(Vector3 &sourcePos, Vector3 &targetPos); void buildCameraTransform(Matrix3D *transform, const Vector3 &sourcePos, const Vector3 &targetPos); ///< calculate (but do not set) the transform matrix of m_3DCamera, based on m_pos & m_angle Bool zoomCameraToDesiredHeight(); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index f35e62beade..ad10aa2e3e9 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -705,13 +705,27 @@ Real W3DView::getMaxZoom(Real x, Real y) const //------------------------------------------------------------------------------------------------- /** set the transform matrix of m_3DCamera, based on m_pos & m_angle */ //------------------------------------------------------------------------------------------------- -void W3DView::setCameraTransform() +void W3DView::updateCameraTransform() { if (TheGlobalData->m_headless) return; - m_cameraHasMovedSinceRequest = true; + updateCameraClipPlanes(); + + Vector3 sourcePos; + Vector3 targetPos; + buildCameraPosition(sourcePos, targetPos); + + Matrix3D cameraTransform; + buildCameraTransform(&cameraTransform, sourcePos, targetPos); + + setCameraTransform(cameraTransform); +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void W3DView::updateCameraClipPlanes() +{ Real farZ = 1200.0f; if (m_useRealZoomCam) //WST 10.19.2002 @@ -730,18 +744,19 @@ void W3DView::setCameraTransform() } m_3DCamera->Set_Clip_Planes(NearZ, farZ); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void W3DView::setCameraTransform(const Matrix3D &transform) +{ + m_cameraHasMovedSinceRequest = true; #if defined(RTS_DEBUG) m_3DCamera->Set_View_Plane( m_FOV, -1 ); #endif - // rebuild it (even if we just did it due to camera constraints) - Vector3 sourcePos; - Vector3 targetPos; - buildCameraPosition(sourcePos, targetPos); - Matrix3D cameraTransform; - buildCameraTransform(&cameraTransform, sourcePos, targetPos); - m_3DCamera->Set_Transform( cameraTransform ); + m_3DCamera->Set_Transform(transform); if (TheTerrainRenderObject) { @@ -790,14 +805,35 @@ void W3DView::init() } //------------------------------------------------------------------------------------------------- -const Coord3D& W3DView::get3DCameraPosition() const +Coord3D W3DView::get3DCameraPosition() const { Vector3 camera = m_3DCamera->Get_Position(); - static Coord3D pos; - pos.set( camera.X, camera.Y, camera.Z ); + Coord3D pos = { camera.X, camera.Y, camera.Z }; return pos; } +//------------------------------------------------------------------------------------------------- +Coord3D W3DView::get3DCameraDirection() const +{ + Vector3 forward = m_3DCamera->Get_Forward_Dir(); + Coord3D dir = { forward.X, forward.Y, forward.Z }; + return dir; +} + +//------------------------------------------------------------------------------------------------- +void W3DView::set3DCameraLookAt(const Coord3D &pos, const Coord3D &dir, Real roll) +{ + Vector3 camPos(pos.x, pos.y, pos.z); + Vector3 camDir(dir.x, dir.y, dir.z); + Matrix3D transform; + transform.Look_At_Dir(camPos, camDir, roll); + + updateCameraClipPlanes(); + setCameraTransform(transform); + + m_recalcCamera = false; +} + //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void W3DView::reset() @@ -1560,7 +1596,7 @@ void W3DView::update() // (gth) C&C3 if m_isCameraSlaved then force the camera to update each frame if (m_recalcCamera || m_isCameraSlaved) { - setCameraTransform(); + updateCameraTransform(); m_recalcCamera = false; } diff --git a/Core/Libraries/Include/Lib/BaseType.h b/Core/Libraries/Include/Lib/BaseType.h index 99361609f94..f951a593c9e 100644 --- a/Core/Libraries/Include/Lib/BaseType.h +++ b/Core/Libraries/Include/Lib/BaseType.h @@ -203,6 +203,11 @@ struct RealRange hi = 0.0f; } + bool is(Real value) const + { + return lo == value && hi == value; + } + // combine the given range with us such that we now encompass // both ranges void combine( RealRange &other ) @@ -222,6 +227,11 @@ struct Coord2D y = 0.0f; } + bool is(Real value) const + { + return x == value && y == value; + } + Real length() const { return (Real)sqrt( x*x + y*y ); } Real lengthSqr() const { return x*x + y*y; } @@ -309,6 +319,11 @@ struct ICoord2D y = 0; } + bool is(Int value) const + { + return x == value && y == value; + } + Int length() const { return (Int)sqrt( (double)(x*x + y*y) ); } }; @@ -322,6 +337,11 @@ struct Region2D hi.zero(); } + bool is(Real value) const + { + return lo.is(value) && hi.is(value); + } + Real width() const { return hi.x - lo.x; } Real height() const { return hi.y - lo.y; } Bool isInRegion( Real x, Real y ) const { return (lo.x < x) && (x < hi.x) && (lo.y < y) && (y < hi.y); } @@ -337,6 +357,11 @@ struct IRegion2D hi.zero(); } + bool is(Int value) const + { + return lo.is(value) && hi.is(value); + } + Int width() const { return hi.x - lo.x; } Int height() const { return hi.y - lo.y; } Bool isInRegion( Int x, Int y ) const { return (lo.x < x) && (x < hi.x) && (lo.y < y) && (y < hi.y); } @@ -376,6 +401,11 @@ struct Coord3D z = 0.0f; } + bool is(Real value) const + { + return x == value && y == value && z == value; + } + void add( const Coord3D *a ) { x += a->x; @@ -438,6 +468,11 @@ struct ICoord3D y = 0; z = 0; } + + bool is(Int value) const + { + return x == value && y == value && z == value; + } }; // For alternative see AABoxClass @@ -451,6 +486,11 @@ struct Region3D void zero() { lo.zero(); hi.zero(); } + bool is(Real value) const + { + return lo.is(value) && hi.is(value); + } + void setFromPointsNoZ(const Coord3D* points, Int count) { lo.x = points[0].x; @@ -515,6 +555,11 @@ struct IRegion3D hi.zero(); } + bool is(Int value) const + { + return lo.is(value) && hi.is(value); + } + Int width() const { return hi.x - lo.x; } Int height() const { return hi.y - lo.y; } Int depth() const { return hi.z - lo.z; } diff --git a/Core/Libraries/Source/WWVegas/WWMath/matrix3d.cpp b/Core/Libraries/Source/WWVegas/WWMath/matrix3d.cpp index 43c3eea16b5..5f2c886884a 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/matrix3d.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/matrix3d.cpp @@ -355,26 +355,30 @@ Vector3 Matrix3D::Inverse_Rotate_Vector(const Vector3 &vect) const *=============================================================================================*/ void Matrix3D::Look_At(const Vector3 &p,const Vector3 &t,float roll) { - float dx,dy,dz; //vector from p to t - float len1,len2; - float sinp,cosp; //sine and cosine of the pitch ("up-down" tilt about x) - float siny,cosy; //sine and cosine of the yaw ("left-right"tilt about z) + Vector3 dir(t - p); + dir.Normalize(); - dx = (t[0] - p[0]); - dy = (t[1] - p[1]); - dz = (t[2] - p[2]); + Look_At_Dir(p, dir, roll); +} - len1 = (float)WWMath::Sqrt(dx*dx + dy*dy + dz*dz); - len2 = (float)WWMath::Sqrt(dx*dx + dy*dy); - if (len1 != 0.0f) { - sinp = dz/len1; - cosp = len2/len1; - } else { - sinp = 0.0f; - cosp = 1.0f; - } +void Matrix3D::Look_At_Dir(const Vector3 &pos, const Vector3 &dir, float roll) +{ + float sinp, cosp; //sine and cosine of the pitch ("up-down" tilt about x) + float siny, cosy; //sine and cosine of the yaw ("left-right"tilt about z) + + float dx = dir.X; + float dy = dir.Y; + float dz = dir.Z; + + // length of projection onto XY plane + float len2 = (float)WWMath::Sqrt(dx*dx + dy*dy); + + // pitch + sinp = dz; + cosp = len2; + // yaw if (len2 != 0.0f) { siny = dy/len2; cosy = dx/len2; @@ -388,9 +392,9 @@ void Matrix3D::Look_At(const Vector3 &p,const Vector3 &t,float roll) Row[1].X = -1.0f; Row[1].Y = 0.0f; Row[1].Z = 0.0f; Row[2].X = 0.0f; Row[2].Y = 1.0f; Row[2].Z = 0.0f; - Row[0].W = p.X; - Row[1].W = p.Y; - Row[2].W = p.Z; + Row[0].W = pos.X; + Row[1].W = pos.Y; + Row[2].W = pos.Z; // Yaw rotation to make the matrix look at the projection of the target // into the x-y plane diff --git a/Core/Libraries/Source/WWVegas/WWMath/matrix3d.h b/Core/Libraries/Source/WWVegas/WWMath/matrix3d.h index daedde26eeb..3651ee2cf2c 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/matrix3d.h +++ b/Core/Libraries/Source/WWVegas/WWMath/matrix3d.h @@ -284,6 +284,10 @@ class Matrix3D // Used for pointing cameras at targets. void Look_At(const Vector3 &p,const Vector3 &t,float roll); + // Points the negative Z axis at dir. + // Used for looking with cameras into directions. + void Look_At_Dir(const Vector3 &pos, const Vector3 &dir, float roll); + // Previous look_at function follows the camera coordinate convention. // This one follows the object convention used in Commando and G. I // special cased this convention since it is used so much by us rather diff --git a/GeneralsMD/Code/GameEngine/Source/Common/MessageStream.cpp b/GeneralsMD/Code/GameEngine/Source/Common/MessageStream.cpp index db521353132..9baef2bd2b3 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/MessageStream.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/MessageStream.cpp @@ -88,15 +88,14 @@ GameMessage::~GameMessage() */ const GameMessageArgumentType *GameMessage::getArgument( Int argIndex ) const { - static const GameMessageArgumentType junk = { 0 }; - int i=0; for( GameMessageArgument *a = m_argList; a; a=a->m_next, i++ ) if (i == argIndex) return &a->m_data; DEBUG_CRASH(("argument not found")); - return &junk; + static const GameMessageArgumentType zero = { 0 }; + return &zero; } /** diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp index db8665d513a..77a8177e436 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp @@ -548,6 +548,9 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage msg->appendRealArgument( currentView.getZoom() ); msg->appendIntegerArgument( (Int)TheMouse->getMouseCursor() ); msg->appendPixelArgument( m_currentPos ); + // TheSuperHackers @tweak Save 3D camera position and direction to recover optimal playback precision + msg->appendLocationArgument( TheTacticalView->get3DCameraPosition() ); + msg->appendLocationArgument( TheTacticalView->get3DCameraDirection() ); } break; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 04c364ab8b9..75920caceb3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -1962,6 +1962,11 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) const Real angle = msg->getArgument( 1 )->real; const Real pitch = msg->getArgument( 2 )->real; const Real zoom = msg->getArgument( 3 )->real; + const Mouse::MouseCursor mouseCursor = static_cast(msg->getArgument( 4 )->integer); + const ICoord2D mousePos = msg->getArgument( 5 )->pixel; + // TheSuperHackers @tweak Load 3D camera position and direction to recover optimal playback precision + const Coord3D camPos = msg->getArgument( 6 )->location; + const Coord3D camDir = msg->getArgument( 7 )->location; // TheSuperHackers @info Definitely call in user mode to ensure the camera operates with auto-zoom // over terrain elevations, because the Replay Camera does not store the absolute camera location, @@ -1975,13 +1980,23 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) const Coord2D scroll = {0, 0}; TheTacticalView->userScrollBy(&scroll); - // TheSuperHackers @fix xezon 18/09/2025 Lock the new location to avoid user input from changing the camera in this frame. + if (!camPos.is(0) && !camDir.is(0)) + { + // TheSuperHackers @feature Override all the settings above with real camera position and view direction. + // This ensures that the camera looks EXACTLY like it was at the time of recording, no matter how the + // View is configured or tweaked. Note that the above setting are still required to set regardless, because + // when the replay camera is exited, then the pivot position and angles will be needed to build the camera + // where it was left off. + TheTacticalView->setUserControlled(false); + TheTacticalView->set3DCameraLookAt(camPos, camDir, 0.0f); + } + + // TheSuperHackers @fix Lock the new location to avoid user input from changing the camera in this frame. TheTacticalView->lockUserControlUntilFrame( getFrame() + 1 ); if (!TheLookAtTranslator->hasMouseMovedRecently()) { - TheMouse->setCursor( (Mouse::MouseCursor)(msg->getArgument( 4 )->integer) ); - ICoord2D mousePos = msg->getArgument( 5 )->pixel; + TheMouse->setCursor( mouseCursor ); TheMouse->setPosition( mousePos.x, mousePos.y ); TheLookAtTranslator->setCurrentPos( mousePos ); } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp index ba81d577238..0f736808bb3 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp @@ -289,6 +289,21 @@ void CameraClass::Set_Position(const Vector3 &v) } +Vector3 CameraClass::Get_Right_Dir() const +{ + return Get_Transform().Get_X_Vector(); +} + +Vector3 CameraClass::Get_Forward_Dir() const +{ + return -Get_Transform().Get_Z_Vector(); +} + +Vector3 CameraClass::Get_Up_Dir() const +{ + return Get_Transform().Get_Y_Vector(); +} + /*********************************************************************************************** * CameraClass::Set_View_Plane -- control over the view plane * * * diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.h b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.h index 6465fcd2a6d..39e05474f70 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.h +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.h @@ -137,6 +137,11 @@ class CameraClass : public RenderObjClass virtual void Set_Transform(const Matrix3D &m) override; virtual void Set_Position(const Vector3 &v) override; + // Get camera directions + Vector3 Get_Right_Dir() const; + Vector3 Get_Forward_Dir() const; + Vector3 Get_Up_Dir() const; + ///////////////////////////////////////////////////////////////////////////// // Render Object Interface - Bounding Volumes ///////////////////////////////////////////////////////////////////////////// From 79c26f54818c87f1eaf6677fa7e4692ae8270570 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 19 Apr 2026 18:21:26 +0200 Subject: [PATCH 2/5] Add argument count test --- .../Source/GameLogic/System/GameLogicDispatch.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 75920caceb3..7a447822748 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -1964,9 +1964,6 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) const Real zoom = msg->getArgument( 3 )->real; const Mouse::MouseCursor mouseCursor = static_cast(msg->getArgument( 4 )->integer); const ICoord2D mousePos = msg->getArgument( 5 )->pixel; - // TheSuperHackers @tweak Load 3D camera position and direction to recover optimal playback precision - const Coord3D camPos = msg->getArgument( 6 )->location; - const Coord3D camDir = msg->getArgument( 7 )->location; // TheSuperHackers @info Definitely call in user mode to ensure the camera operates with auto-zoom // over terrain elevations, because the Replay Camera does not store the absolute camera location, @@ -1980,13 +1977,16 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) const Coord2D scroll = {0, 0}; TheTacticalView->userScrollBy(&scroll); - if (!camPos.is(0) && !camDir.is(0)) + if (msg->getArgumentCount() >= 8) { // TheSuperHackers @feature Override all the settings above with real camera position and view direction. // This ensures that the camera looks EXACTLY like it was at the time of recording, no matter how the // View is configured or tweaked. Note that the above setting are still required to set regardless, because // when the replay camera is exited, then the pivot position and angles will be needed to build the camera // where it was left off. + const Coord3D camPos = msg->getArgument( 6 )->location; + const Coord3D camDir = msg->getArgument( 7 )->location; + TheTacticalView->setUserControlled(false); TheTacticalView->set3DCameraLookAt(camPos, camDir, 0.0f); } From ac342dc7366f16fcb49e927de9350884061b3045 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 19 Apr 2026 18:24:04 +0200 Subject: [PATCH 3/5] Fix wbview3d --- GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp index 0062c2c661d..5bec610e5c4 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/wbview3d.cpp @@ -279,8 +279,6 @@ class PlaceholderView : public View virtual void forceCameraAreaConstraintRecalc() override { } virtual void rotateCameraTowardPosition(const Coord3D *pLoc, Int milliseconds, Real easeIn, Real easeOut, Bool reverseRotation) override {}; ///< Rotate camera to face an object, and hold on it - virtual const Coord3D& get3DCameraPosition() const override { static Coord3D dummy; return dummy; } ///< Returns the actual camera position - virtual void setGuardBandBias( const Coord2D *gb ) override {}; }; From 168dca21d59c6a1468c7510d17bd22f380a265ae Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:16:39 +0200 Subject: [PATCH 4/5] Fix type error --- .../GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 7a447822748..99b2f763cc9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -1981,7 +1981,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) { // TheSuperHackers @feature Override all the settings above with real camera position and view direction. // This ensures that the camera looks EXACTLY like it was at the time of recording, no matter how the - // View is configured or tweaked. Note that the above setting are still required to set regardless, because + // View is configured or tweaked. Note that the above settings are still required to set regardless, because // when the replay camera is exited, then the pivot position and angles will be needed to build the camera // where it was left off. const Coord3D camPos = msg->getArgument( 6 )->location; From c6cd02e4038813218459bc4e094414870f2c7477 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:55:23 +0200 Subject: [PATCH 5/5] Replicate in Generals --- .../Source/Common/MessageStream.cpp | 5 ++--- .../GameClient/MessageStream/LookAtXlat.cpp | 3 +++ .../GameLogic/System/GameLogicDispatch.cpp | 21 ++++++++++++++++--- .../Libraries/Source/WWVegas/WW3D2/camera.cpp | 15 +++++++++++++ .../Libraries/Source/WWVegas/WW3D2/camera.h | 5 +++++ .../Code/Tools/WorldBuilder/src/wbview3d.cpp | 4 +--- 6 files changed, 44 insertions(+), 9 deletions(-) diff --git a/Generals/Code/GameEngine/Source/Common/MessageStream.cpp b/Generals/Code/GameEngine/Source/Common/MessageStream.cpp index 8b66dd87296..7b7058f3d73 100644 --- a/Generals/Code/GameEngine/Source/Common/MessageStream.cpp +++ b/Generals/Code/GameEngine/Source/Common/MessageStream.cpp @@ -88,15 +88,14 @@ GameMessage::~GameMessage() */ const GameMessageArgumentType *GameMessage::getArgument( Int argIndex ) const { - static const GameMessageArgumentType junk = { 0 }; - int i=0; for( GameMessageArgument *a = m_argList; a; a=a->m_next, i++ ) if (i == argIndex) return &a->m_data; DEBUG_CRASH(("argument not found")); - return &junk; + static const GameMessageArgumentType zero = { 0 }; + return &zero; } /** diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp index e4e4327b8d1..249bda90791 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp @@ -549,6 +549,9 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage msg->appendRealArgument( currentView.getZoom() ); msg->appendIntegerArgument( (Int)TheMouse->getMouseCursor() ); msg->appendPixelArgument( m_currentPos ); + // TheSuperHackers @tweak Save 3D camera position and direction to recover optimal playback precision + msg->appendLocationArgument( TheTacticalView->get3DCameraPosition() ); + msg->appendLocationArgument( TheTacticalView->get3DCameraDirection() ); } break; } diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index e0a62791822..86b9420cf31 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -1929,6 +1929,8 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) const Real angle = msg->getArgument( 1 )->real; const Real pitch = msg->getArgument( 2 )->real; const Real zoom = msg->getArgument( 3 )->real; + const Mouse::MouseCursor mouseCursor = static_cast(msg->getArgument( 4 )->integer); + const ICoord2D mousePos = msg->getArgument( 5 )->pixel; // TheSuperHackers @info Definitely call in user mode to ensure the camera operates with auto-zoom // over terrain elevations, because the Replay Camera does not store the absolute camera location, @@ -1942,13 +1944,26 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) const Coord2D scroll = {0, 0}; TheTacticalView->userScrollBy(&scroll); - // TheSuperHackers @fix xezon 18/09/2025 Lock the new location to avoid user input from changing the camera in this frame. + if (msg->getArgumentCount() >= 8) + { + // TheSuperHackers @feature Override all the settings above with real camera position and view direction. + // This ensures that the camera looks EXACTLY like it was at the time of recording, no matter how the + // View is configured or tweaked. Note that the above settings are still required to set regardless, because + // when the replay camera is exited, then the pivot position and angles will be needed to build the camera + // where it was left off. + const Coord3D camPos = msg->getArgument( 6 )->location; + const Coord3D camDir = msg->getArgument( 7 )->location; + + TheTacticalView->setUserControlled(false); + TheTacticalView->set3DCameraLookAt(camPos, camDir, 0.0f); + } + + // TheSuperHackers @fix Lock the new location to avoid user input from changing the camera in this frame. TheTacticalView->lockUserControlUntilFrame( getFrame() + 1 ); if (!TheLookAtTranslator->hasMouseMovedRecently()) { - TheMouse->setCursor( (Mouse::MouseCursor)(msg->getArgument( 4 )->integer) ); - ICoord2D mousePos = msg->getArgument( 5 )->pixel; + TheMouse->setCursor( mouseCursor ); TheMouse->setPosition( mousePos.x, mousePos.y ); TheLookAtTranslator->setCurrentPos( mousePos ); } diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp index 4025f183bb9..530d16f90d0 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp @@ -289,6 +289,21 @@ void CameraClass::Set_Position(const Vector3 &v) } +Vector3 CameraClass::Get_Right_Dir() const +{ + return Get_Transform().Get_X_Vector(); +} + +Vector3 CameraClass::Get_Forward_Dir() const +{ + return -Get_Transform().Get_Z_Vector(); +} + +Vector3 CameraClass::Get_Up_Dir() const +{ + return Get_Transform().Get_Y_Vector(); +} + /*********************************************************************************************** * CameraClass::Set_View_Plane -- control over the view plane * * * diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.h b/Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.h index 5b5d7549213..7fdffc9497f 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.h +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.h @@ -137,6 +137,11 @@ class CameraClass : public RenderObjClass virtual void Set_Transform(const Matrix3D &m) override; virtual void Set_Position(const Vector3 &v) override; + // Get camera directions + Vector3 Get_Right_Dir() const; + Vector3 Get_Forward_Dir() const; + Vector3 Get_Up_Dir() const; + ///////////////////////////////////////////////////////////////////////////// // Render Object Interface - Bounding Volumes ///////////////////////////////////////////////////////////////////////////// diff --git a/Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp b/Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp index 4cf30dc0f4c..4e0c57e7bc1 100644 --- a/Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp @@ -276,9 +276,7 @@ class PlaceholderView : public View virtual Real getFXPitch() const override { return 1.0f; } virtual void forceCameraAreaConstraintRecalc() override { } - virtual void rotateCameraTowardPosition(const Coord3D *pLoc, Int milliseconds) {}; ///< Rotate camera to face an object, and hold on it - - virtual const Coord3D& get3DCameraPosition() const override { static Coord3D dummy; return dummy; } ///< Returns the actual camera position + virtual void rotateCameraTowardPosition(const Coord3D *pLoc, Int milliseconds, Real easeIn, Real easeOut, Bool reverseRotation) override {}; ///< Rotate camera to face an object, and hold on it virtual void setGuardBandBias( const Coord2D *gb ) override {};