Obstacle avoidance refactoring

This commit is contained in:
Alexander Nozik 2023-04-23 13:44:07 +03:00
parent cac5841401
commit 4e7ef35280

View File

@ -10,15 +10,24 @@ import space.kscience.kmath.operations.DoubleField.pow
import kotlin.math.*
internal data class Tangent(
val startCircle: Circle2D,
val endCircle: Circle2D,
val startObstacle: ObstacleShell,
val endObstacle: ObstacleShell,
internal data class ObstacleNode(
val obstacle: Obstacle,
val nodeIndex: Int,
val direction: Trajectory2D.Direction,
){
val circle get() = obstacle.circles[nodeIndex]
}
internal data class ObstacleTangent(
val lineSegment: LineSegment2D,
val startDirection: Trajectory2D.Direction,
val endDirection: Trajectory2D.Direction = startDirection,
) : LineSegment2D by lineSegment
val beginNode: ObstacleNode,
val endNode: ObstacleNode,
) : LineSegment2D by lineSegment{
val startCircle get() = beginNode.circle
val startDirection get() = beginNode.direction
val endCircle get() = endNode.circle
val endDirection get() = endNode.direction
}
private class LR<T>(val l: T, val r: T) {
@ -28,11 +37,11 @@ private class LR<T>(val l: T, val r: T) {
}
}
private class TangentPath(val tangents: List<Tangent>) {
private class TangentPath(val tangents: List<ObstacleTangent>) {
fun last() = tangents.last()
}
private fun TangentPath(vararg tangents: Tangent) = TangentPath(listOf(*tangents))
private fun TangentPath(vararg tangents: ObstacleTangent) = TangentPath(listOf(*tangents))
/**
* Create inner and outer tangents between two circles.
@ -146,6 +155,8 @@ internal class ObstacleShell(
}
}
constructor(obstacle: Obstacle) : this(obstacle.circles)
/**
* Check if segment has any intersections with this obstacle
*/
@ -153,8 +164,10 @@ internal class ObstacleShell(
shell.any { tangent -> segment.intersectsSegment(tangent) }
|| circles.any { circle -> segment.intersectsCircle(circle) }
fun nextTangent(circle: Circle2D, direction: Trajectory2D.Direction): Tangent {
val circleIndex = circles.indexOf(circle)
/**
* Tangent to next obstacle node in given direction
*/
fun nextTangent(circleIndex:Int, direction: Trajectory2D.Direction): ObstacleTangent {
if (circleIndex == -1) error("Circle does not belong to this tangent")
val nextCircleIndex = if (direction == this.shellDirection) {
@ -163,32 +176,29 @@ internal class ObstacleShell(
if (circleIndex == 0) circles.lastIndex else circleIndex - 1
}
return Tangent(
circle,
circles[nextCircleIndex],
this,
this,
return ObstacleTangent(
LineSegment(
shell[nextCircleIndex].end,
shell[nextCircleIndex].begin
),
direction
ObstacleNode(this,circleIndex, direction),
ObstacleNode(this,nextCircleIndex, direction),
)
}
internal fun tangentsAlong(
initialCircle: Circle2D,
initialCircleIndex: Int,
direction: Trajectory2D.Direction,
finalCircle: Circle2D,
): List<Tangent> {
val dubinsTangents = mutableListOf<Tangent>()
var tangent = nextTangent(initialCircle, direction)
dubinsTangents.add(tangent)
while (tangent.endCircle != finalCircle) {
tangent = nextTangent(tangent.endCircle, direction)
dubinsTangents.add(tangent)
finalCircleIndex: Int,
): List<ObstacleTangent> {
return buildList {
var currentIndex = initialCircleIndex
do {
val tangent = nextTangent(currentIndex, direction)
add(tangent)
currentIndex = tangent.endNode.nodeIndex
} while (currentIndex != finalCircleIndex)
}
return dubinsTangents
}
override fun equals(other: Any?): Boolean {
@ -246,19 +256,18 @@ private fun LineSegment2D.intersectsCircle(circle: Circle2D): Boolean {
* In general generates 4 paths.
* TODO check intersections.
*/
private fun outerTangents(first: ObstacleShell, second: ObstacleShell): Map<DubinsPath.Type, Tangent> = buildMap {
private fun outerTangents(first: Obstacle, second: Obstacle): Map<DubinsPath.Type, ObstacleTangent> = buildMap {
for (firstCircle in first.circles) {
for (secondCircle in second.circles) {
for ((pathType, segment) in tangentsBetweenCircles(firstCircle, secondCircle)) {
val tangent = Tangent(
firstCircle,
secondCircle,
first,
second,
for (firstCircleIndex in first.circles.indices) {
for (secondCircleIndex in second.circles.indices) {
for ((pathType, segment) in tangentsBetweenCircles(
first.circles[firstCircleIndex],
second.circles[secondCircleIndex]
)) {
val tangent = ObstacleTangent(
segment,
pathType.first,
pathType.third
ObstacleNode(first, firstCircleIndex, pathType.first),
ObstacleNode(second, secondCircleIndex, pathType.third)
)
if (!(first.intersects(tangent)) && !(second.intersects(tangent))) {
@ -331,9 +340,9 @@ private fun constructTangentCircles(
}
private fun sortedObstacles(
currentObstacle: ObstacleShell,
obstacles: List<ObstacleShell>,
): List<ObstacleShell> {
currentObstacle: Obstacle,
obstacles: List<Obstacle>,
): List<Obstacle> {
return obstacles.sortedBy { Euclidean2DSpace.norm(it.center - currentObstacle.center) }
}
@ -345,7 +354,7 @@ private fun allFinished(
finalObstacle: Obstacle,
): Boolean {
for (path in paths) {
if (path.last().endObstacle != finalObstacle) {
if (path.last().endNode.obstacle !== finalObstacle) {
return false
}
}
@ -357,7 +366,7 @@ private fun LineSegment2D.toTrajectory() = StraightTrajectory2D(begin, end)
private fun TangentPath.toTrajectory(): CompositeTrajectory2D = CompositeTrajectory2D(
buildList {
tangents.zipWithNext().forEach { (left, right) ->
tangents.zipWithNext().forEach { (left, right: ObstacleTangent) ->
add(left.lineSegment.toTrajectory())
add(
CircleTrajectory2D.of(
@ -403,18 +412,16 @@ internal fun findAllPaths(
for (i in listOf(Trajectory2D.L, Trajectory2D.R)) {
for (j in listOf(Trajectory2D.L, Trajectory2D.R)) {
//Using obstacle to minimize code bloat
val initialObstacle = ObstacleShell(initialCircles[i])
val finalObstacle = ObstacleShell(finalCircles[j])
var currentPaths: List<TangentPath> = listOf(
TangentPath(
//We need only the direction of the final segment from this
Tangent(
initialCircles[i],
initialCircles[i],
ObstacleShell(initialCircles[i]),
ObstacleShell(initialCircles[i]),
ObstacleTangent(
LineSegment(start, start),
i
ObstacleNode(initialObstacle, 0, i),
ObstacleNode(initialObstacle, 0, i),
)
)
)
@ -423,16 +430,16 @@ internal fun findAllPaths(
val newPaths = mutableListOf<TangentPath>()
// for each path propagate it one obstacle further
for (tangentPath: TangentPath in currentPaths) {
val currentCircle = tangentPath.last().endCircle
val currentNode = tangentPath.last().endNode
val currentDirection: Trajectory2D.Direction = tangentPath.last().endDirection
val currentObstacle = tangentPath.last().endObstacle
val currentObstacle: ObstacleShell = ObstacleShell(tangentPath.last().endNode.obstacle)
// If path is finished, ignore it
// TODO avoid returning to ignored obstacle on the next cycle
if (currentObstacle == finalObstacle) {
newPaths.add(tangentPath)
} else {
val tangentToFinal: Tangent = outerTangents(currentObstacle, finalObstacle)[DubinsPath.Type(
val tangentToFinal: ObstacleTangent = outerTangents(currentObstacle, finalObstacle)[DubinsPath.Type(
currentDirection,
Trajectory2D.S,
j
@ -446,7 +453,7 @@ internal fun findAllPaths(
//TODO add break check for end of path
// All valid tangents from current obstacle to the next one
val nextTangents: Collection<Tangent> = outerTangents(
val nextTangents: Collection<ObstacleTangent> = outerTangents(
currentObstacle,
nextObstacle
).filter { (key, tangent) ->
@ -456,18 +463,18 @@ internal fun findAllPaths(
}.values
for (tangent in nextTangents) {
val tangentsAlong = if (tangent.startCircle == tangentPath.last().endCircle) {
val tangentsAlong: List<ObstacleTangent> = if (tangent.startCircle === tangentPath.last().endCircle) {
//if the previous segment last circle is the same as first circle of the next segment
//If obstacle consists of single circle, do not walk around
if (tangent.startObstacle.circles.size < 2) {
if (currentObstacle.circles.size < 2) {
emptyList()
} else {
val lengthMaxPossible = arcLength(
tangent.startCircle,
tangentPath.last().lineSegment.end,
tangent.startObstacle.nextTangent(
tangent.startCircle,
currentObstacle.nextTangent(
tangent.beginNode.nodeIndex,
currentDirection
).lineSegment.begin,
currentDirection
@ -482,9 +489,9 @@ internal fun findAllPaths(
// ensure that path does not go inside the obstacle
if (lengthCalculated > lengthMaxPossible) {
currentObstacle.tangentsAlong(
currentCircle,
currentNode.nodeIndex,
currentDirection,
tangent.startCircle,
tangent.beginNode.nodeIndex,
)
} else {
emptyList()
@ -492,9 +499,9 @@ internal fun findAllPaths(
}
} else {
currentObstacle.tangentsAlong(
currentCircle,
currentNode.nodeIndex,
currentDirection,
tangent.startCircle,
tangent.beginNode.nodeIndex,
)
}
newPaths.add(TangentPath(tangentPath.tangents + tangentsAlong + tangent))
@ -506,17 +513,13 @@ internal fun findAllPaths(
trajectories += currentPaths.map { tangentPath ->
val lastDirection: Trajectory2D.Direction = tangentPath.last().endDirection
val end = finalCircles[j]
val end = Obstacle(finalCircles[j])
TangentPath(
tangentPath.tangents +
Tangent(
end,
end,
ObstacleShell(end),
ObstacleShell(end),
ObstacleTangent(
LineSegment(finish, finish),
startDirection = lastDirection,
endDirection = j
ObstacleNode(end, 0, j),
ObstacleNode(end, 0, j)
)
)
}.map { it.toTrajectory() }