diff --git a/src/common/robot/robot.js b/src/common/robot/robot.js index 03e97a3..0e5d832 100644 --- a/src/common/robot/robot.js +++ b/src/common/robot/robot.js @@ -252,7 +252,7 @@ export default class Robot { } reached(point) { - const ret = this.getDistanceTo(point) <= this.radius / 50; + const ret = this.getDistanceTo(point) <= this.radius / 5; return ret; } diff --git a/src/common/scene/index.js b/src/common/scene/index.js index 70ef6a2..b8edd78 100644 --- a/src/common/scene/index.js +++ b/src/common/scene/index.js @@ -306,8 +306,8 @@ export default class Scene { return d3.range(numOfRobots) .map((i) => new Robot( i, - this.getPos(), - this.getPos(), + this.getPos(radius), + this.getPos(radius), controllers, sensors, actuators, @@ -329,7 +329,7 @@ export default class Scene { ...d3.range(puckGroup.count) .map((i) => new Puck( i + id, - this.getPos(), + this.getPos(puckGroup.radius), puckGroup.radius, puckGroup.goal, puckGroup.goalRadius, diff --git a/src/common/utils/positionsGenerators/circularPositionsGenerator.js b/src/common/utils/positionsGenerators/circularPositionsGenerator.js new file mode 100644 index 0000000..0049c7f --- /dev/null +++ b/src/common/utils/positionsGenerators/circularPositionsGenerator.js @@ -0,0 +1,39 @@ +export default function getCircularPositionsGenerator(posNum, radius, envWidth, envHeight) { + const circleRadius = (Math.min(envWidth, envHeight) * 20) / 42; + const resolution = (Math.PI * 2) / posNum; + const envCenter = { x: envWidth / 2, y: envHeight / 2 }; + + if (circleRadius * resolution < radius * 4) { + throw new Error('Invalid inputs, number and size of robots are too high for this environment size!'); + } + + const positions = []; + const start = Math.random() * Math.PI * 2; + let i = start; + while (i < start + Math.PI * 2) { + const newX = envCenter.x + circleRadius * Math.cos(i); + const newY = envCenter.y + circleRadius * Math.sin(i); + const newGoalX = envCenter.x - circleRadius * Math.cos(i); + const newGoalY = envCenter.y - circleRadius * Math.sin(i); + const newPos = { x: newX, y: newY }; + const newGoalPos = { x: newGoalX, y: newGoalY }; + + positions.push(newPos); + positions.push(newGoalPos); + + i += resolution + (Math.random() * resolution) / 100 - resolution / 50; + } + + if (positions.length < posNum * 2) { + throw new Error('Invalid inputs, number and size of robots are too high for this environment size!'); + } + + const getPos = () => { + if (positions.length === 0) { + throw new Error('No positions available!'); + } + return positions.pop(); + }; + + return getPos; +} diff --git a/src/common/utils/positionsGenerators/index.js b/src/common/utils/positionsGenerators/index.js index 9943872..94ec52f 100644 --- a/src/common/utils/positionsGenerators/index.js +++ b/src/common/utils/positionsGenerators/index.js @@ -1,5 +1,7 @@ import randomCollisionFree from './randomPositionsGenerator'; +import circularPositionsGenerator from './circularPositionsGenerator'; export default { - randomCollisionFree + randomCollisionFree, + circularPositionsGenerator }; diff --git a/src/common/utils/positionsGenerators/randomPositionsGenerator.js b/src/common/utils/positionsGenerators/randomPositionsGenerator.js index 896bc3d..281e793 100644 --- a/src/common/utils/positionsGenerators/randomPositionsGenerator.js +++ b/src/common/utils/positionsGenerators/randomPositionsGenerator.js @@ -1,59 +1,47 @@ // Module to generate random initial positions for robots and pucks -// TODO: replace with a js generator import { getDistance } from '../geometry'; -const positions = []; - -const getPos = () => { - if (positions.length === 0) { - throw new Error('No positions available!'); - } - return positions.pop(); -}; - -export default function getRandCollFreePosGenerator( - numOfPos, radius, envWidth, envHeight, staticObjects -) { - const resolution = (radius * 2.1); - const xCount = envWidth / resolution; - const yCount = envHeight / resolution; - const positionsCount = parseInt(numOfPos, 10); - - /* - if (xCount * yCount < positionsCount * 4) { - throw new Error('Invalid inputs, number and size of robots and pucks are too high for this environment size!'); - } - */ - - let i = 0; - while (positions.length < positionsCount * 3 && i < positionsCount * 100) { - const newX = Math.max( - radius * 2, - Math.min(envWidth - radius * 2, Math.floor(Math.random() * xCount) * resolution) - ); - const newY = Math.max( - radius * 2, - Math.min(envHeight - radius * 2, Math.floor(Math.random() * yCount) * resolution) - ); +export default function getRandCollFreePosGenerator(posNum, radius, envWidth, envHeight, staticObjects = []) { + const positionsAndRadii = []; + + const generatePos = (r) => { + const xCount = envWidth / r; + const yCount = envHeight / r; + const minX = r * 2; + const maxX = envWidth - r * 2; + const minY = r * 2; + const maxY = envHeight - r * 2; + + const newX = Math.max(minX, Math.min(maxX, Math.floor(Math.random() * xCount) * r)); + const newY = Math.max(minY, Math.min(maxY, Math.floor(Math.random() * yCount) * r)); const newPos = { x: newX, y: newY }; - const doesNotCollideWithRobots = positions - .findIndex((x) => getDistance(x, newPos) < radius * 2.2) === -1; - const doesNotCollideWithObstacles = staticObjects - .reduce((acc, cur) => !cur.containsPoint(newPos) - && cur.getDistanceToBorder(newPos) > radius && acc, true); - - if (doesNotCollideWithRobots && doesNotCollideWithObstacles) { - positions.push(newPos); + + const doesNotCollideWithOtherPositions = positionsAndRadii + .findIndex(([p, pr]) => getDistance(p, newPos) < (r + pr) * 1.1) === -1; + + const doesNotCollideWithObstacles = staticObjects.reduce((acc, cur) => !cur.containsPoint(newPos) + && cur.getDistanceToBorder(newPos) > r && acc, true); + + if (doesNotCollideWithOtherPositions && doesNotCollideWithObstacles) { + return newPos; } - i += 1; - } - - /* - if (positions.length < positionsCount * 2) { - throw new Error('Invalid inputs, number and size of robots are too high for this environment!'); - } - */ + + return null; + }; + + const getPos = (r) => { + for (let tries = 0; tries < 100000; tries += 1) { + const newPos = generatePos(r); + + if (newPos) { + positionsAndRadii.push([newPos, r]); + return newPos; + } + } + + throw new Error('No collision-free positions available!'); + }; return getPos; } diff --git a/src/scenes/CollisionAvoidance/index.js b/src/scenes/CollisionAvoidance/index.js new file mode 100644 index 0000000..aac521c --- /dev/null +++ b/src/scenes/CollisionAvoidance/index.js @@ -0,0 +1,113 @@ +import { + CoreActuators, + CoreSensors, + ExtraSensors, + CorePositionsGenerators, + CorePerformanceTrakers, + CoreControllers +} from '@common'; + +import SceneRenderables from '@common/scene/renderables'; +import RobotRenderables from '@common/robot/renderables'; +import PuckRenderables from '@common/puck/renderables'; + +const renderables = [ + { module: 'Scene', elements: SceneRenderables }, + { module: 'Puck', elements: PuckRenderables }, + { module: 'Robot', elements: RobotRenderables } +]; + +const simConfig = { + env: { + width: 800, + height: 500, + renderSkip: 1 + }, + robots: { + count: 25, + radius: 7, + params: { + velocityScale: 10 + }, + controllers: { + waypoint: CoreControllers.waypoint.bvcWaypointController, + // velocity: CoreControllers.velocity.omniDirVelocityController + velocity: { + controller: CoreControllers.velocity.diffVelocityController, + params: { angularVelocityScale: 0.01 } + }, + supportsUserDefinedControllers: false + }, + sensors: [...Object.values(CoreSensors), ...Object.values(ExtraSensors)], + actuators: Object.values(CoreActuators), + useVoronoiDiagram: true + }, + pucks: { + groups: [], + useGlobalPuckMaps: false + }, + objects: [], + positionsGenerator: CorePositionsGenerators.circularPositionsGenerator, + renderables +}; + +// TODO: add other waypoint controller to benchmark +const benchmarkConfig = { + simConfigs: [ + { + name: '5 Robots', + simConfig: { + env: { + renderSkip: 50 + }, + robots: { + count: 5, + params: { + velocityScale: 50 + } + } + } + }, + { + name: '20 Robots', + simConfig: { + env: { + renderSkip: 50 + }, + robots: { + params: { + velocityScale: 50 + } + } + } + } + ], + trackers: [ + CorePerformanceTrakers.RobotToGoalDistanceTracker, + CorePerformanceTrakers.PucksOutsideGoalTracker, + CorePerformanceTrakers.MinRobotRobotDistanceTracker + ], + maxTimeStep: 50000, + timeStep: 1000 +}; + +const description = { + html: `

Object sorting using Buffered Voronoi Cells (BVC). Each robot chooses an intermediate goal location within its BVC. This avoids the possibility of collision or conflict with other robots. Their goal is to incremental shift the pucks towards their respective goal locations.

+ +

Rather than pushing pucks directly towards their goals, the robots make use of a goal map for each type of pucks. A goal map specifies the direction a puck should be pushed in order to reach the goal, accounting for obstacles that might be in the way.

+ +

+ + Abdullhak, Mohammed, and Andrew Vardy. "Distributed Sorting in Complex Environments." Swarm Intelligence: 13th International Conference, ANTS 2022, Málaga, Spain, November 2–4, 2022, Proceedings. Cham: Springer International Publishing, 2022. + +

+ ` +}; + +export default { + title: 'Collision Avoidance', + name: 'collisionAvoidance', + simConfig, + benchmarkConfig, + description +}; diff --git a/src/scenes/index.js b/src/scenes/index.js index 813148f..3d401e0 100644 --- a/src/scenes/index.js +++ b/src/scenes/index.js @@ -7,6 +7,7 @@ import voronoiSorting from './VoronoiSorting'; import simpleSorting from './Sorting'; import demo from './Demo'; import labyrinth from './Labyrinth'; +import collisionAvoidance from './CollisionAvoidance'; export default { labyrinth, @@ -17,5 +18,5 @@ export default { fieldManipulation, simpleSorting, voronoiSorting, - ExternalEngine + collisionAvoidance };