vmap and dyn + improvements

This commit is contained in:
VG-prog 2025-09-25 17:26:54 +02:00
parent a31b747bfb
commit e07481c099
10 changed files with 262 additions and 29 deletions

View File

@ -308,6 +308,76 @@ float DynamicMapTree::getHeight(float x, float y, float z, float maxSearchDist,
}
}
static inline bool _finiteF(float v) { return std::isfinite(v); }
namespace { constexpr float INV_SQRT2 = 0.70710678118654752440f; }
float DynamicMapTree::getHeightAccurate(float x, float y, float z, float maxSearchDist, uint32 phasemask,
float radius, float yaw, float blend, float clamp, float sampleDelta) const
{
auto sample = [&](float sx, float sy) -> float
{
G3D::Vector3 v(sx, sy, z);
G3D::Ray r(v, G3D::Vector3(0, 0, -1));
float dist = maxSearchDist;
DynamicTreeIntersectionCallback cb(phasemask, VMAP::ModelIgnoreFlags::Nothing);
impl->intersectZAllignedRay(r, cb, dist);
if (cb.didHit())
return z - dist;
return std::numeric_limits<float>::quiet_NaN();
};
const float h0 = sample(x, y);
if (!_finiteF(h0))
return -G3D::finf();
if (radius <= 0.0f)
return h0;
const float d = (sampleDelta > 0.0f) ? sampleDelta
: std::max(0.05f, std::min(0.5f, radius * 0.5f));
const float hx1 = sample(x + d, y);
const float hx2 = sample(x - d, y);
const float hy1 = sample(x, y + d);
const float hy2 = sample(x, y - d);
auto diff = [&](float p, float m) -> float
{
if (_finiteF(p) && _finiteF(m)) return (p - m) / (2.0f * d);
if (_finiteF(p)) return (p - h0) / d;
if (_finiteF(m)) return (h0 - m) / d;
return 0.0f;
};
float gx = diff(hx1, hx2); // dz/dx
float gy = diff(hy1, hy2); // dz/dy
if (clamp > 0.0f)
{
const float g2 = gx*gx + gy*gy;
const float c2 = clamp*clamp;
if (g2 > c2)
{
const float s = clamp / std::sqrt(g2);
gx *= s; gy *= s;
}
}
const float slopeL2 = std::sqrt(std::max(0.0f, gx*gx + gy*gy));
float totalSlope = slopeL2;
if (blend < 1.0f)
{
float c = std::cos(yaw), s = std::sin(yaw);
float rx = gx * c + gy * s;
float ry = -gx * s + gy * c;
float slopeL1 = std::abs(rx) + std::abs(ry);
totalSlope = blend * slopeL2 + (1.0f - blend) * (INV_SQRT2 * slopeL1);
}
return h0 + radius * totalSlope;
}
bool DynamicMapTree::GetAreaInfo(float x, float y, float& z, uint32 phasemask, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const
{
G3D::Vector3 v(x, y, z + 0.5f);

View File

@ -55,6 +55,8 @@ public:
float pModifyDist) const;
[[nodiscard]] float getHeight(float x, float y, float z, float maxSearchDist, uint32 phasemask) const;
[[nodiscard]] float getHeightAccurate(float x, float y, float z, float maxSearchDist, uint32 phasemask,
float radius, float yaw, float blend, float clamp, float sampleDelta) const;
void insert(const GameObjectModel&);
void remove(const GameObjectModel&);

View File

@ -1420,6 +1420,48 @@ Height.Accurate.SquareBlend = 0.2
Height.Accurate.SlopeClamp = 0.0
#
# Gradient mode
# 0 = Plane (exact face-plane gradient; maximizes geometric accuracy)
# 1 = LS (least-squares gradient; smoother, slightly less exact)
#
Height.Accurate.GradientMode = 0
#
# Normal epsilon used to detect degenerate faces (|nz| < eps)
# Recommended: 1e-6 .. 1e-5
#
Height.Accurate.NormalEps = 0.000001
#
# Accurate VMAP height (apply same footprint & slope logic on VMAP)
# 1 = enable (recommended), 0 = legacy point-ray height
#
Height.Accurate.VMap.Enable = 1
#
# Lateral sample delta (meters) to estimate VMAP gradient with 4 extra rays:
# (x±delta,y) and (x,y±delta). Typical 0.15..0.35. Affects precision & cost.
#
Height.Accurate.VMap.Delta = 0.25
#
# -- Accurate DYNAMIC (MMAP) planar offset (gradient sampling) --
# 1 = enable (recommended), 0 = legacy point ray
#
Height.Accurate.Dynamic.Enable = 1
#
# Gradient sampling delta for Dynamic (meters). Typical 0.15..0.35
#
Height.Accurate.Dynamic.Delta = 0.25
#
# DetectPosCollision
# Description: Check final move position, summon position, etc for visible collision with

View File

@ -617,7 +617,7 @@ LiquidData const GridTerrainData::GetLiquidData(float x, float y, float z, float
return liquidData;
}
namespace
namespace
{
constexpr float INV_SQRT2 = 0.70710678118654752440f; // 1/sqrt(2)
}
@ -684,21 +684,22 @@ bool GridTerrainData::SampleHeights(uint32 xInt, uint32 yInt, float& h1, float&
float GridTerrainData::GetHeightAccurate(float x, float y, float radius) const
{
return GetHeightAccurate(x, y, radius, GroundFootprintShape::Circle, 0.0f, 1.0f, 0.0f);
return GetHeightAccurate(x, y, radius, GroundFootprintShape::Circle, 0.0f, 1.0f, 0.0f, 0u, 1.0e-6f);
}
float GridTerrainData::GetHeightAccurate(float x, float y, float radius, GroundFootprintShape shape) const
{
return GetHeightAccurate(x, y, radius, shape, 0.0f, (shape == GroundFootprintShape::Square ? 0.0f : 1.0f), 0.0f);
return GetHeightAccurate(x, y, radius, shape, 0.0f, (shape == GroundFootprintShape::Square ? 0.0f : 1.0f), 0.0f, 0u, 1.0e-6f);
}
float GridTerrainData::GetHeightAccurate(float x, float y, float radius, GroundFootprintShape shape, float yaw) const
{
return GetHeightAccurate(x, y, radius, shape, yaw, (shape == GroundFootprintShape::Square ? 0.0f : 1.0f), 0.0f);
// Wrapper with default blend = 1 for circle, 0 for square.
return GetHeightAccurate(x, y, radius, shape, yaw, (shape == GroundFootprintShape::Square ? 0.0f : 1.0f), 0.0f, 0u, 1.0e-6f);
}
float GridTerrainData::GetHeightAccurate(float x, float y, float radius, GroundFootprintShape shape, float yaw,
float squareBlend, float slopeClamp) const
float squareBlend, float slopeClamp, uint32 gradientMode, float normalEps) const
{
if (!_loadedHeightData)
return INVALID_HEIGHT;
@ -710,6 +711,7 @@ float GridTerrainData::GetHeightAccurate(float x, float y, float radius, GroundF
int yInt = static_cast<int>(std::floor(yf));
float fx = xf - static_cast<float>(xInt);
float fy = yf - static_cast<float>(yInt);
if (fx < 0.0f) { fx += 1.0f; --xInt; }
if (fy < 0.0f) { fy += 1.0f; --yInt; }
if (fx >= 1.0f) { fx -= 1.0f; ++xInt; }
@ -767,45 +769,54 @@ float GridTerrainData::GetHeightAccurate(float x, float y, float radius, GroundF
const G3D::Vector3 V = C - A;
const G3D::Vector3 n = U.cross(V);
const float nzAbs = std::abs(n.z);
if (nzAbs < eps)
if (nzAbs < std::max(eps, normalEps))
return getHeight(x, y);
const float zPlane = A.z - (n.x * (P.x - A.x) + n.y * (P.y - A.y)) / n.z;
const float inv2S = 1.0f / (2.0f * S);
float gx, gy;
float a = ((h2 + h4) - (h1 + h3)) * inv2S; // dz/dx
float b = ((h3 + h4) - (h1 + h2)) * inv2S; // dz/dy
if (gradientMode == 0u)
{
// Plane-exact gradient: z = (-n.x*x - n.y*y - d)/n.z => ∇z = (-n.x/n.z, -n.y/n.z)
gx = -n.x / n.z;
gy = -n.y / n.z;
}
else
{
// LS gradient (smoother)
gx = ((h2 + h4) - (h1 + h3)) * inv2S;
gy = ((h3 + h4) - (h1 + h2)) * inv2S;
}
// Optional clamp
if (slopeClamp > 0.0f)
{
const float g2 = a * a + b * b;
const float g2 = gx*gx + gy*gy;
const float c2 = slopeClamp * slopeClamp;
if (g2 > c2)
{
const float scale = slopeClamp / std::sqrt(g2);
a *= scale; b *= scale;
const float s = slopeClamp / std::sqrt(g2);
gx *= s; gy *= s;
}
}
const float slopeL2 = std::sqrt(std::max(0.0f, a*a + b*b));
const float slopeL2 = std::sqrt(std::max(0.0f, gx*gx + gy*gy));
// Fastpath radius 0
if (radius <= 0.0f)
return zPlane;
float totalSlope = slopeL2;
float totalSlope = slopeL2; // por defecto: círculo (o blend==1)
if (shape == GroundFootprintShape::Square && blend < 1.0f)
{
float slopeL1;
if (a == 0.0f && b == 0.0f)
{
slopeL1 = 0.0f;
}
else
float slopeL1 = 0.0f;
if (!(gx == 0.0f && gy == 0.0f))
{
const float c = std::cos(yaw);
const float s = std::sin(yaw);
const float rx = a * c + b * s;
const float ry = -a * s + b * c;
const float rx = gx * c + gy * s;
const float ry = -gx * s + gy * c;
slopeL1 = std::abs(rx) + std::abs(ry);
}
totalSlope = blend * slopeL2 + (1.0f - blend) * (INV_SQRT2 * slopeL1);
@ -813,4 +824,4 @@ float GridTerrainData::GetHeightAccurate(float x, float y, float radius, GroundF
// Final height (Minkowski): base + radius * slope
return zPlane + radius * totalSlope;
}
}

View File

@ -261,10 +261,12 @@ public:
return "unknown";
}
float GetHeightAccurate(float x, float y, float radius, GroundFootprintShape shape, float yaw, float squareBlend, float slopeClamp) const;
// Accurate height with footprint & yaw. squareBlend in [0..1]. slopeClamp in [0..10] (0 disables).
float GetHeightAccurate(float x, float y, float radius, GroundFootprintShape shape, float yaw,
float squareBlend, float slopeClamp, uint32 gradientMode, float normalEps) const;
// Convenience wrappers (kept for compatibility).
float GetHeightAccurate(float x, float y, float radius) const;
float GetHeightAccurate(float x, float y, float radius, GroundFootprintShape shape) const;
float GetHeightAccurate(float x, float y, float radius, GroundFootprintShape shape, float yaw /*rads*/) const;
inline std::string to_string(GridTerrainData::GroundFootprintShape s) { return GridTerrainData::ToString(s); }
uint16 getArea(float x, float y) const;

View File

@ -1175,6 +1175,65 @@ float Map::GetHeight(float x, float y, float z, bool checkVMap /*= true*/, float
return mapHeight; // explicitly use map data
}
namespace { constexpr float INV_SQRT2 = 0.70710678118654752440f; }
float Map::GetVMapHeightAccurate(float x, float y, float z, float radius, float yaw,
GridTerrainData::GroundFootprintShape shape, float blend, float clamp, float sampleDelta) const
{
VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr();
auto sample = [&](float sx, float sy) -> float
{
float h = vmgr->getHeight(GetId(), sx, sy, z, DEFAULT_HEIGHT_SEARCH);
return (h > INVALID_HEIGHT) ? h : std::numeric_limits<float>::quiet_NaN();
};
float h0 = sample(x, y);
if (!std::isfinite(h0))
return VMAP_INVALID_HEIGHT_VALUE;
if (radius <= 0.0f)
return h0;
const float d = (sampleDelta > 0.0f) ? sampleDelta
: std::max(0.05f, std::min(0.5f, radius * 0.5f));
float hx1 = sample(x + d, y), hx2 = sample(x - d, y);
float hy1 = sample(x, y + d), hy2 = sample(x, y - d);
auto diff = [&](float p, float m) -> float
{
if (std::isfinite(p) && std::isfinite(m)) return (p - m) / (2.0f * d);
if (std::isfinite(p)) return (p - h0) / d;
if (std::isfinite(m)) return (h0 - m) / d;
return 0.0f;
};
float gx = diff(hx1, hx2); // dz/dx
float gy = diff(hy1, hy2); // dz/dy
if (clamp > 0.0f)
{
float g2 = gx*gx + gy*gy, c2 = clamp*clamp;
if (g2 > c2)
{
float s = clamp / std::sqrt(g2);
gx *= s; gy *= s;
}
}
float slopeL2 = std::sqrt(std::max(0.0f, gx*gx + gy*gy));
float totalSlope = slopeL2;
if (shape == GridTerrainData::GroundFootprintShape::Square && blend < 1.0f)
{
float c = std::cos(yaw), s = std::sin(yaw);
float rx = gx * c + gy * s, ry = -gx * s + gy * c;
float slopeL1 = std::abs(rx) + std::abs(ry);
totalSlope = blend * slopeL2 + (1.0f - blend) * (INV_SQRT2 * slopeL1);
}
return h0 + radius * totalSlope;
}
float Map::GetHeightAccurate(float x, float y, float z, float radius, bool checkVMap /*= true*/, float maxSearchDist /*= DEFAULT_HEIGHT_SEARCH*/) const
{
return GetHeightAccurate(x, y, z, radius, 0.0f, checkVMap, maxSearchDist);
@ -1195,8 +1254,20 @@ float Map::GetHeightAccurate(float x, float y, float z, float radius, float yaw,
float vmapHeight = VMAP_INVALID_HEIGHT_VALUE;
if (checkVMap)
{
VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr();
vmapHeight = vmgr->getHeight(GetId(), x, y, z, maxSearchDist); // VMAP: same criterion as GetHeight
const bool useAccurateVMap = (sWorld->getIntConfig(CONFIG_HEIGHT_ACCURATE_VMAP_ENABLE) != 0);
if (useAccurateVMap)
{
auto shape = static_cast<GridTerrainData::GroundFootprintShape>(sWorld->getIntConfig(CONFIG_HEIGHT_ACCURATE_SHAPE));
float blend = sWorld->getFloatConfig(CONFIG_HEIGHT_ACCURATE_SQUARE_BLEND);
float clamp = sWorld->getFloatConfig(CONFIG_HEIGHT_ACCURATE_SLOPE_CLAMP);
float delta = sWorld->getFloatConfig(CONFIG_HEIGHT_ACCURATE_VMAP_DELTA);
vmapHeight = GetVMapHeightAccurate(x, y, z, radius, yaw, shape, blend, clamp, delta);
}
else
{
VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr();
vmapHeight = vmgr->getHeight(GetId(), x, y, z, maxSearchDist);
}
}
// mapHeight set for any above raw ground Z or <= INVALID_HEIGHT
@ -1664,7 +1735,7 @@ float Map::GetHeight(uint32 phasemask, float x, float y, float z, bool vmap/*=tr
float Map::GetHeightAccurate(uint32 phasemask, float x, float y, float z, float radius,
bool vmap/*=true*/, float maxSearchDist /*= DEFAULT_HEIGHT_SEARCH*/) const
{
{
return GetHeightAccurate(phasemask, x, y, z, radius, 0.0f, vmap, maxSearchDist);
}
@ -1672,7 +1743,22 @@ float Map::GetHeightAccurate(uint32 phasemask, float x, float y, float z, float
bool vmap/*=true*/, float maxSearchDist /*= DEFAULT_HEIGHT_SEARCH*/) const
{
const float hMapMix = GetHeightAccurate(x, y, z, radius, yaw, vmap, maxSearchDist);
const float hDyn = _dynamicTree.getHeight(x, y, z, maxSearchDist, phasemask);
float hDyn;
const bool dynAcc = (sWorld->getIntConfig(CONFIG_HEIGHT_ACCURATE_DYNAMIC_ENABLE) != 0);
if (dynAcc)
{
const auto shape = static_cast<GridTerrainData::GroundFootprintShape>(sWorld->getIntConfig(CONFIG_HEIGHT_ACCURATE_SHAPE));
const float blend = sWorld->getFloatConfig(CONFIG_HEIGHT_ACCURATE_SQUARE_BLEND);
const float clamp = sWorld->getFloatConfig(CONFIG_HEIGHT_ACCURATE_SLOPE_CLAMP);
const float dlt = sWorld->getFloatConfig(CONFIG_HEIGHT_ACCURATE_DYNAMIC_DELTA);
const float effBlend = (shape == GridTerrainData::GroundFootprintShape::Square) ? blend : 1.0f;
hDyn = _dynamicTree.getHeightAccurate(x, y, z, maxSearchDist, phasemask, radius, yaw, effBlend, clamp, dlt);
}
else
{
hDyn = _dynamicTree.getHeight(x, y, z, maxSearchDist, phasemask);
}
return std::max<float>(hMapMix, hDyn);
}

View File

@ -527,6 +527,9 @@ private:
bool EnsureGridLoaded(Cell const& cell);
MapGridType* GetMapGrid(uint16 const x, uint16 const y);
[[nodiscard]] float GetVMapHeightAccurate(float x, float y, float z, float radius, float yaw,
GridTerrainData::GroundFootprintShape shape, float blend, float clamp, float sampleDelta) const;
void ScriptsProcess();
void SendObjectUpdates();

View File

@ -286,6 +286,12 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<float>(CONFIG_HEIGHT_ACCURATE_RADIUS_SCALE, "Height.Accurate.RadiusScale", 1.0f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.1f && value <= 4.0f; }, ">= 0.1 and <= 4.0");
SetConfigValue<float>(CONFIG_HEIGHT_ACCURATE_SQUARE_BLEND, "Height.Accurate.SquareBlend", 0.20f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f && value <= 1.0f; }, ">= 0.0 and <= 1.0");
SetConfigValue<float>(CONFIG_HEIGHT_ACCURATE_SLOPE_CLAMP, "Height.Accurate.SlopeClamp", 0.0f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f && value <= 10.0f; }, ">= 0.0 and <= 10.0");
SetConfigValue<uint32>(CONFIG_HEIGHT_ACCURATE_GRADIENT_MODE, "Height.Accurate.GradientMode", 0, ConfigValueCache::Reloadable::Yes, [](uint32 const& value) { return value <= 1; }, "<= 1");
SetConfigValue<float>(CONFIG_HEIGHT_ACCURATE_NORMAL_EPS, "Height.Accurate.NormalEps", 1.0e-6f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 1e-8f && value <= 1e-3f; }, ">= 1e-8f and <= 1e-3f");
SetConfigValue<uint32>(CONFIG_HEIGHT_ACCURATE_VMAP_ENABLE, "Height.Accurate.VMap.Enable", 1, ConfigValueCache::Reloadable::Yes, [](uint32 const& value) { return value <= 1; }, "<= 1");
SetConfigValue<float>(CONFIG_HEIGHT_ACCURATE_VMAP_DELTA, "Height.Accurate.VMap.Delta", 0.25f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.01f && value <= 1.0f; }, ">= 0.01f and <= 1.0f");
SetConfigValue<uint32>(CONFIG_HEIGHT_ACCURATE_DYNAMIC_ENABLE, "Height.Accurate.Dynamic.Enable", 1, ConfigValueCache::Reloadable::Yes, [](uint32 const& value) { return value <= 1; }, "<= 1");
SetConfigValue<float>(CONFIG_HEIGHT_ACCURATE_DYNAMIC_DELTA, "Height.Accurate.Dynamic.Delta", 0.25f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.01f && value <= 1.0f; }, ">= 0.01f and <= 1.0f");
SetConfigValue<uint32>(CONFIG_GROUP_VISIBILITY, "Visibility.GroupMode", 1);

View File

@ -485,6 +485,12 @@ enum ServerConfigs
CONFIG_HEIGHT_ACCURATE_SQUARE_BLEND,
CONFIG_HEIGHT_ACCURATE_SLOPE_CLAMP,
CONFIG_HEIGHT_ACCURATE_SHAPE,
CONFIG_HEIGHT_ACCURATE_NORMAL_EPS,
CONFIG_HEIGHT_ACCURATE_VMAP_DELTA,
CONFIG_HEIGHT_ACCURATE_DYNAMIC_DELTA,
CONFIG_HEIGHT_ACCURATE_GRADIENT_MODE,
CONFIG_HEIGHT_ACCURATE_VMAP_ENABLE,
CONFIG_HEIGHT_ACCURATE_DYNAMIC_ENABLE,
MAX_NUM_SERVER_CONFIGS
};

View File

@ -633,6 +633,10 @@ public:
const float rScale = sWorld->getFloatConfig(CONFIG_HEIGHT_ACCURATE_RADIUS_SCALE);
const float blend = sWorld->getFloatConfig(CONFIG_HEIGHT_ACCURATE_SQUARE_BLEND);
const float clamp = sWorld->getFloatConfig(CONFIG_HEIGHT_ACCURATE_SLOPE_CLAMP);
const uint32 vmapAcc = sWorld->getIntConfig(CONFIG_HEIGHT_ACCURATE_VMAP_ENABLE);
const float vdelta = sWorld->getFloatConfig(CONFIG_HEIGHT_ACCURATE_VMAP_DELTA);
const uint32 dynAcc = sWorld->getIntConfig(CONFIG_HEIGHT_ACCURATE_DYNAMIC_ENABLE);
const float dyndelta = sWorld->getFloatConfig(CONFIG_HEIGHT_ACCURATE_DYNAMIC_DELTA);
const float yaw = object->GetOrientation();
const float probeRScaled = probeR * rScale;
@ -669,7 +673,8 @@ public:
cell.GridX(), cell.GridY(), cell.CellX(), cell.CellY(), object->GetInstanceId(),
zoneX, zoneY, groundZ, floorZ, haveMap, haveVMap, haveMMAP);
handler->PSendSysMessage("Accurate Height Shape: {} (SquareBlend: {:0.2f}, RadiusScale: {:0.2f}, SlopeClamp: {:0.2f})", shapeStr, blend, rScale, clamp);
handler->PSendSysMessage("Accurate Height Shape: {} (SquareBlend: {:0.2f}, RadiusScale: {:0.2f}, SlopeClamp: {:0.2f}, GradMode: {}, NormalEps: {:0.6f}, VMapAccurate: {}, VMapDelta: {:0.2f}, DynAccurate: {}, DynDelta: {:0.2f})",
shapeStr, blend, rScale, clamp, gmode, neps, vmapAcc, vdelta, dynAcc, dyndelta);
handler->PSendSysMessage("Accurate Height Grid: {}", GridZAccurate);
handler->PSendSysMessage("Accurate Height Map: {}", MapZAccurate);
handler->PSendSysMessage("Probe radius (pre-scale): {:0.3f}", probeR);