/* * Agent3d.cc * RVO2-3D Library * * SPDX-FileCopyrightText: 2008 University of North Carolina at Chapel Hill * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Please send all bug reports to . * * The authors may be contacted via: * * Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha * Dept. of Computer Science * 201 S. Columbia St. * Frederick P. Brooks, Jr. Computer Science Bldg. * Chapel Hill, N.C. 27599-3175 * United States of America * * */ #include "Agent3d.h" #include #include #include "KdTree3d.h" #include "RVOSimulator3d.h" namespace RVO3D { namespace { /** * @brief A sufficiently small positive number. */ const float RVO3D_EPSILON = 0.00001F; /** * @brief Defines a directed line. */ class Line3D { public: /** * @brief Constructs a directed line.`` */ Line3D(); /** * @brief The direction of the directed line. */ Vector3 direction; /** * @brief A point on the directed line. */ Vector3 point; }; Line3D::Line3D() {} /** * @brief Solves a one-dimensional linear program on a specified line * subject to linear constraints defined by planes and a spherical * constraint. * @param[in] planes Planes defining the linear constraints. * @param[in] planeNo The plane on which the line lies. * @param[in] line The line on which the one-dimensional linear program * is solved. * @param[in] radius The radius of the spherical constraint. * @param[in] optVelocity The optimization velocity. * @param[in] directionOpt True if the direction should be optimized. * @param[in] result A reference to the result of the linear program. * @return True if successful. */ bool linearProgram1(const std::vector &planes, std::size_t planeNo, const Line3D &line, float radius, const Vector3 &optVelocity, bool directionOpt, Vector3 &result) { /* NOLINT(runtime/references) */ const float dotProduct = line.point * line.direction; const float discriminant = dotProduct * dotProduct + radius * radius - absSq(line.point); if (discriminant < 0.0F) { /* Max speed sphere fully invalidates line. */ return false; } const float sqrtDiscriminant = std::sqrt(discriminant); float tLeft = -dotProduct - sqrtDiscriminant; float tRight = -dotProduct + sqrtDiscriminant; for (std::size_t i = 0U; i < planeNo; ++i) { const float numerator = (planes[i].point - line.point) * planes[i].normal; const float denominator = line.direction * planes[i].normal; if (denominator * denominator <= RVO3D_EPSILON) { /* Lines line is (almost) parallel to plane i. */ if (numerator > 0.0F) { return false; } continue; } const float t = numerator / denominator; if (denominator >= 0.0F) { /* Plane i bounds line on the left. */ tLeft = std::max(tLeft, t); } else { /* Plane i bounds line on the right. */ tRight = std::min(tRight, t); } if (tLeft > tRight) { return false; } } if (directionOpt) { /* Optimize direction. */ if (optVelocity * line.direction > 0.0F) { /* Take right extreme. */ result = line.point + tRight * line.direction; } else { /* Take left extreme. */ result = line.point + tLeft * line.direction; } } else { /* Optimize closest point. */ const float t = line.direction * (optVelocity - line.point); if (t < tLeft) { result = line.point + tLeft * line.direction; } else if (t > tRight) { result = line.point + tRight * line.direction; } else { result = line.point + t * line.direction; } } return true; } /** * @brief Solves a two-dimensional linear program on a specified plane * subject to linear constraints defined by planes and a spherical * constraint. * @param[in] planes Planes defining the linear constraints. * @param[in] planeNo The plane on which the two-dimensional linear * program is solved. * @param[in] radius The radius of the spherical constraint. * @param[in] optVelocity The optimization velocity. * @param[in] directionOpt True if the direction should be optimized. * @param[out] result A reference to the result of the linear program. * @return True if successful. */ bool linearProgram2(const std::vector &planes, std::size_t planeNo, float radius, const Vector3 &optVelocity, bool directionOpt, Vector3 &result) { /* NOLINT(runtime/references) */ const float planeDist = planes[planeNo].point * planes[planeNo].normal; const float planeDistSq = planeDist * planeDist; const float radiusSq = radius * radius; if (planeDistSq > radiusSq) { /* Max speed sphere fully invalidates plane planeNo. */ return false; } const float planeRadiusSq = radiusSq - planeDistSq; const Vector3 planeCenter = planeDist * planes[planeNo].normal; if (directionOpt) { /* Project direction optVelocity on plane planeNo. */ const Vector3 planeOptVelocity = optVelocity - (optVelocity * planes[planeNo].normal) * planes[planeNo].normal; const float planeOptVelocityLengthSq = absSq(planeOptVelocity); if (planeOptVelocityLengthSq <= RVO3D_EPSILON) { result = planeCenter; } else { result = planeCenter + std::sqrt(planeRadiusSq / planeOptVelocityLengthSq) * planeOptVelocity; } } else { /* Project point optVelocity on plane planeNo. */ result = optVelocity + ((planes[planeNo].point - optVelocity) * planes[planeNo].normal) * planes[planeNo].normal; /* If outside planeCircle, project on planeCircle. */ if (absSq(result) > radiusSq) { const Vector3 planeResult = result - planeCenter; const float planeResultLengthSq = absSq(planeResult); result = planeCenter + std::sqrt(planeRadiusSq / planeResultLengthSq) * planeResult; } } for (std::size_t i = 0U; i < planeNo; ++i) { if (planes[i].normal * (planes[i].point - result) > 0.0F) { /* Result does not satisfy constraint i. Compute new optimal result. * Compute intersection line of plane i and plane planeNo. */ Vector3 crossProduct = cross(planes[i].normal, planes[planeNo].normal); if (absSq(crossProduct) <= RVO3D_EPSILON) { /* Planes planeNo and i are (almost) parallel, and plane i fully * invalidates plane planeNo. */ return false; } Line3D line; line.direction = normalize(crossProduct); const Vector3 lineNormal = cross(line.direction, planes[planeNo].normal); line.point = planes[planeNo].point + (((planes[i].point - planes[planeNo].point) * planes[i].normal) / (lineNormal * planes[i].normal)) * lineNormal; if (!linearProgram1(planes, i, line, radius, optVelocity, directionOpt, result)) { return false; } } } return true; } /** * @brief Solves a three-dimensional linear program subject to linear * constraints defined by planes and a spherical constraint. * @param[in] planes Planes defining the linear constraints. * @param[in] radius The radius of the spherical constraint. * @param[in] optVelocity The optimization velocity. * @param[in] directionOpt True if the direction should be optimized. * @param[out] result A reference to the result of the linear program. * @return The number of the plane it fails on, and the number of planes if * successful. */ std::size_t linearProgram3(const std::vector &planes, float radius, const Vector3 &optVelocity, bool directionOpt, Vector3 &result) { /* NOLINT(runtime/references) */ if (directionOpt) { /* Optimize direction. Note that the optimization velocity is of unit length * in this case. */ result = optVelocity * radius; } else if (absSq(optVelocity) > radius * radius) { /* Optimize closest point and outside circle. */ result = normalize(optVelocity) * radius; } else { /* Optimize closest point and inside circle. */ result = optVelocity; } for (std::size_t i = 0U; i < planes.size(); ++i) { if (planes[i].normal * (planes[i].point - result) > 0.0F) { /* Result does not satisfy constraint i. Compute new optimal result. */ const Vector3 tempResult = result; if (!linearProgram2(planes, i, radius, optVelocity, directionOpt, result)) { result = tempResult; return i; } } } return planes.size(); } /** * @brief Solves a four-dimensional linear program subject to linear * constraints defined by planes and a spherical constraint. * @param[in] planes Planes defining the linear constraints. * @param[in] beginPlane The plane on which the three-dimensional linear * program failed. * @param[in] radius The radius of the spherical constraint. * @param[out] result A reference to the result of the linear program. */ void linearProgram4(const std::vector &planes, std::size_t beginPlane, float radius, Vector3 &result) { /* NOLINT(runtime/references) */ float distance = 0.0F; for (std::size_t i = beginPlane; i < planes.size(); ++i) { if (planes[i].normal * (planes[i].point - result) > distance) { /* Result does not satisfy constraint of plane i. */ std::vector projPlanes; for (std::size_t j = 0U; j < i; ++j) { Plane plane; const Vector3 crossProduct = cross(planes[j].normal, planes[i].normal); if (absSq(crossProduct) <= RVO3D_EPSILON) { /* Plane i and plane j are (almost) parallel. */ if (planes[i].normal * planes[j].normal > 0.0F) { /* Plane i and plane j point in the same direction. */ continue; } /* Plane i and plane j point in opposite direction. */ plane.point = 0.5F * (planes[i].point + planes[j].point); } else { /* Plane.point is point on line of intersection between plane i and * plane j. */ const Vector3 lineNormal = cross(crossProduct, planes[i].normal); plane.point = planes[i].point + (((planes[j].point - planes[i].point) * planes[j].normal) / (lineNormal * planes[j].normal)) * lineNormal; } plane.normal = normalize(planes[j].normal - planes[i].normal); projPlanes.push_back(plane); } const Vector3 tempResult = result; if (linearProgram3(projPlanes, radius, planes[i].normal, true, result) < projPlanes.size()) { /* This should in principle not happen. The result is by definition * already in the feasible region of this linear program. If it fails, * it is due to small floating point error, and the current result is * kept. */ result = tempResult; } distance = planes[i].normal * (planes[i].point - result); } } } } /* namespace */ Agent3D::Agent3D() : id_(0U), maxNeighbors_(0U), maxSpeed_(0.0F), neighborDist_(0.0F), radius_(0.0F), timeHorizon_(0.0F) {} Agent3D::~Agent3D() {} void Agent3D::computeNeighbors(RVOSimulator3D *sim_) { agentNeighbors_.clear(); if (maxNeighbors_ > 0) { sim_->kdTree_->computeAgentNeighbors(this, neighborDist_ * neighborDist_); } } void Agent3D::computeNewVelocity(RVOSimulator3D *sim_) { orcaPlanes_.clear(); const float invTimeHorizon = 1.0F / timeHorizon_; /* Create agent ORCA planes. */ for (std::size_t i = 0U; i < agentNeighbors_.size(); ++i) { const Agent3D *const other = agentNeighbors_[i].second; const Vector3 relativePosition = other->position_ - position_; const Vector3 relativeVelocity = velocity_ - other->velocity_; const float distSq = absSq(relativePosition); const float combinedRadius = radius_ + other->radius_; const float combinedRadiusSq = combinedRadius * combinedRadius; Plane plane; Vector3 u; if (distSq > combinedRadiusSq) { /* No collision. */ const Vector3 w = relativeVelocity - invTimeHorizon * relativePosition; /* Vector from cutoff center to relative velocity. */ const float wLengthSq = absSq(w); const float dotProduct = w * relativePosition; if (dotProduct < 0.0F && dotProduct * dotProduct > combinedRadiusSq * wLengthSq) { /* Project on cut-off circle. */ const float wLength = std::sqrt(wLengthSq); const Vector3 unitW = w / wLength; plane.normal = unitW; u = (combinedRadius * invTimeHorizon - wLength) * unitW; } else { /* Project on cone. */ const float a = distSq; const float b = relativePosition * relativeVelocity; const float c = absSq(relativeVelocity) - absSq(cross(relativePosition, relativeVelocity)) / (distSq - combinedRadiusSq); const float t = (b + std::sqrt(b * b - a * c)) / a; const Vector3 ww = relativeVelocity - t * relativePosition; const float wwLength = abs(ww); const Vector3 unitWW = ww / wwLength; plane.normal = unitWW; u = (combinedRadius * t - wwLength) * unitWW; } } else { /* Collision. */ const float invTimeStep = 1.0F / sim_->timeStep_; const Vector3 w = relativeVelocity - invTimeStep * relativePosition; const float wLength = abs(w); const Vector3 unitW = w / wLength; plane.normal = unitW; u = (combinedRadius * invTimeStep - wLength) * unitW; } plane.point = velocity_ + 0.5F * u; orcaPlanes_.push_back(plane); } const std::size_t planeFail = linearProgram3( orcaPlanes_, maxSpeed_, prefVelocity_, false, newVelocity_); if (planeFail < orcaPlanes_.size()) { linearProgram4(orcaPlanes_, planeFail, maxSpeed_, newVelocity_); } } void Agent3D::insertAgentNeighbor(const Agent3D *agent, float &rangeSq) { if (this != agent) { const float distSq = absSq(position_ - agent->position_); if (distSq < rangeSq) { if (agentNeighbors_.size() < maxNeighbors_) { agentNeighbors_.push_back(std::make_pair(distSq, agent)); } std::size_t i = agentNeighbors_.size() - 1U; while (i != 0U && distSq < agentNeighbors_[i - 1U].first) { agentNeighbors_[i] = agentNeighbors_[i - 1U]; --i; } agentNeighbors_[i] = std::make_pair(distSq, agent); if (agentNeighbors_.size() == maxNeighbors_) { rangeSq = agentNeighbors_.back().first; } } } } void Agent3D::update(RVOSimulator3D *sim_) { velocity_ = newVelocity_; position_ += velocity_ * sim_->timeStep_; } } /* namespace RVO3D */