Javascript: Calculate angle of rectangle with 4 corner points - javascript

I have an matrix like this:
var matrix = [
"00000000000000000000000",
"00000000001100000000000",
"00000000111110000000000",
"00000001111111000000000",
"00000111111111100000000",
"00000111111111000000000",
"00000011111100000000000",
"00000001110000000000000",
"00000000100000000000000",
"00000000000000000000000"
]
...and I know the 4 corner points (x/y-coordinates) of the figure (rectangle of '1'-characters) like..
[11,1]
[5,4]
[14,4]
[8,8]
Is there an easy way to calculate the rotation angle of the rectangle like I've symbolized in this image?
Because I have no clue how to continue I can not provide you more code than this:
Edit: The function above gets the minY and the maxX value from the 4 points. Afterwards the function is calculating the distance between the two points. But now how to calculate the angle?
function calculate_angle(corner_object) {
Array.prototype.calculate_distance = function() {
var x1=this[0],y1=this[1],x2=this[2],y2=this[3]
return Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)).toFixed(2);
}
var list = [my_object.corner1,my_object.corner2,my_object.corner3,my_object.corner4]
var extreme_result = {
'xMax': list.filter(e => e[0] === Math.max(...list.map(e => e[0]))),
'yMin': list.filter(e => e[1] === Math.min(...list.map(e => e[1])))
}
var distance = [extreme_result.xMax[0][0],extreme_result.xMax[0][1],extreme_result.yMin[0][0],extreme_result.yMin[0][1]].calculate_distance()
// distance between two points is "distance"
console.log(distance)
}
var my_object = {
"corner1":[5,4],
"corner2":[11,1],
"corner3":[14,4],
"corner4":[8,8]
}
calculate_angle(my_object)
I hope somebody can help me with my code... Thanks a lot in advance, jonas

UPDATED
I think you can follow this script:
https://codepen.io/pablodarde/pen/aVEmGO
Get the position of the highest rectangle base vertex;
Get the position of the lower rectangle base vertex;
Calc the rectangle opposite Leg: (H) with the above information;
Calc the rectangle adjacent leg: (W) with the above information;
Calc the hypotenuse (I): const I = Math.sqrt(Math.pow(H, 2) + Math.pow(W, 2));
Then, you have the angle cosine. const cosAngle = Math.cos(W/I);
Get the angle value by doing: const angle = Math.acos(cosAngle);
//https://stackoverflow.com/questions/47365879/javascript-calculate-angle-of-rectangle-with-4-corner-points/47366425#47366425
const matrix = [
"00000000000000000000000",
"00000000001100000000000",
"00000000111110000000000",
"00000001111111000000000",
"00000111111111100000000",
"00000111111111000000000",
"00000011111100000000000",
"00000001110000000000000",
"00000000100000000000000",
"00000000000000000000000"
];
function getRectangleLowerBaseVertice(arr) {
for (let i = arr.length -1; i > 0; i--) {
if (arr[i].indexOf('1') !== -1) {
return {line: i, column: arr[i].indexOf('1')};
}
}
}
function getRectangleHigherBaseVertice(arr) {
let col = 0;
let previous = 0;
let line = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i].lastIndexOf('1') !== -1) {
if (arr[i].lastIndexOf('1') > previous) {
col = arr[i].lastIndexOf('1');
previous = col;
line = i;
}
}
}
return {line: line, column: col};
}
const higherBaseVertex = [
Number(getRectangleHigherBaseVertice(matrix).line),
Number(getRectangleHigherBaseVertice(matrix).column),
];
const lowerBaseVertex = [
Number(getRectangleLowerBaseVertice(matrix).line),
Number(getRectangleLowerBaseVertice(matrix).column),
];
function toDegrees (angle) {
return angle * (180 / Math.PI);
}
const oppositeLeg = lowerBaseVertex[0] - higherBaseVertex[0];
const adjacentLeg = Math.abs(lowerBaseVertex[1] - higherBaseVertex[1]);
const hypotenuse = Math.sqrt(Math.pow(oppositeLeg, 2) + Math.pow(adjacentLeg, 2));
const cosAngle = Math.cos(adjacentLeg / hypotenuse);
const angle = toDegrees(Math.acos(cosAngle));
document.getElementById('lower-base-vertex').innerHTML = `lower base vertex, line: ${getRectangleLowerBaseVertice(matrix).line}, column: ${getRectangleLowerBaseVertice(matrix).column}`;
document.getElementById('higher-base-vertex').innerHTML = `higher base vertex, line: ${getRectangleHigherBaseVertice(matrix).line}, column: ${getRectangleHigherBaseVertice(matrix).column}`;
document.getElementById('opposite-leg').innerHTML = `Opposite leg: ${oppositeLeg}`;
document.getElementById('adjacent-leg').innerHTML = `Adjacent leg: ${adjacentLeg}`;
document.getElementById('hypotenuse').innerHTML = `hypotenuse: ${hypotenuse}`;
document.getElementById('cos-angle').innerHTML = `angle cosine: ${cosAngle}`;
document.getElementById('angle').innerHTML = `angle: ${angle}`;
<div id="lower-base-vertex"></div>
<div id="higher-base-vertex"></div>
<div id="rectangle-height"></div>
<div id="opposite-leg"></div>
<div id="adjacent-leg"></div>
<div id="hypotenuse"></div>
<div id="cos-angle"></div>
<div id="angle"></div>

Related

Point Doubling (P -> 2P) The Base Point of the Secp256k1 Elliptic Curve

as a learning exercise, I am trying to code the first point doubling (Base point P -> 2P) for the Secp256k1 Elliptic Curve. I am using Javascript, and the ethers package for BigNumber. Frustratingly, I am running into a problem where the result I am getting for 2P doesn't appear to lie on the curve. Can someone please help me determine where I am making a mistake?
The coordinates I'm getting as a result are:
X: 0xf1b9e9c77c87bf0ac622382b581826898cfc9232e025d86d904bfd33375faf1a
Y: 0x8162c7b446b54638e9181b71770b2d718e6953a360625a02392097c7db09c608
Which returns false from my isPointOnCurve() method. As a sanity check, I checked the base point in the isPointOnCurve() method, and that returns true (thankfully).
Please see my code below:
const { ethers, BigNumber } = require('ethers');
//variable initialization found from https://en.bitcoin.it/wiki/Secp256k1
bigZero = BigNumber.from(0);
bigTwo = BigNumber.from(2);
bigThree = BigNumber.from(3);
ellipticCurveB = BigNumber.from(7);
generatorPrime = BigNumber.from("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F");
order = BigNumber.from("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141");
baseXCoord = BigNumber.from("0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798");
baseYCoord = BigNumber.from("0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8");
// slope = ( (3*x^2) * (2*y)^-1 ) mod order
// 2Px = slope^2 - 2*baseXCoord
// 2Py = slope * ( 2Px - baseXCoord ) - baseYCoord
m = (bigThree.mul(baseXCoord.pow(bigTwo)).mul(modinv(bigTwo.mul(baseYCoord), order))).mod(order);
TwoPx = (m.pow(bigTwo).sub(bigTwo.mul(baseXCoord))).mod(order);
TwoPy = ((m.mul(baseXCoord.sub(TwoPx))).sub(baseYCoord)).mod(order);
console.log(TwoPx);
console.log(TwoPy);
console.log(isPointOnCurve(TwoPx, TwoPy));
// Helper Functions:
// Check if point is on Curve, Calculate extended GCD, modular inverse
function isPointOnCurve(x,y){
b = ellipticCurveB;
p = generatorPrime;
rem = (y.pow(bigTwo).sub(x.pow(bigThree)).sub(b)).mod(p);
return rem.eq(bigZero);
}
function egcd(a, b) {
var s = BigNumber.from(0), t = BigNumber.from(1), u = BigNumber.from(1), v = BigNumber.from(0);
while (!a.eq(BigNumber.from(0))) {
var q = b.div(a) | BigNumber.from(0), r = b.mod(a);
var m = s.sub(u.mul(q)), n = t.sub(v.mul(q));
b = a;
a = r;
s = u;
t = v;
u = m;
v = n;
}
return [b, s, t];
}
function mod(x, y) {
return (x.mod(y).add(y)).mod(y);
}
function modinv(x, y) {
var tuple = egcd(x.mod(y), y);
if (!tuple[0].eq(BigNumber.from(1))) {
return null;
}
return mod(tuple[1], y);
}
As kelalaka pointed out in a comment on the original post, I was confusing the the order of the group and the finite field Fp. I was getting values modulo the Group Order, when I should've been using the values modulo prime p used to define the finite field.
The new and correct result I get is:
X: 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
Y: 0x1ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a
If anyone would like to use this code, I've updated it to be correct, and cleaned it up to make it a little more readable:
bigZero = BigNumber.from(0);
bigTwo = BigNumber.from(2);
bigThree = BigNumber.from(3);
ellipticCurveB = BigNumber.from(7);
generatorPrime = BigNumber.from("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F");
baseXCoord = BigNumber.from("0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798");
baseYCoord = BigNumber.from("0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8");
// slope = ( (3*x^2) * (2*y)^-1 ) mod order
threeXSquared = bigThree.mul(baseXCoord.pow(bigTwo));
modInv2y = modinv(bigTwo.mul(baseYCoord), generatorPrime);
m = threeXSquared.mul(modInv2y).mod(generatorPrime);
// 2Px = slope^2 - 2*baseXCoord
mSquared = m.pow(bigTwo);
twoXbase = bigTwo.mul(baseXCoord);
TwoPx = (mSquared.sub(twoXbase)).mod(generatorPrime);
// 2Py = slope * ( 2Px - baseXCoord ) - baseYCoord
pointSlopeX = m.mul(baseXCoord.sub(TwoPx));
TwoPy = (pointSlopeX).sub(baseYCoord).mod(generatorPrime);
console.log(TwoPx);
console.log(TwoPy);
console.log(isPointOnCurve(TwoPx, TwoPy));
// Helper Functions:
// Check if point is on Curve, Calculate extended GCD, modular inverse
function isPointOnCurve(x,y){
b = ellipticCurveB;
p = generatorPrime;
rem = (y.pow(bigTwo).sub(x.pow(bigThree)).sub(b)).mod(p);
return rem.eq(bigZero);
}
function egcd(a, b) {
var s = BigNumber.from(0), t = BigNumber.from(1), u = BigNumber.from(1), v = BigNumber.from(0);
while (!a.eq(BigNumber.from(0))) {
var q = b.div(a) | BigNumber.from(0), r = b.mod(a);
var m = s.sub(u.mul(q)), n = t.sub(v.mul(q));
b = a;
a = r;
s = u;
t = v;
u = m;
v = n;
}
return [b, s, t];
}
function modulus(x, y) {
return (x.mod(y).add(y)).mod(y);
}
function modinv(x, y) {
var tuple = egcd(x.mod(y), y);
if (!tuple[0].eq(BigNumber.from(1))) {
return null;
}
return modulus(tuple[1], y);
}

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.

Canvas drawing using query/angular

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
};
});

Categories