#include "gamelib/aas2/obstacleavoidancecore.h" #include #include #include #include #include #include namespace { float Cross2(const idVec2& first, const idVec2& second) { return first.x * second.y - first.y * second.x; } idVec2 Sub2(const idVec2& first, const idVec2& second) { return idVec2(first.x - second.x, first.y - second.y); } float Dot2(const idVec2& first, const idVec2& second) { return first.x * second.x + first.y * second.y; } float Length2(const idVec2& value) { return std::sqrt(Dot2(value, value)); } idVec2 Normalize2(const idVec2& value) { const float length = Length2(value); return length > 1.0e-6f ? idVec2(value.x / length, value.y / length) : idVec2(0.0f, 0.0f); } float SegmentPointDistanceSqr(const idVec2& start, const idVec2& end, const idVec2& point) { const idVec2 delta = Sub2(end, start); const float lengthSqr = Dot2(delta, delta); float fraction = lengthSqr > 0.0f ? Dot2(Sub2(point, start), delta) / lengthSqr : 0.0f; fraction = (std::max)(0.0f, (std::min)(1.0f, fraction)); const idVec2 closest(start.x + delta.x * fraction, start.y + delta.y * fraction); return Dot2(Sub2(point, closest), Sub2(point, closest)); } bool SegmentIntersection(const idVec2& a, const idVec2& b, const idVec2& c, const idVec2& d) { const idVec2 r = Sub2(b, a); const idVec2 s = Sub2(d, c); const float denominator = Cross2(r, s); if (std::fabs(denominator) < 1.0e-6f) return false; const idVec2 delta = Sub2(c, a); const float t = Cross2(delta, s) / denominator; const float u = Cross2(delta, r) / denominator; return t >= 0.0f && t <= 1.0f && u >= 0.0f && u <= 1.0f; } } // namespace idObstacleAvoidanceCore::idObstacleAvoidanceCore() : bspNodes(nullptr), numBspNodes(0), obstacleIndices(nullptr), expandedObstacles(nullptr), maxExpandedObstacles(0), numExpandedObstacles(0), windingPoints(nullptr), maxWindingPoints(0), numWindingPoints(0), tempMemory(nullptr), tempMemorySize(0), tempMemoryStart(nullptr), tempMemoryEnd(nullptr), pathNodes(nullptr), maxPathNodes(0), numPathNodes(0), debugLines(nullptr), maxDebugLines(0), numDebugLines(0), debugText(nullptr), maxDebugText(0), numDebugText(0), inputTransformValid(false), inputTransform(), flags(0) { } idObstacleAvoidanceCore::~idObstacleAvoidanceCore() = default; void idObstacleAvoidanceCore::AddDebugLine(const avoidColor_t color, const idVec2& start, const idVec2& end) { if (debugLines == nullptr || numDebugLines >= maxDebugLines - 1) return; avoidDebugLine_t& line = debugLines[numDebugLines++]; line.color = color; line.start.Set(start.x, start.y, 0.0f); line.end.Set(end.x, end.y, 0.0f); } void idObstacleAvoidanceCore::AddDebugInt(const avoidColor_t color, const idVec2& position, const int value) { if (debugText == nullptr || numDebugText >= maxDebugText - 1) return; avoidDebugText_t& text = debugText[numDebugText++]; std::snprintf(text.text, sizeof(text.text), "%d", value); text.pos.Set(position.x, position.y, 0.0f); text.color = color; } int idObstacleAvoidanceCore::BoxParallelProjectionSilhouetteVerts( const idMat3& axis, const idVec3& center, const idVec3& extents, const idVec3& projectionDir, idVec3* const silhouette) { if (silhouette == nullptr) return 0; idVec3 corners[8]; int count = 0; for (int x = -1; x <= 1; x += 2) { for (int y = -1; y <= 1; y += 2) { for (int z = -1; z <= 1; z += 2) { corners[count++] = center + axis[0] * (extents.x * x) + axis[1] * (extents.y * y) + axis[2] * (extents.z * z); } } } int output = 0; for (int index = 0; index < 8 && output < 6; ++index) { const float facing = (corners[index] - center).Dot(projectionDir); if (facing <= 0.0f) silhouette[output++] = corners[index]; } return output; } void idObstacleAvoidanceCore::GetCirclePoints(const idVec2& center, const float radius, idVec2* const points) { if (points == nullptr) return; constexpr float diagonal = 0.70710678118f; const idVec2 directions[8] = { idVec2(1, 0), idVec2(diagonal, diagonal), idVec2(0, 1), idVec2(-diagonal, diagonal), idVec2(-1, 0), idVec2(-diagonal, -diagonal), idVec2(0, -1), idVec2(diagonal, -diagonal) }; for (int index = 0; index < 8; ++index) { points[index].Set(center.x + directions[index].x * radius, center.y + directions[index].y * radius); } } idVec3 idObstacleAvoidanceCore::Plane2DFromPoints(const idVec2& start, const idVec2& end, const bool normalize) { return Plane2DFromVecs(start, Sub2(end, start), normalize); } idVec3 idObstacleAvoidanceCore::Plane2DFromVecs(const idVec2& start, const idVec2& direction, const bool normalize) { idVec2 normal(-direction.y, direction.x); if (normalize) normal = Normalize2(normal); return idVec3(normal.x, normal.y, -(normal.x * start.x + normal.y * start.y)); } bool idObstacleAvoidanceCore::PointInsideWinding(const idVec2* const winding, const int numPoints, const idVec2& point, const float epsilon) { if (winding == nullptr || numPoints <= 0) return true; float sign = 0.0f; for (int index = 0; index < numPoints; ++index) { const float side = Cross2(Sub2(winding[(index + 1) % numPoints], winding[index]), Sub2(point, winding[index])); if (std::fabs(side) <= epsilon) continue; if (sign == 0.0f) sign = side; else if ((sign < 0.0f) != (side < 0.0f)) return false; } return true; } void idObstacleAvoidanceCore::ExpandWinding(const idVec2* const winding, const int numPoints, const float distance, idVec2* const expanded) { if (winding == nullptr || expanded == nullptr || numPoints <= 0) return; for (int index = 0; index < numPoints; ++index) { const idVec2 previous = winding[(index + numPoints - 1) % numPoints]; const idVec2 current = winding[index]; const idVec2 next = winding[(index + 1) % numPoints]; const idVec2 first = Normalize2(idVec2( -(current.y - previous.y), current.x - previous.x)); const idVec2 second = Normalize2(idVec2( -(next.y - current.y), next.x - current.x)); idVec2 direction = Normalize2(idVec2(first.x + second.x, first.y + second.y)); const float denominator = (std::max)(0.1f, std::fabs(Dot2(direction, first))); expanded[index].Set(current.x + direction.x * distance / denominator, current.y + direction.y * distance / denominator); } } void idObstacleAvoidanceCore::GetWindingBounds( const idVec2* const winding, const int numPoints, idVec2 bounds[2]) { const float maximum = (std::numeric_limits::max)(); bounds[0].Set(maximum, maximum); bounds[1].Set(-maximum, -maximum); for (int index = 0; index < numPoints; ++index) { bounds[0].x = (std::min)(bounds[0].x, winding[index].x); bounds[0].y = (std::min)(bounds[0].y, winding[index].y); bounds[1].x = (std::max)(bounds[1].x, winding[index].x); bounds[1].y = (std::max)(bounds[1].y, winding[index].y); } } bool idObstacleAvoidanceCore::WindingRayIntersection( const idVec2* const winding, const int numPoints, const idVec2& start, const idVec2& direction, float& firstScale, float& secondScale, int edgeNums[2]) { firstScale = (std::numeric_limits::max)(); secondScale = -(std::numeric_limits::max)(); int intersections = 0; for (int index = 0; index < numPoints; ++index) { const idVec2 edgeStart = winding[index]; const idVec2 edge = Sub2(winding[(index + 1) % numPoints], edgeStart); const float denominator = Cross2(direction, edge); if (std::fabs(denominator) < 1.0e-6f) continue; const idVec2 delta = Sub2(edgeStart, start); const float scale = Cross2(delta, edge) / denominator; const float edgeScale = Cross2(delta, direction) / denominator; if (edgeScale < 0.0f || edgeScale > 1.0f) continue; if (scale < firstScale) { firstScale = scale; if (edgeNums != nullptr) edgeNums[0] = index; } if (scale > secondScale) { secondScale = scale; if (edgeNums != nullptr) edgeNums[1] = index; } ++intersections; } return intersections >= 2; } void idObstacleAvoidanceCore::FindPathAroundObstacles(obstaclePath_t& path, avoidDebugLine_t* const outputDebugLines, const int outputMaxDebugLines, avoidDebugText_t* const outputDebugText, const int outputMaxDebugText, std::uint8_t* const temp, const int tempSize, const idBounds&, const idVec3&, const idVec3& lastDir, const idVec4& lastCorner, const float obstacleRadius, const float frameMoveDist, const float, const int queryFlags, const obstacleRoute_t& route, const obstacleBox_t* const obstacles, const int numObstacles, const obstacleVertex_t* const vertices, const int numVertices, const obstacleEdge_t* const edges, const int numEdges, const obstacleCorner_t*, const int, const obstacleTrace_t*, const int, float*) { debugLines = outputDebugLines; maxDebugLines = outputMaxDebugLines; numDebugLines = 0; debugText = outputDebugText; maxDebugText = outputMaxDebugText; numDebugText = 0; tempMemory = temp; tempMemorySize = tempSize; flags = queryFlags; const idVec3 start3 = route.numAreas > 0 ? route.areas[0].start : path.startPosOutsideObstacles; const idVec3 goal3 = route.numAreas > 0 ? route.areas[route.numAreas - 1].end : path.seekPos[0]; const idVec2 start(start3.x, start3.y); const idVec2 goal(goal3.x, goal3.y); path.numSeekPos = 0; path.firstObstacle = -1; path.firstObstaclePosition.Zero(); path.startPosOutsideObstacles = start3; path.seekPosOutsideObstacles = goal3; path.startPosObstacle = -1; path.seekPosObstacle = -1; path.targetDist = Length2(Sub2(goal, start)); path.pathLength = path.targetDist; path.hasValidPath = true; path.startPosValid = true; path.seekPosValid = true; int blocking = -1; float blockingRadius = 0.0f; for (int index = 0; index < numObstacles; ++index) { const obstacleBox_t& obstacle = obstacles[index]; if (obstacle.soft && (queryFlags & 0x200) == 0) continue; const float radius = std::sqrt(obstacle.extents.x * obstacle.extents.x + obstacle.extents.y * obstacle.extents.y) + obstacleRadius; if (SegmentPointDistanceSqr(start, goal, idVec2(obstacle.center.x, obstacle.center.y)) < radius * radius) { blocking = index; blockingRadius = radius; break; } } if (blocking < 0) { for (int index = 0; index < numEdges; ++index) { const int first = edges[index].vertNums[0]; const int second = edges[index].vertNums[1]; if (first < 0 || second < 0 || first >= numVertices || second >= numVertices) continue; const idVec2 edgeStart(vertices[first].position.x, vertices[first].position.y); const idVec2 edgeEnd(vertices[second].position.x, vertices[second].position.y); if (SegmentIntersection(start, goal, edgeStart, edgeEnd)) { blocking = numObstacles + index; break; } } } if (blocking >= 0 && blocking < numObstacles) { const obstacleBox_t& obstacle = obstacles[blocking]; const idVec2 center(obstacle.center.x, obstacle.center.y); idVec2 direction = Normalize2(Sub2(goal, start)); idVec2 perpendicular(-direction.y, direction.x); const idVec2 candidates[2] = { idVec2(center.x + perpendicular.x * blockingRadius, center.y + perpendicular.y * blockingRadius), idVec2(center.x - perpendicular.x * blockingRadius, center.y - perpendicular.y * blockingRadius) }; const float firstLength = Length2(Sub2(candidates[0], start)) + Length2(Sub2(goal, candidates[0])); const float secondLength = Length2(Sub2(candidates[1], start)) + Length2(Sub2(goal, candidates[1])); const idVec2 corner = firstLength <= secondLength ? candidates[0] : candidates[1]; path.seekPos[0].Set(corner.x, corner.y, start3.z); path.seekPos[1] = goal3; path.numSeekPos = 2; path.firstObstacle = obstacle.id; path.firstObstaclePosition = obstacle.center; path.pathLength = (std::min)(firstLength, secondLength); path.nextCorner.Set(corner.x, corner.y, start3.z, static_cast(obstacle.id)); AddDebugLine(COLOR_RED, start, corner); AddDebugLine(COLOR_GREEN, corner, goal); } else if (blocking >= numObstacles) { path.hasValidPath = false; path.seekPos[0] = start3; path.numSeekPos = 1; } else { path.seekPos[0] = goal3; path.numSeekPos = 1; path.nextCorner.Set(goal3.x, goal3.y, goal3.z, -1.0f); AddDebugLine(COLOR_GREEN, start, goal); } path.done = true; path.useRadarForward = Length2(idVec2(lastDir.x, lastDir.y)) > 0.0f; path.radarForward = lastDir; if (lastCorner.w >= 0.0f && path.firstObstacle < 0) { path.nextCorner = lastCorner; } if (frameMoveDist > 0.0f && path.numSeekPos > 0) { const idVec2 move = Sub2(idVec2(path.seekPos[0].x, path.seekPos[0].y), start); const float moveLength = Length2(move); if (moveLength > frameMoveDist) { const float scale = frameMoveDist / moveLength; path.seekPos[0].x = start.x + move.x * scale; path.seekPos[0].y = start.y + move.y * scale; } } }