@@ -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/*
430459Note 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// -------------------------------------------------------------------------------------------------
497562Bool 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