Skip to content

Commit 920854b

Browse files
committed
bugfix(view): Fix and improve camera area constraints behavior (#2506)
1 parent 920c4e6 commit 920854b

3 files changed

Lines changed: 173 additions & 70 deletions

File tree

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ class W3DView : public View, public SubsystemInterface
238238
virtual void set3DWireFrameMode(Bool enable) override; ///<enables custom wireframe rendering of 3D viewport
239239

240240
Bool updateCameraMovements();
241-
virtual void forceCameraAreaConstraintRecalc() override { calcCameraAreaConstraints(); }
241+
virtual void forceCameraAreaConstraintRecalc() override { m_cameraAreaConstraintsValid = false; }
242242

243243
virtual void setGuardBandBias( const Coord2D *gb ) override { m_guardBandBias.x = gb->x; m_guardBandBias.y = gb->y; }
244244

@@ -283,13 +283,18 @@ class W3DView : public View, public SubsystemInterface
283283

284284
Region2D m_cameraAreaConstraints; ///< Camera should be constrained to be within this area
285285
Bool m_cameraAreaConstraintsValid; ///< If false, recalculates the camera area constraints in the next render update
286+
Bool m_recalcCameraConstraintsAfterScrolling; ///< Recalculates the camera area constraints after the user has moved the camera
286287
Bool m_recalcCamera; ///< Recalculates the camera transform in the next render update
287288

288289
void setCameraTransform(); ///< set the transform matrix of m_3DCamera, based on m_pos & m_angle
289290
void buildCameraPosition(Vector3 &sourcePos, Vector3 &targetPos);
290291
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
292+
Bool zoomCameraToDesiredHeight();
293+
void updateCameraAreaConstraints();
291294
void calcCameraAreaConstraints(); ///< Recalculates the camera area constraints
292-
Real calcCameraAreaOffset(Real maxEdgeZ);
295+
Real calcCameraAreaOffset(Real maxEdgeZ, Bool isLookingDown);
296+
void clipCameraIntoAreaConstraints();
297+
Bool isWithinCameraAreaConstraints() const;
293298
Bool isWithinCameraHeightConstraints() const;
294299
virtual void setUserControlled(Bool value);
295300
Bool hasScriptedState(ScriptedState state) const;

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

Lines changed: 119 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ W3DView::W3DView()
167167
m_shakerAngles.Y =0.0f;
168168
m_shakerAngles.Z =0.0f;
169169

170+
m_cameraAreaConstraints.zero();
170171
m_recalcCamera = false;
171172

172173
}
@@ -260,14 +261,6 @@ void W3DView::buildCameraPosition( Vector3& sourcePos, Vector3& targetPos )
260261
pos.x += m_shakeOffset.x;
261262
pos.y += m_shakeOffset.y;
262263

263-
if (m_cameraAreaConstraintsValid)
264-
{
265-
pos.x = maxf(m_cameraAreaConstraints.lo.x, pos.x);
266-
pos.x = minf(m_cameraAreaConstraints.hi.x, pos.x);
267-
pos.y = maxf(m_cameraAreaConstraints.lo.y, pos.y);
268-
pos.y = minf(m_cameraAreaConstraints.hi.y, pos.y);
269-
}
270-
271264
sourcePos.X = m_cameraOffset.x;
272265
sourcePos.Y = m_cameraOffset.y;
273266
sourcePos.Z = m_cameraOffset.z;
@@ -425,6 +418,42 @@ void W3DView::buildCameraTransform( Matrix3D *transform, const Vector3 &sourcePo
425418
}
426419
}
427420

421+
//-------------------------------------------------------------------------------------------------
422+
// TheSuperHackers @info Original logic responsible for zooming the camera to the desired height.
423+
Bool W3DView::zoomCameraToDesiredHeight()
424+
{
425+
const Real desiredHeight = (m_terrainHeightAtPivot + m_heightAboveGround);
426+
const Real desiredZoom = desiredHeight / m_cameraOffset.z;
427+
const Real adjustZoom = desiredZoom - m_zoom;
428+
if (fabs(adjustZoom) >= 0.001f)
429+
{
430+
const Real fpsRatio = TheFramePacer->getBaseOverUpdateFpsRatio();
431+
const Real adjustFactor = TheGlobalData->m_cameraAdjustSpeed * fpsRatio;
432+
m_zoom += adjustZoom * adjustFactor;
433+
return true;
434+
}
435+
return false;
436+
}
437+
438+
void W3DView::updateCameraAreaConstraints()
439+
{
440+
#if defined(RTS_DEBUG)
441+
if (!TheGlobalData->m_useCameraConstraints)
442+
return;
443+
#endif
444+
445+
if (!m_cameraAreaConstraintsValid)
446+
{
447+
calcCameraAreaConstraints();
448+
}
449+
450+
if (m_cameraAreaConstraintsValid && !isWithinCameraAreaConstraints())
451+
{
452+
clipCameraIntoAreaConstraints();
453+
m_recalcCamera = true;
454+
}
455+
}
456+
428457
//-------------------------------------------------------------------------------------------------
429458
/*
430459
Note the following restrictions on camera constraints!
@@ -448,7 +477,23 @@ void W3DView::calcCameraAreaConstraints()
448477
Region3D mapRegion;
449478
TheTerrainLogic->getExtent( &mapRegion );
450479

451-
Real offset = calcCameraAreaOffset(m_groundLevel);
480+
// Update the 3D camera before using its transform to calculate the constraints with.
481+
Vector3 sourcePos;
482+
Vector3 targetPos;
483+
buildCameraPosition(sourcePos, targetPos);
484+
Matrix3D cameraTransform;
485+
buildCameraTransform(&cameraTransform, sourcePos, targetPos);
486+
487+
Matrix3D prevCameraTransform = m_3DCamera->Get_Transform();
488+
m_3DCamera->Set_Transform(cameraTransform);
489+
490+
const Vector3 cameraForward = -cameraTransform.Get_Z_Vector();
491+
const Bool isLookingDown = cameraForward.Z <= 0.0f;
492+
Real offset = calcCameraAreaOffset(m_groundLevel, isLookingDown);
493+
494+
// Revert the 3D camera transform.
495+
m_3DCamera->Set_Transform(prevCameraTransform);
496+
452497
m_cameraAreaConstraints.lo.x = mapRegion.lo.x + offset;
453498
m_cameraAreaConstraints.hi.x = mapRegion.hi.x - offset;
454499
m_cameraAreaConstraints.lo.y = mapRegion.lo.y + offset;
@@ -459,40 +504,60 @@ void W3DView::calcCameraAreaConstraints()
459504
}
460505

461506
//-------------------------------------------------------------------------------------------------
462-
Real W3DView::calcCameraAreaOffset(Real maxEdgeZ)
507+
Real W3DView::calcCameraAreaOffset(Real maxEdgeZ, Bool isLookingDown)
463508
{
464-
Coord3D center, bottom;
509+
Coord2D center;
465510
ICoord2D screen;
511+
Vector3 rayStart;
512+
Vector3 rayEnd;
466513

467514
//Pick at the center
468-
screen.x=0.5f*getWidth()+m_originX;
469-
screen.y=0.5f*getHeight()+m_originY;
470-
471-
Vector3 rayStart,rayEnd;
472-
473-
getPickRay(&screen,&rayStart,&rayEnd);
515+
screen.x = 0.5f * getWidth() + m_originX;
516+
screen.y = 0.5f * getHeight() + m_originY;
517+
getPickRay(&screen, &rayStart, &rayEnd);
474518

475519
center.x = Vector3::Find_X_At_Z(maxEdgeZ, rayStart, rayEnd);
476520
center.y = Vector3::Find_Y_At_Z(maxEdgeZ, rayStart, rayEnd);
477-
center.z = maxEdgeZ;
478521

479-
screen.y = m_originY+ 0.95f*getHeight();
480-
getPickRay(&screen,&rayStart,&rayEnd);
481-
bottom.x = Vector3::Find_X_At_Z(maxEdgeZ, rayStart, rayEnd);
482-
bottom.y = Vector3::Find_Y_At_Z(maxEdgeZ, rayStart, rayEnd);
483-
bottom.z = maxEdgeZ;
484-
center.x -= bottom.x;
485-
center.y -= bottom.y;
522+
const Real height = isLookingDown ? getHeight() : 0.0f;
523+
screen.y = height + m_originY;
524+
getPickRay(&screen, &rayStart, &rayEnd);
525+
526+
Real bottomX = Vector3::Find_X_At_Z(maxEdgeZ, rayStart, rayEnd);
527+
Real bottomY = Vector3::Find_Y_At_Z(maxEdgeZ, rayStart, rayEnd);
528+
529+
center.x -= bottomX;
530+
center.y -= bottomY;
486531

487532
Real offset = center.length();
488533

534+
// TheSuperHackers @tweak Reduces the offset to allow scrolling closer to the edges.
535+
offset *= 0.85f;
536+
489537
if (TheGlobalData->m_debugAI) {
490-
offset = -1000; // push out the constraints so we can look at staging areas.
538+
offset -= 1000; // push out the constraints so we can look at staging areas.
491539
}
492540

493541
return offset;
494542
}
495543

544+
//-------------------------------------------------------------------------------------------------
545+
void W3DView::clipCameraIntoAreaConstraints()
546+
{
547+
constexpr const Real eps = 1e-6f;
548+
Coord3D pos = *getPosition();
549+
pos.x = clamp(m_cameraAreaConstraints.lo.x + eps, pos.x, m_cameraAreaConstraints.hi.x - eps);
550+
pos.y = clamp(m_cameraAreaConstraints.lo.y + eps, pos.y, m_cameraAreaConstraints.hi.y - eps);
551+
setPosition(&pos);
552+
}
553+
554+
//-------------------------------------------------------------------------------------------------
555+
Bool W3DView::isWithinCameraAreaConstraints() const
556+
{
557+
const Coord3D* pos = getPosition();
558+
return m_cameraAreaConstraints.isInRegion(pos->x, pos->y);
559+
}
560+
496561
//-------------------------------------------------------------------------------------------------
497562
Bool W3DView::isWithinCameraHeightConstraints() const
498563
{
@@ -551,33 +616,6 @@ void W3DView::setCameraTransform()
551616
}
552617
}
553618

554-
#if defined(RTS_DEBUG)
555-
if (TheGlobalData->m_useCameraConstraints)
556-
#endif
557-
{
558-
if (!m_cameraAreaConstraintsValid)
559-
{
560-
Vector3 sourcePos;
561-
Vector3 targetPos;
562-
buildCameraPosition(sourcePos, targetPos);
563-
Matrix3D cameraTransform;
564-
buildCameraTransform(&cameraTransform, sourcePos, targetPos);
565-
m_3DCamera->Set_Transform( cameraTransform );
566-
calcCameraAreaConstraints();
567-
}
568-
DEBUG_ASSERTLOG(m_cameraAreaConstraintsValid,("*** cam constraints are not valid!!!"));
569-
570-
if (m_cameraAreaConstraintsValid)
571-
{
572-
Coord3D pos = *getPosition();
573-
pos.x = maxf(m_cameraAreaConstraints.lo.x, pos.x);
574-
pos.x = minf(m_cameraAreaConstraints.hi.x, pos.x);
575-
pos.y = maxf(m_cameraAreaConstraints.lo.y, pos.y);
576-
pos.y = minf(m_cameraAreaConstraints.hi.y, pos.y);
577-
setPosition(&pos);
578-
}
579-
}
580-
581619
m_3DCamera->Set_Clip_Planes(NearZ, farZ);
582620

583621
#if defined(RTS_DEBUG)
@@ -634,10 +672,10 @@ void W3DView::init()
634672
m_2DCamera->Set_View_Plane( min, max );
635673
m_2DCamera->Set_Clip_Planes( 0.995f, 2.0f );
636674

637-
m_cameraAreaConstraintsValid = false;
638-
639675
m_scrollAmountCutoffSqr = sqr(TheGlobalData->m_scrollAmountCutoff);
640676

677+
m_cameraAreaConstraintsValid = false;
678+
m_recalcCameraConstraintsAfterScrolling = false;
641679
m_recalcCamera = true;
642680
}
643681

@@ -673,6 +711,8 @@ void W3DView::reset()
673711

674712
Coord2D gb = { 0,0 };
675713
setGuardBandBias( &gb );
714+
715+
m_recalcCameraConstraintsAfterScrolling = false;
676716
}
677717

678718
//-------------------------------------------------------------------------------------------------
@@ -1325,14 +1365,17 @@ void W3DView::update()
13251365
// TheSuperHackers @tweak Can now also zoom when the game is paused.
13261366
// TheSuperHackers @tweak The camera zoom speed is now decoupled from the render update.
13271367
// TheSuperHackers @bugfix The camera terrain height adjustment now also works in replay playback.
1368+
// TheSuperHackers @bugfix xezon 26/10/2025 The camera area constraints are now recalculated when
1369+
// the camera zoom changes, for example because of terrain elevation changes in the camera's view.
1370+
// Additionally, the camera can be smoothly pushed away from the constraints, but not while the user
1371+
// is scrolling, to make the scrolling along the map border a pleasant experience. This behavior
1372+
// ensures that the view can reach and see all areas of the map, and especially the bottom map border.
13281373

13291374
m_terrainHeightAtPivot = getHeightAroundPos(m_pos.x, m_pos.y);
13301375
m_currentHeightAboveGround = m_cameraOffset.z * m_zoom - m_terrainHeightAtPivot;
13311376

13321377
if (m_okToAdjustHeight)
13331378
{
1334-
Real desiredHeight = (m_terrainHeightAtPivot + m_heightAboveGround);
1335-
Real desiredZoom = desiredHeight / m_cameraOffset.z;
13361379

13371380
if (didScriptedMovement)
13381381
{
@@ -1353,20 +1396,35 @@ void W3DView::update()
13531396

13541397
if (adjustZoomWhenScrolling || adjustZoomWhenNotScrolling)
13551398
{
1356-
const Real fpsRatio = TheFramePacer->getBaseOverUpdateFpsRatio();
1357-
const Real zoomAdj = (desiredZoom - m_zoom) * TheGlobalData->m_cameraAdjustSpeed * fpsRatio;
1358-
if (fabs(zoomAdj) >= 0.0001f)
1399+
if (zoomCameraToDesiredHeight())
13591400
{
1360-
m_zoom += zoomAdj;
13611401
m_recalcCamera = true;
1402+
1403+
if (isScrolling)
1404+
{
1405+
// Does not update the constraints while scrolling to maintain consistent edge collisions.
1406+
m_recalcCameraConstraintsAfterScrolling = true;
1407+
}
1408+
else
1409+
{
1410+
m_cameraAreaConstraintsValid = false;
1411+
}
13621412
}
13631413
}
1414+
1415+
if (m_recalcCameraConstraintsAfterScrolling && !isScrolling)
1416+
{
1417+
m_recalcCameraConstraintsAfterScrolling = false;
1418+
m_cameraAreaConstraintsValid = false;
1419+
}
13641420
}
13651421

13661422
if (TheScriptEngine->isTimeFast()) {
13671423
return; // don't draw - makes it faster :) jba.
13681424
}
13691425

1426+
updateCameraAreaConstraints();
1427+
13701428
// (gth) C&C3 if m_isCameraSlaved then force the camera to update each frame
13711429
if (m_recalcCamera || m_isCameraSlaved)
13721430
{

0 commit comments

Comments
 (0)