diff options
Diffstat (limited to 'thirdparty/rvo2/rvo2_3d/Agent3d.cc')
-rw-r--r-- | thirdparty/rvo2/rvo2_3d/Agent3d.cc | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/thirdparty/rvo2/rvo2_3d/Agent3d.cc b/thirdparty/rvo2/rvo2_3d/Agent3d.cc new file mode 100644 index 0000000000..860ebdc58c --- /dev/null +++ b/thirdparty/rvo2/rvo2_3d/Agent3d.cc @@ -0,0 +1,474 @@ +/* + * 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 <geom@cs.unc.edu>. + * + * 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 + * + * <https://gamma.cs.unc.edu/RVO2/> + */ + +#include "Agent3d.h" + +#include <algorithm> +#include <cmath> + +#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<Plane> &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<Plane> &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<Plane> &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<Plane> &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<Plane> 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 */ |