Path: blob/master/thirdparty/rvo2/rvo2_2d/Agent2d.cpp
9903 views
/*1* Agent2d.cpp2* RVO2 Library3*4* Copyright 2008 University of North Carolina at Chapel Hill5*6* Licensed under the Apache License, Version 2.0 (the "License");7* you may not use this file except in compliance with the License.8* You may obtain a copy of the License at9*10* http://www.apache.org/licenses/LICENSE-2.011*12* Unless required by applicable law or agreed to in writing, software13* distributed under the License is distributed on an "AS IS" BASIS,14* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.15* See the License for the specific language governing permissions and16* limitations under the License.17*18* Please send all bug reports to <[email protected]>.19*20* The authors may be contacted via:21*22* Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha23* Dept. of Computer Science24* 201 S. Columbia St.25* Frederick P. Brooks, Jr. Computer Science Bldg.26* Chapel Hill, N.C. 27599-317527* United States of America28*29* <http://gamma.cs.unc.edu/RVO2/>30*/3132#include "Agent2d.h"3334#include "KdTree2d.h"35#include "Obstacle2d.h"3637namespace RVO2D {38Agent2D::Agent2D() : maxNeighbors_(0), maxSpeed_(0.0f), neighborDist_(0.0f), radius_(0.0f), timeHorizon_(0.0f), timeHorizonObst_(0.0f), id_(0) { }3940void Agent2D::computeNeighbors(RVOSimulator2D *sim_)41{42obstacleNeighbors_.clear();43float rangeSq = sqr(timeHorizonObst_ * maxSpeed_ + radius_);44sim_->kdTree_->computeObstacleNeighbors(this, rangeSq);4546agentNeighbors_.clear();4748if (maxNeighbors_ > 0) {49rangeSq = sqr(neighborDist_);50sim_->kdTree_->computeAgentNeighbors(this, rangeSq);51}52}5354/* Search for the best new velocity. */55void Agent2D::computeNewVelocity(RVOSimulator2D *sim_)56{57orcaLines_.clear();5859const float invTimeHorizonObst = 1.0f / timeHorizonObst_;6061/* Create obstacle ORCA lines. */62for (size_t i = 0; i < obstacleNeighbors_.size(); ++i) {6364const Obstacle2D *obstacle1 = obstacleNeighbors_[i].second;65const Obstacle2D *obstacle2 = obstacle1->nextObstacle_;6667const Vector2 relativePosition1 = obstacle1->point_ - position_;68const Vector2 relativePosition2 = obstacle2->point_ - position_;6970/*71* Check if velocity obstacle of obstacle is already taken care of by72* previously constructed obstacle ORCA lines.73*/74bool alreadyCovered = false;7576for (size_t j = 0; j < orcaLines_.size(); ++j) {77if (det(invTimeHorizonObst * relativePosition1 - orcaLines_[j].point, orcaLines_[j].direction) - invTimeHorizonObst * radius_ >= -RVO_EPSILON && det(invTimeHorizonObst * relativePosition2 - orcaLines_[j].point, orcaLines_[j].direction) - invTimeHorizonObst * radius_ >= -RVO_EPSILON) {78alreadyCovered = true;79break;80}81}8283if (alreadyCovered) {84continue;85}8687/* Not yet covered. Check for collisions. */8889const float distSq1 = absSq(relativePosition1);90const float distSq2 = absSq(relativePosition2);9192const float radiusSq = sqr(radius_);9394const Vector2 obstacleVector = obstacle2->point_ - obstacle1->point_;95const float s = (-relativePosition1 * obstacleVector) / absSq(obstacleVector);96const float distSqLine = absSq(-relativePosition1 - s * obstacleVector);9798Line line;99100if (s < 0.0f && distSq1 <= radiusSq) {101/* Collision with left vertex. Ignore if non-convex. */102if (obstacle1->isConvex_) {103line.point = Vector2(0.0f, 0.0f);104line.direction = normalize(Vector2(-relativePosition1.y(), relativePosition1.x()));105orcaLines_.push_back(line);106}107108continue;109}110else if (s > 1.0f && distSq2 <= radiusSq) {111/* Collision with right vertex. Ignore if non-convex112* or if it will be taken care of by neighoring obstace */113if (obstacle2->isConvex_ && det(relativePosition2, obstacle2->unitDir_) >= 0.0f) {114line.point = Vector2(0.0f, 0.0f);115line.direction = normalize(Vector2(-relativePosition2.y(), relativePosition2.x()));116orcaLines_.push_back(line);117}118119continue;120}121else if (s >= 0.0f && s < 1.0f && distSqLine <= radiusSq) {122/* Collision with obstacle segment. */123line.point = Vector2(0.0f, 0.0f);124line.direction = -obstacle1->unitDir_;125orcaLines_.push_back(line);126continue;127}128129/*130* No collision.131* Compute legs. When obliquely viewed, both legs can come from a single132* vertex. Legs extend cut-off line when nonconvex vertex.133*/134135Vector2 leftLegDirection, rightLegDirection;136137if (s < 0.0f && distSqLine <= radiusSq) {138/*139* Obstacle viewed obliquely so that left vertex140* defines velocity obstacle.141*/142if (!obstacle1->isConvex_) {143/* Ignore obstacle. */144continue;145}146147obstacle2 = obstacle1;148149const float leg1 = std::sqrt(distSq1 - radiusSq);150leftLegDirection = Vector2(relativePosition1.x() * leg1 - relativePosition1.y() * radius_, relativePosition1.x() * radius_ + relativePosition1.y() * leg1) / distSq1;151rightLegDirection = Vector2(relativePosition1.x() * leg1 + relativePosition1.y() * radius_, -relativePosition1.x() * radius_ + relativePosition1.y() * leg1) / distSq1;152}153else if (s > 1.0f && distSqLine <= radiusSq) {154/*155* Obstacle viewed obliquely so that156* right vertex defines velocity obstacle.157*/158if (!obstacle2->isConvex_) {159/* Ignore obstacle. */160continue;161}162163obstacle1 = obstacle2;164165const float leg2 = std::sqrt(distSq2 - radiusSq);166leftLegDirection = Vector2(relativePosition2.x() * leg2 - relativePosition2.y() * radius_, relativePosition2.x() * radius_ + relativePosition2.y() * leg2) / distSq2;167rightLegDirection = Vector2(relativePosition2.x() * leg2 + relativePosition2.y() * radius_, -relativePosition2.x() * radius_ + relativePosition2.y() * leg2) / distSq2;168}169else {170/* Usual situation. */171if (obstacle1->isConvex_) {172const float leg1 = std::sqrt(distSq1 - radiusSq);173leftLegDirection = Vector2(relativePosition1.x() * leg1 - relativePosition1.y() * radius_, relativePosition1.x() * radius_ + relativePosition1.y() * leg1) / distSq1;174}175else {176/* Left vertex non-convex; left leg extends cut-off line. */177leftLegDirection = -obstacle1->unitDir_;178}179180if (obstacle2->isConvex_) {181const float leg2 = std::sqrt(distSq2 - radiusSq);182rightLegDirection = Vector2(relativePosition2.x() * leg2 + relativePosition2.y() * radius_, -relativePosition2.x() * radius_ + relativePosition2.y() * leg2) / distSq2;183}184else {185/* Right vertex non-convex; right leg extends cut-off line. */186rightLegDirection = obstacle1->unitDir_;187}188}189190/*191* Legs can never point into neighboring edge when convex vertex,192* take cutoff-line of neighboring edge instead. If velocity projected on193* "foreign" leg, no constraint is added.194*/195196const Obstacle2D *const leftNeighbor = obstacle1->prevObstacle_;197198bool isLeftLegForeign = false;199bool isRightLegForeign = false;200201if (obstacle1->isConvex_ && det(leftLegDirection, -leftNeighbor->unitDir_) >= 0.0f) {202/* Left leg points into obstacle. */203leftLegDirection = -leftNeighbor->unitDir_;204isLeftLegForeign = true;205}206207if (obstacle2->isConvex_ && det(rightLegDirection, obstacle2->unitDir_) <= 0.0f) {208/* Right leg points into obstacle. */209rightLegDirection = obstacle2->unitDir_;210isRightLegForeign = true;211}212213/* Compute cut-off centers. */214const Vector2 leftCutoff = invTimeHorizonObst * (obstacle1->point_ - position_);215const Vector2 rightCutoff = invTimeHorizonObst * (obstacle2->point_ - position_);216const Vector2 cutoffVec = rightCutoff - leftCutoff;217218/* Project current velocity on velocity obstacle. */219220/* Check if current velocity is projected on cutoff circles. */221const float t = (obstacle1 == obstacle2 ? 0.5f : ((velocity_ - leftCutoff) * cutoffVec) / absSq(cutoffVec));222const float tLeft = ((velocity_ - leftCutoff) * leftLegDirection);223const float tRight = ((velocity_ - rightCutoff) * rightLegDirection);224225if ((t < 0.0f && tLeft < 0.0f) || (obstacle1 == obstacle2 && tLeft < 0.0f && tRight < 0.0f)) {226/* Project on left cut-off circle. */227const Vector2 unitW = normalize(velocity_ - leftCutoff);228229line.direction = Vector2(unitW.y(), -unitW.x());230line.point = leftCutoff + radius_ * invTimeHorizonObst * unitW;231orcaLines_.push_back(line);232continue;233}234else if (t > 1.0f && tRight < 0.0f) {235/* Project on right cut-off circle. */236const Vector2 unitW = normalize(velocity_ - rightCutoff);237238line.direction = Vector2(unitW.y(), -unitW.x());239line.point = rightCutoff + radius_ * invTimeHorizonObst * unitW;240orcaLines_.push_back(line);241continue;242}243244/*245* Project on left leg, right leg, or cut-off line, whichever is closest246* to velocity.247*/248const float distSqCutoff = ((t < 0.0f || t > 1.0f || obstacle1 == obstacle2) ? std::numeric_limits<float>::infinity() : absSq(velocity_ - (leftCutoff + t * cutoffVec)));249const float distSqLeft = ((tLeft < 0.0f) ? std::numeric_limits<float>::infinity() : absSq(velocity_ - (leftCutoff + tLeft * leftLegDirection)));250const float distSqRight = ((tRight < 0.0f) ? std::numeric_limits<float>::infinity() : absSq(velocity_ - (rightCutoff + tRight * rightLegDirection)));251252if (distSqCutoff <= distSqLeft && distSqCutoff <= distSqRight) {253/* Project on cut-off line. */254line.direction = -obstacle1->unitDir_;255line.point = leftCutoff + radius_ * invTimeHorizonObst * Vector2(-line.direction.y(), line.direction.x());256orcaLines_.push_back(line);257continue;258}259else if (distSqLeft <= distSqRight) {260/* Project on left leg. */261if (isLeftLegForeign) {262continue;263}264265line.direction = leftLegDirection;266line.point = leftCutoff + radius_ * invTimeHorizonObst * Vector2(-line.direction.y(), line.direction.x());267orcaLines_.push_back(line);268continue;269}270else {271/* Project on right leg. */272if (isRightLegForeign) {273continue;274}275276line.direction = -rightLegDirection;277line.point = rightCutoff + radius_ * invTimeHorizonObst * Vector2(-line.direction.y(), line.direction.x());278orcaLines_.push_back(line);279continue;280}281}282283const size_t numObstLines = orcaLines_.size();284285const float invTimeHorizon = 1.0f / timeHorizon_;286287/* Create agent ORCA lines. */288for (size_t i = 0; i < agentNeighbors_.size(); ++i) {289const Agent2D *const other = agentNeighbors_[i].second;290291//const float timeHorizon_mod = (avoidance_priority_ - other->avoidance_priority_ + 1.0f) * 0.5f;292//const float invTimeHorizon = (1.0f / timeHorizon_) * timeHorizon_mod;293294const Vector2 relativePosition = other->position_ - position_;295const Vector2 relativeVelocity = velocity_ - other->velocity_;296const float distSq = absSq(relativePosition);297const float combinedRadius = radius_ + other->radius_;298const float combinedRadiusSq = sqr(combinedRadius);299300Line line;301Vector2 u;302303if (distSq > combinedRadiusSq) {304/* No collision. */305const Vector2 w = relativeVelocity - invTimeHorizon * relativePosition;306/* Vector from cutoff center to relative velocity. */307const float wLengthSq = absSq(w);308309const float dotProduct1 = w * relativePosition;310311if (dotProduct1 < 0.0f && sqr(dotProduct1) > combinedRadiusSq * wLengthSq) {312/* Project on cut-off circle. */313const float wLength = std::sqrt(wLengthSq);314const Vector2 unitW = w / wLength;315316line.direction = Vector2(unitW.y(), -unitW.x());317u = (combinedRadius * invTimeHorizon - wLength) * unitW;318}319else {320/* Project on legs. */321const float leg = std::sqrt(distSq - combinedRadiusSq);322323if (det(relativePosition, w) > 0.0f) {324/* Project on left leg. */325line.direction = Vector2(relativePosition.x() * leg - relativePosition.y() * combinedRadius, relativePosition.x() * combinedRadius + relativePosition.y() * leg) / distSq;326}327else {328/* Project on right leg. */329line.direction = -Vector2(relativePosition.x() * leg + relativePosition.y() * combinedRadius, -relativePosition.x() * combinedRadius + relativePosition.y() * leg) / distSq;330}331332const float dotProduct2 = relativeVelocity * line.direction;333334u = dotProduct2 * line.direction - relativeVelocity;335}336}337else {338/* Collision. Project on cut-off circle of time timeStep. */339const float invTimeStep = 1.0f / sim_->timeStep_;340341/* Vector from cutoff center to relative velocity. */342const Vector2 w = relativeVelocity - invTimeStep * relativePosition;343344const float wLength = abs(w);345const Vector2 unitW = w / wLength;346347line.direction = Vector2(unitW.y(), -unitW.x());348u = (combinedRadius * invTimeStep - wLength) * unitW;349}350351line.point = velocity_ + 0.5f * u;352orcaLines_.push_back(line);353}354355size_t lineFail = linearProgram2(orcaLines_, maxSpeed_, prefVelocity_, false, newVelocity_);356357if (lineFail < orcaLines_.size()) {358linearProgram3(orcaLines_, numObstLines, lineFail, maxSpeed_, newVelocity_);359}360}361362void Agent2D::insertAgentNeighbor(const Agent2D *agent, float &rangeSq)363{364// no point processing same agent365if (this == agent) {366return;367}368// ignore other agent if layers/mask bitmasks have no matching bit369if ((avoidance_mask_ & agent->avoidance_layers_) == 0) {370return;371}372// ignore other agent if this agent is below or above373if ((elevation_ > agent->elevation_ + agent->height_) || (elevation_ + height_ < agent->elevation_)) {374return;375}376377if (avoidance_priority_ > agent->avoidance_priority_) {378return;379}380381const float distSq = absSq(position_ - agent->position_);382383if (distSq < rangeSq) {384if (agentNeighbors_.size() < maxNeighbors_) {385agentNeighbors_.push_back(std::make_pair(distSq, agent));386}387388size_t i = agentNeighbors_.size() - 1;389390while (i != 0 && distSq < agentNeighbors_[i - 1].first) {391agentNeighbors_[i] = agentNeighbors_[i - 1];392--i;393}394395agentNeighbors_[i] = std::make_pair(distSq, agent);396397if (agentNeighbors_.size() == maxNeighbors_) {398rangeSq = agentNeighbors_.back().first;399}400}401}402403void Agent2D::insertObstacleNeighbor(const Obstacle2D *obstacle, float rangeSq)404{405const Obstacle2D *const nextObstacle = obstacle->nextObstacle_;406407// ignore obstacle if no matching layer/mask408if ((avoidance_mask_ & nextObstacle->avoidance_layers_) == 0) {409return;410}411// ignore obstacle if below or above412if ((elevation_ > obstacle->elevation_ + obstacle->height_) || (elevation_ + height_ < obstacle->elevation_)) {413return;414}415416const float distSq = distSqPointLineSegment(obstacle->point_, nextObstacle->point_, position_);417418if (distSq < rangeSq) {419obstacleNeighbors_.push_back(std::make_pair(distSq, obstacle));420421size_t i = obstacleNeighbors_.size() - 1;422423while (i != 0 && distSq < obstacleNeighbors_[i - 1].first) {424obstacleNeighbors_[i] = obstacleNeighbors_[i - 1];425--i;426}427428obstacleNeighbors_[i] = std::make_pair(distSq, obstacle);429}430//}431}432433void Agent2D::update(RVOSimulator2D *sim_)434{435velocity_ = newVelocity_;436position_ += velocity_ * sim_->timeStep_;437}438439bool linearProgram1(const std::vector<Line> &lines, size_t lineNo, float radius, const Vector2 &optVelocity, bool directionOpt, Vector2 &result)440{441const float dotProduct = lines[lineNo].point * lines[lineNo].direction;442const float discriminant = sqr(dotProduct) + sqr(radius) - absSq(lines[lineNo].point);443444if (discriminant < 0.0f) {445/* Max speed circle fully invalidates line lineNo. */446return false;447}448449const float sqrtDiscriminant = std::sqrt(discriminant);450float tLeft = -dotProduct - sqrtDiscriminant;451float tRight = -dotProduct + sqrtDiscriminant;452453for (size_t i = 0; i < lineNo; ++i) {454const float denominator = det(lines[lineNo].direction, lines[i].direction);455const float numerator = det(lines[i].direction, lines[lineNo].point - lines[i].point);456457if (std::fabs(denominator) <= RVO_EPSILON) {458/* Lines lineNo and i are (almost) parallel. */459if (numerator < 0.0f) {460return false;461}462else {463continue;464}465}466467const float t = numerator / denominator;468469if (denominator >= 0.0f) {470/* Line i bounds line lineNo on the right. */471tRight = std::min(tRight, t);472}473else {474/* Line i bounds line lineNo on the left. */475tLeft = std::max(tLeft, t);476}477478if (tLeft > tRight) {479return false;480}481}482483if (directionOpt) {484/* Optimize direction. */485if (optVelocity * lines[lineNo].direction > 0.0f) {486/* Take right extreme. */487result = lines[lineNo].point + tRight * lines[lineNo].direction;488}489else {490/* Take left extreme. */491result = lines[lineNo].point + tLeft * lines[lineNo].direction;492}493}494else {495/* Optimize closest point. */496const float t = lines[lineNo].direction * (optVelocity - lines[lineNo].point);497498if (t < tLeft) {499result = lines[lineNo].point + tLeft * lines[lineNo].direction;500}501else if (t > tRight) {502result = lines[lineNo].point + tRight * lines[lineNo].direction;503}504else {505result = lines[lineNo].point + t * lines[lineNo].direction;506}507}508509return true;510}511512size_t linearProgram2(const std::vector<Line> &lines, float radius, const Vector2 &optVelocity, bool directionOpt, Vector2 &result)513{514if (directionOpt) {515/*516* Optimize direction. Note that the optimization velocity is of unit517* length in this case.518*/519result = optVelocity * radius;520}521else if (absSq(optVelocity) > sqr(radius)) {522/* Optimize closest point and outside circle. */523result = normalize(optVelocity) * radius;524}525else {526/* Optimize closest point and inside circle. */527result = optVelocity;528}529530for (size_t i = 0; i < lines.size(); ++i) {531if (det(lines[i].direction, lines[i].point - result) > 0.0f) {532/* Result does not satisfy constraint i. Compute new optimal result. */533const Vector2 tempResult = result;534535if (!linearProgram1(lines, i, radius, optVelocity, directionOpt, result)) {536result = tempResult;537return i;538}539}540}541542return lines.size();543}544545void linearProgram3(const std::vector<Line> &lines, size_t numObstLines, size_t beginLine, float radius, Vector2 &result)546{547float distance = 0.0f;548549for (size_t i = beginLine; i < lines.size(); ++i) {550if (det(lines[i].direction, lines[i].point - result) > distance) {551/* Result does not satisfy constraint of line i. */552std::vector<Line> projLines(lines.begin(), lines.begin() + static_cast<ptrdiff_t>(numObstLines));553554for (size_t j = numObstLines; j < i; ++j) {555Line line;556557float determinant = det(lines[i].direction, lines[j].direction);558559if (std::fabs(determinant) <= RVO_EPSILON) {560/* Line i and line j are parallel. */561if (lines[i].direction * lines[j].direction > 0.0f) {562/* Line i and line j point in the same direction. */563continue;564}565else {566/* Line i and line j point in opposite direction. */567line.point = 0.5f * (lines[i].point + lines[j].point);568}569}570else {571line.point = lines[i].point + (det(lines[j].direction, lines[i].point - lines[j].point) / determinant) * lines[i].direction;572}573574line.direction = normalize(lines[j].direction - lines[i].direction);575projLines.push_back(line);576}577578const Vector2 tempResult = result;579580if (linearProgram2(projLines, radius, Vector2(-lines[i].direction.y(), lines[i].direction.x()), true, result) < projLines.size()) {581/* This should in principle not happen. The result is by definition582* already in the feasible region of this linear program. If it fails,583* it is due to small floating point error, and the current result is584* kept.585*/586result = tempResult;587}588589distance = det(lines[i].direction, lines[i].point - result);590}591}592}593}594595596