Skip to content

Commit 638dacf

Browse files
committed
fix(heightmap): Properly draw larger terrain area on low camera pitch (#2677)
1 parent bcedd55 commit 638dacf

11 files changed

Lines changed: 143 additions & 111 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
@@ -407,13 +407,13 @@ rendered portion of the terrain. Only a 96x96 section is rendered at any time,
407407
even though maps can be up to 1024x1024. This function determines which subset
408408
is rendered. */
409409
//=============================================================================
410-
void FlatHeightMapRenderObjClass::updateCenter(CameraClass *camera , RefRenderObjListIterator *pLightsIterator)
410+
void FlatHeightMapRenderObjClass::updateCenter(CameraClass *camera, const Vector3 *cameraPivot, RefRenderObjListIterator *pLightsIterator)
411411
{
412412
#ifdef DO_UNIT_TIMINGS
413413
#pragma MESSAGE("*** WARNING *** DOING DO_UNIT_TIMINGS!!!!")
414414
return;
415415
#endif
416-
BaseHeightMapRenderObjClass::updateCenter(camera, pLightsIterator);
416+
BaseHeightMapRenderObjClass::updateCenter(camera, cameraPivot, pLightsIterator);
417417
m_needFullUpdate = false;
418418
Int i, j;
419419
Int culled = 0;

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

Lines changed: 111 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1658,11 +1658,10 @@ static void calcVis(const FrustumClass & frustum, WorldHeightMap *pMap, Int minX
16581658
//=============================================================================
16591659
/** Updates the positioning of the drawn portion of the height map in the
16601660
heightmap. As the view slides around, this determines what is the actually
1661-
rendered portion of the terrain. Only a 96x96 section is rendered at any time,
1662-
even though maps can be up to 1024x1024. This function determines which subset
1663-
is rendered. */
1661+
rendered portion of the terrain. Only a small section is rendered at any time.
1662+
*/
16641663
//=============================================================================
1665-
void HeightMapRenderObjClass::updateCenter(CameraClass *camera , RefRenderObjListIterator *pLightsIterator)
1664+
void HeightMapRenderObjClass::updateCenter(CameraClass *camera, const Vector3 *cameraPivot, RefRenderObjListIterator *pLightsIterator)
16661665
{
16671666
if (m_map==nullptr) {
16681667
return;
@@ -1673,105 +1672,127 @@ void HeightMapRenderObjClass::updateCenter(CameraClass *camera , RefRenderObjLis
16731672
if (m_vertexBufferTiles ==nullptr)
16741673
return; //did not initialize resources yet.
16751674

1676-
BaseHeightMapRenderObjClass::updateCenter(camera, pLightsIterator);
1675+
BaseHeightMapRenderObjClass::updateCenter(camera, cameraPivot, pLightsIterator);
16771676

16781677
if (m_x >= m_map->getXExtent() && m_y >= m_map->getYExtent())
16791678
{
16801679
return; // no need to center.
16811680
}
16821681

1682+
const Real cameraPitch = WWMath::Asin(fabsf(camera->Get_Forward_Dir().Z));
16831683
Int newOrgX;
16841684
Int newOrgY;
16851685

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

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

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

17761797
WorldHeightMap::DrawArea newDrawArea = m_map->createDrawArea(newOrgX, newOrgY);
17771798

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3695,7 +3695,27 @@ void W3DView::updateTerrain()
36953695
DEBUG_ASSERTCRASH(TheTerrainRenderObject != nullptr, ("TheTerrainRenderObject is null"));
36963696

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