I'm developing a tool for modifying different geometrical shapes from an assortment of templates. The shapes are basic ones that could be found in rooms.
For example: L-shape, T-shape, Hexagon, Rectangle etc.
What I need to do is making the shape conform all necessary edges as to keep the shape's symmetry and bounding dimensions intact when the user modifies an edge.
A shape is simply implemented like this, with the first node starting in the upper left corner and going around the shape clockwise (I use TypeScript):
public class Shape {
private nodes: Array<Node>;
private scale: number; // Scale for calculating correct coordinate compared to given length
... // A whole lot of transformation methods
Which then is drawn as a graph, connecting each node to the next in the array. (See below)
If I, for example, would change the length of edge C to 3m from 3.5m, then I'd also want either edge E or G to change their length to keep the side to 12m and also push down E so that edge D is still fully horizontal.
If I instead would change side D to 2m, then B would have to change its length to 10m and so on.
(I do have shapes which have slanted angles as well, like a rectangle with one of its corners cut off)
The problem
I have the following code for modifying the specific edge:
public updateEdgeLength(start: Point, length: number): void {
let startNode: Node;
let endNode: Node;
let nodesSize = this.nodes.length;
// Find start node, and then select end node of selected edge.
for (let i = 0; i < nodesSize; i++) {
if (this.nodes[i].getX() === start.x && this.nodes[i].getY() === start.y) {
startNode = this.nodes[i];
endNode = this.nodes[(i + 1) % nodesSize];
break;
}
}
// Calculate linear transformation scalar and create a vector of the edge
let scaledLength = (length * this.scale);
let edge: Vector = Vector.create([endNode.getX() - startNode.getX(), endNode.getY() - startNode.getY()]);
let scalar = scaledLength / startNode.getDistance(endNode);
edge = edge.multiply(scalar);
// Translate the new vector to its correct position
edge = edge.add([startNode.getX(), startNode.getY()]);
// Calculate tranlation vector
edge = edge.subtract([endNode.getX(), endNode.getY()]);
endNode.translate({x: edge.e(1), y: edge.e(2)});
}
Now I need a more general case for finding the corresponding edges that will also need to be modified. I have begun implementing shape-specific algorithms as I know which nodes correspond to the edges of the shape, but this won't be very extensible for the future.
For example, the shape above could be implemented somewhat like this:
public updateSideLength(edge: Position): void {
// Get start node coordinates
let startX = edge.start.getX();
let startY = edge.start.getY();
// Find index of start node;
let index: num;
for (let i = 0; i < this.nodes.length; i++) {
let node: Node = this.nodes[i];
if(node.getX() === startX && node.getY() === startY) {
index = i;
break;
}
}
// Determine side
let side: number;
if (index === 0 || index === 2) {
side = this.TOP;
}
else if (index === 1 || index === 3 || index === 5) {
side = this.RIGHT;
}
else if (index === 4 || index === 6) {
side = this.BOTTOM;
}
else if (index === 7) {
side = this.LEFT;
}
adaptSideToBoundingBox(index, side); // adapts all other edges of the side except for the one that has been modified
}
public adaptSideToBoundingBox(exceptionEdge: number, side: number) {
// Modify all other edges
// Example: C and G will be modified
Move C.end Y-coord to D.start Y-coord;
Move G.start Y-coord to D.end Y-coord;
}
And so on.. But implementing this for each shape (5 atm.) and for future shapes would be very time consuming.
So what I'm wondering is if there is a more general approach to this problem?
Thanks!
Keep a list of point pairs and the key that constrains them and use that to overwrite coordinates on update.
This works with the example you gave:
var Point = (function () {
function Point(x, y, connectedTo) {
if (connectedTo === void 0) { connectedTo = []; }
this.x = x;
this.y = y;
this.connectedTo = connectedTo;
}
return Point;
}());
var Polygon = (function () {
function Polygon(points, constrains) {
if (constrains === void 0) { constrains = []; }
this.points = points;
this.constrains = constrains;
}
return Polygon;
}());
var Sketch = (function () {
function Sketch(polygons, canvas) {
if (polygons === void 0) { polygons = []; }
if (canvas === void 0) { canvas = document.body.appendChild(document.createElement("canvas")); }
this.polygons = polygons;
this.canvas = canvas;
this.canvas.width = 1000;
this.canvas.height = 1000;
this.ctx = this.canvas.getContext("2d");
this.ctx.fillStyle = "#0971CE";
this.ctx.strokeStyle = "white";
this.canvas.onmousedown = this.clickHandler.bind(this);
this.canvas.onmouseup = this.clickHandler.bind(this);
this.canvas.onmousemove = this.clickHandler.bind(this);
requestAnimationFrame(this.draw.bind(this));
}
Sketch.prototype.clickHandler = function (evt) {
if (evt.type == "mousedown") {
if (this.selectedPoint != void 0) {
this.selectedPoint = null;
}
else {
var score = null;
var best = null;
for (var p = 0; p < this.polygons.length; p++) {
var polygon = this.polygons[p];
for (var pi = 0; pi < polygon.points.length; pi++) {
var point = polygon.points[pi];
var dist = Math.abs(point.x - evt.offsetX) + Math.abs(point.y - evt.offsetY);
if (score == null ? true : dist < score) {
score = dist;
best = point;
}
}
}
this.selectedPoint = best;
}
}
if (evt.type == "mousemove" && this.selectedPoint != void 0) {
this.selectedPoint.x = Math.round(evt.offsetX / 5) * 5;
this.selectedPoint.y = Math.round(evt.offsetY / 5) * 5;
for (var pi = 0; pi < this.polygons.length; pi++) {
var polygon = this.polygons[pi];
if (polygon.points.indexOf(this.selectedPoint) < 0) {
continue;
}
for (var pa = 0; pa < polygon.constrains.length; pa++) {
var constrain = polygon.constrains[pa];
if (constrain.a == this.selectedPoint || constrain.b == this.selectedPoint) {
constrain.a[constrain.key] = this.selectedPoint[constrain.key];
constrain.b[constrain.key] = this.selectedPoint[constrain.key];
if (constrain.offset != void 0) {
if (constrain.a == this.selectedPoint) {
constrain.b[constrain.key] += constrain.offset;
}
else {
constrain.a[constrain.key] -= constrain.offset;
}
}
}
}
}
}
requestAnimationFrame(this.draw.bind(this));
};
Sketch.prototype.draw = function () {
var ctx = this.ctx;
//clear
ctx.fillStyle = "#0971CE";
ctx.fillRect(0, 0, 1000, 1000);
//grid
ctx.strokeStyle = "rgba(255,255,255,0.25)";
for (var x = 0; x <= this.canvas.width; x += 5) {
ctx.beginPath();
ctx.moveTo(x, -1);
ctx.lineTo(x, this.canvas.height);
ctx.stroke();
ctx.closePath();
}
for (var y = 0; y <= this.canvas.height; y += 5) {
ctx.beginPath();
ctx.moveTo(-1, y);
ctx.lineTo(this.canvas.width, y);
ctx.stroke();
ctx.closePath();
}
ctx.strokeStyle = "white";
ctx.fillStyle = "white";
//shapes
for (var i = 0; i < this.polygons.length; i++) {
var polygon = this.polygons[i];
for (var pa = 0; pa < polygon.points.length; pa++) {
var pointa = polygon.points[pa];
if (pointa == this.selectedPoint) {
ctx.beginPath();
ctx.fillRect(pointa.x - 2, pointa.y - 2, 4, 4);
ctx.closePath();
}
ctx.beginPath();
for (var pb = 0; pb < pointa.connectedTo.length; pb++) {
var pointb = pointa.connectedTo[pb];
if (polygon.points.indexOf(pointb) < pa) {
continue;
}
ctx.moveTo(pointa.x, pointa.y);
ctx.lineTo(pointb.x, pointb.y);
}
ctx.stroke();
ctx.closePath();
}
}
};
return Sketch;
}());
//==Test==
//Build polygon 1 (House)
var poly1 = new Polygon([
new Point(10, 10),
new Point(80, 10),
new Point(80, 45),
new Point(130, 45),
new Point(130, 95),
new Point(80, 95),
new Point(80, 135),
new Point(10, 135),
]);
//Connect dots
for (var x = 0; x < poly1.points.length; x++) {
var a = poly1.points[x];
var b = poly1.points[(x + 1) % poly1.points.length];
a.connectedTo.push(b);
b.connectedTo.push(a);
}
//Setup constrains
for (var x = 0; x < poly1.points.length; x++) {
var a = poly1.points[x];
var b = poly1.points[(x + 1) % poly1.points.length];
poly1.constrains.push({ a: a, b: b, key: x % 2 == 1 ? 'x' : 'y' });
}
poly1.constrains.push({ a: poly1.points[1], b: poly1.points[5], key: 'x' }, { a: poly1.points[2], b: poly1.points[5], key: 'x' }, { a: poly1.points[1], b: poly1.points[6], key: 'x' }, { a: poly1.points[2], b: poly1.points[6], key: 'x' });
//Build polygon 2 (Triangle)
var poly2 = new Polygon([
new Point(250, 250),
new Point(300, 300),
new Point(200, 300),
]);
//Connect dots
for (var x = 0; x < poly2.points.length; x++) {
var a = poly2.points[x];
var b = poly2.points[(x + 1) % poly2.points.length];
a.connectedTo.push(b);
b.connectedTo.push(a);
}
//Setup constrains
poly2.constrains.push({ a: poly2.points[0], b: poly2.points[1], key: 'x', offset: 50 }, { a: poly2.points[0], b: poly2.points[1], key: 'y', offset: 50 });
//Generate sketch
var s = new Sketch([poly1, poly2]);
<!-- TYPESCRIPT -->
<!--
class Point {
constructor(public x: number, public y: number, public connectedTo: Point[] = []) {
}
}
interface IConstrain {
a: Point,
b: Point,
key: string,
offset?: number
}
class Polygon {
constructor(public points: Point[], public constrains: IConstrain[] = []) {
}
}
class Sketch {
public ctx: CanvasRenderingContext2D;
constructor(public polygons: Polygon[] = [], public canvas = document.body.appendChild(document.createElement("canvas"))) {
this.canvas.width = 1000;
this.canvas.height = 1000;
this.ctx = this.canvas.getContext("2d");
this.ctx.fillStyle = "#0971CE";
this.ctx.strokeStyle = "white";
this.canvas.onmousedown = this.clickHandler.bind(this)
this.canvas.onmouseup = this.clickHandler.bind(this)
this.canvas.onmousemove = this.clickHandler.bind(this)
requestAnimationFrame(this.draw.bind(this))
}
public selectedPoint: Point
public clickHandler(evt: MouseEvent) {
if (evt.type == "mousedown") {
if (this.selectedPoint != void 0) {
this.selectedPoint = null;
} else {
let score = null;
let best = null;
for (let p = 0; p < this.polygons.length; p++) {
let polygon = this.polygons[p];
for (let pi = 0; pi < polygon.points.length; pi++) {
let point = polygon.points[pi];
let dist = Math.abs(point.x - evt.offsetX) + Math.abs(point.y - evt.offsetY)
if (score == null ? true : dist < score) {
score = dist;
best = point;
}
}
}
this.selectedPoint = best;
}
}
if (evt.type == "mousemove" && this.selectedPoint != void 0) {
this.selectedPoint.x = Math.round(evt.offsetX / 5) * 5;
this.selectedPoint.y = Math.round(evt.offsetY / 5) * 5;
for (let pi = 0; pi < this.polygons.length; pi++) {
let polygon = this.polygons[pi];
if (polygon.points.indexOf(this.selectedPoint) < 0) {
continue;
}
for (let pa = 0; pa < polygon.constrains.length; pa++) {
let constrain = polygon.constrains[pa];
if (constrain.a == this.selectedPoint || constrain.b == this.selectedPoint) {
constrain.a[constrain.key] = this.selectedPoint[constrain.key]
constrain.b[constrain.key] = this.selectedPoint[constrain.key]
if (constrain.offset != void 0) {
if (constrain.a == this.selectedPoint) {
constrain.b[constrain.key] += constrain.offset
} else {
constrain.a[constrain.key] -= constrain.offset
}
}
}
}
}
}
requestAnimationFrame(this.draw.bind(this))
}
public draw() {
var ctx = this.ctx;
//clear
ctx.fillStyle = "#0971CE";
ctx.fillRect(0, 0, 1000, 1000)
//grid
ctx.strokeStyle = "rgba(255,255,255,0.25)"
for (let x = 0; x <= this.canvas.width; x += 5) {
ctx.beginPath()
ctx.moveTo(x, -1)
ctx.lineTo(x, this.canvas.height)
ctx.stroke();
ctx.closePath()
}
for (let y = 0; y <= this.canvas.height; y += 5) {
ctx.beginPath()
ctx.moveTo(-1, y)
ctx.lineTo(this.canvas.width, y)
ctx.stroke();
ctx.closePath()
}
ctx.strokeStyle = "white"
ctx.fillStyle = "white";
//shapes
for (let i = 0; i < this.polygons.length; i++) {
let polygon = this.polygons[i];
for (let pa = 0; pa < polygon.points.length; pa++) {
let pointa = polygon.points[pa];
if (pointa == this.selectedPoint) {
ctx.beginPath();
ctx.fillRect(pointa.x - 2, pointa.y - 2, 4, 4)
ctx.closePath();
}
ctx.beginPath();
for (var pb = 0; pb < pointa.connectedTo.length; pb++) {
var pointb = pointa.connectedTo[pb];
if (polygon.points.indexOf(pointb) < pa) {
continue;
}
ctx.moveTo(pointa.x, pointa.y)
ctx.lineTo(pointb.x, pointb.y)
}
ctx.stroke();
ctx.closePath();
}
}
}
}
//==Test==
//Build polygon 1 (House)
var poly1 = new Polygon([
new Point(10, 10),
new Point(80, 10),
new Point(80, 45),
new Point(130, 45),
new Point(130, 95),
new Point(80, 95),
new Point(80, 135),
new Point(10, 135),
])
//Connect dots
for (let x = 0; x < poly1.points.length; x++) {
let a = poly1.points[x];
let b = poly1.points[(x + 1) % poly1.points.length]
a.connectedTo.push(b)
b.connectedTo.push(a)
}
//Setup constrains
for (let x = 0; x < poly1.points.length; x++) {
let a = poly1.points[x];
let b = poly1.points[(x + 1) % poly1.points.length]
poly1.constrains.push({ a: a, b: b, key: x % 2 == 1 ? 'x' : 'y' })
}
poly1.constrains.push(
{ a: poly1.points[1], b: poly1.points[5], key: 'x' },
{ a: poly1.points[2], b: poly1.points[5], key: 'x' },
{ a: poly1.points[1], b: poly1.points[6], key: 'x' },
{ a: poly1.points[2], b: poly1.points[6], key: 'x' }
)
//Build polygon 2 (Triangle)
var poly2 = new Polygon([
new Point(250, 250),
new Point(300, 300),
new Point(200, 300),
])
//Connect dots
for (let x = 0; x < poly2.points.length; x++) {
let a = poly2.points[x];
let b = poly2.points[(x + 1) % poly2.points.length]
a.connectedTo.push(b)
b.connectedTo.push(a)
}
//Setup constrains
poly2.constrains.push(
{ a: poly2.points[0], b: poly2.points[1], key: 'x', offset: 50 },
{ a: poly2.points[0], b: poly2.points[1], key: 'y', offset: 50 },
)
//Generate sketch
var s = new Sketch([poly1, poly2])
-->
UPDATE - Constrain offsets
Based on feedback in the comments i added a "offset" key in the constrains to handle uneven relationships.
The Triangles top-right-most edge (at least initially) is constrained with an offset.
Related
I've got an image like this:
The little circles are not regular but they are all black.
I'm looking for an algorithm to obtain an array made of all the rectangle bounds of every shape, like this:
I'm trying to do this with HTML5 canvas, but I'm not finding a way to get the bounds and go ahead with next shape. What I've done is obtaining the 2d array with x and y position of black pixels:
var x = 0,
y = 0,
cells = [];
for (var i = 0; i < imgdata.data.length; i += 4) {
if (imgdata.data[i] != 255) {
var p = [x,y];
cells.push(p);
}
if (x > image.width) {
x = 0;
y = y+1;
}
x = x+1;
}
console.log(cells);
You can do this naively with a simple flood fill-style algorithm that keeps track of the max/min points of each black area in all 4 directions.
The algorithm may visit every pixel twice and uses linear space. It also uses recursion, so it'll blow the stack if your black blobs are too large. Refactoring to use an explicit stack would avoid that. In other words, it's not optimized at all, just a proof of concept.
Here's the basic algorithm without a canvas. Note that it gives the points only within grid bounds, which would technically put the box on the edges of the shape, but you can add/subtract 1 (or another padding amount) to each corner to move the bounding box outside of the shape as desired.
const findBoundingBoxes = grid => {
const flood = (i, j, best) => {
if (i < 0 || j < 0 ||
i >= grid.length || j >= grid[i].length ||
!grid[i][j] || visited[i][j]) {
return;
}
visited[i][j] = true;
best.top = Math.min(best.top, i);
best.bottom = Math.max(best.bottom, i);
best.left = Math.min(best.left, j);
best.right = Math.max(best.right, j);
for (let di = -1; di < 2; di++) {
for (let dj = -1; dj < 2; dj++) {
if (di !== 0 || dj !== 0) {
flood(i + di, j + dj, best);
}
}
}
};
const boxes = [];
const visited = [...Array(grid.length)]
.map((_, i) => [...Array(grid[i].length)].fill(false));
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[i].length; j++) {
if (!grid[i][j] || visited[i][j]) {
continue;
}
const best = {top: i, bottom: i, left: j, right: j};
flood(i, j, best);
boxes.push({
topLeft: {x: best.left, y: best.top},
topRight: {x: best.right, y: best.top},
bottomLeft: {x: best.left, y: best.bottom},
bottomRight: {x: best.right, y: best.bottom},
});
}
}
return boxes;
};
const grid = [
"0000000000000",
"0001100001000",
"0011110111110",
"0001100111100",
"0000000000000",
].map(e => e.split("").map(Number));
console.log(findBoundingBoxes(grid));
Now, on a canvas. I'm converting the image data to a 2d grid for ease of coding, which isn't performance-conscious.
const findBoundingBoxes = grid => {
const flood = (i, j, best) => {
if (i < 0 || j < 0 ||
i >= grid.length || j >= grid[i].length ||
!grid[i][j] || visited[i][j]) {
return;
}
visited[i][j] = true;
best.top = Math.min(best.top, i);
best.bottom = Math.max(best.bottom, i);
best.left = Math.min(best.left, j);
best.right = Math.max(best.right, j);
for (let di = -1; di < 2; di++) {
for (let dj = -1; dj < 2; dj++) {
if (di !== 0 || dj !== 0) {
flood(i + di, j + dj, best);
}
}
}
};
const boxes = [];
const visited = [...Array(grid.length)]
.map((_, i) => [...Array(grid[i].length)].fill(false));
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[i].length; j++) {
if (!grid[i][j] || visited[i][j]) {
continue;
}
const best = {top: i, bottom: i, left: j, right: j};
flood(i, j, best);
boxes.push({
topLeft: {x: best.left, y: best.top},
topRight: {x: best.right, y: best.top},
bottomRight: {x: best.right, y: best.bottom},
bottomLeft: {x: best.left, y: best.bottom},
});
}
}
return boxes;
};
const canvas = document.createElement("canvas");
canvas.style.border = "1px solid blue";
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");
const img = new Image();
img.onload = function() {
const {width: w, height: h} = this;
canvas.width = w;
canvas.height = h;
ctx.drawImage(this, 0, 0, w, h);
const {data} = ctx.getImageData(0, 0, w, h);
const grid = [...Array(h)].map((_, i) =>
[...Array(w)].map((_, j) => data[(i*w+j)*4] < 255 ? 1 : 0)
);
const pad = 1;
ctx.strokeStyle = "red";
for (const box of findBoundingBoxes(grid)) {
const w = box.topRight.x - box.topLeft.x;
const h = box.bottomLeft.y - box.topLeft.y;
ctx.strokeRect(
box.topLeft.x - pad + 0.5,
box.topLeft.y - pad + 0.5,
w + pad * 2, h + pad * 2
);
}
};
img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAIAAAAiOjnJAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TxSIVB4uIimSoThZERRylikWwUNoKrTqYXPoFTRqSFBdHwbXg4Mdi1cHFWVcHV0EQ/ABxdHJSdJES/5cUWsR4cNyPd/ced+8AoV5mqtkxAaiaZSRjUTGTXRW7XuFHAP0YwZDETD2eWkzDc3zdw8fXuwjP8j735+hRciYDfCLxHNMNi3iDeGbT0jnvE4dYUVKIz4nHDbog8SPXZZffOBccFnhmyEgn54lDxGKhjeU2ZkVDJZ4mDiuqRvlCxmWF8xZntVxlzXvyFwZz2kqK6zSHEcMS4khAhIwqSijDQoRWjRQTSdqPevgHHX+CXDK5SmDkWEAFKiTHD/4Hv7s181OTblIwCnS+2PbHKNC1CzRqtv19bNuNE8D/DFxpLX+lDsx+kl5raeEjoHcbuLhuafIecLkDDDzpkiE5kp+mkM8D72f0TVmg7xboXnN7a+7j9AFIU1fLN8DBITBWoOx1j3cH2nv790yzvx9wDHKmj+oRMgAAAAlwSFlzAAAuIwAALiMBeKU/dgAAAAd0SU1FB+YDCBYgCuMnEn4AAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAC7klEQVR42u3d0WrkMAxA0Trk/3/ZfS20U5jBUizr3NeFZRmfkZ1smo4555e0ustHILAElsCSwBJYAksCS2AJLAksgSWwJLAElsCSwBJYAksCS2AJLAks7di92z9ojPHqj/wIZKHGDqv1DybIwEoihRdYgaTwAiuQFF6uCmNVhf7N2h1W9Nqz1RFWzqqz1QtW5nqz1QVW/kqzdT6sp9aYrZNhPbu6bHW5KhRYhwwMQ8vEElilRoWhZWIJrFJDwtAysQSWBJbKw9rzQOOYZWIJLAksgSWwJLAElsCSEmDt+RPJfk7axBJYElgqD2u3A40DloklsIoMCePKxBJYRUaFcWViCawiA8O4OnliPbW6VJ2/FeavMVVdzliZK01Vr8N7znpT1fGqMHrVqWoKK3Ttqdoqv0tHZ8FawgspsBbzQgqsZchgAkuuCiWwBJbAksBSiW4fQeFL+jfv/2XeAXC7oQWpfGdgdSSVYMsZC1OwVIisrdCwiTBgYslWKLAElgSWwBJYqteqewRB95vAkomllGEDlvalCZbAUp2dFKymOKLPZx5NPh/cz+cg0s77JpYxZmJpV0a/86CfTKyPvz3vPGzpmwbWYlLqBeuzN2YhBdYCc6+urgWWLe+o3McSWAJLYEltDu8O4yaWVGdiPZL/zDGxqAJLYElgCSwHrH4VeII09J4WUn0n1pzT8oNltMgZS53PWEFHLiPQxFoPgioTa/0Mowos2QolsASWwJLAElgCSwJLYAksCSyBJbAElo9AYKlMXgryR55+XvAZetBvuSfCwMp+w1ufT/twWBu+GbCJrXNgFXq7ZAdbN1ICCyZbIVK9d8OLKnWHRRVYVHX/ktzWQM5YAsu4AksCS2AJLAksgSWwJLAElsCSwBJYAksCS2AJLAksgSWwpMNheX0XWGzJViiwBJbdUNUmFltgsSVnLIFlaIElsKplaIElsGTugmU3BIstsCSwDC2w1PErcVkhgcUWWALL0BJYbNXoG/O8PCbkm47lAAAAAElFTkSuQmCC";
I am creating a simple javascript game where a paddle needs to catch raining blocks. When there is a collision, score should increase by 1, but in my case, the score increases by 12.
I am quite sure that there is collision detection, because the block disappears when it touches the paddle and I have a console.log to show me that there is a collision.
My collision detection code
var spawnRate = 1500;
var spawnRateOfDescent = 2;
var lastSpawn = -10;
var objects = [];
var startTime = Date.now();
var score =0;
function spawnRandomObject() {
var t;
if (Math.random() < 0.50)
{
t = "red";
}
else
{
t = "blue";
}
var object = {
type: t,
x: Math.random() * (canvas.width - 30) + 15,
y: 100,
r: 8,
status: 1
}
objects.push(object);
}
function animate()
{
var time = Date.now();
if (time > (lastSpawn + spawnRate)) {
lastSpawn = time;
spawnRandomObject();
}
var paddleLeft=paddleX;
var paddleRight=paddleX+paddleWidth;
var paddleTop=paddleY;
var paddleBottom=paddleY+paddleHeight;
for (var i = 0; i < objects.length; i++)
{
var object = objects[i];
object.y += spawnRateOfDescent;
if (object.status == 1){ //only draw the ball if the status is 1
ctx.beginPath();
ctx.arc(object.x, object.y, object.r, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = object.type;
ctx.fill();
}
var objectTop=object.y-object.r;
var objectBottom=object.y+object.r;
var objectLeft=object.x-object.r;
var objectRight=object.x+object.r;
if (objectRight>paddleX && objectLeft<paddleX+paddleWidth && objectBottom>paddleY && objectTop<paddleY+paddleHeight)
{
object.status = 0;
console.log("collision");
score = score +1;
}
}
}
In the following piece of code, you're adding the score:
if (objectRight>paddleX && objectLeft<paddleX+paddleWidth && objectBottom>paddleY && objectTop<paddleY+paddleHeight)
{
object.status = 0;
console.log("collision");
score = score +1;
}
I see you're setting the status of the object to 0. You could use this in the condition:
if (objectRight>paddleX && objectLeft<paddleX+paddleWidth && objectBottom>paddleY && objectTop<paddleY+paddleHeight && object.status === 1)
{
object.status = 0;
console.log("collision");
score++;
}
Keep in mind that you're not deleting the object from the array.
jsfiddle: https://jsfiddle.net/nragrkb8/3/
I'm trying to get a spectrogram drawn, works in chrome but draws nothing in all other browsers. I'm using getImageData and putImageData to move existing contents down by a pixel every time new data is pushed in. That new data is then drawn into an offscreen image and then putimagedata to apply it to the full canvas.
Spectrogram = (function() {
//constructor function
var Spectrogram = function(options) {
//compose options
this.canvas = options.canvas;
this.width = options.size; //the size of the FFT
this.height = options.length; //the number of FFT to keep
if (typeof options.min !== 'undefined' && typeof options.max !== 'undefined') {
this.rangeType = 'static';
this.min = options.min;
this.max = options.max;
} else {
this.rangeType = 'auto';
this.min = Infinity;
this.max = -Infinity;
}
//set the function used to determine a value's color
this.colorScaleFn = options.colorScaleFn || defaultColorScaling;
//precalculate the range used in color scaling
this.range = this.max - this.min;
//the 2d drawing context
this.ctx = this.canvas.getContext('2d', { alpha: false });
//single pixel image used to draw new data points
this.im = this.ctx.createImageData(this.width, 1);
this.canvas.width = this.width;
this.canvas.height = this.height;
//adds a new row of data to the edge of the spectrogram, shifting existing
//contents by 1px
this.addData = function (newData) {
if (newData.length != this.width) {
console.error('Unmatched dataset size. Expected ' + this.width + ', was + ' + newData.length);
return;
}
if (this.rangeType == 'auto') {
var changed = false;
for (var i = 0; i < newData.length; ++i) {
if (newData[i] < this.min) {
this.min = newData[i];
changed = true;
} else if (newData[i] > this.max) {
this.max = newData[i];
changed = true;
}
}
if (changed) {
this.range = this.max - this.min;
}
}
//move the current contents by 1px
var im = this.ctx.getImageData(0, 0, this.width, this.height - 1);
this.ctx.putImageData(im, 0, 1);
//draw the new data values into the temporary image
var j = 0;
for (var i = 0; i < newData.length; ++i) {
var c = this.colorScaleFn(newData[i]);
this.im.data[j] = c[0];
this.im.data[j + 1] = c[1];
this.im.data[j + 2] = c[2];
j += 4;
}
//put the new data colors into the full canvas
this.ctx.putImageData(this.im, 0, 0);
}
}
function defaultColorScaling(value) {
var x = (value - this.min) / this.range;
if (x < 0) {
x = 0;
} else if (x > 1) {
x = 1;
}
var g = Math.pow(x, 2);
var b = 0.75 - 1.5 * Math.abs(x - 0.45);
var r = 0;
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
return Spectrogram;
})();
const size = 1024;
const length = 200;
const dataMax = 100;
const velMax = 1;
const canvas = document.getElementById('spec');
canvas.width = size;
canvas.height = length;
const spec = new Spectrogram({
canvas: canvas,
size: size,
length: length
});
const data = [];
const vel = [];
for (var i = 0; i < size; ++i) {
data.push(0);
vel.push(0);
}
setInterval(function() {
for (var i = 0; i < size; ++i) {
data[i] += vel[i];
if (i > 0) data[i] += vel[i - 1];
if (i < size - 1) data[i] += vel[i + 1];
if (data[i] >= dataMax) {
data[i] = dataMax;
vel[i] = 0;
} else if (data[i] <= -dataMax) {
data[i] = -dataMax;
vel[i] = 0;
}
if (vel[i] == 0) {
vel[i] = (Math.random() - 0.5) * 100;
}
}
spec.addData(data);
}, 100);
Your problem was induced by a misconception:
getContext('2d', {alpha: false}) should have no incidence on createImageData function, so you still have to set the alpha value of your ImageData to something else than 0.
There are also other things in your code that don't go well, even if unrealted with your current issue:
You should avoid at all costs an high frequency setInterval where you perform a task that can take more than the interval (and in your case it can), but rather use something like a requestAnimationFrame loop.
Don't use getImageData + putImageData to draw your context on itself, simply use ctx.drawImage(ctx.canvas, x, y).
There might be other things to fix in your code, but here is an update with these three major things:
Spectrogram = (function() {
//constructor function
var Spectrogram = function(options) {
//compose options
this.canvas = options.canvas;
this.width = options.size; //the size of the FFT
this.height = options.length; //the number of FFT to keep
if (typeof options.min !== 'undefined' && typeof options.max !== 'undefined') {
this.rangeType = 'static';
this.min = options.min;
this.max = options.max;
} else {
this.rangeType = 'auto';
this.min = Infinity;
this.max = -Infinity;
}
//set the function used to determine a value's color
this.colorScaleFn = options.colorScaleFn || defaultColorScaling;
//precalculate the range used in color scaling
this.range = this.max - this.min;
//the 2d drawing context
this.ctx = this.canvas.getContext('2d', {
alpha: false
});
//single pixel image used to draw new data points
this.im = this.ctx.createImageData(this.width, 1);
this.canvas.width = this.width;
this.canvas.height = this.height;
//adds a new row of data to the edge of the spectrogram, shifting existing
//contents by 1px
this.addData = function(newData) {
if (newData.length != this.width) {
console.error('Unmatched dataset size. Expected ' + this.width + ', was + ' + newData.length);
return;
}
if (this.rangeType == 'auto') {
var changed = false;
for (var i = 0; i < newData.length; ++i) {
if (newData[i] < this.min) {
this.min = newData[i];
changed = true;
} else if (newData[i] > this.max) {
this.max = newData[i];
changed = true;
}
}
if (changed) {
this.range = this.max - this.min;
}
}
//move the current contents by 1px
this.ctx.drawImage(this.ctx.canvas, 0, 1)
//draw the new data values into the temporary image
var j = 0;
for (var i = 0; i < newData.length; ++i) {
var c = this.colorScaleFn(newData[i]);
this.im.data[j] = c[0];
this.im.data[j + 1] = c[1];
this.im.data[j + 2] = c[2];
// don't forget the alpha channel, createImageData is always full of zeroes
this.im.data[j + 3] = 255;
j += 4;
}
this.ctx.putImageData(this.im, 0, 0);
}
}
function defaultColorScaling(value) {
var x = (value - this.min) / this.range;
if (x < 0) {
x = 0;
} else if (x > 1) {
x = 1;
}
var g = Math.pow(x, 2);
var b = 0.75 - 1.5 * Math.abs(x - 0.45);
var r = 0;
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
return Spectrogram;
})();
const size = 1024;
const length = 200;
const dataMax = 100;
const velMax = 1;
const canvas = document.getElementById('spec');
canvas.width = size;
canvas.height = length;
const spec = new Spectrogram({
canvas: canvas,
size: size,
length: length
});
const data = [];
const vel = [];
for (var i = 0; i < size; ++i) {
data.push(0);
vel.push(0);
}
// our animation loop
function draw() {
for (var i = 0; i < size; ++i) {
data[i] += vel[i];
if (i > 0) data[i] += vel[i - 1];
if (i < size - 1) data[i] += vel[i + 1];
if (data[i] >= dataMax) {
data[i] = dataMax;
vel[i] = 0;
} else if (data[i] <= -dataMax) {
data[i] = -dataMax;
vel[i] = 0;
}
if (vel[i] == 0) {
vel[i] = (Math.random() - 0.5) * 100;
}
}
spec.addData(data);
requestAnimationFrame(draw);
}
draw();
<div style="width: 100%; height: 100%; padding: 0; margin: 0;">
<canvas style="width: 100%; height: 100%;" id="spec"></canvas>
</div>
Algorithm "NoObtuse" in javascript.
I have to implement this algo below in a canvas.
The user put a set of points and click on the button to call the function "noObtuse" and I have to draw the graph (see image).
How can I do it ?
No obtuse algoritm
EDIT:
I change the code with the informations of "MBo" but I don't get what I need, the nextPoint is not the right one (Neither in CW nor in CCW).
My code:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
this.col = 'red';
}
drawDot() {
fill(this.col);
ellipse(this.x, this.y, 8, 8);
}
tagNotExtreme() {
this.col = 'blue';
}
setColor(color) {
this.col = color;
}
}
var points;
var vertices = [];
var rightMost= null;
var tableau;
var angle1;
var angle2;
var a ;
var b;
var p1 ;
var indice;
var pointSize= 0;
var canvas;
var button_CH;
var button_clear;
var isEnter;
var times_clicked = 0;
function setup() {
canvas = createCanvas(500, 500);
background(255);
pointSize = 8;
points = [];
tableau = [];
rightMost = {x: 0, y:0};
a = {x: 0, y:0};
b = {x: 0, y:0};
p1 = {x: 0, y:0};
indice = 2;
isEnter = -1;
angle1 = 0;
angle2 = 0;
canvas.parent('candiv');
}
function draw() {
}
function contient(point, tableau){
var oui = -1;
for (var i = 0; i < tableau.length;i++){
if (point == tableau[i]){
oui = 1;
}
}
return oui;
}
function NoObtuse(){
p1 = rightMost;
angle1 = 0;
angle2 = 0;
a = points[0];
for (var k=0; k < points.length; k++){
angle = Math.atan2(points[k].y - rightMost.y, points[k].x - rightMost.x);
if (angle < angle1){
angle1 = angle;
a = points[k];
}
}
tableau[0] = p1;
tableau[1] = a;
var flag = -1;
var n = points.length-2;
var i;
for (i = 0; i <= 0; i++){
if (contient(points[i],tableau) == -1){
b = nextPoint(points[i], a, flag);
if (Math.degrees(find_angle(points[i],a,b)) <= 90 ){
points[i+1] = a;
a = b;
} else {
points[i+1] = b;
flag = -flag;
}
tableau[indice] = points[i];
indice = indice+1;
// ...
} else{
if(i == 0){
strokeWeight(2);
line(p1.x, p1.y, a.x, a.y);
}
}
}
points[points.length - 1] = a;
}
Math.degrees = function(radians) {
return radians * 180 / Math.PI;
};
function find_angle(A,B,C) {
var AB = Math.sqrt(Math.pow(B.x-A.x,2)+ Math.pow(B.y-A.y,2));
var BC = Math.sqrt(Math.pow(B.x-C.x,2)+ Math.pow(B.y-C.y,2));
var AC = Math.sqrt(Math.pow(C.x-A.x,2)+ Math.pow(C.y-A.y,2));
return Math.acos((BC*BC+AB*AB-AC*AC)/(2*BC*AB));
}
function nextPoint(pi, a, flag){
/* HELP : Let ρ be a ray attached to a and continuing the directed segment pia*/
fill('blue');
strokeWeight(2);
line(pi.x, pi.y, a.x, a.y);
b = a; /* HELP : Let b be the first point encountered by ρ when rotating it around a in the direction indicated by flag*/
return b;
}
function mousePressed() {
if(isEnter == -1){
newPointN = new Point(mouseX,mouseY);
if (rightMost && (rightMost.x < newPointN.x)) {
rightMost = newPointN;
}
points.push(newPointN);
newPointN.drawDot();
}else{
setup();
}
}
function keyPressed() {
if (keyCode == 82) {
setup();
} else if (keyCode == ENTER){
NoObtuse();
isEnter = 1;
} else if (keyCode == 77){
rightMost.setColor('green');
rightMost.drawDot();
}
}
To get the first point from the rightmost, choose one with the smallest value of
angle = Math.atan2(P[i].Y - rightmost.Y, P[i].X - rightmost.X)
To get the next point that goes after current A and B points in CCW order, you can compare angles A-B-C, calculated through atan2, cross-product and dot product of vectors.
angle = Math.atan2(cross(AB, BC), dot(AB, BC))
I am new to game development and I have build a car game where the automatically moves and when it hits a monster.Now I want to make the car move towards the monster.So I looked into the path finding algorithms and for now I thought to implement A-Star path finding algorithm in my game.So the function for finding path is like below:
function findPath(world, pathStart, pathEnd)
{
// shortcuts for speed
var abs = Math.abs;
var max = Math.max;
var pow = Math.pow;
var sqrt = Math.sqrt;
// the world data are integers:
// anything higher than this number is considered blocked
// this is handy is you use numbered sprites, more than one
// of which is walkable road, grass, mud, etc
var maxWalkableTileNum = 0;
// keep track of the world dimensions
// Note that this A-star implementation expects the world array to be square:
// it must have equal height and width. If your game world is rectangular,
// just fill the array with dummy values to pad the empty space.
var worldWidth = world[0].length;
var worldHeight = world.length;
var worldSize = worldWidth * worldHeight;
// which heuristic should we use?
// default: no diagonals (Manhattan)
var distanceFunction = ManhattanDistance;
var findNeighbours = function(){}; // empty
/*
// alternate heuristics, depending on your game:
// diagonals allowed but no sqeezing through cracks:
var distanceFunction = DiagonalDistance;
var findNeighbours = DiagonalNeighbours;
// diagonals and squeezing through cracks allowed:
var distanceFunction = DiagonalDistance;
var findNeighbours = DiagonalNeighboursFree;
// euclidean but no squeezing through cracks:
var distanceFunction = EuclideanDistance;
var findNeighbours = DiagonalNeighbours;
// euclidean and squeezing through cracks allowed:
var distanceFunction = EuclideanDistance;
var findNeighbours = DiagonalNeighboursFree;
*/
// distanceFunction functions
// these return how far away a point is to another
function ManhattanDistance(Point, Goal)
{ // linear movement - no diagonals - just cardinal directions (NSEW)
return abs(Point.x - Goal.x) + abs(Point.y - Goal.y);
}
function DiagonalDistance(Point, Goal)
{ // diagonal movement - assumes diag dist is 1, same as cardinals
return max(abs(Point.x - Goal.x), abs(Point.y - Goal.y));
}
function EuclideanDistance(Point, Goal)
{ // diagonals are considered a little farther than cardinal directions
// diagonal movement using Euclide (AC = sqrt(AB^2 + BC^2))
// where AB = x2 - x1 and BC = y2 - y1 and AC will be [x3, y3]
return sqrt(pow(Point.x - Goal.x, 2) + pow(Point.y - Goal.y, 2));
}
// Neighbours functions, used by findNeighbours function
// to locate adjacent available cells that aren't blocked
// Returns every available North, South, East or West
// cell that is empty. No diagonals,
// unless distanceFunction function is not Manhattan
function Neighbours(x, y)
{
var N = y - 1,
S = y + 1,
E = x + 1,
W = x - 1,
myN = N > -1 && canWalkHere(x, N),
myS = S < worldHeight && canWalkHere(x, S),
myE = E < worldWidth && canWalkHere(E, y),
myW = W > -1 && canWalkHere(W, y),
result = [];
if(myN)
result.push({x:x, y:N});
if(myE)
result.push({x:E, y:y});
if(myS)
result.push({x:x, y:S});
if(myW)
result.push({x:W, y:y});
findNeighbours(myN, myS, myE, myW, N, S, E, W, result);
return result;
}
// returns every available North East, South East,
// South West or North West cell - no squeezing through
// "cracks" between two diagonals
function DiagonalNeighbours(myN, myS, myE, myW, N, S, E, W, result)
{
if(myN)
{
if(myE && canWalkHere(E, N))
result.push({x:E, y:N});
if(myW && canWalkHere(W, N))
result.push({x:W, y:N});
}
if(myS)
{
if(myE && canWalkHere(E, S))
result.push({x:E, y:S});
if(myW && canWalkHere(W, S))
result.push({x:W, y:S});
}
}
// returns every available North East, South East,
// South West or North West cell including the times that
// you would be squeezing through a "crack"
function DiagonalNeighboursFree(myN, myS, myE, myW, N, S, E, W, result)
{
myN = N > -1;
myS = S < worldHeight;
myE = E < worldWidth;
myW = W > -1;
if(myE)
{
if(myN && canWalkHere(E, N))
result.push({x:E, y:N});
if(myS && canWalkHere(E, S))
result.push({x:E, y:S});
}
if(myW)
{
if(myN && canWalkHere(W, N))
result.push({x:W, y:N});
if(myS && canWalkHere(W, S))
result.push({x:W, y:S});
}
}
// returns boolean value (world cell is available and open)
function canWalkHere(x, y)
{
return ((world[x] != null) &&
(world[x][y] != null) &&
(world[x][y] <= maxWalkableTileNum));
};
// Node function, returns a new object with Node properties
// Used in the calculatePath function to store route costs, etc.
function Node(Parent, Point)
{
var newNode = {
// pointer to another Node object
Parent:Parent,
// array index of this Node in the world linear array
value:Point.x + (Point.y * worldWidth),
// the location coordinates of this Node
x:Point.x,
y:Point.y,
// the heuristic estimated cost
// of an entire path using this node
f:0,
// the distanceFunction cost to get
// from the starting point to this node
g:0
};
return newNode;
}
// Path function, executes AStar algorithm operations
function calculatePath()
{
// create Nodes from the Start and End x,y coordinates
var mypathStart = Node(null, {x:pathStart[0], y:pathStart[1]});
var mypathEnd = Node(null, {x:pathEnd[0], y:pathEnd[1]});
// create an array that will contain all world cells
var AStar = new Array(worldSize);
// list of currently open Nodes
var Open = [mypathStart];
// list of closed Nodes
var Closed = [];
// list of the final output array
var result = [];
// reference to a Node (that is nearby)
var myNeighbours;
// reference to a Node (that we are considering now)
var myNode;
// reference to a Node (that starts a path in question)
var myPath;
// temp integer variables used in the calculations
var length, max, min, i, j;
// iterate through the open list until none are left
while(length = Open.length)
{
max = worldSize;
min = -1;
for(i = 0; i < length; i++)
{
if(Open[i].f < max)
{
max = Open[i].f;
min = i;
}
}
// grab the next node and remove it from Open array
myNode = Open.splice(min, 1)[0];
// is it the destination node?
if(myNode.value === mypathEnd.value)
{
myPath = Closed[Closed.push(myNode) - 1];
do
{
result.push([myPath.x, myPath.y]);
}
while (myPath = myPath.Parent);
// clear the working arrays
AStar = Closed = Open = [];
// we want to return start to finish
result.reverse();
}
else // not the destination
{
// find which nearby nodes are walkable
myNeighbours = Neighbours(myNode.x, myNode.y);
// test each one that hasn't been tried already
for(i = 0, j = myNeighbours.length; i < j; i++)
{
myPath = Node(myNode, myNeighbours[i]);
if (!AStar[myPath.value])
{
// estimated cost of this particular route so far
myPath.g = myNode.g + distanceFunction(myNeighbours[i], myNode);
// estimated cost of entire guessed route to the destination
myPath.f = myPath.g + distanceFunction(myNeighbours[i], mypathEnd);
// remember this new path for testing above
Open.push(myPath);
// mark this node in the world graph as visited
AStar[myPath.value] = true;
}
}
// remember this route as having no more untested options
Closed.push(myNode);
}
} // keep iterating until the Open list is empty
return result;
}
// actually calculate the a-star path!
// this returns an array of coordinates
// that is empty if no path is possible
return calculatePath();
} // end of findPath() function
and then call the function by
currentPath = findPath(world,pathStart,pathEnd);
But not working.My working pen
Any help is appreciated.
Here is a simple path finding script to start from.
Once you have a path calculated, it should be trivial to move the car along it.
This script has two stages:
World generation
Where the map is scanned for hindrances and monsters
Path generation
Where a monster is found and a path is being calculated.
//HTML elements
var canvas = document.body.appendChild(document.createElement("canvas"));
canvas.height = 500;
canvas.width = canvas.height;
var ctx = canvas.getContext("2d");
//Logic elements
var tileSize = 16;
var monster = {
x: Math.floor(Math.random() * Math.ceil(canvas.width / tileSize) / 2) * 2,
y: Math.floor(Math.random() * Math.ceil(canvas.height / tileSize) / 2) * 2
};
var player = {
x: 9,
y: 9
};
var aStar = {
path: [],
opened: [],
closed: [],
done: false
};
//Simple distance formular
function distance(a, b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
//Tested Tiles
ctx.fillStyle = "cyan";
for (var pi = 0; pi < aStar.closed.length; pi++) {
var p = aStar.closed[pi];
ctx.fillRect(p.x * tileSize, p.y * tileSize, tileSize, tileSize);
}
//Path
ctx.fillStyle = "blue";
for (var pi = 0; pi < aStar.path.length; pi++) {
var p = aStar.path[pi];
ctx.fillRect(p.x * tileSize, p.y * tileSize, tileSize, tileSize);
}
//Monster
ctx.fillStyle = "red";
ctx.fillRect(monster.x * tileSize, monster.y * tileSize, tileSize, tileSize);
//Player
ctx.fillStyle = "green";
ctx.fillRect(player.x * tileSize, player.y * tileSize, tileSize, tileSize);
//Tiles
for (var x = 0; x < Math.ceil(canvas.width / tileSize); x++) {
for (var y = 0; y < Math.ceil(canvas.height / tileSize); y++) {
ctx.strokeRect(x * tileSize, y * tileSize, tileSize, tileSize);
}
}
}
function main() {
//If no steps, open "player"
if (aStar.opened.length == 0) {
aStar.opened.push({
x: player.x,
y: player.y,
step: 0
});
}
//Check for monster
if ((aStar.opened.some(function(c) {
return c.x === monster.x && c.y === monster.y;
})) == true) {
//if monster found
if (aStar.path.length < 1) {
//If no steps in path, add monster as first
aStar.path.push(aStar.opened.find(function(c) {
return c.x === monster.x && c.y === monster.y;
}));
} else if ((aStar.path.length > 0 ? aStar.path[aStar.path.length - 1].step == 0 : false) === false) {
//If last step of path isn't player, compute a step to path
var lastTile = aStar.path[aStar.path.length - 1];
var bestTile = {
x: lastTile.x,
y: lastTile.y,
step: lastTile.step
};
//Loop through tiles adjacent to the last path tile and pick the "best"
for (var x = lastTile.x - 1; x < lastTile.x + 2; x++) {
for (var y = lastTile.y - 1; y < lastTile.y + 2; y++) {
var suspect = aStar.closed.find(function(c) {
return c.x === x && c.y === y;
});
if (suspect !== void 0) {
if (suspect.step + distance(suspect, player) < bestTile.step + distance(bestTile, player)) {
bestTile = suspect;
}
}
}
}
//Add best tile to path
aStar.path.push(bestTile);
}
} else {
//If monster isn't found, continue world mapping
//"newOpen" will hold the next "opened" list
var newOpen = [];
//For each opened, check neighbours
for (var oi = 0; oi < aStar.opened.length; oi++) {
var o = aStar.opened[oi];
for (var x = o.x - 1; x < o.x + 2; x++) {
for (var y = o.y - 1; y < o.y + 2; y++) {
if (x === o.x && y === o.y ||
aStar.closed.some(function(c) {
return c.x === x && c.y === y;
}) ||
aStar.opened.some(function(c) {
return c.x === x && c.y === y;
}) ||
newOpen.some(function(c) {
return c.x === x && c.y === y;
})) {
continue;
}
//If neighbours isn't in any list, add it to the newOpen list
newOpen.push({
x: x,
y: y,
step: o.step + 1
});
}
}
}
//Close the previously opened list
aStar.closed = aStar.closed.concat(aStar.opened);
//Add new opened list
aStar.opened = newOpen;
}
//Draw progress
draw();
requestAnimationFrame(main);
}
//Start process
requestAnimationFrame(main);
EDIT 1 - No pathfinding
I am not even sure you need pathfinding for this.
In the example below the cars are simply pushed towards a target relative to their angle to it:
var __extends = (this && this.__extends) || (function() {
var extendStatics = Object.setPrototypeOf ||
({
__proto__: []
}
instanceof Array && function(d, b) {
d.__proto__ = b;
}) ||
function(d, b) {
for (var p in b)
if (b.hasOwnProperty(p)) d[p] = b[p];
};
return function(d, b) {
extendStatics(d, b);
function __() {
this.constructor = d;
}
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Game;
(function(Game) {
var GameImage = (function() {
function GameImage(name, src) {
this.name = name;
this.src = src;
this.node = document.createElement("img");
GameImage._pending++;
this.node.onload = GameImage._loading;
this.node.src = this.src;
GameImage.all.push(this);
}
GameImage.loaded = function() {
return this._loaded === this._pending;
};
GameImage._loading = function() {
this._loaded++;
};
GameImage.getImage = function(id) {
return this.all.find(function(img) {
return img.name === id;
});
};
return GameImage;
}());
GameImage.all = [];
GameImage._loaded = 0;
GameImage._pending = 0;
new GameImage("background", "http://res.cloudinary.com/dfhppjli0/image/upload/c_scale,w_2048/v1492045665/road_dwsmux.png");
new GameImage("hero", "http://res.cloudinary.com/dfhppjli0/image/upload/c_scale,w_32/v1491958999/car_p1k2hw.png");
new GameImage("monster", "http://res.cloudinary.com/dfhppjli0/image/upload/v1491958478/monster_rsm0po.png");
new GameImage("hero_other", "http://res.cloudinary.com/dfhppjli0/image/upload/v1492579967/car_03_ilt08o.png");
function distance(a, b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
function degreeToRadian(degrees) {
return degrees * (Math.PI / 180);
}
function radianToDegree(radians) {
return radians * (180 / Math.PI);
}
function angleBetweenTwoPoints(p1, p2) {
return Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
}
var Actor = (function() {
function Actor() {
this.angle = 0;
}
Actor.prototype.main = function() {};
Actor.prototype.render = function(ctx) {
if (this.angle != 0) {
var rads = degreeToRadian(this.angle - 90);
ctx.translate(this.position.x + 0.5 * this.image.node.naturalWidth, this.position.y + 0.5 * this.image.node.naturalHeight);
ctx.rotate(rads);
ctx.drawImage(this.image.node, 0, 0);
ctx.rotate(-rads);
ctx.translate(-(this.position.x + 0.5 * this.image.node.naturalWidth), -(this.position.y + 0.5 * this.image.node.naturalHeight));
} else {
ctx.drawImage(this.image.node, this.position.x, this.position.y);
}
};
return Actor;
}());
var Monster = (function(_super) {
__extends(Monster, _super);
function Monster(position) {
var _this = _super.call(this) || this;
_this.position = position;
_this.image = GameImage.getImage("monster");
Monster.all.push(_this);
return _this;
}
return Monster;
}(Actor));
Monster.all = [];
var Car = (function(_super) {
__extends(Car, _super);
function Car(position, target) {
if (target === void 0) {
target = null;
}
var _this = _super.call(this) || this;
_this.position = position;
_this.target = target;
_this.hitCount = 0;
_this.image = GameImage.getImage("hero");
_this.speed = 10;
Car.all.push(_this);
return _this;
}
Car.prototype.main = function() {
var angle = angleBetweenTwoPoints(this.target.position, this.position);
var cos = Math.cos(degreeToRadian(angle)) * -1;
var sin = Math.sin(degreeToRadian(angle));
this.angle = angle;
this.position.x += cos * this.speed;
this.position.y -= sin * this.speed;
if (distance(this.position, this.target.position) < 10) {
this.target.position.x = Math.random() * mainCanvas.width;
this.target.position.y = Math.random() * mainCanvas.height;
this.hitCount++;
console.log("Hit!");
}
};
return Car;
}(Actor));
Car.all = [];
var background = GameImage.getImage("background");
var mainCanvas = document.body.appendChild(document.createElement("canvas"));
mainCanvas.width = background.node.naturalWidth;
mainCanvas.height = background.node.naturalHeight;
var ctx = mainCanvas.getContext("2d");
var monster1 = new Monster({
x: Math.random() * mainCanvas.width,
y: Math.random() * mainCanvas.height
});
var monster2 = new Monster({
x: Math.random() * mainCanvas.width,
y: Math.random() * mainCanvas.height
});
new Car({
x: Math.random() * mainCanvas.width,
y: Math.random() * mainCanvas.height
}, monster1);
new Car({
x: Math.random() * mainCanvas.width,
y: Math.random() * mainCanvas.height
}, monster2);
function main() {
ctx.drawImage(background.node, 0, 0);
for (var ci = 0; ci < Car.all.length; ci++) {
var c = Car.all[ci];
c.main();
c.render(ctx);
}
for (var mi = 0; mi < Monster.all.length; mi++) {
var m = Monster.all[mi];
m.main();
m.render(ctx);
}
requestAnimationFrame(main);
}
requestAnimationFrame(main);
})(Game || (Game = {}));
As long as there are not obstacles, this works fine.