Skip to content

Commit dbc5175

Browse files
committed
fix(heightmap): Properly draw larger terrain area on low camera pitch (#2677)
1 parent 2aef67e commit dbc5175

11 files changed

Lines changed: 144 additions & 112 deletions

File tree

Core/GameEngine/Include/GameClient/View.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ enum FilterModes CPP_11(: Int);
5252

5353
// ------------------------------------------------------------------------------------------------
5454
constexpr const Real ViewDefaultPitchRadians = DEG_TO_RADF(37.5f);
55+
constexpr const Real ViewDefaultLowPitchRadians = DEG_TO_RADF(37.0f);
5556
constexpr const Real ViewDefaultYawRadians = DEG_TO_RADF(0.0f);
5657
constexpr const Real ViewDefaultMaxHeightAboveTerrain = 310.0f;
5758

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ class BaseHeightMapRenderObjClass : public RenderObjClass, public DX8_CleanupHoo
119119
///allocate resources needed to render heightmap
120120
virtual int initHeightData(Int width, Int height, WorldHeightMap *pMap, RefRenderObjListIterator *pLightsIterator, Bool updateExtraPassTiles=TRUE);
121121
virtual Int freeMapResources(); ///< free resources used to render heightmap
122-
virtual void updateCenter(CameraClass *camera, RefRenderObjListIterator *pLightsIterator);
122+
virtual void updateCenter(CameraClass *camera, const Vector3 *cameraPivot, RefRenderObjListIterator *pLightsIterator);
123123
virtual void adjustTerrainLOD(Int adj);
124124
virtual void doPartialUpdate(const IRegion2D &partialRange, WorldHeightMap *htMap, RefRenderObjListIterator *pLightsIterator) = 0;
125125
virtual void staticLightingChanged();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class FlatHeightMapRenderObjClass : public BaseHeightMapRenderObjClass
6666
///allocate resources needed to render heightmap
6767
virtual int initHeightData(Int width, Int height, WorldHeightMap *pMap, RefRenderObjListIterator *pLightsIterator,Bool updateExtraPassTiles=TRUE) override;
6868
virtual Int freeMapResources() override; ///< free resources used to render heightmap
69-
virtual void updateCenter(CameraClass *camera, RefRenderObjListIterator *pLightsIterator) override;
69+
virtual void updateCenter(CameraClass *camera, const Vector3 *cameraPivot, RefRenderObjListIterator *pLightsIterator) override;
7070
virtual void adjustTerrainLOD(Int adj) override;
7171
virtual void reset() override;
7272
virtual void oversizeTerrain(Int tilesToOversize) override; ///< Oversize the visible terrain area.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class HeightMapRenderObjClass : public BaseHeightMapRenderObjClass
7373
///allocate resources needed to render heightmap
7474
virtual int initHeightData(Int width, Int height, WorldHeightMap *pMap, RefRenderObjListIterator *pLightsIterator, Bool updateExtraPassTiles=TRUE) override;
7575
virtual Int freeMapResources() override; ///< free resources used to render heightmap
76-
virtual void updateCenter(CameraClass *camera, RefRenderObjListIterator *pLightsIterator) override;
76+
virtual void updateCenter(CameraClass *camera, const Vector3 *cameraPivot, RefRenderObjListIterator *pLightsIterator) override;
7777

7878
virtual void staticLightingChanged() override;
7979
virtual void adjustTerrainLOD(Int adj) override;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ class WorldHeightMap : public RefCountClass,
125125
NORMAL_DRAW_HEIGHT = 1 + 4*VERTEX_BUFFER_TILE_LENGTH,
126126
STRETCH_DRAW_WIDTH = 1 + 2*VERTEX_BUFFER_TILE_LENGTH,
127127
STRETCH_DRAW_HEIGHT = 1 + 2*VERTEX_BUFFER_TILE_LENGTH,
128+
LOW_ANGLE_DRAW_WIDTH = 1 + (NORMAL_DRAW_WIDTH-1) * 2,
129+
LOW_ANGLE_DRAW_HEIGHT = 1 + (NORMAL_DRAW_HEIGHT-1) * 2,
128130
};
129131

130132
protected:

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

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,18 +1318,6 @@ Bool BaseHeightMapRenderObjClass::evaluateAsVisibleCliff(Int xIndex, Int yIndex,
13181318
return anyImpassable;
13191319
}
13201320

1321-
//=============================================================================
1322-
// BaseHeightMapRenderObjClass::oversizeTerrain
1323-
//=============================================================================
1324-
/** Sets the terrain oversize amount. */
1325-
//=============================================================================
1326-
void BaseHeightMapRenderObjClass::oversizeTerrain(Int tilesToOversize)
1327-
{
1328-
// Not needed with flat version. [3/20/2003]
1329-
}
1330-
1331-
1332-
13331321
//=============================================================================
13341322
// BaseHeightMapRenderObjClass::Get_Obj_Space_Bounding_Sphere
13351323
//=============================================================================
@@ -2394,7 +2382,7 @@ rendered portion of the terrain. Only a 96x96 section is rendered at any time,
23942382
even though maps can be up to 1024x1024. This function determines which subset
23952383
is rendered. */
23962384
//=============================================================================
2397-
void BaseHeightMapRenderObjClass::updateCenter(CameraClass *camera , RefRenderObjListIterator *pLightsIterator)
2385+
void BaseHeightMapRenderObjClass::updateCenter(CameraClass *camera, const Vector3 *cameraPivot, RefRenderObjListIterator *pLightsIterator)
23982386
{
23992387
if (m_map==nullptr) {
24002388
return;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,13 +408,13 @@ rendered portion of the terrain. Only a 96x96 section is rendered at any time,
408408
even though maps can be up to 1024x1024. This function determines which subset
409409
is rendered. */
410410
//=============================================================================
411-
void FlatHeightMapRenderObjClass::updateCenter(CameraClass *camera , RefRenderObjListIterator *pLightsIterator)
411+
void FlatHeightMapRenderObjClass::updateCenter(CameraClass *camera, const Vector3 *cameraPivot, RefRenderObjListIterator *pLightsIterator)
412412
{
413413
#ifdef DO_UNIT_TIMINGS
414414
#pragma MESSAGE("*** WARNING *** DOING DO_UNIT_TIMINGS!!!!")
415415
return;
416416
#endif
417-
BaseHeightMapRenderObjClass::updateCenter(camera, pLightsIterator);
417+
BaseHeightMapRenderObjClass::updateCenter(camera, cameraPivot, pLightsIterator);
418418
m_needFullUpdate = false;
419419
Int i, j;
420420
Int culled = 0;

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

Lines changed: 111 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,11 +1654,10 @@ static void calcVis(const FrustumClass & frustum, WorldHeightMap *pMap, Int minX
16541654
//=============================================================================
16551655
/** Updates the positioning of the drawn portion of the height map in the
16561656
heightmap. As the view slides around, this determines what is the actually
1657-
rendered portion of the terrain. Only a 96x96 section is rendered at any time,
1658-
even though maps can be up to 1024x1024. This function determines which subset
1659-
is rendered. */
1657+
rendered portion of the terrain. Only a small section is rendered at any time.
1658+
*/
16601659
//=============================================================================
1661-
void HeightMapRenderObjClass::updateCenter(CameraClass *camera , RefRenderObjListIterator *pLightsIterator)
1660+
void HeightMapRenderObjClass::updateCenter(CameraClass *camera, const Vector3 *cameraPivot, RefRenderObjListIterator *pLightsIterator)
16621661
{
16631662
if (m_map==nullptr) {
16641663
return;
@@ -1669,105 +1668,127 @@ void HeightMapRenderObjClass::updateCenter(CameraClass *camera , RefRenderObjLis
16691668
if (m_vertexBufferTiles ==nullptr)
16701669
return; //did not initialize resources yet.
16711670

1672-
BaseHeightMapRenderObjClass::updateCenter(camera, pLightsIterator);
1671+
BaseHeightMapRenderObjClass::updateCenter(camera, cameraPivot, pLightsIterator);
16731672

16741673
if (m_x >= m_map->getXExtent() && m_y >= m_map->getYExtent())
16751674
{
16761675
return; // no need to center.
16771676
}
16781677

1678+
const Real cameraPitch = asin(fabs(camera->Get_Forward_Dir().Z));
16791679
Int newOrgX;
16801680
Int newOrgY;
16811681

1682-
// determine the ray corresponding to the camera and distance to projection plane
1683-
Matrix3D camera_matrix = camera->Get_Transform();
1684-
1685-
Vector3 camera_location = camera->Get_Position();
1686-
1687-
Vector3 rayLocation;
1688-
Vector3 rayDirection;
1689-
Vector3 rayDirectionPt;
1690-
// the projected ray has the same origin as the camera
1691-
rayLocation = camera_location;
1692-
// determine the location of the screen coordinate in camera-model space
1693-
const ViewportClass &viewport = camera->Get_Viewport();
1694-
Int i, j, minHt;
1695-
1696-
Real intersectionZ;
1697-
minHt = m_map->getMaxHeightValue();
1698-
for (j=0; j<m_y; j+=4) {
1699-
for (i=0; i<m_x; i+=4) {
1700-
Short cur = m_map->getDisplayHeight(i,j);
1701-
if (cur<minHt) minHt = cur;
1682+
if (cameraPitch > ViewDefaultLowPitchRadians)
1683+
{
1684+
// TheSuperHackers @info This is the original code to determine the center position for the visible terrain area.
1685+
// It is relatively expensive and breaks when the frustum planes can no longer intersect with the terrain at low camera
1686+
// pitch or when the camera is too far from the terrain, but it is very accurate when the camera is close to the
1687+
// terrain. For now, we prefer to keep this code for the original camera pitch and above.
1688+
1689+
// determine the ray corresponding to the camera and distance to projection plane
1690+
const Matrix3D& camera_matrix = camera->Get_Transform();
1691+
Vector3 camera_location = camera->Get_Position();
1692+
Vector3 rayLocation;
1693+
Vector3 rayDirection;
1694+
Vector3 rayDirectionPt;
1695+
// the projected ray has the same origin as the camera
1696+
rayLocation = camera_location;
1697+
// determine the location of the screen coordinate in camera-model space
1698+
const ViewportClass &viewport = camera->Get_Viewport();
1699+
Int i, j, minHt;
1700+
1701+
Real intersectionZ;
1702+
minHt = m_map->getMaxHeightValue();
1703+
for (j=0; j<m_y; j+=4) {
1704+
for (i=0; i<m_x; i+=4) {
1705+
Short cur = m_map->getDisplayHeight(i,j);
1706+
if (cur<minHt) minHt = cur;
1707+
}
17021708
}
1703-
}
1704-
intersectionZ = (float)minHt;
1705-
// float aspect = camera->Get_Aspect_Ratio();
1706-
1707-
Vector2 min,max;
1708-
camera->Get_View_Plane(min,max);
1709-
float xscale = (max.X - min.X);
1710-
float yscale = (max.Y - min.Y);
1711-
1712-
float zmod = -1.0; // Scene->vpd; // Note: view plane distance is now always 1.0 from the camera
1713-
float minX = 200000;
1714-
float maxX = -minX;
1715-
float minY = 200000;
1716-
float maxY = -minY;
1717-
for (i=0; i<2; i++) {
1718-
for (j=0; j<2; j++) {
1719-
float xmod = (-i + 0.5 + viewport.Min.X) * zmod * xscale;// / aspect;
1720-
float ymod = (j - 0.5 - viewport.Min.Y) * zmod * yscale;// * aspect;
1721-
1722-
// transform the screen coordinates by the camera's matrix into world coordinates.
1723-
float x = zmod * camera_matrix[0][2] + xmod * camera_matrix[0][0] + ymod * camera_matrix[0][1];
1724-
float y = zmod * camera_matrix[1][2] + xmod * camera_matrix[1][0] + ymod * camera_matrix[1][1];
1725-
float z = zmod * camera_matrix[2][2] + xmod * camera_matrix[2][0] + ymod * camera_matrix[2][1];
1726-
1727-
rayDirection.Set(x,y,z);
1728-
rayDirection.Normalize();
1729-
rayDirectionPt = rayLocation+rayDirection;
1730-
1731-
x = Vector3::Find_X_At_Z(intersectionZ, rayLocation, rayDirectionPt);
1732-
y = Vector3::Find_Y_At_Z(intersectionZ, rayLocation, rayDirectionPt);
1733-
if (x<minX) minX = x;
1734-
if (x>maxX) maxX = x;
1735-
if (y<minY) minY = y;
1736-
if (y>maxY) maxY = y;
1709+
intersectionZ = (float)minHt;
1710+
// float aspect = camera->Get_Aspect_Ratio();
1711+
1712+
Vector2 min,max;
1713+
camera->Get_View_Plane(min,max);
1714+
float xscale = (max.X - min.X);
1715+
float yscale = (max.Y - min.Y);
1716+
1717+
float zmod = -1.0; // Scene->vpd; // Note: view plane distance is now always 1.0 from the camera
1718+
float minX = 200000;
1719+
float maxX = -minX;
1720+
float minY = 200000;
1721+
float maxY = -minY;
1722+
for (i=0; i<2; i++) {
1723+
for (j=0; j<2; j++) {
1724+
float xmod = (-i + 0.5 + viewport.Min.X) * zmod * xscale;// / aspect;
1725+
float ymod = (j - 0.5 - viewport.Min.Y) * zmod * yscale;// * aspect;
1726+
1727+
// transform the screen coordinates by the camera's matrix into world coordinates.
1728+
float x = zmod * camera_matrix[0][2] + xmod * camera_matrix[0][0] + ymod * camera_matrix[0][1];
1729+
float y = zmod * camera_matrix[1][2] + xmod * camera_matrix[1][0] + ymod * camera_matrix[1][1];
1730+
float z = zmod * camera_matrix[2][2] + xmod * camera_matrix[2][0] + ymod * camera_matrix[2][1];
1731+
1732+
rayDirection.Set(x,y,z);
1733+
rayDirection.Normalize();
1734+
rayDirectionPt = rayLocation+rayDirection;
1735+
1736+
x = Vector3::Find_X_At_Z(intersectionZ, rayLocation, rayDirectionPt);
1737+
y = Vector3::Find_Y_At_Z(intersectionZ, rayLocation, rayDirectionPt);
1738+
if (x<minX) minX = x;
1739+
if (x>maxX) maxX = x;
1740+
if (y<minY) minY = y;
1741+
if (y>maxY) maxY = y;
1742+
}
17371743
}
1738-
}
17391744

1740-
// convert back to cell indexes.
1741-
minX /= MAP_XY_FACTOR;
1742-
maxX /= MAP_XY_FACTOR;
1743-
minY /= MAP_XY_FACTOR;
1744-
maxY /= MAP_XY_FACTOR;
1745-
1746-
minX += m_map->getBorderSizeInline();
1747-
maxX += m_map->getBorderSizeInline();
1748-
minY += m_map->getBorderSizeInline();
1749-
maxY += m_map->getBorderSizeInline();
1750-
1751-
visMinX = m_map->getXExtent();
1752-
visMinY = m_map->getYExtent();
1753-
visMaxX = 0;
1754-
visMaxY = 0;
1755-
1756-
///< @todo find out why values go out of range
1757-
if (minX<0) minX=0;
1758-
if (minY<0) minY=0;
1759-
if (maxX > visMinX) maxX = visMinX;
1760-
if (maxY > visMinY) maxY = visMinY;
1761-
1762-
const FrustumClass & frustum = camera->Get_Frustum();
1763-
Int limit = (maxX-minX)/2;
1764-
if (limit > WIDE_STEP/2) {
1765-
limit=WIDE_STEP/2;
1766-
}
1767-
calcVis(frustum, m_map, minX-WIDE_STEP/2, minY-WIDE_STEP/2, maxX+WIDE_STEP/2, maxY+WIDE_STEP/2, limit);
1745+
// convert back to cell indexes.
1746+
minX /= MAP_XY_FACTOR;
1747+
maxX /= MAP_XY_FACTOR;
1748+
minY /= MAP_XY_FACTOR;
1749+
maxY /= MAP_XY_FACTOR;
1750+
1751+
minX += m_map->getBorderSizeInline();
1752+
maxX += m_map->getBorderSizeInline();
1753+
minY += m_map->getBorderSizeInline();
1754+
maxY += m_map->getBorderSizeInline();
1755+
1756+
visMinX = m_map->getXExtent();
1757+
visMinY = m_map->getYExtent();
1758+
visMaxX = 0;
1759+
visMaxY = 0;
1760+
1761+
///< @todo find out why values go out of range
1762+
if (minX<0) minX=0;
1763+
if (minY<0) minY=0;
1764+
if (maxX > visMinX) maxX = visMinX;
1765+
if (maxY > visMinY) maxY = visMinY;
1766+
1767+
const FrustumClass & frustum = camera->Get_Frustum();
1768+
Int limit = (maxX-minX)/2;
1769+
if (limit > WIDE_STEP/2) {
1770+
limit=WIDE_STEP/2;
1771+
}
1772+
calcVis(frustum, m_map, minX-WIDE_STEP/2, minY-WIDE_STEP/2, maxX+WIDE_STEP/2, maxY+WIDE_STEP/2, limit);
17681773

1769-
newOrgX = (visMaxX+visMinX)/2 - m_x/2.0;
1770-
newOrgY = (visMaxY+visMinY)/2 - m_y/2.0;
1774+
newOrgX = (visMaxX+visMinX)/2 - m_x/2;
1775+
newOrgY = (visMaxY+visMinY)/2 - m_y/2;
1776+
}
1777+
else
1778+
{
1779+
// TheSuperHackers @fix Very fast approximation. Works well for all camera pitches, but is less accurate
1780+
// than the original implementation. Using this method for higher camera pitch would require to increase
1781+
// the normal draw width by at least one tile length.
1782+
const Real visibleTerrainEdgeLen = (m_x+m_y)/2 * MAP_XY_FACTOR;
1783+
const Real magicEdgeLenScale = 0.25f * visibleTerrainEdgeLen;
1784+
Vector3 viewDir = camera->Get_Forward_Dir();
1785+
Vector2 shiftPivot;
1786+
shiftPivot.X = viewDir.X * magicEdgeLenScale;
1787+
shiftPivot.Y = viewDir.Y * magicEdgeLenScale;
1788+
1789+
newOrgX = WWMath::Round((cameraPivot->X + shiftPivot.X)/MAP_XY_FACTOR) - m_x/2 + m_map->getBorderSizeInline();
1790+
newOrgY = WWMath::Round((cameraPivot->Y + shiftPivot.Y)/MAP_XY_FACTOR) - m_y/2 + m_map->getBorderSizeInline();
1791+
}
17711792

17721793
WorldHeightMap::DrawArea newDrawArea = m_map->createDrawArea(newOrgX, newOrgY);
17731794

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ Bool W3DView::movePivotToGround()
486486

487487
// Adjust the strength of the repositioning for low camera pitch, because
488488
// it feels bad to move the camera around when it looks over the terrain.
489-
const Real pitch = WWMath::Asin(fabs(delta.Z) / delta.Length());
489+
const Real pitch = asin(fabs(delta.Z) / delta.Length());
490490
constexpr const Real lowerPitch = DEG_TO_RADF(15.f);
491491
constexpr const Real upperPitch = DEG_TO_RADF(30.f);
492492
Real repositionStrength = WWMath::Inverse_Lerp(lowerPitch, upperPitch, pitch);
@@ -3697,7 +3697,27 @@ void W3DView::updateTerrain()
36973697
DEBUG_ASSERTCRASH(TheTerrainRenderObject != nullptr, ("TheTerrainRenderObject is null"));
36983698

36993699
RefRenderObjListIterator *it = W3DDisplay::m_3DScene->createLightsIterator();
3700-
TheTerrainRenderObject->updateCenter(m_3DCamera, it);
3700+
const Vector3 cameraPivot(m_pos.x, m_pos.y, m_pos.z);
3701+
const Real cameraPitch = asin(fabs(m_3DCamera->Get_Forward_Dir().Z));
3702+
Int drawWidth;
3703+
Int drawHeight;
3704+
3705+
if (cameraPitch > ViewDefaultLowPitchRadians)
3706+
{
3707+
drawWidth = WorldHeightMap::NORMAL_DRAW_WIDTH;
3708+
drawHeight = WorldHeightMap::NORMAL_DRAW_HEIGHT;
3709+
}
3710+
else
3711+
{
3712+
// TheSuperHackers @tweak xezon 31/12/2025 Increases visible terrain area when lowering the camera pitch.
3713+
// Note: The default camera pitch in Generals was 37.5, which we prefer to keep the normal draw size for.
3714+
drawWidth = WorldHeightMap::LOW_ANGLE_DRAW_WIDTH;
3715+
drawHeight = WorldHeightMap::LOW_ANGLE_DRAW_HEIGHT;
3716+
}
3717+
3718+
TheTerrainRenderObject->setTerrainDrawSize(drawWidth, drawHeight);
3719+
TheTerrainRenderObject->updateCenter(m_3DCamera, &cameraPivot, it);
3720+
37013721
if (it)
37023722
{
37033723
W3DDisplay::m_3DScene->destroyLightsIterator(it);

Generals/Code/Tools/WorldBuilder/src/wbview3d.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1964,7 +1964,7 @@ void WbView3d::redraw()
19641964
++m_updateCount;
19651965
Int curTicks = GetTickCount();
19661966
RefRenderObjListIterator lightListIt(&m_lightList);
1967-
m_heightMapRenderObj->updateCenter(m_camera, &lightListIt);
1967+
m_heightMapRenderObj->updateCenter(m_camera, &m_cameraTarget, &lightListIt);
19681968
--m_updateCount;
19691969

19701970
curTicks = GetTickCount()-curTicks;

0 commit comments

Comments
 (0)