Skip to content

feat(view): Save position and view direction of player camera in MSG_SET_REPLAY_CAMERA#2631

Merged
xezon merged 5 commits intoTheSuperHackers:mainfrom
xezon:xezon/add-replay-camera-transform
Apr 23, 2026
Merged

feat(view): Save position and view direction of player camera in MSG_SET_REPLAY_CAMERA#2631
xezon merged 5 commits intoTheSuperHackers:mainfrom
xezon:xezon/add-replay-camera-transform

Conversation

@xezon
Copy link
Copy Markdown

@xezon xezon commented Apr 19, 2026

This change saves the position and view direction of the player camera in MSG_SET_REPLAY_CAMERA.

This way, in new replays (single player), the camera will always be at the exact location that the player recorded the replay with. This has advantages and disadvantages:

Advantages

  • If the camera positioning logic is changed (in code or in INI files), the 3d camera position will keep working regardless, because it stores absolute positions.

Disadvantages

  • If the local user uses a display resolution that is different from the one that the replay was recorded with, then the camera will not be auto corrected to accomodate the different resolution (wide screen camera adjustments are not yet implemented).

Further considerations

This may need another look in the future, especially if we plan to store player camera in Multiplayer replays.

TODO

  • Replicate in Generals

@xezon xezon added this to the Camera Improvements milestone Apr 19, 2026
@xezon xezon added Enhancement Is new feature or request Minor Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour labels Apr 19, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 19, 2026

Greptile Summary

This PR saves the absolute 3D camera position and view direction into MSG_SET_REPLAY_CAMERA (args 6 & 7) so replays restore the exact camera state at recording time, regardless of subsequent INI/code changes to the camera system. The dispatch is fully backward-compatible via a getArgumentCount() >= 8 guard, and the setCameraTransform refactoring cleanly separates clip-plane updates from transform application to enable the new set3DCameraLookAt path.

Confidence Score: 5/5

Safe to merge; the only remaining finding is a P2 defensive coding suggestion.

The previously flagged debug-build backward-compatibility crash is fully addressed by the getArgumentCount() >= 8 guard. All remaining findings are P2 style/robustness suggestions that do not affect correctness in practice.

No files require special attention.

Important Files Changed

Filename Overview
Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp Refactored setCameraTransform into updateCameraTransform/updateCameraClipPlanes/setCameraTransform(Matrix3D); added get3DCameraDirection and set3DCameraLookAt. Minor: direction not normalized before Look_At_Dir.
Core/GameEngine/Include/GameClient/View.h Changed get3DCameraPosition from pure virtual returning const ref to virtual returning by value; added get3DCameraDirection and set3DCameraLookAt virtuals with no-op defaults, removing dead overrides in LineDraw.
GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp Backward-compatible replay camera dispatch: args 4/5 hoisted, args 6/7 (camPos, camDir) read only when getArgumentCount() >= 8, then applied via set3DCameraLookAt.
Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp Same backward-compatible replay camera dispatch changes as GeneralsMD counterpart.
GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp Appends two new location arguments (camPos, camDir via get3DCameraPosition/get3DCameraDirection) to MSG_SET_REPLAY_CAMERA messages for new replays.
Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp Same replay camera message encoding changes as GeneralsMD counterpart.
Core/Libraries/Include/Lib/BaseType.h Added Coord3D::is(Real) utility method for component-wise equality check against a scalar, mirroring the existing pattern on Coord2D and ICoord2D.
Core/Libraries/Source/WWVegas/WWMath/matrix3d.cpp Added Look_At_Dir method (extracted from Look_At) used by set3DCameraLookAt; constructs a camera transform from position and direction vector.
GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp Added Get_Right_Dir, Get_Forward_Dir, Get_Up_Dir helpers derived from the camera's orthonormal transform columns.
Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp Same Get_Right_Dir/Get_Forward_Dir/Get_Up_Dir helpers as GeneralsMD counterpart.

Sequence Diagram

sequenceDiagram
    participant LA as LookAtXlat
    participant MS as MessageStream
    participant GL as GameLogicDispatch
    participant TV as TheTacticalView (W3DView)
    participant CAM as CameraClass

    Note over LA: Each game tick (recording)
    LA->>TV: get3DCameraPosition()
    TV->>CAM: Get_Position()
    CAM-->>TV: Vector3
    TV-->>LA: Coord3D camPos

    LA->>TV: get3DCameraDirection()
    TV->>CAM: Get_Forward_Dir() = -Get_Z_Vector()
    CAM-->>TV: Vector3
    TV-->>LA: Coord3D camDir

    LA->>MS: appendMessage(MSG_SET_REPLAY_CAMERA)
    LA->>MS: appendLocationArgument(pos, angle, pitch, zoom, cursor, mousePos)
    LA->>MS: appendLocationArgument(camPos) [arg 6]
    LA->>MS: appendLocationArgument(camDir) [arg 7]

    Note over GL: Each game tick (playback)
    GL->>GL: getArgumentCount() >= 8?
    alt New replay >= 8 args
        GL->>TV: userSetPosition / Angle / Pitch / Zoom
        GL->>TV: setUserControlled(false)
        GL->>TV: set3DCameraLookAt(camPos, camDir, 0)
        TV->>TV: Look_At_Dir(camPos, camDir, roll)
        TV->>TV: updateCameraClipPlanes()
        TV->>CAM: Set_Transform(matrix)
        TV->>TV: m_recalcCamera = false
    else Old replay < 8 args
        GL->>TV: userSetPosition / Angle / Pitch / Zoom only
    end
    GL->>TV: lockUserControlUntilFrame(frame+1)
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp
Line: 824-835

Comment:
**Normalize direction vector before `Look_At_Dir`**

`Look_At_Dir` is implemented using `sinp = dir.Z` and `cosp = sqrt(dir.X² + dir.Y²)`, which are only valid sin/cos values when the input direction is a unit vector. While the direction is read from an orthonormal camera transform and should be unit-length, a small normalization guard would make this robust against any accumulated floating-point drift in a long replay.

```suggestion
	Vector3 camPos(pos.x, pos.y, pos.z);
	Vector3 camDir(dir.x, dir.y, dir.z);
	camDir.Normalize();
	Matrix3D transform;
	transform.Look_At_Dir(camPos, camDir, roll);
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (4): Last reviewed commit: "Replicate in Generals" | Re-trigger Greptile

Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp Outdated
@xezon
Copy link
Copy Markdown
Author

xezon commented Apr 19, 2026

Generals fails to compile until replicated.

Comment thread Core/Libraries/Source/WWVegas/WWMath/matrix3d.cpp
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp Outdated
Copy link
Copy Markdown

@Skyaero42 Skyaero42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

@xezon xezon force-pushed the xezon/add-replay-camera-transform branch from aaf13c1 to c6cd02e Compare April 21, 2026 18:55
@xezon
Copy link
Copy Markdown
Author

xezon commented Apr 21, 2026

Replicated in Generals with conflict in wbview.cpp

D:\Projects\TheSuperHackers\GeneralsGameCode>FOR /F "delims=" %b IN ('git merge-base --fork-point main') DO git diff %b  1>changes.patch

D:\Projects\TheSuperHackers\GeneralsGameCode>git diff 88bdb7c2aef138c45f4e531b3e1f8b1034165db7  1>changes.patch

D:\Projects\TheSuperHackers\GeneralsGameCode>git apply -p2 --directory=Generals --reject --whitespace=fix changes.patch
changes.patch:472: space before tab in indent.
        /////////////////////////////////////////////////////////////////////////////
Checking patch Generals/GameEngine/Include/GameClient/View.h...
error: Generals/GameEngine/Include/GameClient/View.h: No such file or directory
Checking patch Generals/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h...
error: Generals/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h: No such file or directory
Checking patch Generals/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp...
error: Generals/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp: No such file or directory
Checking patch Generals/Libraries/Include/Lib/BaseType.h...
error: Generals/Libraries/Include/Lib/BaseType.h: No such file or directory
Checking patch Generals/Libraries/Source/WWVegas/WWMath/matrix3d.cpp...
error: Generals/Libraries/Source/WWVegas/WWMath/matrix3d.cpp: No such file or directory
Checking patch Generals/Libraries/Source/WWVegas/WWMath/matrix3d.h...
error: Generals/Libraries/Source/WWVegas/WWMath/matrix3d.h: No such file or directory
Checking patch Generals/Code/GameEngine/Source/Common/MessageStream.cpp...
Checking patch Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp...
Hunk #1 succeeded at 549 (offset 1 line).
Checking patch Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp...
Hunk #1 succeeded at 1929 (offset -33 lines).
Hunk #2 succeeded at 1944 (offset -33 lines).
Checking patch Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp...
Checking patch Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.h...
Checking patch Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp...
error: while searching for:
        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 {};

};

error: patch failed: Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp:279
Applied patch Generals/Code/GameEngine/Source/Common/MessageStream.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp cleanly.
Applied patch Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp cleanly.
Applied patch Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.h cleanly.
Applying patch Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp with 1 reject...
Rejected hunk #1.

@xezon xezon merged commit 3c73dd0 into TheSuperHackers:main Apr 23, 2026
17 checks passed
@xezon xezon deleted the xezon/add-replay-camera-transform branch April 23, 2026 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement Is new feature or request Gen Relates to Generals Minor Severity: Minor < Major < Critical < Blocker ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants