Skip to content

Commit f56249d

Browse files
committed
bugfix(view): Fix and improve camera pivoting (#2480)
1 parent a1e6432 commit f56249d

File tree

6 files changed

+110
-16
lines changed

6 files changed

+110
-16
lines changed

Core/GameEngine/Include/GameClient/View.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ class View : public Snapshot
131131
virtual void forceRedraw() = 0;
132132

133133
virtual void lookAt( const Coord3D *o ); ///< Center the view on the given coordinate
134-
virtual void initHeightForMap() {}; ///< Init the camera height for the map at the current position.
134+
virtual void initHeightForMap() {}; ///< Init the camera height for the map at the current position.
135+
virtual void resetPivotToGround() {}; ///< Set the camera pivot to the terrain height at the current position
135136
virtual void scrollBy( const Coord2D *delta ); ///< Shift the view by the given delta
136137

137138
virtual void moveCameraTo(const Coord3D *o, Int frames, Int shutter, Bool orient, Real easeIn=0.0f, Real easeOut=0.0f) { lookAt( o ); }
@@ -204,6 +205,7 @@ class View : public Snapshot
204205
Bool userSetZoomToDefault() { return doUserAction(&View::setZoomToDefault); }
205206
Bool userSetFieldOfView(Real angle) { return doUserAction(&View::setFieldOfView, angle); }
206207
Bool userLookAt(const Coord3D *o) { return doUserAction(&View::lookAt, o); }
208+
Bool userResetPivotToGround() { return doUserAction(&View::resetPivotToGround); }
207209
Bool userScrollBy(const Coord2D *delta) { return doUserAction(&View::scrollBy, delta); }
208210
Bool userSetLocation(const ViewLocation *location) { return doUserAction(&View::setLocation, location); }
209211
Bool userSetCameraLock(ObjectID id) { return doUserAction(&View::setCameraLock, id); }

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ class W3DView : public View, public SubsystemInterface
176176

177177
virtual void lookAt( const Coord3D *o ); ///< Center the view on the given coordinate
178178
virtual void initHeightForMap(); ///< Init the camera height for the map at the current position.
179+
virtual void resetPivotToGround(); ///< Set the camera pivot to the terrain height at the current position
179180
virtual void moveCameraTo(const Coord3D *o, Int milliseconds, Int shutter, Bool orient, Real easeIn, Real easeOut);
180181
virtual void moveCameraAlongWaypointPath(Waypoint *pWay, Int frames, Int shutter, Bool orient, Real easeIn, Real easeOut);
181182
virtual Bool isCameraMovementFinished();
@@ -290,6 +291,7 @@ class W3DView : public View, public SubsystemInterface
290291
void buildCameraPosition(Vector3 &sourcePos, Vector3 &targetPos);
291292
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
292293
Bool zoomCameraToDesiredHeight();
294+
Bool movePivotToGround();
293295
void updateCameraAreaConstraints();
294296
void calcCameraAreaConstraints(); ///< Recalculates the camera area constraints
295297
Real calcCameraAreaOffset(Real maxEdgeZ, Bool isLookingDown);

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

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,57 @@ Bool W3DView::zoomCameraToDesiredHeight()
435435
return false;
436436
}
437437

438+
//-------------------------------------------------------------------------------------------------
439+
// TheSuperHackers @bugfix New logic responsible for moving the camera pivot to the terrain ground.
440+
// This is essential to correctly center the camera above the ground when playing.
441+
Bool W3DView::movePivotToGround()
442+
{
443+
const Real fpsRatio = TheFramePacer->getBaseOverUpdateFpsRatio();
444+
const Real adjustFactor = TheGlobalData->m_cameraAdjustSpeed * fpsRatio;
445+
const Real groundLevel = m_groundLevel;
446+
const Real groundLevelDiff = m_terrainHeightAtPivot - groundLevel;
447+
if (fabs(groundLevelDiff) > 0.1f)
448+
{
449+
// Adjust the ground level. This will change the world height of the camera.
450+
m_groundLevel += groundLevelDiff * adjustFactor;
451+
452+
// Reposition the camera relative to its pitch.
453+
// This effectively zooms the camera in the view direction together with the ground level change.
454+
Vector3 sourcePos;
455+
Vector3 targetPos;
456+
buildCameraPosition(sourcePos, targetPos);
457+
const Vector3 delta = targetPos - sourcePos;
458+
459+
if (fabs(delta.Z) > 0.1f)
460+
{
461+
Vector2 groundLevelCenter;
462+
Vector2 terrainHeightCenter;
463+
groundLevelCenter.X = Vector3::Find_X_At_Z(groundLevel, sourcePos, targetPos);
464+
groundLevelCenter.Y = Vector3::Find_Y_At_Z(groundLevel, sourcePos, targetPos);
465+
terrainHeightCenter.X = Vector3::Find_X_At_Z(m_terrainHeightAtPivot, sourcePos, targetPos);
466+
terrainHeightCenter.Y = Vector3::Find_Y_At_Z(m_terrainHeightAtPivot, sourcePos, targetPos);
467+
Vector2 posDiff = terrainHeightCenter - groundLevelCenter;
468+
469+
// Adjust the strength of the repositioning for low camera pitch, because
470+
// it feels bad to move the camera around when it looks over the terrain.
471+
const Real pitch = WWMath::Asin(fabs(delta.Z) / delta.Length());
472+
constexpr const Real lowerPitch = DEG_TO_RADF(15.f);
473+
constexpr const Real upperPitch = DEG_TO_RADF(30.f);
474+
Real repositionStrength = WWMath::Inverse_Lerp(lowerPitch, upperPitch, pitch);
475+
repositionStrength = WWMath::Clamp(repositionStrength, 0.0f, 1.0f);
476+
posDiff *= repositionStrength;
477+
478+
Coord3D pos = *getPosition();
479+
pos.x += posDiff.X * adjustFactor;
480+
pos.y += posDiff.Y * adjustFactor;
481+
setPosition(&pos);
482+
}
483+
484+
return true;
485+
}
486+
return false;
487+
}
488+
438489
void W3DView::updateCameraAreaConstraints()
439490
{
440491
#if defined(RTS_DEBUG)
@@ -1396,7 +1447,26 @@ void W3DView::update()
13961447

13971448
if (adjustZoomWhenScrolling || adjustZoomWhenNotScrolling)
13981449
{
1450+
// TheSuperHackers @info The camera zoom has two modes:
1451+
// 1. Zoom by scaling the distance of the camera origin to the look-at target.
1452+
// Used by user zooming and the scripted camera.
1453+
// 2. Zoom by moving the camera pivot to the ground while repositioning the
1454+
// camera origin towards the look-at target. Visually this looks identical
1455+
// to (1), but changes the pivot point which is important for the rotation
1456+
// origin and map border collisions.
1457+
Bool isZoomingOrMovingPivot = false;
1458+
13991459
if (zoomCameraToDesiredHeight())
1460+
{
1461+
isZoomingOrMovingPivot = true;
1462+
}
1463+
1464+
if (movePivotToGround())
1465+
{
1466+
isZoomingOrMovingPivot = true;
1467+
}
1468+
1469+
if (isZoomingOrMovingPivot)
14001470
{
14011471
m_recalcCamera = true;
14021472

@@ -2376,16 +2446,18 @@ void W3DView::lookAt( const Coord3D *o )
23762446
//-------------------------------------------------------------------------------------------------
23772447
void W3DView::initHeightForMap()
23782448
{
2379-
m_groundLevel = TheTerrainLogic->getGroundHeight(m_pos.x, m_pos.y);
2380-
const Real MAX_GROUND_LEVEL = 120.0; // jba - starting ground level can't exceed this height.
2381-
if (m_groundLevel>MAX_GROUND_LEVEL) {
2382-
m_groundLevel = MAX_GROUND_LEVEL;
2383-
}
2449+
resetPivotToGround();
23842450

23852451
m_cameraOffset.z = m_groundLevel+TheGlobalData->m_cameraHeight;
23862452
m_cameraOffset.y = -(m_cameraOffset.z / tan(TheGlobalData->m_cameraPitch * (PI / 180.0)));
23872453
m_cameraOffset.x = -(m_cameraOffset.y * tan(TheGlobalData->m_cameraYaw * (PI / 180.0)));
2388-
m_cameraAreaConstraintsValid = false; // possible ground level change invalidates camera constraints
2454+
}
2455+
//-------------------------------------------------------------------------------------------------
2456+
//-------------------------------------------------------------------------------------------------
2457+
void W3DView::resetPivotToGround( void )
2458+
{
2459+
m_groundLevel = getHeightAroundPos(m_pos.x, m_pos.y);
2460+
m_cameraAreaConstraintsValid = false; // possible ground level change invalidates camera constraints
23892461
m_recalcCamera = true;
23902462
}
23912463

Core/Libraries/Source/WWVegas/WWMath/wwmath.h

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,15 @@ static WWINLINE float Max(float a, float b);
155155

156156
static WWINLINE int Float_As_Int(const float f) { return *((int*)&f); }
157157

158-
static WWINLINE float Lerp(float a, float b, float lerp );
159-
static WWINLINE double Lerp(double a, double b, float lerp );
158+
// Linearly interpolates between a and b using parameter t in [0, 1].
159+
// t = 0 returns a, t = 1 returns b, values in between return a proportionate blend.
160+
static WWINLINE float Lerp(float a, float b, float t);
161+
static WWINLINE double Lerp(double a, double b, float t);
162+
163+
// Computes the interpolation parameter t such that v = Lerp(a, b, t).
164+
// Returns where v lies between a and b as a ratio, typically in [0, 1].
165+
static WWINLINE float Inverse_Lerp(float a, float b, float v);
166+
static WWINLINE double Inverse_Lerp(double a, double b, float v);
160167

161168
static WWINLINE long Float_To_Long(double f);
162169

@@ -258,16 +265,25 @@ WWINLINE float WWMath::Max(float a, float b)
258265
return b;
259266
}
260267

261-
WWINLINE float WWMath::Lerp(float a, float b, float lerp )
268+
WWINLINE float WWMath::Lerp(float a, float b, float t)
269+
{
270+
return (a + (b - a)*t);
271+
}
272+
273+
WWINLINE double WWMath::Lerp(double a, double b, float t)
262274
{
263-
return (a + (b - a)*lerp);
275+
return (a + (b - a)*t);
264276
}
265277

266-
WWINLINE double WWMath::Lerp(double a, double b, float lerp )
278+
WWINLINE float WWMath::Inverse_Lerp(float a, float b, float v)
267279
{
268-
return (a + (b - a)*lerp);
280+
return (v - a) / (b - a);
269281
}
270282

283+
WWINLINE double WWMath::Inverse_Lerp(double a, double b, float v)
284+
{
285+
return (v - a) / (b - a);
286+
}
271287

272288
WWINLINE bool WWMath::Is_Valid_Float(float x)
273289
{

GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4503,9 +4503,10 @@ Bool InGameUI::areSelectedObjectsControllable() const
45034503
//------------------------------------------------------------------------------
45044504
void InGameUI::resetCamera()
45054505
{
4506-
ViewLocation currentView;
4507-
TheTacticalView->getLocation( &currentView );
4508-
TheTacticalView->resetCamera( &currentView.getPosition(), 1, 0.0f, 0.0f );
4506+
TheTacticalView->userResetPivotToGround();
4507+
TheTacticalView->userSetAngleToDefault();
4508+
TheTacticalView->userSetPitchToDefault();
4509+
TheTacticalView->userSetZoomToDefault();
45094510
}
45104511

45114512
//------------------------------------------------------------------------------

GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage
314314
// if middle button is "clicked", reset to "home" orientation
315315
if (!didMove && elapsedMsec < CLICK_DURATION_MSEC)
316316
{
317+
TheTacticalView->userResetPivotToGround();
317318
TheTacticalView->userSetAngleToDefault();
318319
TheTacticalView->userSetPitchToDefault();
319320
TheTacticalView->userSetZoomToDefault();

0 commit comments

Comments
 (0)