Skip to content

Commit 476a57c

Browse files
authored
fix(view): Introduce PRESERVE_RETAIL_SCRIPTED_CAMERA and fix or improve camera offset and zoom calculations (TheSuperHackers#2524)
1 parent ff1baff commit 476a57c

File tree

7 files changed

+114
-50
lines changed

7 files changed

+114
-50
lines changed

Core/GameEngine/Include/Common/GameDefines.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
#define PRESERVE_RETAIL_BEHAVIOR (1) // Retain behavior present in retail Generals 1.08 and Zero Hour 1.04
2828
#endif
2929

30+
#ifndef PRESERVE_RETAIL_SCRIPTED_CAMERA
31+
#define PRESERVE_RETAIL_SCRIPTED_CAMERA (1) // Retain scripted camera behavior present in retail Generals 1.08 and Zero Hour 1.04
32+
#endif
33+
3034
#ifndef RETAIL_COMPATIBLE_CRC
3135
#define RETAIL_COMPATIBLE_CRC (1) // Game is expected to be CRC compatible with retail Generals 1.08, Zero Hour 1.04
3236
#endif

Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,18 +275,27 @@ class W3DView : public View, public SubsystemInterface
275275
Bool m_cameraHasMovedSinceRequest; ///< If true, throw out all saved locations
276276
VecPosRequests m_locationRequests; ///< These are cached. New requests are added here
277277

278-
Coord3D m_cameraOffset; ///< offset for camera from view center
279-
Coord3D m_previousLookAtPosition; ///< offset for camera from view center
278+
Coord3D m_previousLookAtPosition;
280279
Coord2D m_scrollAmount; ///< scroll speed
281280
Real m_scrollAmountCutoffSqr; ///< scroll speed at which we do not adjust height
282281

283282
Real m_groundLevel; ///< height of ground.
283+
#if PRESERVE_RETAIL_SCRIPTED_CAMERA
284+
// TheSuperHackers @tweak Uses the initial ground level for preserving the original look of the scripted camera,
285+
// because alterations to the ground level do affect the positioning in subtle ways.
286+
Real m_initialGroundLevel;
287+
#endif
284288

285289
Region2D m_cameraAreaConstraints; ///< Camera should be constrained to be within this area
286290
Bool m_cameraAreaConstraintsValid; ///< If false, recalculates the camera area constraints in the next render update
287291
Bool m_recalcCameraConstraintsAfterScrolling; ///< Recalculates the camera area constraints after the user has moved the camera
288292
Bool m_recalcCamera; ///< Recalculates the camera transform in the next render update
289293

294+
Real getCameraOffsetZ() const;
295+
Real getDesiredHeight(Real x, Real y) const;
296+
Real getDesiredZoom(Real x, Real y) const;
297+
Real getMaxHeight(Real x, Real y) const;
298+
Real getMaxZoom(Real x, Real y) const;
290299
void setCameraTransform(); ///< set the transform matrix of m_3DCamera, based on m_pos & m_angle
291300
void buildCameraPosition(Vector3 &sourcePos, Vector3 &targetPos);
292301
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

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp

Lines changed: 87 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,9 @@ W3DView::W3DView()
142142
m_3DCamera = nullptr;
143143
m_2DCamera = nullptr;
144144
m_groundLevel = 10.0f;
145-
m_cameraOffset.z = TheGlobalData->m_cameraHeight;
146-
m_cameraOffset.y = -(m_cameraOffset.z / tan(TheGlobalData->m_cameraPitch * (PI / 180.0)));
147-
m_cameraOffset.x = -(m_cameraOffset.y * tan(TheGlobalData->m_cameraYaw * (PI / 180.0)));
145+
#if PRESERVE_RETAIL_SCRIPTED_CAMERA
146+
m_initialGroundLevel = m_groundLevel;
147+
#endif
148148

149149
m_viewFilterMode = FM_VIEW_DEFAULT;
150150
m_viewFilter = FT_VIEW_DEFAULT;
@@ -261,9 +261,11 @@ void W3DView::buildCameraPosition( Vector3& sourcePos, Vector3& targetPos )
261261
pos.x += m_shakeOffset.x;
262262
pos.y += m_shakeOffset.y;
263263

264-
sourcePos.X = m_cameraOffset.x;
265-
sourcePos.Y = m_cameraOffset.y;
266-
sourcePos.Z = m_cameraOffset.z;
264+
// TheSuperHackers @info The default pitch affects the look-at distance to the target.
265+
// This is strange math which would need special attention when changed.
266+
sourcePos.Z = getCameraOffsetZ();
267+
sourcePos.Y = -(sourcePos.Z / tan(TheGlobalData->m_cameraPitch * (PI / 180.0)));
268+
sourcePos.X = -(sourcePos.Y * tan(TheGlobalData->m_cameraYaw * (PI / 180.0)));
267269

268270
// set position of camera itself
269271
if (m_useRealZoomCam) //WST 10/10/2002 Real Zoom using FOV
@@ -358,7 +360,7 @@ void W3DView::buildCameraTransform( Matrix3D *transform, const Vector3 &sourcePo
358360
{
359361
//m_3DCamera->Set_View_Plane(DEG_TO_RADF(50.0f));
360362
//DEBUG_LOG(("zoom %f, SourceZ %f, posZ %f, groundLevel %f CamOffZ %f",
361-
// zoom, sourcePos.Z, pos.z, groundLevel,m_cameraOffset.z));
363+
// zoom, sourcePos.Z, pos.z, groundLevel, getCameraOffsetZ()));
362364

363365
// build new camera transform
364366
transform->Make_Identity();
@@ -422,8 +424,7 @@ void W3DView::buildCameraTransform( Matrix3D *transform, const Vector3 &sourcePo
422424
// TheSuperHackers @info Original logic responsible for zooming the camera to the desired height.
423425
Bool W3DView::zoomCameraToDesiredHeight()
424426
{
425-
const Real desiredHeight = (m_terrainHeightAtPivot + m_heightAboveGround);
426-
const Real desiredZoom = desiredHeight / m_cameraOffset.z;
427+
const Real desiredZoom = getDesiredZoom(m_pos.x, m_pos.y);
427428
const Real adjustZoom = desiredZoom - m_zoom;
428429
if (fabs(adjustZoom) >= 0.001f)
429430
{
@@ -640,6 +641,67 @@ void W3DView::getPickRay(const ICoord2D *screen, Vector3 *rayStart, Vector3 *ray
640641
*rayEnd += *rayStart; //get point on far clip plane along ray from camera.
641642
}
642643

644+
//-------------------------------------------------------------------------------------------------
645+
//-------------------------------------------------------------------------------------------------
646+
Real W3DView::getCameraOffsetZ() const
647+
{
648+
#if PRESERVE_RETAIL_SCRIPTED_CAMERA
649+
// TheSuperHackers @info xezon 04/12/2025 It is necessary to use the initial ground level for the
650+
// scripted camera height to preserve the original look of it. Otherwise the forward distance
651+
// of the camera will slightly change the view pitch.
652+
if (!m_isUserControlled)
653+
{
654+
return m_initialGroundLevel + TheGlobalData->m_cameraHeight;
655+
}
656+
#endif
657+
658+
return m_groundLevel + TheGlobalData->m_maxCameraHeight;
659+
}
660+
661+
//-------------------------------------------------------------------------------------------------
662+
//-------------------------------------------------------------------------------------------------
663+
Real W3DView::getDesiredHeight(Real x, Real y) const
664+
{
665+
#if PRESERVE_RETAIL_SCRIPTED_CAMERA
666+
// TheSuperHackers @info xezon 06/12/2025 The height above ground must be relative to the current
667+
// terrain height because the ground level is not updated for it.
668+
if (!m_isUserControlled)
669+
{
670+
return getHeightAroundPos(x, y) + m_heightAboveGround;
671+
}
672+
#endif
673+
674+
return m_groundLevel + m_heightAboveGround;
675+
}
676+
677+
//-------------------------------------------------------------------------------------------------
678+
//-------------------------------------------------------------------------------------------------
679+
Real W3DView::getMaxHeight(Real x, Real y) const
680+
{
681+
#if PRESERVE_RETAIL_SCRIPTED_CAMERA
682+
if (!m_isUserControlled)
683+
{
684+
return getHeightAroundPos(x, y) + m_maxHeightAboveGround;
685+
}
686+
#endif
687+
688+
return m_groundLevel + m_maxHeightAboveGround;
689+
}
690+
691+
//-------------------------------------------------------------------------------------------------
692+
//-------------------------------------------------------------------------------------------------
693+
Real W3DView::getDesiredZoom(Real x, Real y) const
694+
{
695+
return getDesiredHeight(x, y) / getCameraOffsetZ();
696+
}
697+
698+
//-------------------------------------------------------------------------------------------------
699+
//-------------------------------------------------------------------------------------------------
700+
Real W3DView::getMaxZoom(Real x, Real y) const
701+
{
702+
return getMaxHeight(x, y) / getCameraOffsetZ();
703+
}
704+
643705
//-------------------------------------------------------------------------------------------------
644706
/** set the transform matrix of m_3DCamera, based on m_pos & m_angle */
645707
//-------------------------------------------------------------------------------------------------
@@ -1423,7 +1485,7 @@ void W3DView::update()
14231485
// ensures that the view can reach and see all areas of the map, and especially the bottom map border.
14241486

14251487
m_terrainHeightAtPivot = getHeightAroundPos(m_pos.x, m_pos.y);
1426-
m_currentHeightAboveGround = m_cameraOffset.z * m_zoom - m_terrainHeightAtPivot;
1488+
m_currentHeightAboveGround = getCameraOffsetZ() * m_zoom - m_terrainHeightAtPivot;
14271489

14281490
if (m_okToAdjustHeight)
14291491
{
@@ -2055,7 +2117,7 @@ void W3DView::setHeightAboveGround(Real z)
20552117
void W3DView::setZoom(Real z)
20562118
{
20572119
m_heightAboveGround = m_maxHeightAboveGround * z;
2058-
m_zoom = z;
2120+
m_zoom = getDesiredZoom(m_pos.x, m_pos.y);
20592121

20602122
stopDoingScriptedCamera();
20612123
m_CameraArrivedAtWaypointOnPathFlag = false;
@@ -2068,18 +2130,8 @@ void W3DView::setZoom(Real z)
20682130
void W3DView::setZoomToDefault()
20692131
{
20702132
// default zoom has to be max, otherwise players will just zoom to max always
2071-
2072-
// terrain height + desired height offset == cameraOffset * actual zoom
2073-
// find best approximation of max terrain height we can see
2074-
Real terrainHeightMax = getHeightAroundPos(m_pos.x, m_pos.y);
2075-
2076-
Real desiredHeight = (terrainHeightMax + m_maxHeightAboveGround);
2077-
Real desiredZoom = desiredHeight / m_cameraOffset.z;
2078-
2079-
//DEBUG_LOG(("W3DView::setZoomToDefault() Current zoom: %g Desired zoom: %g", m_zoom, desiredZoom));
2080-
2081-
m_zoom = desiredZoom;
20822133
m_heightAboveGround = m_maxHeightAboveGround;
2134+
m_zoom = getMaxZoom(m_pos.x, m_pos.y);
20832135

20842136
stopDoingScriptedCamera();
20852137
m_CameraArrivedAtWaypointOnPathFlag = false;
@@ -2448,10 +2500,14 @@ void W3DView::initHeightForMap()
24482500
{
24492501
resetPivotToGround();
24502502

2451-
m_cameraOffset.z = m_groundLevel+TheGlobalData->m_cameraHeight;
2452-
m_cameraOffset.y = -(m_cameraOffset.z / tan(TheGlobalData->m_cameraPitch * (PI / 180.0)));
2453-
m_cameraOffset.x = -(m_cameraOffset.y * tan(TheGlobalData->m_cameraYaw * (PI / 180.0)));
2503+
#if PRESERVE_RETAIL_SCRIPTED_CAMERA
2504+
// jba - starting ground level can't exceed this height.
2505+
constexpr const Real MAX_GROUND_LEVEL = 120.0f;
2506+
const Real accurateGroundLevel = TheTerrainLogic->getGroundHeight(m_pos.x, m_pos.y);
2507+
m_initialGroundLevel = min(MAX_GROUND_LEVEL, accurateGroundLevel);
2508+
#endif
24542509
}
2510+
24552511
//-------------------------------------------------------------------------------------------------
24562512
//-------------------------------------------------------------------------------------------------
24572513
void W3DView::resetPivotToGround( void )
@@ -2638,22 +2694,14 @@ void W3DView::cameraModFinalZoom( Real finalZoom, Real easeIn, Real easeOut )
26382694
{
26392695
if (hasScriptedState(Scripted_Rotate))
26402696
{
2641-
Real terrainHeightMax = getHeightAroundPos(m_pos.x, m_pos.y);
2642-
Real maxHeight = (terrainHeightMax + m_maxHeightAboveGround);
2643-
Real maxZoom = maxHeight / m_cameraOffset.z;
2644-
26452697
Real time = (m_rcInfo.numFrames + m_rcInfo.numHoldFrames - m_rcInfo.curFrame)*TheW3DFrameLengthInMsec;
2646-
zoomCamera( finalZoom*maxZoom, time, time*easeIn, time*easeOut );
2698+
zoomCamera( finalZoom*getMaxZoom(m_pos.x, m_pos.y), time, time*easeIn, time*easeOut );
26472699
}
26482700
if (hasScriptedState(Scripted_MoveOnWaypointPath))
26492701
{
26502702
Coord3D pos = m_mcwpInfo.waypoints[m_mcwpInfo.numWaypoints];
2651-
Real terrainHeightMax = getHeightAroundPos(pos.x, pos.y);
2652-
Real maxHeight = (terrainHeightMax + m_maxHeightAboveGround);
2653-
Real maxZoom = maxHeight / m_cameraOffset.z;
2654-
26552703
Real time = m_mcwpInfo.totalTimeMilliseconds - m_mcwpInfo.elapsedTimeMilliseconds;
2656-
zoomCamera( finalZoom*maxZoom, time, time*easeIn, time*easeOut );
2704+
zoomCamera( finalZoom*getMaxZoom(pos.x, pos.y), time, time*easeIn, time*easeOut );
26572705
}
26582706
}
26592707

@@ -2888,13 +2936,7 @@ void W3DView::resetCamera(const Coord3D *location, Int milliseconds, Real easeIn
28882936
// m_mcwpInfo.cameraAngle[2] = m_defaultAngle;
28892937
View::setAngle(m_mcwpInfo.cameraAngle[0]);
28902938

2891-
// terrain height + desired height offset == cameraOffset * actual zoom
2892-
// find best approximation of max terrain height we can see
2893-
Real terrainHeightMax = getHeightAroundPos(location->x, location->y);
2894-
Real desiredHeight = (terrainHeightMax + m_maxHeightAboveGround);
2895-
Real desiredZoom = desiredHeight / m_cameraOffset.z;
2896-
2897-
zoomCamera( desiredZoom, milliseconds, easeIn, easeOut ); // this isn't right... or is it?
2939+
zoomCamera( getMaxZoom(location->x, location->y), milliseconds, easeIn, easeOut );
28982940

28992941
pitchCamera( 1.0f, milliseconds, easeIn, easeOut );
29002942
}
@@ -3194,6 +3236,9 @@ void W3DView::setUserControlled(Bool value)
31943236
if (m_isUserControlled != value)
31953237
{
31963238
m_isUserControlled = value;
3239+
#if PRESERVE_RETAIL_SCRIPTED_CAMERA
3240+
m_zoom = getDesiredZoom(m_pos.x, m_pos.y);
3241+
#endif
31973242
}
31983243
}
31993244

@@ -3248,9 +3293,6 @@ void W3DView::moveAlongWaypointPath(Real milliseconds)
32483293
View::setAngle(m_mcwpInfo.cameraAngle[m_mcwpInfo.numWaypoints]);
32493294

32503295
m_groundLevel = m_mcwpInfo.groundHeight[m_mcwpInfo.numWaypoints];
3251-
/////////////////////m_cameraOffset.z = m_groundLevel+TheGlobalData->m_cameraHeight;
3252-
m_cameraOffset.y = -(m_cameraOffset.z / tan(TheGlobalData->m_cameraPitch * (PI / 180.0)));
3253-
m_cameraOffset.x = -(m_cameraOffset.y * tan(TheGlobalData->m_cameraYaw * (PI / 180.0)));
32543296

32553297
Coord3D pos = m_mcwpInfo.waypoints[m_mcwpInfo.numWaypoints];
32563298
pos.z = 0;
@@ -3324,9 +3366,6 @@ void W3DView::moveAlongWaypointPath(Real milliseconds)
33243366

33253367
m_groundLevel = m_mcwpInfo.groundHeight[m_mcwpInfo.curSegment]*factor1 +
33263368
m_mcwpInfo.groundHeight[m_mcwpInfo.curSegment+1]*factor2;
3327-
//////////////m_cameraOffset.z = m_groundLevel+TheGlobalData->m_cameraHeight;
3328-
m_cameraOffset.y = -(m_cameraOffset.z / tan(TheGlobalData->m_cameraPitch * (PI / 180.0)));
3329-
m_cameraOffset.x = -(m_cameraOffset.y * tan(TheGlobalData->m_cameraYaw * (PI / 180.0)));
33303369

33313370
Coord3D start, mid, end;
33323371
if (factor<0.5) {

Generals/Code/GameEngine/Include/Common/GlobalData.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,9 @@ class GlobalData : public SubsystemInterface
184184
Real m_viewportHeightScale; // The height scale of the tactical view ranging 0..1. Used to hide the world behind the Control Bar.
185185
Real m_cameraPitch;
186186
Real m_cameraYaw;
187+
#if PRESERVE_RETAIL_SCRIPTED_CAMERA
187188
Real m_cameraHeight;
189+
#endif
188190
Real m_maxCameraHeight;
189191
Real m_minCameraHeight;
190192
Real m_terrainHeightAtEdgeOfMap;

Generals/Code/GameEngine/Source/Common/GlobalData.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,9 @@ GlobalData* GlobalData::m_theOriginal = nullptr;
182182
{ "ViewportHeightScale", INI::parseReal, nullptr, offsetof( GlobalData, m_viewportHeightScale ) },
183183
{ "CameraPitch", INI::parseReal, nullptr, offsetof( GlobalData, m_cameraPitch ) },
184184
{ "CameraYaw", INI::parseReal, nullptr, offsetof( GlobalData, m_cameraYaw ) },
185+
#if PRESERVE_RETAIL_SCRIPTED_CAMERA
185186
{ "CameraHeight", INI::parseReal, nullptr, offsetof( GlobalData, m_cameraHeight ) },
187+
#endif
186188
{ "MaxCameraHeight", INI::parseReal, nullptr, offsetof( GlobalData, m_maxCameraHeight ) },
187189
{ "MinCameraHeight", INI::parseReal, nullptr, offsetof( GlobalData, m_minCameraHeight ) },
188190
{ "TerrainHeightAtEdgeOfMap", INI::parseReal, nullptr, offsetof( GlobalData, m_terrainHeightAtEdgeOfMap ) },
@@ -838,7 +840,9 @@ GlobalData::GlobalData()
838840

839841
m_cameraPitch = 0.0f;
840842
m_cameraYaw = 0.0f;
843+
#if PRESERVE_RETAIL_SCRIPTED_CAMERA
841844
m_cameraHeight = 0.0f;
845+
#endif
842846
m_minCameraHeight = 100.0f;
843847
m_maxCameraHeight = 300.0f;
844848
m_terrainHeightAtEdgeOfMap = 0.0f;

GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,9 @@ class GlobalData : public SubsystemInterface
185185
Real m_viewportHeightScale; // The height scale of the tactical view ranging 0..1. Used to hide the world behind the Control Bar.
186186
Real m_cameraPitch;
187187
Real m_cameraYaw;
188+
#if PRESERVE_RETAIL_SCRIPTED_CAMERA
188189
Real m_cameraHeight;
190+
#endif
189191
Real m_maxCameraHeight;
190192
Real m_minCameraHeight;
191193
Real m_terrainHeightAtEdgeOfMap;

GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,9 @@ GlobalData* GlobalData::m_theOriginal = nullptr;
182182
{ "ViewportHeightScale", INI::parseReal, nullptr, offsetof( GlobalData, m_viewportHeightScale ) },
183183
{ "CameraPitch", INI::parseReal, nullptr, offsetof( GlobalData, m_cameraPitch ) },
184184
{ "CameraYaw", INI::parseReal, nullptr, offsetof( GlobalData, m_cameraYaw ) },
185+
#if PRESERVE_RETAIL_SCRIPTED_CAMERA
185186
{ "CameraHeight", INI::parseReal, nullptr, offsetof( GlobalData, m_cameraHeight ) },
187+
#endif
186188
{ "MaxCameraHeight", INI::parseReal, nullptr, offsetof( GlobalData, m_maxCameraHeight ) },
187189
{ "MinCameraHeight", INI::parseReal, nullptr, offsetof( GlobalData, m_minCameraHeight ) },
188190
{ "TerrainHeightAtEdgeOfMap", INI::parseReal, nullptr, offsetof( GlobalData, m_terrainHeightAtEdgeOfMap ) },
@@ -842,7 +844,9 @@ GlobalData::GlobalData()
842844

843845
m_cameraPitch = 0.0f;
844846
m_cameraYaw = 0.0f;
847+
#if PRESERVE_RETAIL_SCRIPTED_CAMERA
845848
m_cameraHeight = 0.0f;
849+
#endif
846850
m_minCameraHeight = 100.0f;
847851
m_maxCameraHeight = 300.0f;
848852
m_terrainHeightAtEdgeOfMap = 0.0f;

0 commit comments

Comments
 (0)