From d7191ad868dec9815c8e302cb434663be0375010 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 28 Jun 2026 07:51:12 +0200 Subject: [PATCH 1/2] bugfix(heightmap): Reduce ray cast lengths and fix ray casting in BaseHeightMapRenderObjClass::Cast_Ray --- .../W3DDevice/GameClient/BaseHeightMap.cpp | 89 ++++++++++--------- .../Source/W3DDevice/GameClient/W3DView.cpp | 4 +- 2 files changed, 48 insertions(+), 45 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp index 0103ca60685..27b9992b838 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp @@ -665,7 +665,7 @@ relative to the ray so we can early exit as soon as we have a hit. //============================================================================= /** Return intersection of a ray with the heightmap mesh. This is a quick version that just checks every polygon inside -a 2D bounding rectangle of the ray projected onto the heightfield plane. +a 2D bounding rectangle of the ray projected onto the height map plane. For most of our view-picking cases the ray is almost perpendicular to the 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 @@ -681,16 +681,17 @@ bool BaseHeightMapRenderObjClass::Cast_Ray(RayCollisionTestClass & raytest) if (!m_map) return false; //need valid pointer to heightmap samples -//HeightSampleType *pData = m_map->getDataPtr(); - //Clip ray to extents of heightfield + + //Clip ray to extents of height map AABoxClass hbox; LineSegClass lineseg,lineseg2; CastResultStruct result; - Int StartCellX = 0; - Int EndCellX = 0; - Int StartCellY = 0; - Int EndCellY = 0; - const Int overhang = 2*VERTEX_BUFFER_TILE_LENGTH + m_map->getBorderSizeInline(); // Allow picking past the edge for scrolling & objects. + Int startCellX = 0; + Int startCellY = 0; + Int endCellX = 0; + 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); @@ -699,50 +700,52 @@ bool BaseHeightMapRenderObjClass::Cast_Ray(RayCollisionTestClass & raytest) lineseg=raytest.Ray; - //Set initial ray endpoints - P0 = raytest.Ray.Get_P0(); - P1 = raytest.Ray.Get_P1(); - result.ComputeContactPoint=true; - - Int p; - for (p=0; p<3; p++) { + { //find intersection point of ray and terrain bounding box - result.Reset(); result.ComputeContactPoint=true; + Int contactCount=0; + if (CollisionMath::Collide(lineseg,hbox,&result)) - { //ray intersects terrain or starts inside the terrain. + { + //ray intersects terrain or starts inside the terrain. if (!result.StartBad) //check if start point inside terrain + { P0 = result.ContactPoint; //make intersection point the new start of the ray. + ++contactCount; + } - //reverse direction of original ray and clip again to extent of - //heightmap + //reverse direction of original ray and clip again to extent of heightmap result.Fraction=1.0f; //reset the result result.StartBad=false; lineseg2.Set(lineseg.Get_P1(),lineseg.Get_P0()); //reverse line segment if (CollisionMath::Collide(lineseg2,hbox,&result)) - { if (!result.StartBad) //check if end point inside terrain - P1 = result.ContactPoint; //make intersection point the new end pont of ray + { + if (!result.StartBad) //check if end point inside terrain + { + P1 = result.ContactPoint; //make intersection point the new end point of ray + ++contactCount; + } } - } else { - if (p==0) return(false); - break; } + if (contactCount != 2) + return false; + // Take the 2D bounding box of ray and check heights // inside this box for intersection. if (P0.X > P1.X) { //flip start/end points - StartCellX = REAL_TO_INT_FLOOR(P1.X/MAP_XY_FACTOR); - EndCellX = REAL_TO_INT_CEIL(P0.X/MAP_XY_FACTOR); + startCellX = REAL_TO_INT_FLOOR(P1.X/MAP_XY_FACTOR); + endCellX = REAL_TO_INT_CEIL(P0.X/MAP_XY_FACTOR); } else { - StartCellX = REAL_TO_INT_FLOOR(P0.X/MAP_XY_FACTOR); - EndCellX = REAL_TO_INT_CEIL(P1.X/MAP_XY_FACTOR); + startCellX = REAL_TO_INT_FLOOR(P0.X/MAP_XY_FACTOR); + endCellX = REAL_TO_INT_CEIL(P1.X/MAP_XY_FACTOR); } if (P0.Y > P1.Y) { //flip start/end points - StartCellY = REAL_TO_INT_FLOOR(P1.Y/MAP_XY_FACTOR); - EndCellY = REAL_TO_INT_CEIL(P0.Y/MAP_XY_FACTOR); + startCellY = REAL_TO_INT_FLOOR(P1.Y/MAP_XY_FACTOR); + endCellY = REAL_TO_INT_CEIL(P0.Y/MAP_XY_FACTOR); } else { - StartCellY = REAL_TO_INT_FLOOR(P0.Y/MAP_XY_FACTOR); - EndCellY = REAL_TO_INT_CEIL(P1.Y/MAP_XY_FACTOR); + startCellY = REAL_TO_INT_FLOOR(P0.Y/MAP_XY_FACTOR); + endCellY = REAL_TO_INT_CEIL(P1.Y/MAP_XY_FACTOR); } Int i, j, minHt, maxHt; @@ -750,15 +753,15 @@ bool BaseHeightMapRenderObjClass::Cast_Ray(RayCollisionTestClass & raytest) minHt = m_map->getMaxHeightValue(); maxHt = 0; - for (j=StartCellY; j<=EndCellY; j++) { - for (i=StartCellX; i<=EndCellX; i++) { - Short cur = getClipHeight(i+m_map->getBorderSizeInline(),j+m_map->getBorderSizeInline()); + for (j=startCellY; j<=endCellY; j++) { + for (i=startCellX; i<=endCellX; i++) { + Short cur = getClipHeight(i+borderSize,j+borderSize); if (curgetBorderSizeInline(); - EndCellX += m_map->getBorderSizeInline(); - StartCellY += m_map->getBorderSizeInline(); - EndCellY += m_map->getBorderSizeInline(); + startCellX += borderSize; + endCellX += borderSize; + startCellY += borderSize; + endCellY += borderSize; Int offset; for (offset = 1; offset < 5; offset *= 3) { - for (Y=StartCellY-offset; Y<=EndCellY+offset; Y++) { + for (Y=startCellY-offset; Y<=endCellY+offset; Y++) { - for (X=StartCellX-offset; X<=EndCellX+offset; X++) { + for (X=startCellX-offset; X<=endCellX+offset; X++) { //test the 2 triangles in this cell // 3-----2 // | /| diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index 3f684ea7597..67256611aae 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -657,7 +657,7 @@ void W3DView::getPickRay(const ICoord2D *screen, Vector3 *rayStart, Vector3 *ray m_3DCamera->Un_Project(*rayEnd,Vector2(logX,logY)); //get world space point *rayEnd -= *rayStart; //vector camera to world space point rayEnd->Normalize(); //make unit vector - *rayEnd *= sqr(m_3DCamera->Get_Depth()); //adjust length to reach far clip plane and beyond + *rayEnd *= m_3DCamera->Get_Depth() * 2; //adjust length to reach far clip plane and beyond *rayEnd += *rayStart; //get point on far clip plane along ray from camera. } @@ -2622,7 +2622,7 @@ void W3DView::lookAt( const Coord3D *o ) m_3DCamera->Un_Project(rayEnd,Vector2(0.0f,0.0f)); //get world space point rayEnd -= rayStart; //vector camera to world space point rayEnd.Normalize(); //make unit vector - rayEnd *= sqr(m_3DCamera->Get_Depth()); //adjust length to reach far clip plane and beyond + rayEnd *= m_3DCamera->Get_Depth() * 2; //adjust length to reach far clip plane and beyond rayStart.Set(pos.x, pos.y, pos.z); rayEnd += rayStart; //get point on far clip plane along ray from camera. lineseg.Set(rayStart,rayEnd); From ce7d1bfbc41f56c668d4fe508904be5d6a349a5a Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 28 Jun 2026 08:27:54 +0200 Subject: [PATCH 2/2] Refine fixes to BaseHeightMapRenderObjClass::Cast_Ray --- .../W3DDevice/GameClient/BaseHeightMap.cpp | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp index 27b9992b838..95f41b57f24 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp @@ -678,6 +678,8 @@ bool BaseHeightMapRenderObjClass::Cast_Ray(RayCollisionTestClass & raytest) 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 @@ -700,18 +702,22 @@ bool BaseHeightMapRenderObjClass::Cast_Ray(RayCollisionTestClass & raytest) lineseg=raytest.Ray; - { + Int p; + for (p=0; p<3; p++) { //find intersection point of ray and terrain bounding box + result.Reset(); result.ComputeContactPoint=true; - Int contactCount=0; + Bool newP0 = false; + Bool newP1 = false; if (CollisionMath::Collide(lineseg,hbox,&result)) { //ray intersects terrain or starts inside the terrain. if (!result.StartBad) //check if start point inside terrain { - P0 = result.ContactPoint; //make intersection point the new start of the ray. - ++contactCount; + newP0 = P0 != result.ContactPoint; + hasP0 = true; + P0 = result.ContactPoint; //make intersection point the new start of the ray. } //reverse direction of original ray and clip again to extent of heightmap @@ -722,14 +728,15 @@ 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 - ++contactCount; } } } - if (contactCount != 2) - return false; + if (!newP0 || !newP1) + break; // Take the 2D bounding box of ray and check heights // inside this box for intersection. @@ -766,6 +773,9 @@ bool BaseHeightMapRenderObjClass::Cast_Ray(RayCollisionTestClass & raytest) hbox.Init(mmbox); } + if (!hasP0 || !hasP1) + return false; + raytest.Result->ComputeContactPoint=true; //tell CollisionMath that we need point. // Adjust indexes into the bordered height map.