I'm making an AI agent (blue entity) that pursues a target (green entity) while avoiding an obstacle (red entity).
The code works as it should most of the times, but will fail if all the entities are aligned in either the X or Y axis or any direction diagonally. I suspect this is due to dividing by zero.
I fixed the problem by simply checking if the axes were zero and then applying a slight offset.
if (vectorTargetX === 0) vectorTargetX += 0.1;
if (vectorTargetY === 0) vectorTargetY += 0.1;
However, this feels like a cheap fix and it seems like there might be some underlying problems in my code. Are there any better ways to go about this, maybe even more efficient?
You can see the how the AI agent fails by clicking the buttons in the example below.
const ctx = document.getElementById('canvas').getContext('2d');
let targetX = 150;
let targetY = 100;
let obstacleX = 200;
let obstacleY = 100;
let agentX = 300;
let agentY = 101;
function loop() {
requestAnimationFrame(loop);
// Calculate difference vectors
let vectorTargetX = targetX - agentX;
let vectorTargetY = targetY - agentY;
let vectorObstacleX = obstacleX - agentX;
let vectorObstacleY = obstacleY - agentY;
// Calculate length between vectors
const lengthTarget = Math.sqrt(vectorTargetX * vectorTargetX + vectorTargetY * vectorTargetY);
const lengthObstacle = Math.sqrt(vectorObstacleX * vectorObstacleX + vectorObstacleY * vectorObstacleY);
// Normalize vectors
vectorTargetX = vectorTargetX / lengthTarget;
vectorTargetY = vectorTargetY / lengthTarget;
vectorObstacleX = vectorObstacleX / lengthObstacle;
vectorObstacleY = vectorObstacleY / lengthObstacle;
// Check if agent is within collision distance
if (lengthObstacle < 60) {
// Append displacement vector
vectorTargetX -= vectorObstacleX * 0.7;
vectorTargetY -= vectorObstacleY * 0.7;
const displacedLength = Math.sqrt(vectorTargetX * vectorTargetX + vectorTargetY * vectorTargetY);
// Move agent towards target while adding avoidance force
agentX += vectorTargetX / displacedLength;
agentY += vectorTargetY / displacedLength;
} else {
// Move agent towards target
agentX += vectorTargetX;
agentY += vectorTargetY;
}
if (lengthTarget < 12) {
unaligned();
}
ctx.clearRect(0, 0, 500, 500);
ctx.beginPath();
ctx.fillStyle = '#00ff00';
ctx.arc(targetX, targetY, 6, 0, Math.PI * 2, false);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = '#ff0000';
ctx.arc(obstacleX, obstacleY, 6, 0, Math.PI * 2, false);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = '#0000ff';
ctx.arc(agentX, agentY, 6, 0, Math.PI * 2, false);
ctx.fill();
ctx.font = '18px sans-serif';
ctx.fillStyle = "#000000";
ctx.fillText("X: " + agentX.toFixed(2), 10, 20);
ctx.fillText("Y: " + agentY.toFixed(2), 10, 40);
}
function aligned() {
agentX = 300;
agentY = 100;
}
function unaligned() {
agentX = 300;
agentY = 101;
}
loop();
<canvas id="canvas"></canvas><br>
<button onclick="aligned()">Spawn agent aligned</button>
<button onclick="unaligned()">Spawn agent unaligned</button>
targetY
andagentY
andobstacleY
are the same, making bothvectorTargetY
andvectorObstacleY
zero. You use these values in multiplications, multiplying anything by zero becomes zero. It all goes downhill from there. \$\endgroup\$