Canvas drawing using query/angular - javascript

Here's what I'm trying to achieve. Draw a circle(first circle) on the screen on a mouse click. Then draw successive circles on successive mouse clicks and connect each to the first circle.
I've managed to get till here.
Now the task is if any of the circles have the same y-coordinate as the first one, the connection is a straight line, else it should be a s-curve/inverted s-curve depending on whether the next circle is above or below the first one based on its y-axis.
It may be assumed that all successive circle will be on the right of the first circle.
Here's my code
var app = angular.module('plunker', []);
app.controller('MainController', function($scope) {
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
var isDown=false;
var startX,startY;
var radius=10;
var lastX,lastY;
ctx.fillStyle='red';
$("#canvas").mousedown(function(e){handleMouseDown(e);});
function drawCircle(cx,cy){
if(lastX){
ctx.globalCompositeOperation='destination-over';
ctx.beginPath();
ctx.moveTo(lastX,lastY);
ctx.lineTo(cx,cy);
ctx.stroke();
ctx.globalCompositeOperation='source-over';
}else{
lastX=cx;
lastY=cy;
}
ctx.beginPath();
ctx.arc(cx,cy,radius,0,Math.PI*2);
ctx.closePath();
ctx.fill();
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mx=parseInt(e.clientX-offsetX);
my=parseInt(e.clientY-offsetY);
drawCircle(mx,my);
}
});
Here's a link to the plunk that will demonstrate the behavior
http://plnkr.co/edit/rYVLgB14IutNh1F4MN6T?p=preview
Any help appreciated.

I don't know exactly which kind of s-curve are you interested in. As I understand it will be always only two points to connect, the first point and the rest, and you are looking for some sort of quadratic curve to do so. Under this situation you can build a s-curve by joining two ctx.quadraticCurveTo calls.
ctx.beginPath();
ctx.moveTo(lastX,lastY);
ctx.quadraticCurveTo(
(lastX+cx)/2, lastY,
(lastX+cx)/2, (lastY+cy)/2
);
ctx.quadraticCurveTo(
(lastX+cx)/2, cy,
cx, cy
);
ctx.lineWidth = 3;
http://plnkr.co/edit/t10cMPcUtX5ifkWi2LBF?p=preview

To make each of your connectors avoid existing circles, you must use a pathfinding algorithm (A* for example).
Pathfinding algorithms will give you a set of points from Circle1 to Circle2 that avoid all other circles.
Then you can use that set of points to build a connector between those circles using a Spline. See this very good answer by Stackoverflow's Ken Fyrstenberg on how to draw a spline. Make sure you keep the tension on the spline tight (closer to zero) so that your spline connector doesn't stray too far from the unobstructed path:
how to draw smooth curve through N points using javascript HTML5 canvas?
This is a nice script implementing the A* algorithm by Brian Grinstead:
https://github.com/bgrins/javascript-astar/
And here's a Demo of Brian Grinstead's A* script:
http://www.briangrinstead.com/files/astar/
To avoid a link-only answer, I'm attaching Brian's script from GitHub below...
But seriously...if GitHub disappears many of us subscribers are in trouble!
// javascript-astar 0.4.0
// http://github.com/bgrins/javascript-astar
// Freely distributable under the MIT License.
// Implements the astar search algorithm in javascript using a Binary Heap.
// Includes Binary Heap (with modifications) from Marijn Haverbeke.
// http://eloquentjavascript.net/appendix2.html
(function(definition) {
/* global module, define */
if(typeof module === 'object' && typeof module.exports === 'object') {
module.exports = definition();
} else if(typeof define === 'function' && define.amd) {
define([], definition);
} else {
var exports = definition();
window.astar = exports.astar;
window.Graph = exports.Graph;
}
})(function() {
function pathTo(node){
var curr = node,
path = [];
while(curr.parent) {
path.push(curr);
curr = curr.parent;
}
return path.reverse();
}
function getHeap() {
return new BinaryHeap(function(node) {
return node.f;
});
}
var astar = {
/**
* Perform an A* Search on a graph given a start and end node.
* #param {Graph} graph
* #param {GridNode} start
* #param {GridNode} end
* #param {Object} [options]
* #param {bool} [options.closest] Specifies whether to return the
path to the closest node if the target is unreachable.
* #param {Function} [options.heuristic] Heuristic function (see
* astar.heuristics).
*/
search: function(graph, start, end, options) {
graph.cleanDirty();
options = options || {};
var heuristic = options.heuristic || astar.heuristics.manhattan,
closest = options.closest || false;
var openHeap = getHeap(),
closestNode = start; // set the start node to be the closest if required
start.h = heuristic(start, end);
openHeap.push(start);
while(openHeap.size() > 0) {
// Grab the lowest f(x) to process next. Heap keeps this sorted for us.
var currentNode = openHeap.pop();
// End case -- result has been found, return the traced path.
if(currentNode === end) {
return pathTo(currentNode);
}
// Normal case -- move currentNode from open to closed, process each of its neighbors.
currentNode.closed = true;
// Find all neighbors for the current node.
var neighbors = graph.neighbors(currentNode);
for (var i = 0, il = neighbors.length; i < il; ++i) {
var neighbor = neighbors[i];
if (neighbor.closed || neighbor.isWall()) {
// Not a valid node to process, skip to next neighbor.
continue;
}
// The g score is the shortest distance from start to current node.
// We need to check if the path we have arrived at this neighbor is the shortest one we have seen yet.
var gScore = currentNode.g + neighbor.getCost(currentNode),
beenVisited = neighbor.visited;
if (!beenVisited || gScore < neighbor.g) {
// Found an optimal (so far) path to this node. Take score for node to see how good it is.
neighbor.visited = true;
neighbor.parent = currentNode;
neighbor.h = neighbor.h || heuristic(neighbor, end);
neighbor.g = gScore;
neighbor.f = neighbor.g + neighbor.h;
graph.markDirty(neighbor);
if (closest) {
// If the neighbour is closer than the current closestNode or if it's equally close but has
// a cheaper path than the current closest node then it becomes the closest node
if (neighbor.h < closestNode.h || (neighbor.h === closestNode.h && neighbor.g < closestNode.g)) {
closestNode = neighbor;
}
}
if (!beenVisited) {
// Pushing to heap will put it in proper place based on the 'f' value.
openHeap.push(neighbor);
}
else {
// Already seen the node, but since it has been rescored we need to reorder it in the heap
openHeap.rescoreElement(neighbor);
}
}
}
}
if (closest) {
return pathTo(closestNode);
}
// No result was found - empty array signifies failure to find path.
return [];
},
// See list of heuristics: http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html
heuristics: {
manhattan: function(pos0, pos1) {
var d1 = Math.abs(pos1.x - pos0.x);
var d2 = Math.abs(pos1.y - pos0.y);
return d1 + d2;
},
diagonal: function(pos0, pos1) {
var D = 1;
var D2 = Math.sqrt(2);
var d1 = Math.abs(pos1.x - pos0.x);
var d2 = Math.abs(pos1.y - pos0.y);
return (D * (d1 + d2)) + ((D2 - (2 * D)) * Math.min(d1, d2));
}
},
cleanNode:function(node){
node.f = 0;
node.g = 0;
node.h = 0;
node.visited = false;
node.closed = false;
node.parent = null;
}
};
/**
* A graph memory structure
* #param {Array} gridIn 2D array of input weights
* #param {Object} [options]
* #param {bool} [options.diagonal] Specifies whether diagonal moves are allowed
*/
function Graph(gridIn, options) {
options = options || {};
this.nodes = [];
this.diagonal = !!options.diagonal;
this.grid = [];
for (var x = 0; x < gridIn.length; x++) {
this.grid[x] = [];
for (var y = 0, row = gridIn[x]; y < row.length; y++) {
var node = new GridNode(x, y, row[y]);
this.grid[x][y] = node;
this.nodes.push(node);
}
}
this.init();
}
Graph.prototype.init = function() {
this.dirtyNodes = [];
for (var i = 0; i < this.nodes.length; i++) {
astar.cleanNode(this.nodes[i]);
}
};
Graph.prototype.cleanDirty = function() {
for (var i = 0; i < this.dirtyNodes.length; i++) {
astar.cleanNode(this.dirtyNodes[i]);
}
this.dirtyNodes = [];
};
Graph.prototype.markDirty = function(node) {
this.dirtyNodes.push(node);
};
Graph.prototype.neighbors = function(node) {
var ret = [],
x = node.x,
y = node.y,
grid = this.grid;
// West
if(grid[x-1] && grid[x-1][y]) {
ret.push(grid[x-1][y]);
}
// East
if(grid[x+1] && grid[x+1][y]) {
ret.push(grid[x+1][y]);
}
// South
if(grid[x] && grid[x][y-1]) {
ret.push(grid[x][y-1]);
}
// North
if(grid[x] && grid[x][y+1]) {
ret.push(grid[x][y+1]);
}
if (this.diagonal) {
// Southwest
if(grid[x-1] && grid[x-1][y-1]) {
ret.push(grid[x-1][y-1]);
}
// Southeast
if(grid[x+1] && grid[x+1][y-1]) {
ret.push(grid[x+1][y-1]);
}
// Northwest
if(grid[x-1] && grid[x-1][y+1]) {
ret.push(grid[x-1][y+1]);
}
// Northeast
if(grid[x+1] && grid[x+1][y+1]) {
ret.push(grid[x+1][y+1]);
}
}
return ret;
};
Graph.prototype.toString = function() {
var graphString = [],
nodes = this.grid, // when using grid
rowDebug, row, y, l;
for (var x = 0, len = nodes.length; x < len; x++) {
rowDebug = [];
row = nodes[x];
for (y = 0, l = row.length; y < l; y++) {
rowDebug.push(row[y].weight);
}
graphString.push(rowDebug.join(" "));
}
return graphString.join("\n");
};
function GridNode(x, y, weight) {
this.x = x;
this.y = y;
this.weight = weight;
}
GridNode.prototype.toString = function() {
return "[" + this.x + " " + this.y + "]";
};
GridNode.prototype.getCost = function(fromNeighbor) {
// Take diagonal weight into consideration.
if (fromNeighbor && fromNeighbor.x != this.x && fromNeighbor.y != this.y) {
return this.weight * 1.41421;
}
return this.weight;
};
GridNode.prototype.isWall = function() {
return this.weight === 0;
};
function BinaryHeap(scoreFunction){
this.content = [];
this.scoreFunction = scoreFunction;
}
BinaryHeap.prototype = {
push: function(element) {
// Add the new element to the end of the array.
this.content.push(element);
// Allow it to sink down.
this.sinkDown(this.content.length - 1);
},
pop: function() {
// Store the first element so we can return it later.
var result = this.content[0];
// Get the element at the end of the array.
var end = this.content.pop();
// If there are any elements left, put the end element at the
// start, and let it bubble up.
if (this.content.length > 0) {
this.content[0] = end;
this.bubbleUp(0);
}
return result;
},
remove: function(node) {
var i = this.content.indexOf(node);
// When it is found, the process seen in 'pop' is repeated
// to fill up the hole.
var end = this.content.pop();
if (i !== this.content.length - 1) {
this.content[i] = end;
if (this.scoreFunction(end) < this.scoreFunction(node)) {
this.sinkDown(i);
}
else {
this.bubbleUp(i);
}
}
},
size: function() {
return this.content.length;
},
rescoreElement: function(node) {
this.sinkDown(this.content.indexOf(node));
},
sinkDown: function(n) {
// Fetch the element that has to be sunk.
var element = this.content[n];
// When at 0, an element can not sink any further.
while (n > 0) {
// Compute the parent element's index, and fetch it.
var parentN = ((n + 1) >> 1) - 1,
parent = this.content[parentN];
// Swap the elements if the parent is greater.
if (this.scoreFunction(element) < this.scoreFunction(parent)) {
this.content[parentN] = element;
this.content[n] = parent;
// Update 'n' to continue at the new position.
n = parentN;
}
// Found a parent that is less, no need to sink any further.
else {
break;
}
}
},
bubbleUp: function(n) {
// Look up the target element and its score.
var length = this.content.length,
element = this.content[n],
elemScore = this.scoreFunction(element);
while(true) {
// Compute the indices of the child elements.
var child2N = (n + 1) << 1,
child1N = child2N - 1;
// This is used to store the new position of the element, if any.
var swap = null,
child1Score;
// If the first child exists (is inside the array)...
if (child1N < length) {
// Look it up and compute its score.
var child1 = this.content[child1N];
child1Score = this.scoreFunction(child1);
// If the score is less than our element's, we need to swap.
if (child1Score < elemScore){
swap = child1N;
}
}
// Do the same checks for the other child.
if (child2N < length) {
var child2 = this.content[child2N],
child2Score = this.scoreFunction(child2);
if (child2Score < (swap === null ? elemScore : child1Score)) {
swap = child2N;
}
}
// If the element needs to be moved, swap it, and continue.
if (swap !== null) {
this.content[n] = this.content[swap];
this.content[swap] = element;
n = swap;
}
// Otherwise, we are done.
else {
break;
}
}
}
};
return {
astar: astar,
Graph: Graph
};
});

Related

Bidirectional A* (A-Star) Not Returning the Shortest Path

For some reason, my implementation of bidirectional A* isn't returning the shortest path in very specific initializations of the graph.
I'm running two A* searches, one from the source to the destination, and one from the destination to the source. From what I've read, when the closed sets for both of these searches intersect, then we've connected the shortest paths from both searches and have found the shortest path.
The problem is, in very specific situations, the closed sets for both searches are intersecting before the searches can actually discover the nodes which should be included in their respective shortest paths. This means A* doesn't get to explore enough nodes to find the shortest path.
Is this intersection condition the right way to go about things, or is there a different condition I should be using to figure out when to stop both searches?
You can run my code here: https://jasperhuangg.github.io/pathfinding-visualizer.
The cases where this problem occurs are certain (not all) situations when both walls and weights have been placed on the grid.
Here is the code if it helps, sorry if it is very messy!:
async function bidirectionalAStar(graph, startNode, finishNode) {
recolorGrid();
searching = true;
const infinity = Number.MAX_VALUE;
var openSource = [];
var openDest = [];
var closedSource = [];
var closedDest = [];
var numSteps = -3; // -2 for both start and finish nodes + -1 for overlapping connecting node
$("#steps-taken").html("Cells Examined: " + numSteps);
const startX = startNode.x;
const startY = startNode.y;
const finishX = finishNode.x;
const finishY = finishNode.y;
var bidirectionalAStarGraph = shallowCopyGraph(graph, []);
// initialize all nodes to dist infinity from the startNode
for (let i = 0; i < bidirectionalAStarGraph.length; i++) {
for (let j = 0; j < bidirectionalAStarGraph[i].length; j++) {
bidirectionalAStarGraph[i][j].fSrc = infinity;
bidirectionalAStarGraph[i][j].gSrc = infinity;
bidirectionalAStarGraph[i][j].hSrc = infinity;
bidirectionalAStarGraph[i][j].fDest = infinity;
bidirectionalAStarGraph[i][j].gDest = infinity;
bidirectionalAStarGraph[i][j].hDest = infinity;
bidirectionalAStarGraph[i][j].setSource = "neither";
bidirectionalAStarGraph[i][j].setDest = "neither";
}
}
// initialize start/finish node distance from start/finish to 0
bidirectionalAStarGraph[startX][startY].fSrc = 0;
bidirectionalAStarGraph[startX][startY].gSrc = 0;
bidirectionalAStarGraph[startX][startY].hSrc = 0;
bidirectionalAStarGraph[startX][startY].setSource = "open";
openSource.push(bidirectionalAStarGraph[startX][startY]);
bidirectionalAStarGraph[finishX][finishY].fDest = 0;
bidirectionalAStarGraph[finishX][finishY].gDest = 0;
bidirectionalAStarGraph[finishX][finishY].hDest = 0;
bidirectionalAStarGraph[finishX][finishY].setDest = "open";
openDest.push(bidirectionalAStarGraph[finishX][finishY]);
var lastNodeSource;
var lastNodeDest;
while (openSource.length > 0 && openDest.length > 0) {
openSource.sort((a, b) => {
if (a.fSrc !== b.fSrc) return a.fSrc - b.fSrc;
else return a.hSrc - b.hSrc;
});
openDest.sort((a, b) => {
if (a.fDest !== b.fDest) return a.fDest - b.fDest;
else return a.hDest - b.hDest;
});
var currNodeSource = openSource.shift();
var currNodeDest = openDest.shift();
$(".currentNodeGray").removeClass("currentNodeGray");
$(".currentNodeSunset").removeClass("currentNodeSunset");
$(".currentNodeOcean").removeClass("currentNodeOcean");
$(".currentNodeChaos").removeClass("currentNodeChaos");
$(".currentNodeGreen").removeClass("currentNodeGreen");
$(".currentNodeCottonCandy").removeClass("currentNodeCottonCandy");
if (checkIntersection(closedSource, closedDest)) {
break; // the paths have reached each other
}
numSteps += 2;
$("#steps-taken").html("Cells Examined: " + numSteps);
currNodeSource.setSource = "closed";
currNodeDest.setDest = "closed";
closedSource.push(currNodeSource);
closedDest.push(currNodeDest);
colorNode(currNodeSource, "currentNode");
colorNode(currNodeDest, "currentNode");
if (lastNodeSource !== undefined && currentSpeed !== "instantaneous")
colorNode(lastNodeSource, "visited");
if (lastNodeDest !== undefined && currentSpeed !== "instantaneous")
colorNode(lastNodeDest, "visited");
if (currentSpeed === "fast") await sleep(20);
else if (currentSpeed === "medium") await sleep(180);
else if (currentSpeed === "slow") await sleep(500);
var validNeighborsSource = [];
var validNeighborsDest = [];
var left = currNodeSource.x - 1;
var right = currNodeSource.x + 1;
var up = currNodeSource.y - 1;
var down = currNodeSource.y + 1;
// consider all of the current node's (from source) valid neighbors
if (left >= 0 && !bidirectionalAStarGraph[left][currNodeSource.y].blocked) {
validNeighborsSource.push(
bidirectionalAStarGraph[left][currNodeSource.y]
);
}
if (
right < grid_width &&
!bidirectionalAStarGraph[right][currNodeSource.y].blocked
) {
validNeighborsSource.push(
bidirectionalAStarGraph[right][currNodeSource.y]
);
}
if (up >= 0 && !bidirectionalAStarGraph[currNodeSource.x][up].blocked) {
validNeighborsSource.push(bidirectionalAStarGraph[currNodeSource.x][up]);
}
if (
down < grid_height &&
!bidirectionalAStarGraph[currNodeSource.x][down].blocked
) {
validNeighborsSource.push(
bidirectionalAStarGraph[currNodeSource.x][down]
);
}
left = currNodeDest.x - 1;
right = currNodeDest.x + 1;
up = currNodeDest.y - 1;
down = currNodeDest.y + 1;
// consider all of the current node's (from dest) valid neighbors
if (left >= 0 && !bidirectionalAStarGraph[left][currNodeDest.y].blocked) {
validNeighborsDest.push(bidirectionalAStarGraph[left][currNodeDest.y]);
}
if (
right < grid_width &&
!bidirectionalAStarGraph[right][currNodeDest.y].blocked
) {
validNeighborsDest.push(bidirectionalAStarGraph[right][currNodeDest.y]);
}
if (up >= 0 && !bidirectionalAStarGraph[currNodeDest.x][up].blocked) {
validNeighborsDest.push(bidirectionalAStarGraph[currNodeDest.x][up]);
}
if (
down < grid_height &&
!bidirectionalAStarGraph[currNodeDest.x][down].blocked
) {
validNeighborsDest.push(bidirectionalAStarGraph[currNodeDest.x][down]);
}
// UPDATE NEIGHBORS FROM SOURCE
for (let i = 0; i < validNeighborsSource.length; i++) {
let neighbor = validNeighborsSource[i];
if (neighbor.setSource === "closed") continue;
let cost = 0;
if (currNodeSource.weighted === true || neighbor.weighted === true)
cost = currNodeSource.gSrc + 10;
else cost = currNodeSource.gSrc + 1;
if (neighbor.setSource === "open" && cost < neighbor.gSrc) {
neighbor.setSource = "neither";
neighbor.gSrc = cost;
neighbor.fSrc = neighbor.gSrc + neighbor.hSrc;
openSource.remove(neighbor);
}
if (neighbor.setSource === "neither") {
openSource.push(neighbor);
neighbor.setSource = "open";
neighbor.gSrc = cost;
neighbor.hSrc = calculateHeuristic(neighbor, finishNode);
neighbor.fSrc = neighbor.gSrc + neighbor.hSrc;
neighbor.predecessorSource = currNodeSource;
}
}
lastNodeSource = currNodeSource;
// UPDATE NEIGHBORS FROM DEST
for (let i = 0; i < validNeighborsDest.length; i++) {
let neighbor = validNeighborsDest[i];
if (neighbor.setDest === "closed") continue;
let cost = 0;
if (currNodeDest.weighted === true || neighbor.weighted === true)
cost = currNodeDest.gDest + 10;
else cost = currNodeDest.gDest + 1;
if (neighbor.setDest === "open" && cost < neighbor.gDest) {
neighbor.setDest = "neither";
neighbor.gDest = cost;
neighbor.fDest = neighbor.gDest + neighbor.hDest;
openDest.remove(neighbor);
}
if (neighbor.setDest === "neither") {
openDest.push(neighbor);
neighbor.setDest = "open";
neighbor.gDest = cost;
neighbor.hDest = calculateHeuristic(neighbor, startNode);
neighbor.fDest = neighbor.gDest + neighbor.hDest;
neighbor.predecessorDest = currNodeDest;
}
}
lastNodeDest = currNodeDest;
}
Without any code I can't identify any specific bugs in your algorithm but the thing about Bidirectional A* is that it is only as good as your A*.
A* is flexible in that it is capable of acting just like a dumb breadth first search and just like a dumb depth first search - usually it's somewhere in the middle, and that "middle" is defined by the quality of your heuristic.
Adding a second A* on the other side is a good way to "speed up" an A* heuristic that leans towards breadth, but, it won't "fix" a heuristic that leans towards depth.
If you want a guarantee that your Bidirectional A* search will always find the shortest possible path, then your heuristic needs to lean towards breadth. (Usually this is done by estimating the heuristic - the imagined cost of a node to explore as the Manhattan distance to the target plus the distance traveled to that node. Then sort the nodes and toss nodes more than 1.5x the lowest node -- 1.5 being a variable you can play with, too high and you'll do a traditional breadth first and too low and you might toss the actual lowest path, if it's a complicated one.)
Sorry for the vagueness, some code snippets might help give more direction!

Find path between points. Maze algorithm too slow

I am creating an algorithm to find the shortest path between two points in a maze but my current solution is too slow.
This is what I did:
Helper classes:
import { Coord } from "./Coord";
export class MazeResult {
position: Coord;
path: Array<Coord>;
constructor (_position: Coord, _path: Array<Coord>) {
this.position = _position;
this.path = _path;
}
}
export class Coord {
coordX: number;
coordY: number;
isFree: boolean;
element: Element;
distance: number;
constructor (xpos: number, ypos: number) {
this.coordX = xpos;
this.coordY = ypos;
this.distance = 0;
}
}
function isValid(visited: Array<Coord>, position: Coord)
{
let checkPosition = mapPositions.find(_p => _p.coordX == position.coordX &&
_p.coordY == position.coordY);
let isVisited = false;
for (var j = 0; j < visited.length; j ++) {
if ((visited[j].coordX == position.coordX && visited[j].coordY == position.coordY)) {
isVisited = true;
break;
}
}
return (position.coordY >= 0) &&
(position.coordY < lines.length) &&
(position.coordX >= 0) &&
(position.coordX < lines[0].length) &&
(checkPosition != undefined && checkPosition.element.elementType == ElementType.FIELD) &&
!isVisited;
}
function findPath(origin: Coord, target: Coord, minDistance: number) {
let queue = Array<MazeResult>();
let validpaths = Array<Array<Coord>>();
// New points, where we did not check the surroundings:
// remember the position and how we got there
// initially our starting point and a path containing only this point
let tmpElement = new MazeResult(origin, [origin]);
queue.push(tmpElement);
while (queue.length > 0) {
// get next position to check viable directions
let pointToReach = queue.shift();
let position = new Coord(0, 0);
let path = new Array<Coord>();
if(pointToReach != undefined){
position = pointToReach.position;
path = pointToReach.path;
}
// all points in each direction
let direction = [
[ position.coordX, position.coordY - 1 ],
[ position.coordX, position.coordY + 1 ],
[ position.coordX - 1, position.coordY ],
[ position.coordX + 1, position.coordY ]
];
for(var i = 0; i < direction.length; i++) {
let newTarget = new Coord(direction[i][0], direction[i][1]);
// is valid is just a function that checks whether the point is free.
if (isValid(path, newTarget)) {
//
let newPath = path.slice(0);
newPath.push(newTarget);
if ((validpaths.length > 0 && validpaths.sort(_p => _p.length)[0].length < newPath.length) ||
(minDistance > 0 && newPath.length > minDistance))
continue;
// check if we are at end
if (newTarget.coordX != target.coordX || newTarget.coordY != target.coordY) {
// remember position and the path to it
tmpElement = new MazeResult(newTarget, newPath);
queue.push(tmpElement);
} else {
// remember this path from start to end
validpaths.push(newPath);
// break here if you want only one shortest path
}
}
}
}
validpaths = validpaths.sort(sortByArrayPosition);
let result = validpaths.shift();
return result;
}
I have added a third paramether minDistance so I can compare with previous paths calculations to different points but this should not be relevant here.
How could I improve the performance of this algorithm?
If you are asking about path finding alghortims. Dijkstra is way to go. Look up this great open source library : Javascript-alghoritms . Essentialy what you want to do, is to create weighted graph representation. (You need to know basics of "Graph theory".)
Your weight between points will be in this case. Euclidian distance between points. And what keyword you should use for each point. (I used Mac Adress in my scenario). In your case it should be the unique id of each point.
You might want read up on Dijkstra's algorithm. It seems like what you have implemented is kind of similar but more complicated. You don't need to keep track of the entire path to each point, just the minimum distance to each position. You get the shortest path simply by going to to the neighboring cell with the lowest distance value.
Edit: looks like somebody beat me to it!
Thanks for the recommendations.
I ended with this solution:
let resultPath: Array<MazePoint>;
let visistedMazePoints: Array<Coord>;
function findBFSPath(origin: Coord, target: Coord) {
resultPath = new Array<MazePoint>();
visistedMazePoints = new Array<Coord>();
availablePaths = new Array<MazePoint>();
let tmpMazePoint = new MazePoint(origin, null);
resultPath.push(tmpMazePoint);
while(resultPath.length > 0) {
let currentPoint = resultPath.shift();
if (currentPoint != undefined &&
currentPoint.position.isEqual(target)) {
return currentPoint;
}
if (currentPoint != undefined &&
visistedMazePoints.find(_v => _v.isEqual(currentPoint.position)) == undefined) {
let neighbourMazePoint: MazePoint;
let xCord: Array<number>;
let yCord: Array<number>;
xCord = [0, -1, 1, 0];
yCord = [-1, 0, 0, 1];
for (let idx = 0; idx < 4; idx++) {
neighbourMazePoint = new MazePoint(new Coord(currentPoint.position.coordX + xCord[idx], currentPoint.position.coordY + yCord[idx]), currentPoint);
if (isValid(visistedMazePoints, neighbourMazePoint.position)) {
if (visistedMazePoints.find(_v => _v.isEqual(currentPoint.position)) == undefined) {
visistedMazePoints.push(currentPoint.position);
}
resultPath.push(neighbourMazePoint);
}
}
}
}
return null;
}

In javascript, after i pass a non null object to a funtion it says the object is null

i was trying to implement the A* algorithm and followed the wikipedia pseudo code to make this.
when i pass a predefined object pixel to the a funtion getG() it says that the object is null. I'm sorry if i am not pointing to a specific problem but i am not even sure how to really specify the problem by name. i have tried commenting out the code to increase readability.
git repository link of the whole project - https://github.com/NirobNabil/WhirlWind
(things are a little messy here because i didn't use github at first and i uploaded it just a little ago for posting the problem)
[ i'm actually making this to use a* to find path for my bot which is powered by arduino. thats why i'm using involt. ]
here goes the code,
$(function() {
// define the height, width and bot size in centemeter
total_width = 200;
total_height = 200;
bot_size = 20;
total_box = (total_height / bot_size) * (total_width / bot_size);
box_in_x = total_width / bot_size;
box_in_y = total_height / bot_size;
//populating the pixels array
populate(total_width / bot_size, total_height / bot_size, "UND");
pathfind(pixels, pixels[13], pixels[pixels.length - 1]);
})
var pixels = []; //an array to hold all the objects(the grid)
var obstacles = []; //array to hold the obstacles
function pixel(X, Y, obs) {
this.X_co_ordinate = X;
this.Y_co_ordinate = Y;
this.state = obs; //availale states OPN, UND, OBS, DIS, NULL
this.g = 0;
this.h = 0;
this.f = 0;
this.last = null;
} //every block in the grid is a pixel
//01719372596
function populate(height, width, obs_val = "UND") {
pixels[0] = new pixel(0, 10, obs_val);
for (h = height, i = 0; h >= 0; h--) {
for (w = 0; w < width; w++, i++) {
var temp_obs = new pixel(w, h, obs_val);
temp_obs.last = pixels[0];
pixels[i] = temp_obs; //saving temp_pixel object to pixels array
}
}
} //populating the grid AKA pixels with pixel objects or blocks
// this funtion is where the problem shows up
function getG(current, start) {
let g = 1;
while (current != start && current.last != start && current) {
current = current.last;
g++;
}
return g;
} //get the g val(cost to come to this pixel from the start) of the current pixel
function getH(current, end) {
let I = Math.abs(current.X_co_ordinate - end.X_co_ordinate) + Math.abs(current.Y_co_ordinate - end.Y_co_ordinate);
return I;
} //get the h val(heuristic) of the current pixel
function getF(start, current, end) {
let G = getG(current, start);
let H = getH(current, end);
return G + H;
} //get the f val(total) of the current pixel
function lowFinArray(arr, start, end) {
// here arr is the grid/pixel
let current_low = arr[0];
for (let i = 0; i < arr.length; i++) {
let getF1 = getF(start, current_low, end);
let getF2 = getF(start, arr[i], end);
if (getF1 < getF2) {
current_low = arr[i];
}
}
console.log("current low");
console.log(current_low);
return current_low;
}
function getneighbours(grid, current) {
let neightbours = [];
neightbours.push(grid[getIndex(current.X_co_ordinate - 1, current.Y_co_ordinate)]);
neightbours.push(grid[getIndex(current.X_co_ordinate + 1, current.Y_co_ordinate)]);
neightbours.push(grid[getIndex(current.X_co_ordinate, current.Y_co_ordinate - 1)]);
neightbours.push(grid[getIndex(current.X_co_ordinate, current.Y_co_ordinate + 1)]);
/*
for(i=0; i<neightbours.length; i++){
neightbours[i].last = current;
}*/
console.log("neightbours");
console.log(neightbours);
return neightbours;
} //get the neighbour pixels of the current pixel
//main algo
function pathfind(grid, start, end) {
let closedSet = [];
let openSet = [];
openSet.push(start);
let current = start;
//trying to debug
console.log("low F in arr");
console.log(lowFinArray(grid, start, end));
console.log(start);
console.log(current);
console.log(end);
console.log(grid);
let x = 0;
while (openSet.length > 0) {
//trying to debug
console.log("executed " + (x++));
console.log("openset");
console.log(openSet);
current = lowFinArray(grid, start, end); //assigning the pixel with lowest f val to current
console.log("current");
console.log(current);
if (current == end) {
console.log(getPath(current));
}
let neighbours = getneighbours(grid, current);
for (let i = 0; i < neighbours.length; i++) {
let neighbour = neighbours[i];
if (closedSet.includes(neighbour)) {
continue;
}
if (!openSet.includes(neighbours)) {
openSet.push(neighbours);
}
//console.log(current);
let getg = getG(current, start);
let geth = getH(current, end);
//console.log(getg);
let tGscore = getg + geth; //here getH is being used as a distance funtion
if (tGscore >= getg) {
continue;
}
neighbour.last = current;
neighbour.g = tGscore;
neighbour.f = getF(neighbour);
}
if (x > 10) {
return 0;
}; //the loop was running forever so i tried this to stop the loop after 10 iterations
}
}
function getPath(current) {
let path = [current];
while (current.last != null) {
path.push(current.last);
}
return path;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
and here is what the console says,
Uncaught TypeError: Cannot read property 'last' of null
at getG (app.js:226)
at getF (app.js:241)
at lowFinArray (app.js:249)
at pathfind (app.js:292)
at HTMLDocument.<anonymous> (app.js:92)
at mightThrow (jquery-3.1.1.js:3570)
at process (jquery-3.1.1.js:3638)
You're doing your checks in the wrong order:
while (current != start && current.last != start && current) {
There's no point in using && current after you've already used current.last.
Perhaps changing the order would solve the problem. It will at least get rid of your current error:
while (current && current != start && current.last != start) {
Regarding the title of this question:
In javascript, after i pass a non null object to a funtion it says the object is null
It may very well be non-null 100% of the time you pass it into the function, but you are repeatedly overwriting its parameter within the function, so all bets are off.

QuadEdge dual edge coordinate's always the origin

I am trying to implement, in Javascript, a divide and conquer algorithm to calculate a Delanay triangulation, in order to obtain a Voronoi diagram. The coded is based on the C++ implementation described in Graphic Gems IV which, in turn, is based on the famous Guibas and Stolfi paper from 1985. It uses a Quad-Edge data structure.
The algorithm divides the set of points in half; then, it triangulates those parts separately; lastly it proceeds to "marry" the two halves.
The problem is that, well, it does not work. It gets stuck in a infinite recursion when "marrying" the two halves. I think the problem might be in the Quad-Edge structure itself, because the first edge's and its destination edge have the same coordinates and its dual and inverted rotation edge point to (0, 0).
Here is the code (the translation from C++ is not 100 %, I asked for help since I am still a JS noob):
//The data structure starts here.
var Edge = function (num, qe) {
var _this = this;
this.num = num;
this.qe = qe;
this.coord = new paper.Point();
this.sym = function () {
return (_this.num < 2) ? _this.qe[_this.num + 2] : this.qe[_this.num - 2];
};
this.oNext = function () {
return this.next;
};
this.setNext = function (next) {
this.next = next
};
this.rot = function () {
return (this.num < 3) ? _this.qe[_this.num + 1] : _this.qe[_this.num - 3];
};
this.invRot = function () {
return (_this.num > 0) ? _this.qe[_this.num - 1] : _this.qe[_this.num + 3];
};
this.oPrev = function () {
return _this.rot().oNext().rot();
};
this.dNext = function () {
return _this.sym().next.sym();
};
this.dPrev = function () {
return _this.invRot().oNext().invRot();
};
this.lNext = function () {
return _this.invRot().oNext().rot();
};
this.lPrev = function () {
return _this.oNext().sym();
};
this.rNext = function () {
return _this.rot().oNext().invRot();
};
this.rPrev = function () {
return _this.sym().oNext();
};
this.dest = function () {
return _this.sym().coord;
};
this.endPoints = function (or, de) {
this.coord = or;
this.sym().coord = de;
};
};
//The first edge always points to itself
//as in edge.oNext().coord === edge.coord
var QuadEdge = function () {
this.edges = new Array(4);
for (var i = 0; i < 4; i++) {
this.edges[i] = new Edge(i, this.edges);
}
this.edges[0].setNext(this.edges[0]);
this.edges[1].setNext(this.edges[3]);
this.edges[2].setNext(this.edges[2]);
this.edges[3].setNext(this.edges[1]);
};
//Constructs and returns a new QuadEdge.
function makeEdge() {
var qe = new QuadEdge();
return qe.edges[0];
}
//Helper function, returns twice of the area of the triangle formed
//by a, b and c. Negative if the triangle is clockwise.
function triArea(a, b, c) {
return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
}
//Tests if the point d is inside the circumcircle of the triangle formed by
//a, b, and c. As described by Guibas and Stolfi in their paper.
function inCircle(a, b, c, d) {
return (Math.pow(a.x, 2) + Math.pow(a.y, 2)) * triArea(b, c, d) -
(Math.pow(b.x, 2) + Math.pow(b.y, 2)) * triArea(a, c, d) +
(Math.pow(c.x, 2) + Math.pow(c.y, 2)) * triArea(a, b, d) -
(Math.pow(d.x, 2) + Math.pow(d.y, 2)) * triArea(a, b, d) > 0;
}
//Tests if triangle is in counterclockwise order.
function cClockwise(a, b, c) {
return triArea(a, b, c) > 0;
}
//Tests if point is left of the provided edge.
function leftOf(point, edge) {
return cClockwise(point, edge.coord, edge.dest());
}
//Tests if point is right of the provided edge.
function rightOf(point, edge) {
return cClockwise(point, edge.dest(), edge.coord);
}
//If a and b are distinct, splice will combine them; if not, it will
//separate them in two.
function splice(a, b) {
var alpha = a.oNext().rot();
var beta = b.oNext().rot();
var t1 = b.oNext();
var t2 = a.oNext();
var t3 = beta.oNext();
var t4 = alpha.oNext();
a.setNext(t1);
b.setNext(t2);
alpha.setNext(t3);
beta.setNext(t4);
}
function deleteEdge(e) {
splice(e, e.oPrev());
splice(e.sym(), e.sym().oPrev());
}
function connect(e1, e2) {
var e = makeEdge();
e.endPoints(e1.dest(), e2.coord);
splice(e, e1.lNext());
splice(e.sym(), e2);
return e;
}
//Fixed thanks to: http://www.rpenalva.com/blog/?p=74
function valid(e, basel) {
return rightOf(e.dest(), basel);
}
//This is the actual algorithm.
function divideAndConquer(vertices) {
if (vertices.length === 2) {
var a = makeEdge();
a.coord = vertices[0];
a.sym().coord = vertices[1];
return {right: a, left: a.sym()};
}
else if (vertices.length === 3) {
var a = makeEdge(), b = makeEdge();
splice(a.sym(), b);
a.coord = vertices[0];
b.coord = vertices[1];
a.sym().coord = b.coord;
b.sym().coord = vertices[2];
if (cClockwise(vertices[0], vertices[1], vertices[2])) {
connect(b, a);
return {right: a, left: b.sym()};
}
else if (cClockwise(vertices[0], vertices[2], vertices[1])) {
var c = connect(b, a);
return {right: c.sym(), left: c};
}
else
return {right: a, left: b.sym()};
}
else if (vertices.length >= 4) {
var half = Math.floor(vertices.length / 2);
var lObjects = divideAndConquer(vertices.slice(0, half));
var rObjects = divideAndConquer(vertices.slice(half, vertices.length));
var ldo = lObjects.right, ldi = lObjects.left;
var rdi = rObjects.right, rdo = rObjects.left;
while (true) {
if (leftOf(rdi.coord, ldi))
ldi = ldi.lNext();
else if (rightOf(ldi.coord, rdi))
rdi = rdi.rPrev();
else
break;
}
var basel = connect(rdi.sym(), ldi);
if (ldi.coord === ldo.coord)
ldo = basel.sym();
if (rdi.coord === rdo.coord)
rdo = basel;
while (true) {
var lcand = basel.sym().oNext();
if (valid(lcand, basel)) {
while (inCircle(basel.dest(), basel.coord, lcand.dest(), lcand.oPrev().dest())) {
var t = lcand.oNext();
deleteEdge(lcand);
lcand = t;
}
}
var rcand = basel.oPrev();
if (valid(rcand, basel)) {
while (inCircle(basel.dest(), basel.coord, rcand.dest(), rcand.oPrev().dest())) {
var t = rcand.oPrev();
deleteEdge(rcand);
rcand = t;
}
}
//This is the part where it gets stuck,
//it never reaches this condition.
if (!valid(lcand, basel) && !valid(rcand, basel)) {
break;
}
if (!valid(lcand, basel) || (valid(rcand, basel) && inCircle(lcand.dest(), lcand.coord, rcand.coord, rcand.dest()))) {
basel = connect(rcand, basel.sym());
}
else {
basel = connect(basel.sym(), lcand.sym());
}
}
return {right: ldo, left: rdo};
}
}
The point class is from PaperJS.
EDIT: set up a Codepen illustrating the problem; click in the output window to add the points. The recursions happens when the fourth point is added.
EDIT 2: fixed the infinite recursion, caused by the valid function, thanks to code found on Ruben Penalva's blog. But, the problem of the dual edge pointing to the origin still persists. That is:
edge.rot().coord === (0, 0)
That makes me think even more that the problem is with the QuadEdge structure itself.
EDIT 3: reading the code carefully for the nth time, I realized that the coordinates for the rotation and inversed rotation edge are never set. So I tried setting them by rotating the original edge. The output now draws something, though I'm pretty sure thats not a Delaunay triangulation. I updated the Codepen example with the progress.

Finding entry in 'different places' when navigating an implicit binary search tree

So I created an implicit binary tree, and a simple way of navigating through that tree that reacts when, for example, you push the left arrow, right arrow, or up arrow:
var items = [ .... ];
function newItem() {
var me = this;
this.item_location = 1;
this.leftItem = function() {
var left = items[(me.item_location * 2)];
if(left){
me.theItem = //create element, insert item from 'left' var, display item
me.item_location *= 2;
} else {}
}
this.rightItem = function() {
var right = items[(me.item_location * 2) + 1];
if(right){
me.theItem = //create element, insert item from 'right' var, display item
me.item_location = (me.item_location * 2) + 1;
} else {}
this.upItem = function() {
var up = items[Math.floor(me.item_location / 2)];
if(up){
me.theItem = //as above
me.item_location = Math.floor(me.item_location / 2);
} else {
// abandon setting this item, return to a different context/menu
}
}
My issue is that I'm finding 'duplicates' that aren't actually there, so there must be a bug in either this tree navigation code or in the code I'm using to check for duplicates:
function findItem(theItem) {
var itemLocations = [];
for(var i = items.length - 1; i >= 0; i--) {
if(items[i] === theItem) {
itemLocations.push(i);
}
}
for(var i = itemLocations.length - 1; i >= 0; i--) {
console.log(itemLocations[i]);
}
console.log(itemLocations);
return itemLocations;
}
What is wrong with this? It's consistently ending up with certain entries showing up twice, but the findItem function consistently finds only one entry.
There's nothing more to the code that could possibly cause this, so I feel like I've missed something super obvious.

Categories