12 return std::isfinite(
value.x) && std::isfinite(
value.y) && std::isfinite(
value.z);
15template <
typename Vector>
16double distance(Vector lhs, Vector rhs) {
17 const double dx =
static_cast<double>(lhs.x) -
static_cast<double>(rhs.x);
18 const double dy =
static_cast<double>(lhs.y) -
static_cast<double>(rhs.y);
19 if constexpr (
requires { lhs.z; }) {
20 const double dz =
static_cast<double>(lhs.z) -
static_cast<double>(rhs.z);
21 return std::hypot(
dx,
dy,
dz);
23 return std::hypot(
dx,
dy);
26template <
typename Vector>
27Vector scaledDirection(Vector
from, Vector
to,
float magnitude) {
30 const double dx =
static_cast<double>(
to.x) -
static_cast<double>(
from.x);
31 const double dy =
static_cast<double>(
to.y) -
static_cast<double>(
from.y);
33 if (!(
length > 0.0) || !std::isfinite(
length))
return {};
34 if constexpr (
requires {
from.z; }) {
35 const double dz =
static_cast<double>(
to.z) -
static_cast<double>(
from.z);
36 return {
static_cast<float>(
dx /
length * magnitude),
37 static_cast<float>(
dy /
length * magnitude),
38 static_cast<float>(
dz /
length * magnitude)};
40 return {
static_cast<float>(
dx /
length * magnitude),
41 static_cast<float>(
dy /
length * magnitude)};
44template <
typename Vector>
49template <
typename Vector>
50Vector arriveImpl(Vector
position, Vector
target,
float maxSpeed,
float slowRadius,
53 !std::isfinite(stopRadius) || slowRadius <= stopRadius || stopRadius < 0.f)
56 if (targetDistance <= stopRadius)
return {};
57 const double speed =
static_cast<double>(maxSpeed) *
58 std::min(1.0, (targetDistance - stopRadius) /
59 (
static_cast<double>(slowRadius) - stopRadius));
60 return scaledDirection(
position,
target,
static_cast<float>(speed));
63template <
typename Vector>
64Vector separationImpl(Vector
position, std::span<const Vector> neighbors,
float radius,
65 float maxAcceleration) {
67 radius <= 0.f || maxAcceleration <= 0.f)
72 for (
const Vector neighbor : neighbors) {
73 if (!
finite(neighbor))
continue;
75 if (neighborDistance > 0.0 && neighborDistance <
radius) {
79 if constexpr (
requires {
position.z; })
83 const double sumLength = std::hypot(sumX, sumY, sumZ);
84 const double scale = sumLength > maxAcceleration ? maxAcceleration / sumLength : 1.0;
85 if constexpr (
requires {
position.z; })
86 return {
static_cast<float>(sumX *
scale),
static_cast<float>(sumY *
scale),
87 static_cast<float>(sumZ *
scale)};
88 return {
static_cast<float>(sumX *
scale),
static_cast<float>(sumY *
scale)};
91template <
typename Vector>
97 const float acceptedTolerance =
98 std::isfinite(tolerance) ? std::max(0.f, tolerance) : 0.f;
105template <
typename Vector>
106Vector avoidImpl(Vector
position, Vector velocity, Vector obstacle,
float obstacleRadius,
107 float lookAhead,
float maxAcceleration) {
109 !std::isfinite(obstacleRadius) || !std::isfinite(lookAhead) ||
110 !std::isfinite(maxAcceleration) || obstacleRadius <= 0.f || lookAhead < 0.f ||
111 maxAcceleration <= 0.f)
113 const Vector
offset = scaledDirection(Vector{}, velocity, lookAhead);
114 const double predictedX =
static_cast<double>(
position.x) +
offset.x;
115 const double predictedY =
static_cast<double>(
position.y) +
offset.y;
116 const double predictedZ = [&] {
120 const double overlapX = predictedX - obstacle.x;
121 const double overlapY = predictedY - obstacle.y;
122 const double overlapZ = [&] {
123 if constexpr (
requires { obstacle.z; })
return predictedZ - obstacle.z;
126 const double overlapLength = std::hypot(overlapX, overlapY, overlapZ);
127 if (overlapLength > obstacleRadius)
return {};
128 if (overlapLength > 0.0) {
129 if constexpr (
requires {
position.z; })
130 return {
static_cast<float>(overlapX / overlapLength * maxAcceleration),
131 static_cast<float>(overlapY / overlapLength * maxAcceleration),
132 static_cast<float>(overlapZ / overlapLength * maxAcceleration)};
133 return {
static_cast<float>(overlapX / overlapLength * maxAcceleration),
134 static_cast<float>(overlapY / overlapLength * maxAcceleration)};
136 const Vector oppositeVelocity = [&] {
137 if constexpr (
requires { velocity.z; })
138 return Vector{-velocity.x, -velocity.y, -velocity.z};
139 return Vector{-velocity.x, -velocity.y};
141 const Vector fallback = scaledDirection(Vector{}, oppositeVelocity, maxAcceleration);
142 if (
distance(Vector{}, fallback) > 0.0)
return fallback;
143 if constexpr (
requires {
position.z; })
return Vector{maxAcceleration, 0.f, 0.f};
144 return Vector{maxAcceleration, 0.f};
163 return arriveImpl(
position,
target, maxSpeed, slowRadius, stopRadius);
167 return arriveImpl(
position,
target, maxSpeed, slowRadius, stopRadius);
170 float maxAcceleration) {
171 return separationImpl(
position, neighbors,
radius, maxAcceleration);
174 float maxAcceleration) {
175 return separationImpl(
position, neighbors,
radius, maxAcceleration);
186 float lookAhead,
float maxAcceleration) {
187 return avoidImpl(
position, velocity, obstacle, obstacleRadius, lookAhead, maxAcceleration);
190 float lookAhead,
float maxAcceleration) {
191 return avoidImpl(
position, velocity, obstacle, obstacleRadius, lookAhead, maxAcceleration);
Vector2 arrive(Vector2 position, Vector2 target, float maxSpeed, float slowRadius, float stopRadius)
Slows toward target inside slowRadius and stops inside stopRadius.
Vector2 avoid(Vector2 position, Vector2 velocity, Vector2 obstacle, float obstacleRadius, float lookAhead, float maxAcceleration)
Computes avoidance acceleration away from a predicted obstacle overlap.
int pathTarget(Vector2 position, std::span< const Vector2 > points, int current, float tolerance)
Selects the current path point, advancing across points within tolerance.