Skip to content

bugfix(view): Fix ground level of bookmarks, replay camera and game world microphone#2595

Merged
xezon merged 7 commits intoTheSuperHackers:mainfrom
xezon:xezon/fix-view-groundlevel
Apr 19, 2026
Merged

bugfix(view): Fix ground level of bookmarks, replay camera and game world microphone#2595
xezon merged 7 commits intoTheSuperHackers:mainfrom
xezon:xezon/fix-view-groundlevel

Conversation

@xezon
Copy link
Copy Markdown

@xezon xezon commented Apr 12, 2026

This change fixes the ground level of bookmarks, replay camera and the game world microphone by merging View::m_groundLevel into View::m_pos::z.

View::m_pos::z was originally unused and View::m_groundLevel was effectively that. They are now consolidated which automatically adds the ground level into bookmarks and the replay camera through ViewLocation (cool).

The game world microphone, used for positional audio, did not calculate the microphone height correctly which was fixed as well.

In View::lookAt the pivot is now reset to the ground which corrects behavior as well.

TODO

  • Replicate in Generals
  • Test a few cutscenes

@xezon xezon added this to the Camera Improvements milestone Apr 12, 2026
@xezon xezon added Bug Something is not working right, typically is user facing Minor Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Refactor Edits the code with insignificant behavior changes, is never user facing ThisProject The issue was introduced by this project, or this task is specific to this project labels Apr 12, 2026
@xezon
Copy link
Copy Markdown
Author

xezon commented Apr 12, 2026

Generals will fail to compile until replicated.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 12, 2026

Greptile Summary

This PR consolidates View::m_groundLevel into View::m_pos.z, making the pivot's full 3D position the canonical ground-level reference used across bookmarks, the replay camera, and the audio microphone. The camera slave mode fix (using setPosition2D) from the previous review cycle is confirmed present. One regression remains: in the camera-lock follow path (W3DView::stepView, line 1430), m_pos.z = objpos.z was harmless when m_pos.z was unused, but now sets the ground level to the locked object's Z — which equals the object's flying altitude for airborne units — causing the audio microphone to be placed at aerial height instead of near terrain.

Confidence Score: 4/5

Safe to merge for all typical gameplay; the airborne camera-lock regression is a narrow edge case but should be confirmed or fixed before the branch closes.

The core consolidation is well-executed and the previously flagged camera-slave mode issue is resolved. One P1 regression remains: the camera-lock follow path writes the locked object's Z (potentially aerial altitude) into m_pos.z — which was harmless before but now corrupts the ground level concept and mis-positions the audio microphone for airborne locked objects. All other changed files look correct.

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp — specifically line 1430 in the camera-lock follow block.

Important Files Changed

Filename Overview
Core/GameEngine/Include/GameClient/View.h Introduces setPosition2D, getPosition2D, resetPivotToGround, and userResetPivotToGround; removes m_groundLevel field, consolidating ground-level semantics into m_pos.z. Clean implementation.
Core/GameEngine/Source/GameClient/View.cpp View::lookAt now uses setPosition2D to leave m_pos.z (ground level) unchanged; xfer serializes m_pos.z but lookAt immediately recalculates it via resetPivotToGround on load, so save compatibility is preserved.
Core/GameEngine/Source/Common/Audio/GameAudio.cpp Microphone placement now correctly derives desiredHeightAbs from cameraPivot.z (terrain ground level via m_pos.z) instead of calling getGroundHeight separately — a clean simplification.
Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp Most changes are correct: lookAt now calls resetPivotToGround, buildCameraTransform (slave mode) uses setPosition2D, and new movePivotToGround / resetPivotToGround implementations are solid. One regression: line 1430 sets m_pos.z = objpos.z (camera-lock follow), which was harmless when m_pos.z was unused but now corrupts ground level for airborne locked objects.
Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h Adds movePivotToGround() declaration, removes m_groundLevel field declaration, and retains m_initialGroundLevel for the PRESERVE_RETAIL_SCRIPTED_CAMERA path. No issues.
Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp Debug display now shows lookPos.z (terrain ground level) instead of the old ground level accessor, consistent with the new semantics. No issues.
Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp Removes TMoveAlongWaypointPathInfo::groundHeight usage and replaces with lookAt-based camera positioning; replicated cleanly from the core changes.
GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp Same changes as Generals/ScriptActions.cpp replicated for Zero Hour, consistent and correct.
Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp Minor updates to remove ground height references and adapt to the new View position API; no issues found.
GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp Zero Hour counterpart to the Generals CommandXlat.cpp changes; correctly replicated.
Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp Removes now-unnecessary getGroundHeight call that was compensating for the missing ground level in view position; the fix is consistent with the consolidated approach.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["View::m_pos (x, y, z)"] -->|"z = terrain ground level (NEW)"| B["AudioManager::update()"]
    A -->|"z used in getDesiredHeight()"| C["W3DView camera transform"]
    A -->|"z saved/restored via xfer"| D["Save/Load system"]

    E["W3DView::lookAt()"] -->|"setPosition2D (x,y only)"| A
    E -->|"calls"| F["W3DView::resetPivotToGround()"]
    F -->|"m_pos.z = getHeightAroundPos()"| A

    G["buildCameraTransform (slave mode)"] -->|"setPosition2D (x,y only)"| A

    H["Camera-lock follow (stepView)"] -->|"⚠ m_pos.z = objpos.z (line 1430)"| A

    B -->|"cameraPivot.z = terrain height (or aerial if locked)"| I["Microphone placement"]
    C --> J["Camera eye position"]

    style H fill:#f88,stroke:#c00
    style I fill:#ffd,stroke:#aa0
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: 1430

Comment:
**Airborne camera-lock sets `m_pos.z` to aerial altitude, not terrain height**

Before this PR, `m_pos.z` was unused and a separate `m_groundLevel` held the terrain height. This line was therefore a no-op for ground-level semantics. Now that `m_pos.z` *is* the ground level, assigning `objpos.z` here corrupts it for airborne camera-locked objects (planes, helicopters): `m_pos.z` ends up at their flying altitude instead of terrain height.

This directly flows into `AudioManager::update()` where `cameraPivot = TheTacticalView->getPosition()`, so `cameraPivot.z` equals the flying altitude. The `groundToCameraVector.z` becomes very small, and `bestScaleFactor = maxPercentage` always applies, placing the audio microphone at the aerial pivot position rather than near the terrain.

Use terrain height instead, consistent with how `resetPivotToGround` computes it:
```suggestion
			m_pos.z = TheTerrainLogic->getGroundHeight(curpos.x, curpos.y);
```

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

Reviews (7): Last reviewed commit: "Fix ground level and zoom in W3DView::lo..." | Re-trigger Greptile

Comment thread Core/GameEngine/Source/Common/Audio/GameAudio.cpp Outdated
Comment thread Core/GameEngine/Include/GameClient/View.h
Comment thread Core/GameEngine/Include/GameClient/View.h
Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp
Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp
Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp Outdated
Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp
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 to me. May also want to await @Mauller approval before merging

Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp
Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp Outdated
Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp
@xezon xezon force-pushed the xezon/fix-view-groundlevel branch from 3aa88fb to e5ff233 Compare April 14, 2026 19:42
Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp Outdated
Copy link
Copy Markdown

@Mauller Mauller 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, although what greptile brought up might want checking. But i don't see how it changes behaviour.

@xezon
Copy link
Copy Markdown
Author

xezon commented Apr 15, 2026

There are cutscene discrepancies in Generals that need looking into.

@xezon xezon force-pushed the xezon/fix-view-groundlevel branch from 8a416cf to e4d85b4 Compare April 18, 2026 12:21
m_snapImmediate = FALSE;

m_groundLevel = objpos.z;
m_pos.z = objpos.z;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Airborne camera-lock sets m_pos.z to aerial altitude, not terrain height

Before this PR, m_pos.z was unused and a separate m_groundLevel held the terrain height. This line was therefore a no-op for ground-level semantics. Now that m_pos.z is the ground level, assigning objpos.z here corrupts it for airborne camera-locked objects (planes, helicopters): m_pos.z ends up at their flying altitude instead of terrain height.

This directly flows into AudioManager::update() where cameraPivot = TheTacticalView->getPosition(), so cameraPivot.z equals the flying altitude. The groundToCameraVector.z becomes very small, and bestScaleFactor = maxPercentage always applies, placing the audio microphone at the aerial pivot position rather than near the terrain.

Use terrain height instead, consistent with how resetPivotToGround computes it:

Suggested change
m_pos.z = objpos.z;
m_pos.z = TheTerrainLogic->getGroundHeight(curpos.x, curpos.y);
Prompt To Fix With AI
This is a comment left during a code review.
Path: Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp
Line: 1430

Comment:
**Airborne camera-lock sets `m_pos.z` to aerial altitude, not terrain height**

Before this PR, `m_pos.z` was unused and a separate `m_groundLevel` held the terrain height. This line was therefore a no-op for ground-level semantics. Now that `m_pos.z` *is* the ground level, assigning `objpos.z` here corrupts it for airborne camera-locked objects (planes, helicopters): `m_pos.z` ends up at their flying altitude instead of terrain height.

This directly flows into `AudioManager::update()` where `cameraPivot = TheTacticalView->getPosition()`, so `cameraPivot.z` equals the flying altitude. The `groundToCameraVector.z` becomes very small, and `bestScaleFactor = maxPercentage` always applies, placing the audio microphone at the aerial pivot position rather than near the terrain.

Use terrain height instead, consistent with how `resetPivotToGround` computes it:
```suggestion
			m_pos.z = TheTerrainLogic->getGroundHeight(curpos.x, curpos.y);
```

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This exactly replicates what the original did. I see no mistake.

@xezon
Copy link
Copy Markdown
Author

xezon commented Apr 18, 2026

I have appended new fixes for the scripted camera in regards to W3DView::lookAt. It now looks correct in Generals compaign cutscene.

@xezon
Copy link
Copy Markdown
Author

xezon commented Apr 19, 2026

Tested a few more cinematic scenes in Generals and it looked fine.

@xezon xezon merged commit aea77d0 into TheSuperHackers:main Apr 19, 2026
17 checks passed
@xezon xezon deleted the xezon/fix-view-groundlevel branch April 19, 2026 09:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something is not working right, typically is user facing Gen Relates to Generals Minor Severity: Minor < Major < Critical < Blocker Refactor Edits the code with insignificant behavior changes, is never user facing ThisProject The issue was introduced by this project, or this task is specific to this project ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Camera bookmarks zoom out past maximum

3 participants