/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #include "CryLegacy_precompiled.h" #include "Free2DNavRegion.h" #include "CAISystem.h" #include "AILog.h" //=================================================================== // CFree2DNavRegion //=================================================================== CFree2DNavRegion::CFree2DNavRegion(CGraph* pGraph) { m_pDummyNode = 0; m_dummyNodeIndex = 0; } //=================================================================== // ~CFree2DNavRegion //=================================================================== CFree2DNavRegion::~CFree2DNavRegion() { } //=================================================================== // BeautifyPath //=================================================================== void CFree2DNavRegion::BeautifyPath(const VectorConstNodeIndices& inPath, TPathPoints& outPath, const Vec3& startPos, const Vec3& startDir, const Vec3& endPos, const Vec3& endDir, float radius, const AgentMovementAbility& movementAbility, const NavigationBlockers& navigationBlockers) { AIWarning("CFree2DNavRegion::BeautifyPath should never be called"); } //=================================================================== // UglifyPath //=================================================================== void CFree2DNavRegion::UglifyPath(const VectorConstNodeIndices& inPath, TPathPoints& outPath, const Vec3& startPos, const Vec3& startDir, const Vec3& endPos, const Vec3& endDir) { AIWarning("CFree2DNavRegion::UglifyPath should never be called"); } //=================================================================== // GetEnclosing //=================================================================== unsigned CFree2DNavRegion::GetEnclosing(const Vec3& pos, float passRadius, unsigned startIndex, float /*range*/, Vec3* closestValid, bool returnSuspect, const char* requesterName, bool omitWalkabilityTest) { /* const SpecialArea *sa = GetAISystem()->GetSpecialArea(pos, SpecialArea::TYPE_FREE_2D); if (sa) return 0; int nBuildingID = sa->nBuildingID; if (nBuildingID < 0) return 0; */ if (!m_pDummyNode) { m_dummyNodeIndex = gAIEnv.pGraph->CreateNewNode(IAISystem::NAV_FREE_2D, ZERO); m_pDummyNode = gAIEnv.pGraph->GetNode(m_dummyNodeIndex); } return m_dummyNodeIndex; } //=================================================================== // Clear //=================================================================== void CFree2DNavRegion::Clear() { if (m_pDummyNode) { gAIEnv.pGraph->Disconnect(m_dummyNodeIndex); m_pDummyNode = 0; m_dummyNodeIndex = 0; } } //=================================================================== // MemStats //=================================================================== size_t CFree2DNavRegion::MemStats() { size_t size = sizeof(*this); return size; } //=================================================================== // CheckPassability //=================================================================== bool CFree2DNavRegion::CheckPassability(const Vec3& from, const Vec3& to, float radius, const NavigationBlockers& navigationBlockers, IAISystem::tNavCapMask) const { const SpecialArea* sa = gAIEnv.pNavigation->GetSpecialArea(from, SpecialArea::TYPE_FREE_2D); if (!sa) { return false; } return !Overlap::Lineseg_Polygon2D(Lineseg(from, to), sa->GetPolygon(), &sa->GetAABB()); } //=================================================================== // ExpandShape //=================================================================== static void ShrinkShape(const ListPositions& shapeIn, ListPositions& shapeOut, float radius) { // push the points in. The shape is guaranteed to be wound anti-clockwise shapeOut.clear(); for (ListPositions::const_iterator it = shapeIn.begin(); it != shapeIn.end(); ++it) { ListPositions::const_iterator itNext = it; if (++itNext == shapeIn.end()) { itNext = shapeIn.begin(); } ListPositions::const_iterator itNextNext = itNext; if (++itNextNext == shapeIn.end()) { itNextNext = shapeIn.begin(); } Vec3 pos = *it; Vec3 posNext = *itNext; Vec3 posNextNext = *itNextNext; pos.z = posNext.z = posNextNext.z = 0.0f; Vec3 segDirPrev = (posNext - pos).GetNormalizedSafe(); Vec3 segDirNext = (posNextNext - posNext).GetNormalizedSafe(); Vec3 normalInPrev(-segDirPrev.y, segDirPrev.x, 0.0f); Vec3 normalInNext(-segDirNext.y, segDirNext.x, 0.0f); Vec3 normalAv = (normalInPrev + normalInNext).GetNormalizedSafe(); Vec3 cross = segDirPrev.Cross(segDirNext); // convex means the point is convex from the point of view of inside the shape bool convex = cross.z < 0.0f; static float radiusScale = 0.5f; if (convex) { Vec3 newPtPrev = posNext + normalInPrev * (radius * radiusScale); newPtPrev.z = it->z; Vec3 newPtMid = posNext + normalAv * (radius * radiusScale); newPtMid.z = itNext->z; Vec3 newPtNext = posNext + normalInNext * (radius * radiusScale); newPtNext.z = itNextNext->z; shapeOut.push_back(newPtPrev); shapeOut.push_back(newPtMid); shapeOut.push_back(newPtNext); } else { float dot = segDirPrev.Dot(segDirNext); float extraRadiusScale = sqrtf(2.0f / (1.0f + dot)); Vec3 newPtMid = posNext + normalAv * (radius * radiusScale * extraRadiusScale); newPtMid.z = itNext->z; shapeOut.push_back(newPtMid); } } } //=================================================================== // GetSingleNodePath // Danny todo This "works" but doesn't generate ideal paths because it hugs // a single wall - it can't cross over to hug the other wall. Should // be OK for most sensible areas though. Also there is a danger that // the path can get close to the area edge, but I don't know what can // be done to stop that. Also, the shape shrinking could/should // be precalculated. //=================================================================== bool CFree2DNavRegion::GetSingleNodePath(const GraphNode* pNode, const Vec3& startPos, const Vec3& endPos, float radius, const NavigationBlockers& navigationBlockers, std::vector<PathPointDescriptor>& points, IAISystem::tNavCapMask) const { const SpecialArea* sa = gAIEnv.pNavigation->GetSpecialAreaNearestPos(startPos, SpecialArea::TYPE_FREE_2D); if (!sa) { return false; } const ListPositions& origShape = sa->GetPolygon(); ListPositions shape; ShrinkShape(origShape, shape, radius); // simplest straight-line case if (!Overlap::Lineseg_Polygon2D(Lineseg(startPos, endPos), shape)) { points.resize(0); points.push_back(PathPointDescriptor(IAISystem::NAV_FREE_2D, startPos)); points.push_back(PathPointDescriptor(IAISystem::NAV_FREE_2D, endPos)); return true; } if (!Overlap::Point_Polygon2D(endPos, origShape, &sa->GetAABB())) { return false; } // So, now start and end are in the same area. make a "safe" path // by going from startPos to the nearest vertex, then through all // vertices to the vertex nearest to endPos, then to endPos. Subsequently // we'll tidy this up by cutting vertices. ListPositions::const_iterator itStart, itEnd; float bestDistStartSq = std::numeric_limits<float>::max(); float bestDistEndSq = std::numeric_limits<float>::max(); int countToStart = -1; int countToEnd = -1; int count = 0; for (ListPositions::const_iterator it = shape.begin(); it != shape.end(); ++it, ++count) { Vec3 itPos = *it; static float frac = 0.99f; bool reachableStart = !Overlap::Lineseg_Polygon2D(Lineseg(startPos, startPos + frac * (itPos - startPos)), origShape, &sa->GetAABB()); bool reachableEnd = !Overlap::Lineseg_Polygon2D(Lineseg(endPos, endPos + frac * (itPos - endPos)), origShape, &sa->GetAABB()); if (reachableStart) { float distToStartSq = Distance::Point_Point2DSq(startPos, itPos); if (distToStartSq < bestDistStartSq) { bestDistStartSq = distToStartSq; itStart = it; countToStart = count; } } if (reachableEnd) { float distToEndSq = Distance::Point_Point2DSq(endPos, itPos); if (distToEndSq < bestDistEndSq) { bestDistEndSq = distToEndSq; itEnd = it; countToEnd = count; } } } if (countToStart < 0 || countToEnd < 0) { AIWarning("CFree2DNavRegion::GetSingleNodePath failed from (%5.2f, %5.2f, %5.2f) to (%5.2f, %5.2f, %5.2f)", startPos.x, startPos.y, startPos.z, endPos.x, endPos.y, endPos.z); return false; } // add the points points.resize(0); points.push_back(PathPointDescriptor(IAISystem::NAV_FREE_2D, startPos)); bool walkingFwd = true; if (itStart == itEnd) { points.push_back(PathPointDescriptor(IAISystem::NAV_FREE_2D, *itStart)); } else { // decide on the direction using the counts... int shapeCount = shape.size(); int countFwd = (shapeCount + countToEnd - countToStart) % shapeCount; int countBwd = shapeCount - countFwd; walkingFwd = countFwd < countBwd; ListPositions::const_iterator it = itStart; do { points.push_back(PathPointDescriptor(IAISystem::NAV_FREE_2D, *it)); if (walkingFwd) { ++it; if (it == shape.end()) { it = shape.begin(); } } else { if (it == shape.begin()) { it = shape.end(); } --it; } } while (it != itEnd); points.push_back(PathPointDescriptor(IAISystem::NAV_FREE_2D, *itEnd)); } points.push_back(PathPointDescriptor(IAISystem::NAV_FREE_2D, endPos)); // now walk through iteratively cutting points static bool doCutting = true; bool cutOne = doCutting; while (cutOne == true && points.size() > 2) { cutOne = false; for (std::vector<PathPointDescriptor>::iterator it = points.begin(); it != points.end(); ++it) { std::vector<PathPointDescriptor>::iterator itNext = it; if (++itNext == points.end()) { break; } std::vector<PathPointDescriptor>::iterator itNextNext = itNext; if (++itNextNext == points.end()) { break; } Vec3 pos = it->vPos; // Vec3 posNext = itNext->vPos; Vec3 posNextNext = itNextNext->vPos; static float frac = 0.001f; pos += frac * (posNextNext - pos); posNextNext += frac * (pos - posNextNext); Lineseg seg(pos, posNextNext); Vec3 posMid = 0.5f * (pos + posNextNext); std::vector<PathPointDescriptor>::iterator itNextNextNext = itNextNext; ++itNextNextNext; bool doingEnd = it == points.begin() || itNextNextNext == points.end(); const ListPositions& thisShape = doingEnd ? origShape : shape; if (Overlap::Point_Polygon2D(pos, thisShape) && !Overlap::Lineseg_Polygon2D(seg, thisShape)) { points.erase(itNext); cutOne = true; // iterator it is safe because itNext comes after it it = points.begin(); } } } return true; }