Skip to content

Commit

Permalink
Merge pull request #24 from m-abdulhak/6-incorporate-the-ability-for-…
Browse files Browse the repository at this point in the history
…robots-to-modify-scalar-fields

#6 Add Ability for Robots to Modify Scalar Fields
  • Loading branch information
m-abdulhak authored Jun 14, 2023
2 parents 5d03ef9 + 175d118 commit 4061895
Show file tree
Hide file tree
Showing 23 changed files with 935 additions and 466 deletions.
3 changes: 2 additions & 1 deletion src/client/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ const App = () => {
}

const imageElemOnload = (canvasElem, context) => {
field.src = context;
field.canvasElem = canvasElem;
field.context = context;
fieldsElemRef?.current?.appendChild(canvasElem);
};

Expand Down
459 changes: 374 additions & 85 deletions src/client/dist/main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/client/dist/main.js.map

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions src/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,7 @@ export const resetSimulation = (

renderScene = () => {
if (!scene.paused) {

for (let i=0; i<scene.renderSkip; i++) {
for (let i = 0; i < scene.renderSkip; i += 1) {
scene.update();
updateBench(scene, scene.timeInstance);
}
Expand Down
14 changes: 14 additions & 0 deletions src/common/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"prop-types": "^15.7.2",
"socket.io-client": "^4.6.1",
"split-polygon": "^1.0.0",
"stackblur-canvas": "^2.6.0",
"toposort": "^2.0.2"
}
}
4 changes: 3 additions & 1 deletion src/common/robot/actuators/actuatorsManager.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import GrabberActuator from './grabberActuator';
import FieldActuator from './fieldActuator';

const availableActuatorsDefitions = [
GrabberActuator
GrabberActuator,
FieldActuator
];

// Actuators are stored in this object allowing other modules to easily reference them
Expand Down
28 changes: 28 additions & 0 deletions src/common/robot/actuators/fieldActuator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-disable no-param-reassign */
import Actuator from './actuator';
import { updateFieldAtPoint } from '../../utils/canvasUtils';

const name = 'field';

class FieldActuator extends Actuator {
constructor(robot, scene) {
super(robot, scene, name);
}

activate(field, values, coordinates = null) {
if (!field.context) {
return;
}

if (!coordinates) {
coordinates = this.robot.sensors.position;
}

updateFieldAtPoint(field.context, coordinates, values);
}
}

export default {
name,
Actuator: FieldActuator
};
2 changes: 1 addition & 1 deletion src/common/robot/actuators/grabberActuator.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class GrabberActuator extends Actuator {
const relativeAttachmentPoint = { x: this.robot.radius + puck.radius, y: 0 };
const attachmentPoint = Vector.rotate(relativeAttachmentPoint, this.robot.body.angle);

// Set the state to the grapped puck
// Set the state to the grabbed puck
this.state = puck;

// Create a constraint between the robot and the puck, and add it to the world
Expand Down
7 changes: 4 additions & 3 deletions src/common/robot/sensors/env/fieldSensor.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-disable no-console */
import Sensor from '../sensor';
import { sensorSamplingTypes, CoreSensors } from '../sensorManager';
import { getSceneDefinedPointDefinitions, getSceneDefinedPoints, sampleFieldAtPoint } from '../sensorUtils';
import { getSceneDefinedPointDefinitions, getSceneDefinedPoints } from '../sensorUtils';
import { sampleFieldAtPoint } from '../../../utils/canvasUtils';

const name = 'fields';

Expand Down Expand Up @@ -29,11 +30,11 @@ class FieldSensor extends Sensor {
res[fieldKey] = {};

Object.entries(this.sceneDefinedSensingPoints).forEach(([key, sensingPoint]) => {
if (!field.src) {
if (!field.context) {
res[fieldKey][key] = null;
return;
}
const fieldValue = sampleFieldAtPoint(field.src, sensingPoint);
const fieldValue = sampleFieldAtPoint(field.context, sensingPoint);
res[fieldKey][key] = fieldValue;
});
});
Expand Down
87 changes: 36 additions & 51 deletions src/common/robot/sensors/sensorUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,63 +6,48 @@
import { getPolarCoordsFromCartesian, getAbsolutePointFromDistanceAndAngle } from '../../utils/geometry';

export function getSceneDefinedPointDefinitions(points) {
return points.reduce((acc, pDef) => {
if (!pDef.name || !pDef.coords || !pDef.type || (pDef.type !== 'Cartesian' && pDef.type !== 'Polar')) {
console.error('Unrecognized point definitinon:', pDef);
return acc;
}
if (pDef.type === 'Cartesian') {
const coords = getPolarCoordsFromCartesian(pDef.coords.x, pDef.coords.y);
return points.reduce((acc, pDef) => {
if (!pDef.name || !pDef.coords || !pDef.type || (pDef.type !== 'Cartesian' && pDef.type !== 'Polar')) {
// eslint-disable-next-line no-console
console.error('Unrecognized point definitinon:', pDef);
return acc;
}
if (pDef.type === 'Cartesian') {
const coords = getPolarCoordsFromCartesian(pDef.coords.x, pDef.coords.y);

// Keep compatibility with other angles using positive angle direction as clockwise
coords.angle *= -1;
// Keep compatibility with other angles using positive angle direction as clockwise
coords.angle *= -1;

acc[pDef.name] = coords;
return acc;
}
if (pDef.type === 'Polar') {
acc[pDef.name] = pDef.coords;
return acc;
}
return acc;
}, {});
acc[pDef.name] = coords;
return acc;
}
if (pDef.type === 'Polar') {
acc[pDef.name] = pDef.coords;
return acc;
}
return acc;
}, {});
}

export function getSceneDefinedPoints(pointDefinitions, sensors) {
return Object.entries(pointDefinitions)
.reduce((acc, [pointDefKey, pointDef]) => {
const angle = sensors.orientation + pointDef.angle;
const distance = pointDef.distance;
acc[pointDefKey] = getAbsolutePointFromDistanceAndAngle(
sensors.position,
distance,
angle
);
return acc;
}, {});
return Object.entries(pointDefinitions)
.reduce((acc, [pointDefKey, pointDef]) => {
const angle = sensors.orientation + pointDef.angle;
const distance = pointDef.distance;
acc[pointDefKey] = getAbsolutePointFromDistanceAndAngle(
sensors.position,
distance,
angle
);
return acc;
}, {});
}

export function getSceneDefinedPointsAsArray(pointDefinitions, sensors) {

var pointsObject = getSceneDefinedPoints(pointDefinitions, sensors);
var outputArray = [];
for (const [key, point] of Object.entries(pointsObject)) {
// We will ignore the key (i.e. the name) and just add each point
// as a row.
outputArray.push([point.x, point.y])
}
return outputArray;
}

export function sampleFieldAtPoint(context, p) {
if (!context?.getImageData || typeof context.getImageData !== 'function') {
return null;
}

if (p.x < 0 || p.y < 0 || p.x >= context.canvas.width || p.y >= context.canvas.width) {
return [0, 0, 0, 0];
const pointsObject = getSceneDefinedPoints(pointDefinitions, sensors);
const outputArray = [];
for (const point of Object.values(pointsObject)) {
outputArray.push([point.x, point.y]);
}

const imageVal = context.getImageData(p.x, p.y, 1, 1);
return imageVal.data;
};
return outputArray;
}
23 changes: 23 additions & 0 deletions src/common/scene/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,17 @@ export default class Scene {
}
}

// Initializing the scene effects
this.effects = envConfig.effects ?? [];
if (this.effects?.length) {
for (const effConfig of this.effects) {
effConfig.framesSinceLastRun = 0;
if (effConfig.framesBetweenRuns == null) {
effConfig.framesBetweenRuns = 10;
}
}
}

// drawMap(document.getElementById('mapCanvas'), this.distanceTransformMap, true);
// drawMap(document.getElementById('mapCanvas'), this.puckMaps[2], this.puckMapScale, true);

Expand Down Expand Up @@ -234,6 +245,18 @@ export default class Scene {
this.robots.forEach((r) => r.timeStep());
this.pucks.forEach((p) => p.timeStep());

for (const effConfig of this.effects || []) {
if (effConfig.framesSinceLastRun >= effConfig.framesBetweenRuns) {
effConfig.framesSinceLastRun = 0;

if (effConfig.func && typeof effConfig.func === 'function') {
effConfig.func(this);
}
} else {
effConfig.framesSinceLastRun += 1;
}
}

if (this.externalEngine) {
const goalsMsg = {};

Expand Down
80 changes: 80 additions & 0 deletions src/common/utils/canvasUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const getColorIndicesForCoord = (x, y, width) => {
const redIndx = y * (width * 4) + x * 4;
return [redIndx, redIndx + 1, redIndx + 2, redIndx + 3];
};

/**
* Updates the field at a given point on the canvas using the provided kernel.
* @param {CanvasRenderingContext2D} canvasContext - The canvas context to update.
* @param {Object} position - The position to update.
* @param {number} position.x - The x-coordinate of the position to update.
* @param {number} position.y - The y-coordinate of the position to update.
* @param {Array.<Array.<number>>|Array.<number>} value - The kernel to update with. Can be a 2D array of RGBA
* pixel values or a single RGBA pixel value.
* @returns {ImageData|null} The updated image data or null if the update was not successful.
* @throws {Error} If the value is invalid or if the update kernel is invalid.
*/
export function updateFieldAtPoint(canvasContext, position, value) {
const xp = Math.floor(position.x);
const yp = Math.floor(position.y);

if (xp < 0 || yp < 0 || xp >= canvasContext.canvas.width || yp >= canvasContext.canvas.height) {
return null;
}

if (
!value.length
|| !canvasContext
|| typeof canvasContext.getImageData !== 'function'
|| typeof canvasContext.putImageData !== 'function'
) {
return null;
}

if (!Array.isArray(value[0])) {
if (value.length !== 3 && value.length !== 4) {
throw new Error('Invlaid pixel color value received:', value);
}
// eslint-disable-next-line no-param-reassign
value = [[value]];
}

const vLen = value.length;
if (vLen % 2 !== 1 || value.some((row) => row.length !== vLen)) {
throw new Error('Invlaid update kernel received:', value);
}

const minXIndx = xp - Math.floor(vLen / 2);
const minYIndx = yp - Math.floor(vLen / 2);

const imageVal = canvasContext.getImageData(minXIndx, minYIndx, vLen, vLen);

for (let x = 0; x < vLen; x += 1) {
for (let y = 0; y < vLen; y += 1) {
const [rIndx, gIndx, bIndx, aIndx] = getColorIndicesForCoord(x, y, vLen);
const [r, g, b, a] = value[y][x];

imageVal.data[rIndx] = r;
imageVal.data[gIndx] = g;
imageVal.data[bIndx] = b;
imageVal.data[aIndx] = a ?? 255;
}
}

canvasContext.putImageData(imageVal, xp, yp, 0, 0, vLen, vLen);

return imageVal.data;
}

export function sampleFieldAtPoint(context, p) {
if (!context?.getImageData || typeof context.getImageData !== 'function') {
return null;
}

if (p.x < 0 || p.y < 0 || p.x >= context.canvas.width || p.y >= context.canvas.width) {
return [0, 0, 0, 0];
}

const imageVal = context.getImageData(p.x, p.y, 1, 1);
return imageVal.data;
}
Loading

0 comments on commit 4061895

Please sign in to comment.