Skip to content

bugfix(view): Fix view ray cast behavior for mouse picks and drawable occlusion#2818

Open
xezon wants to merge 7 commits into
TheSuperHackers:mainfrom
xezon:xezon/fix-view-ray-casts
Open

bugfix(view): Fix view ray cast behavior for mouse picks and drawable occlusion#2818
xezon wants to merge 7 commits into
TheSuperHackers:mainfrom
xezon:xezon/fix-view-ray-casts

Conversation

@xezon

@xezon xezon commented Jun 21, 2026

Copy link
Copy Markdown

This change fixes view ray casting behavior for mouse picks and drawable occlusion. The behavior for the radar view box is still not working correctly at low camera pitch but is otherwise still ok for regular top-down camera views. I do not yet know how to deal with the view box at low camera pitch.

The view ray now has a very long range making sure that the camera far clip plane is not limiting mouse picks. Also, view rays into the sky will no longer send units to 0,0,0: Invalid ray picks are now treated as no input.

The drawable updates made in the area created with getAxisAlignedViewRegion are now working with low camera pitch. The function falls back to the terrain draw area if the view frustum does not fully intersect with the Z plane.

Known issues

The Radar View Box still looks bad at low camera pitch as did always.

TODO

  • Replicate in Generals

@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 ThisProject The issue was introduced by this project, or this task is specific to this project Critical Severity: Minor < Major < Critical < Blocker and removed Minor Severity: Minor < Major < Critical < Blocker labels Jun 21, 2026
@greptile-apps

greptile-apps Bot commented Jun 21, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes view ray casting for mouse picks and drawable occlusion by making screenToTerrain return Bool and screenToWorldAtZ return PlaneClass::IntersectionResType, so all message-translator call sites can bail out cleanly when the cursor points at sky instead of sending units to the map origin. The pick-ray length is extended to sqr(Get_Depth()) to push the far-segment endpoint well past the far-clip plane, and getAxisAlignedViewRegion falls back to the terrain draw area when the view frustum doesn't fully intersect the Z=0 plane.

  • PlaneClass::Compute_Intersection is refactored from bool to a three-value IntersectionResType enum (NO_INTERSECTION, INSIDE_SEGMENT, OUTSIDE_LINE), with the sole call site that tested the old boolean (BaseHeightMap.cpp) correctly updated to == INSIDE_SEGMENT.
  • All message-translator callers (CommandXlat, GUICommandTranslator, PlaceEventTranslator, SelectionXlat) now guard every screenToTerrain call and abort gracefully on failure, preventing ghost commands to world-origin.
  • getAxisAlignedViewRegion falls back to the heightmap draw region when the frustum corners don't all intersect Z=0, fixing the low-pitch camera occlusion bug; reconstructViewBox clears its dirty flag at the top of the function so failed early-returns don't cause an every-frame retry.

Confidence Score: 5/5

The core ray-casting and message-translator changes are safe to merge; all changed call sites guard the new return values correctly.

The changed files are internally consistent: every message-translator caller was updated, the Compute_Intersection enum change is backwards-compatible at all existing call sites, and the reconstructViewBox flag-clear move is a correctness improvement. The one noteworthy gap — unchecked screenToTerrain calls in InGameUI.cpp — is limited to visual building-placement preview rendering and can only trigger when the camera is pitched low enough to aim at the sky, far outside normal gameplay.

GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp and Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp contain several screenToTerrain calls that were not updated to handle the new return-value semantics.

Important Files Changed

Filename Overview
Core/Libraries/Source/WWVegas/WWMath/plane.h Compute_Intersection return type changed from bool to IntersectionResType enum; all callers that previously tested the bool either don't check the return value (polygon clippers) or were explicitly updated to == INSIDE_SEGMENT.
Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp screenToTerrain and screenToWorldAtZ correctly return failure codes without writing to output parameters; pick-ray extended to sqr(depth); getAxisAlignedViewRegion falls back to heightmap region on partial frustum intersection.
Core/GameEngine/Source/GameClient/View.cpp getScreenCornerWorldPointsAtZ returns combined IntersectionResType; viewport-relative corner calculation added; correctly propagates NO_INTERSECTION and OUTSIDE_LINE from per-corner results.
Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp m_reconstructViewBox flag cleared at function start so early-returns on NO_INTERSECTION don't cause an every-frame retry; variable renames improve clarity.
Core/GameEngineDevice/Source/W3DDevice/GameClient/WorldHeightMap.cpp New getDrawRegion2D() helper extracts the repeated draw-region-to-world-space calculation into one place; correctly accounts for border size.
GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp Not modified in this PR; contains several unchecked screenToTerrain calls that now have subtly changed failure behavior — previously world would be written with (0,0,0); now it is left uninitialized when false is returned.
Core/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp anchorEnd terrain conversion now guarded and only performed when isLineBuild; worldStart/worldEnd variables correctly scoped; placeBuildAvailable(nullptr) cancellation on sky pick is correct.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User as Mouse Input
    participant Xlat as MessageTranslator
    participant View as W3DView
    participant Plane as PlaneClass
    participant Terrain as TerrainRenderObj

    User->>Xlat: MSG_MOUSE_LEFT_CLICK (screen coord)
    Xlat->>View: "screenToTerrain(screen, &world)"
    View->>View: "getPickRay() → rayStart, rayEnd (len=sqr(depth))"
    View->>Terrain: Cast_Ray(raytest)
    alt terrain hit
        Terrain-->>View: ContactPoint
        View-->>Xlat: true (world set)
        Xlat->>Xlat: appendMessage(MSG_DO_MOVE / etc.)
    else no terrain hit (sky)
        Terrain-->>View: no hit
        View-->>Xlat: false (world unchanged)
        Xlat->>Xlat: break — command discarded
    end

    Note over View,Plane: screenToWorldAtZ path
    View->>Plane: "Compute_Intersection(rayStart, rayEnd, &t)"
    alt t in [0,1]
        Plane-->>View: INSIDE_SEGMENT
    else t outside [0,1]
        Plane-->>View: OUTSIDE_LINE
    else ray parallel
        Plane-->>View: NO_INTERSECTION
    end
    View-->>View: "set world coords only if != NO_INTERSECTION"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User as Mouse Input
    participant Xlat as MessageTranslator
    participant View as W3DView
    participant Plane as PlaneClass
    participant Terrain as TerrainRenderObj

    User->>Xlat: MSG_MOUSE_LEFT_CLICK (screen coord)
    Xlat->>View: "screenToTerrain(screen, &world)"
    View->>View: "getPickRay() → rayStart, rayEnd (len=sqr(depth))"
    View->>Terrain: Cast_Ray(raytest)
    alt terrain hit
        Terrain-->>View: ContactPoint
        View-->>Xlat: true (world set)
        Xlat->>Xlat: appendMessage(MSG_DO_MOVE / etc.)
    else no terrain hit (sky)
        Terrain-->>View: no hit
        View-->>Xlat: false (world unchanged)
        Xlat->>Xlat: break — command discarded
    end

    Note over View,Plane: screenToWorldAtZ path
    View->>Plane: "Compute_Intersection(rayStart, rayEnd, &t)"
    alt t in [0,1]
        Plane-->>View: INSIDE_SEGMENT
    else t outside [0,1]
        Plane-->>View: OUTSIDE_LINE
    else ray parallel
        Plane-->>View: NO_INTERSECTION
    end
    View-->>View: "set world coords only if != NO_INTERSECTION"
Loading

Reviews (3): Last reviewed commit: "Move rayStart.Set back" | Re-trigger Greptile

Comment thread Core/Libraries/Include/Lib/BaseType.h Outdated
@xezon xezon force-pushed the xezon/fix-view-ray-casts branch from 7c85fda to 0db89be Compare June 21, 2026 15:55
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 Critical Severity: Minor < Major < Critical < Blocker Gen Relates to Generals 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.

Recent camera change changed behavior for move order to off map position

1 participant