Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 78 additions & 18 deletions rts/Game/Camera/SpringController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ CONFIG(bool, CamSpringEdgeRotate).defaultValue(false).description("Rotate camer
CONFIG(float, CamSpringFastScaleMouseMove).defaultValue(3.0f / 10.0f).description("Scaling for CameraMoveFastMult in spring camera mode while moving mouse.");
CONFIG(float, CamSpringFastScaleMousewheelMove).defaultValue(2.0f / 10.0f).description("Scaling for CameraMoveFastMult in spring camera mode while scrolling with mouse.");
CONFIG(int, CamSpringTrackMapHeightMode).defaultValue(HeightTracking::Terrain).description("Camera height is influenced by terrain height. 0=Static 1=Terrain 2=Smoothmesh");
CONFIG(float, CamSpringSmoothMeshBlendMinDist).defaultValue(150.0f).description("Zoom distance below which smoothmesh height tracking mode (2) follows the raw terrain.");
CONFIG(float, CamSpringSmoothMeshBlendMaxDist).defaultValue(600.0f).description("Zoom distance above which smoothmesh height tracking mode (2) fully follows the smooth mesh.");


CSpringController::CSpringController()
Expand All @@ -51,7 +53,7 @@ CSpringController::CSpringController()
{
RECOIL_DETAILED_TRACY_ZONE;
enabled = configHandler->GetBool("CamSpringEnabled");
configHandler->NotifyOnChange(this, {"CamSpringScrollSpeed", "CamSpringFOV", "CamSpringMinZoomDistance", "CamSpringZoomInToMousePos", "CamSpringZoomOutFromMousePos", "CamSpringFastScaleMousewheelMove", "CamSpringFastScaleMouseMove", "CamSpringEdgeRotate", "CamSpringLockCardinalDirections", "CamSpringTrackMapHeightMode"});
configHandler->NotifyOnChange(this, {"CamSpringScrollSpeed", "CamSpringFOV", "CamSpringMinZoomDistance", "CamSpringZoomInToMousePos", "CamSpringZoomOutFromMousePos", "CamSpringFastScaleMousewheelMove", "CamSpringFastScaleMouseMove", "CamSpringEdgeRotate", "CamSpringLockCardinalDirections", "CamSpringTrackMapHeightMode", "CamSpringSmoothMeshBlendMinDist", "CamSpringSmoothMeshBlendMaxDist"});
ConfigUpdate();
}

Expand All @@ -75,9 +77,11 @@ void CSpringController::ConfigUpdate()
doRotate = configHandler->GetBool("CamSpringEdgeRotate");
lockCardinalDirections = configHandler->GetBool("CamSpringLockCardinalDirections");
trackMapHeight = configHandler->GetInt("CamSpringTrackMapHeightMode");
meshBlendMinDist = configHandler->GetFloat("CamSpringSmoothMeshBlendMinDist");
meshBlendMaxDist = std::max(configHandler->GetFloat("CamSpringSmoothMeshBlendMaxDist"), meshBlendMinDist + 1.0f);

if (trackMapHeight == HeightTracking::Smooth && !modInfo.enableSmoothMesh) {
LOG_L(L_ERROR, "Smooth mesh disabled");
LOG_L(L_WARNING, "[CSpringController] smoothmesh height tracking (mode 2) requested but the game disabled the smooth mesh, falling back to terrain tracking");
trackMapHeight = HeightTracking::Terrain;
}
}
Expand All @@ -88,7 +92,7 @@ void CSpringController::ConfigNotify(const std::string & key, const std::string
ConfigUpdate();
}

void CSpringController::SmoothCamHeight(const float3& prevPos) {
void CSpringController::FreezeCamHeight() {
RECOIL_DETAILED_TRACY_ZONE;
if (!pos.IsInBounds()) {
return;
Expand All @@ -103,12 +107,8 @@ void CSpringController::SmoothCamHeight(const float3& prevPos) {
// when there's a hill blocking the view
const float3 newGroundPos = camPos + dir * distToGround;
if (distToGround > 0.0f && newGroundPos.IsInBounds()) {
const float camHeightDiff = (trackMapHeight == HeightTracking::Smooth) ?
smoothGround.GetHeight(pos.x, pos.z) - smoothGround.GetHeight(prevPos.x, prevPos.z) :
0.0f;

pos = newGroundPos;
curDist = distToGround + (dir * camHeightDiff).Length() * Sign(camHeightDiff);
curDist = distToGround;
}
}

Expand All @@ -126,8 +126,6 @@ void CSpringController::KeyMove(float3 move)
return;
}

const float3 prevPos = pos;

move *= 200.0f;
const float3 flatForward = (dir * XZVector).ANormalize();
pos += (camera->GetRight() * move.x + flatForward * move.y) * pixelSize * 2.0f * scrollSpeed;
Expand All @@ -138,13 +136,17 @@ void CSpringController::KeyMove(float3 move)
// - 'pos' point of focus on the ground
// - 'curDist' camera distance
break;
case HeightTracking::Smooth:
// No pre-step needed here: Update() runs immediately below and applies
// smooth-mesh focus height via GetFocusSurfaceHeight(). FreezeCamHeight()
// is only required for Disabled mode, where we must raycast/recompute
// focus and distance before Update() to avoid resnapping.
break;
case HeightTracking::Disabled:
// freezing camera height requires raycasting from current
// camera position and recalculating
// point of focus and distance
[[fallthrough]];
case HeightTracking::Smooth:
SmoothCamHeight(prevPos);
FreezeCamHeight();
break;
}

Expand Down Expand Up @@ -318,16 +320,74 @@ float CSpringController::ZoomOut(const float3& curCamPos, const float3& newDir,



float CSpringController::GetFocusSurfaceHeight(float x, float z) const
float CSpringController::GetFocusSurfaceHeight(float x, float z, float dist) const
{
RECOIL_DETAILED_TRACY_ZONE;
return CGround::GetHeightReal(x, z, false);
const float groundHeight = CGround::GetHeightReal(x, z, false);

if (trackMapHeight != HeightTracking::Smooth)
return groundHeight;

// fade to the raw ground at close zoom, the mesh hovers high near cliffs
// and would otherwise limit zoom-in depth and panning speed there
const float meshBlend = smoothstep(meshBlendMinDist, meshBlendMaxDist, dist);
return mix(groundHeight, smoothGround.GetHeightSmooth(x, z), meshBlend);
}

float CSpringController::DistanceToFocusSurface(const float3& from) const
{
RECOIL_DETAILED_TRACY_ZONE;
return DistanceToGround(from, dir, pos.y);
const float groundDist = DistanceToGround(from, dir, pos.y);

if (trackMapHeight != HeightTracking::Smooth || groundDist <= 0.0f)
return groundDist;

// intersect the view ray with the focus surface; using the ground distance
// would lift the camera by the mesh-ground gap on every zoom step
const auto heightAboveSurface = [&](float t) {
const float3 p = from + dir * t;
return p.y - GetFocusSurfaceHeight(p.x, p.z, t);
};

if (heightAboveSurface(0.0f) <= 0.0f)
return groundDist; // camera below the surface

if (heightAboveSurface(groundDist) >= 0.0f)
return groundDist; // ground above the surface

// March to bracket the first crossing, then bisect. The march has to be fine
// enough not to step over a near crossing on grazing rays (which can dip below
// the surface and back out several times), otherwise we would bracket a farther
// crossing and lock the camera onto the wrong hill. This runs once per zoom
// action, not per frame, so a generous step count is cheap.
constexpr int NUM_MARCH_STEPS = 16;
constexpr int NUM_BISECTION_STEPS = 16;
const float step = groundDist / NUM_MARCH_STEPS;

float above = 0.0f;
float below = groundDist;

for (int i = 1; i < NUM_MARCH_STEPS; ++i) {
const float t = step * i;

if (heightAboveSurface(t) > 0.0f) {
above = t;
} else {
below = t;
break;
}
}

for (int i = 0; i < NUM_BISECTION_STEPS; ++i) {
const float mid = (above + below) * 0.5f;

if (heightAboveSurface(mid) > 0.0f)
above = mid;
else
below = mid;
}

return (above + below) * 0.5f;
}


Expand All @@ -337,13 +397,13 @@ void CSpringController::Update()

pos.x = std::clamp(pos.x, 0.01f, mapDims.mapx * SQUARE_SIZE - 0.01f);
pos.z = std::clamp(pos.z, 0.01f, mapDims.mapy * SQUARE_SIZE - 0.01f);
pos.y = GetFocusSurfaceHeight(pos.x, pos.z); // always focus on the ground
curDist = std::clamp(curDist, minDist, maxDist);
pos.y = GetFocusSurfaceHeight(pos.x, pos.z, curDist); // always focus on the ground
rot.x = std::clamp(rot.x, math::PI * 0.51f, math::PI * 0.99f);

// camera->SetRot(float3(rot.x, GetAzimuth(), rot.z));
dir = CCamera::GetFwdFromRot(this->GetRot());

curDist = std::clamp(curDist, minDist, maxDist);
pixelSize = (camera->GetTanHalfFov() * 2.0f) / globalRendering->viewSizeY * curDist * 2.0f;
}

Expand Down
7 changes: 5 additions & 2 deletions rts/Game/Camera/SpringController.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ class CSpringController : public CCameraController
inline float ZoomIn(const float3& curCamPos, const float3& dir, const float& curDistPre, const float& scaledMode);
inline float ZoomOut(const float3& curCamPos, const float3& dir, const float& curDistPre, const float& scaledMode);

void SmoothCamHeight(const float3& prevPos);
float GetFocusSurfaceHeight(float x, float z) const;
void FreezeCamHeight();
float GetFocusSurfaceHeight(float x, float z, float dist) const;
float DistanceToFocusSurface(const float3& from) const;

private:
Expand All @@ -57,6 +57,9 @@ class CSpringController : public CCameraController
float fastScaleMove;
float fastScaleMousewheel;

float meshBlendMinDist;
float meshBlendMaxDist;

bool zoomBack;
bool cursorZoomIn;
bool cursorZoomOut;
Expand Down
30 changes: 30 additions & 0 deletions rts/Sim/Misc/SmoothHeightMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,29 @@ static float Interpolate(float x, float y, const int maxx, const int maxy, const
return mix(hi1, hi2, dy);
}


// C1-continuous, unlike Interpolate whose gradient jumps at cell borders
static float SampleBicubic(float x, float y, const int maxx, const int maxy, const float res, const float* heightmap)
{
RECOIL_DETAILED_TRACY_ZONE;
x = std::clamp(x / res, 0.0f, (float)maxx);
y = std::clamp(y / res, 0.0f, (float)maxy);
const int sx = std::min((int)x, maxx - 1);
const int sy = std::min((int)y, maxy - 1);
const float dx = (x - sx);
const float dy = (y - sy);

// gather the read-only 4x4 neighbourhood around the cell, clamped at the edges
float patch[4][4];
for (int j = 0; j < 4; ++j) {
const float* row = &heightmap[std::clamp(sy + j - 1, 0, maxy - 1) * maxx];
for (int i = 0; i < 4; ++i)
patch[j][i] = row[std::clamp(sx + i - 1, 0, maxx - 1)];
}

return InterpolateBicubic(patch, dx, dy);
}

void SmoothHeightMesh::Init(int2 max, int res, int smoothRad)
{
RECOIL_DETAILED_TRACY_ZONE;
Expand Down Expand Up @@ -135,6 +158,13 @@ float SmoothHeightMesh::GetHeightAboveWater(float x, float y)
return std::max(0.0f, Interpolate(x, y, maxx, maxy, fresolution, &mesh[0]));
}

float SmoothHeightMesh::GetHeightSmooth(float x, float y)
{
RECOIL_DETAILED_TRACY_ZONE;
assert(!mesh.empty());
return SampleBicubic(x, y, maxx, maxy, fresolution, &mesh[0]);
}

float SmoothHeightMesh::SetHeight(int index, float h)
{
RECOIL_DETAILED_TRACY_ZONE;
Expand Down
1 change: 1 addition & 0 deletions rts/Sim/Misc/SmoothHeightMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class SmoothHeightMesh

float GetHeight(float x, float y);
float GetHeightAboveWater(float x, float y);
float GetHeightSmooth(float x, float y);
float SetHeight(int index, float h);
float AddHeight(int index, float h);
float SetMaxHeight(int index, float h);
Expand Down