From 7c0d0923bdb535b5f392a20adb9bf0bbbb9b1601 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:43:12 +0200 Subject: [PATCH 1/2] fix(heightmap): Fix ray casting in BaseHeightMapRenderObjClass::Cast_Ray (2) --- .../W3DDevice/GameClient/BaseHeightMap.cpp | 60 +++++++++++++------ 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp index 95f41b57f24..ef777222f08 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp @@ -656,10 +656,6 @@ void BaseHeightMapRenderObjClass::reset() } } -/**@todo: Ray intersection needs to be optimized with some sort of grid-tracing -(ala line drawing). We should also try making the search in a front->back order -relative to the ray so we can early exit as soon as we have a hit. -* //============================================================================= // BaseHeightMapRenderObjClass::Cast_Ray //============================================================================= @@ -671,18 +667,27 @@ map plane so this is very quick (small bounding box). But it can become slow for arbitrary rays such as those used in AI visibility checks(2 units on opposite corners of the map would check every polygon in the map). */ +/** @todo: Ray intersection needs to be optimized with some sort of grid-tracing +(ala line drawing). We should also try making the search in a front->back order +relative to the ray so we can early exit as soon as we have a hit. +*/ +// TheSuperHackers @fix This function now creates its initial hit box +// with dimensions that fit into the ray cast and no longer falls back to an +// infinitely large search region if the initial box cannot be collided with. //============================================================================= bool BaseHeightMapRenderObjClass::Cast_Ray(RayCollisionTestClass & raytest) { + if (!m_map) + return false; //need valid pointer to heightmap samples + TriClass tri; Bool hit = false; Int X,Y; Vector3 normal,P0,P1,P2,P3; - Bool hasP0 = false; - Bool hasP1 = false; - - if (!m_map) - return false; //need valid pointer to heightmap samples + P0.Set(FLT_MAX, FLT_MAX, FLT_MAX); // Set initial bogus value + P1.Set(FLT_MAX, FLT_MAX, FLT_MAX); // Set initial bogus value + Int P0HitCount = 0; + Int P1HitCount = 0; //Clip ray to extents of height map AABoxClass hbox; @@ -694,16 +699,24 @@ bool BaseHeightMapRenderObjClass::Cast_Ray(RayCollisionTestClass & raytest) Int endCellY = 0; const Int borderSize = m_map->getBorderSizeInline(); const Int overhang = 2*VERTEX_BUFFER_TILE_LENGTH + borderSize; // Allow picking past the edge for scrolling & objects. - Vector3 minPt(MAP_XY_FACTOR*(-overhang), MAP_XY_FACTOR*(-overhang), -MAP_XY_FACTOR); - Vector3 maxPt(MAP_XY_FACTOR*(m_map->getXExtent()+overhang), - MAP_XY_FACTOR*(m_map->getYExtent()+overhang), MAP_HEIGHT_SCALE*m_map->getMaxHeightValue()+MAP_XY_FACTOR); + + Real rayMinHeight = std::min(raytest.Ray.Get_P0().Z, raytest.Ray.Get_P1().Z); + Real rayMaxHeight = std::max(raytest.Ray.Get_P0().Z, raytest.Ray.Get_P1().Z); + Real mapMinHeight = MAP_HEIGHT_SCALE * m_map->getMinHeightValue(); // Begin with min map height. + Real mapMaxHeight = MAP_HEIGHT_SCALE * m_map->getMaxHeightValue(); // Begin with max map height. + mapMinHeight = std::max(mapMinHeight, rayMinHeight + 1.0f); // But not lower than the ray end plus a margin. + mapMaxHeight = std::min(mapMaxHeight, rayMaxHeight - 1.0f); // But not higher than the ray start minus a margin. + + // The first hit box is very rough and is only meant to narrow the search. + Vector3 minPt(MAP_XY_FACTOR*(-overhang), MAP_XY_FACTOR*(-overhang), mapMinHeight); + Vector3 maxPt(MAP_XY_FACTOR*(m_map->getXExtent()+overhang), MAP_XY_FACTOR*(m_map->getYExtent()+overhang), mapMaxHeight); MinMaxAABoxClass mmbox(minPt, maxPt); hbox.Init(mmbox); lineseg=raytest.Ray; - Int p; - for (p=0; p<3; p++) { + Int terrainIntersectionIteration = 0; + for (; ; ++terrainIntersectionIteration) { //find intersection point of ray and terrain bounding box result.Reset(); result.ComputeContactPoint=true; @@ -716,8 +729,8 @@ bool BaseHeightMapRenderObjClass::Cast_Ray(RayCollisionTestClass & raytest) if (!result.StartBad) //check if start point inside terrain { newP0 = P0 != result.ContactPoint; - hasP0 = true; P0 = result.ContactPoint; //make intersection point the new start of the ray. + ++P0HitCount; } //reverse direction of original ray and clip again to extent of heightmap @@ -729,13 +742,18 @@ bool BaseHeightMapRenderObjClass::Cast_Ray(RayCollisionTestClass & raytest) if (!result.StartBad) //check if end point inside terrain { newP1 = P1 != result.ContactPoint; - hasP1 = true; P1 = result.ContactPoint; //make intersection point the new end point of ray + ++P1HitCount; } } } - if (!newP0 || !newP1) + // Has not even hit the first hit box? + if (P0HitCount == 0 || P1HitCount == 0) + return false; + + // Has no new hit points? + if (!newP0 && !newP1) break; // Take the 2D bounding box of ray and check heights @@ -755,6 +773,10 @@ bool BaseHeightMapRenderObjClass::Cast_Ray(RayCollisionTestClass & raytest) endCellY = REAL_TO_INT_CEIL(P1.Y/MAP_XY_FACTOR); } + // Stop narrowing after the third iteration + if (terrainIntersectionIteration == 2) + break; + Int i, j, minHt, maxHt; minHt = m_map->getMaxHeightValue(); @@ -773,7 +795,8 @@ bool BaseHeightMapRenderObjClass::Cast_Ray(RayCollisionTestClass & raytest) hbox.Init(mmbox); } - if (!hasP0 || !hasP1) + // Needs at least 2 hit counts for a proper terrain collision. + if (P0HitCount < 2 || P1HitCount < 2) return false; raytest.Result->ComputeContactPoint=true; //tell CollisionMath that we need point. @@ -788,7 +811,6 @@ bool BaseHeightMapRenderObjClass::Cast_Ray(RayCollisionTestClass & raytest) Int offset; for (offset = 1; offset < 5; offset *= 3) { for (Y=startCellY-offset; Y<=endCellY+offset; Y++) { - for (X=startCellX-offset; X<=endCellX+offset; X++) { //test the 2 triangles in this cell // 3-----2 From 47cc702839accaa422f8002f76aa075d94187497 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 12 Jul 2026 17:20:14 +0200 Subject: [PATCH 2/2] Fix ray height margin --- .../Source/W3DDevice/GameClient/BaseHeightMap.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp index ef777222f08..193a8609259 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp @@ -700,12 +700,14 @@ bool BaseHeightMapRenderObjClass::Cast_Ray(RayCollisionTestClass & raytest) const Int borderSize = m_map->getBorderSizeInline(); const Int overhang = 2*VERTEX_BUFFER_TILE_LENGTH + borderSize; // Allow picking past the edge for scrolling & objects. - Real rayMinHeight = std::min(raytest.Ray.Get_P0().Z, raytest.Ray.Get_P1().Z); - Real rayMaxHeight = std::max(raytest.Ray.Get_P0().Z, raytest.Ray.Get_P1().Z); + const Real rayMinHeight = std::min(raytest.Ray.Get_P0().Z, raytest.Ray.Get_P1().Z); + const Real rayMaxHeight = std::max(raytest.Ray.Get_P0().Z, raytest.Ray.Get_P1().Z); + const Real rayHeightDelta = rayMaxHeight - rayMinHeight; + const Real rayHeightMargin = std::min(1.0f, rayHeightDelta * 0.49f); Real mapMinHeight = MAP_HEIGHT_SCALE * m_map->getMinHeightValue(); // Begin with min map height. Real mapMaxHeight = MAP_HEIGHT_SCALE * m_map->getMaxHeightValue(); // Begin with max map height. - mapMinHeight = std::max(mapMinHeight, rayMinHeight + 1.0f); // But not lower than the ray end plus a margin. - mapMaxHeight = std::min(mapMaxHeight, rayMaxHeight - 1.0f); // But not higher than the ray start minus a margin. + mapMinHeight = std::max(mapMinHeight, rayMinHeight + rayHeightMargin); // But not lower than the ray end plus a margin. + mapMaxHeight = std::min(mapMaxHeight, rayMaxHeight - rayHeightMargin); // But not higher than the ray start minus a margin. // The first hit box is very rough and is only meant to narrow the search. Vector3 minPt(MAP_XY_FACTOR*(-overhang), MAP_XY_FACTOR*(-overhang), mapMinHeight);