Sort points in counter-clockwise in javascript - javascript

I have a set of points introduced into a canvas:
My canvas with set of points
I have to apply this algorithm on:
Algo NoObtuse and example of graph produced by this algo
My problem is to find, starting from the rightmost point, the following point in the counter-clockwise order (point 2 in algo).
How, then, can we find the following point in this direction each time starting from a point?
EDIT: -> Result of the code by Blindman67
//First points (before sort and anti-clockwise)
//(6) [Point, Point, Point, Point, Point, Point]
0: Point {x: 458, y: 249, col: "red"}
1: Point {x: 333, y: 40, col: "red"}
2: Point {x: 138, y: 111, col: "red"}
3: Point {x: 336, y: 209, col: "red"}
4: Point {x: 237, y: 251, col: "red"}
5: Point {x: 60, y: 351, col: "red"}
//Points after sort and anti-clockwise
//(6) [Point, Point, Point, Point, Point, Point]
0: Point {x: 336, y: 209, col: "red", angle: 6.456745983859364}
1: Point {x: 333, y: 40, col: "red", angle: 5.156558533568968}
2: Point {x: 138, y: 111, col: "red", angle: 3.75120843247896}
3: Point {x: 60, y: 351, col: "red", angle: 2.4782921522301162}
4: Point {x: 237, y: 251, col: "red", angle: 1.9481922940313214}
5: Point {x: 458, y: 249, col: "red", angle: 0.26263427391514854}

Sorting points in rotational order
To sort points in some direction starting at the right most and using the spatial center as a reference point.
// Array of points;
const points = [{x:?,y:?},{x:?,y:?},{x:?,y:?},...?];
// Find min max to get center
// Sort from top to bottom
points.sort((a,b)=>a.y - b.y);
// Get center y
const cy = (points[0].y + points[points.length -1].y) / 2;
// Sort from right to left
points.sort((a,b)=>b.x - a.x);
// Get center x
const cx = (points[0].x + points[points.length -1].x) / 2;
// Center point
const center = {x:cx,y:cy};
// Pre calculate the angles as it will be slow in the sort
// As the points are sorted from right to left the first point
// is the rightmost
// Starting angle used to reference other angles
var startAng;
points.forEach(point => {
var ang = Math.atan2(point.y - center.y,point.x - center.x);
if(!startAng){ startAng = ang }
else {
if(ang < startAng){ // ensure that all points are clockwise of the start point
ang += Math.PI * 2;
}
}
point.angle = ang; // add the angle to the point
});
// Sort clockwise;
points.sort((a,b)=> a.angle - b.angle);
UPDATE correction
// ****************************************************
// UPDATE the following code is incorrect
// ****************************************************
// Sort anti clockwise;
// points.sort((a,b)=> b.angle - a.angle);
// ****************************************************
//=====================================================
// the correct way to sort anticlockwise
//=====================================================
// first sort clockwise
points.sort((a,b)=> a.angle - b.angle);
// then reverse the order
const ccwPoints = points.reverse();
// move the last point back to the start
ccwPoints.unshift(ccwPoints.pop());
Example
Click canvas to rerun on a new set of random points sorted in counter clockwise order.
//.......................................................
// support code not part of the answer
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); }; // the ; after while loop is important don't remove
const setOf = (count, cb = (i)=>i) => {var a = [],i = 0; while (i < count) { a.push(cb(i ++)) } return a };
const eachOf = (array, cb) => { var i = 0; const len = array.length; while (i < len && cb(array[i], i++, len) !== true ); };
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
//.......................................................
// set up canvas and context
const ctx = canvas.getContext("2d");
canvas.width = 500;
canvas.height = 250;
ctx.font = "12px airal";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// create random points and then sort them in counterclockwise order
// starting at the right most
function doIt() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
function drawPoints() {
eachOf(points, (point, i) => {
ctx.beginPath();
ctx.lineTo(center.x, center.y);
ctx.lineTo(point.x, point.y);
ctx.stroke();
ctx.fillStyle = "white"
ctx.fillText(i, point.x-2, point.y);
ctx.fillText(i, point.x+2, point.y);
ctx.fillText(i, point.x, point.y-2);
ctx.fillText(i, point.x, point.y+2);
ctx.fillStyle = "black"
ctx.fillText(i, point.x, point.y);
})
}
// Array of points;
var points = setOf(8, () => ({
x : rand(20, canvas.width - 40),
y : rand(20, canvas.height - 40),
angle : 0
}));
// Find min max to get center
// Sort from top to bottom
points.sort((a, b) => a.y - b.y);
// Get center y
const cy = (points[0].y + points[points.length - 1].y) / 2;
// Sort from right to left
points.sort((a, b) => b.x - a.x);
// Get center x
const cx = (points[0].x + points[points.length - 1].x) / 2;
// Center point
var center = {
x : cx,
y : cy
};
// Pre calculate the angles as it will be slow in the sort
// As the points are sorted from right to left the first point
// is the rightmost
// Starting angle used to reference other angles
var startAng;
points.forEach(point => {
var ang = Math.atan2(point.y - center.y, point.x - center.x);
if (!startAng) {
startAng = ang
} else {
if (ang < startAng) { // ensure that all points are clockwise of the start point
ang += Math.PI * 2;
}
}
point.angle = ang; // add the angle to the point
});
// first sort clockwise
points.sort((a, b) => a.angle - b.angle);
// then reverse the order
const ccwPoints = points.reverse();
// move the last point back to the start
ccwPoints.unshift(ccwPoints.pop());
drawPoints();
}
doIt()
canvas.onclick = doIt;
canvas { border : 2px solid black; }
<canvas id="canvas"></canvas>

Related

How to calculate a random position outside the bounds of a rectangle?

I'm trying to spawn enemies just outside the bounds of a rectangle. Here's a picture:
That is, the grey area is the playing area that the user can see, and the green is outside the rendering bounds. I'm looking for a way to calculate a spawn position in this green area.
I have a tentative solution, but it's pretty long and involves a bunch of if statements. Is there a more efficient or elegant way of calculating this?
function calcEnemySpawnPos(r) {
const roll = Math.random();
const left = -r;
const right = canvas.width + r;
const top = -r;
const bottom = canvas.height + r;
if (roll <= 0.25) {
return { x: left, y: getRandomInt(top, bottom) };
} else if (roll <= 0.5) {
return { x: right, y: getRandomInt(top, bottom) };
} else if (roll < 0.75) {
return { x: getRandomInt(left, right), y: top };
} else {
return { x: getRandomInt(left, right), y: bottom };
}
}
I have a slight improvement, but still not amazingly elegant. This is pseudocode since I'm not sure of the js syntax:
const rollLeft = Math.random() - 0.5;
const rollTop = Math.random() - 0.5;
if (rollLeft > 0){
x = getRandomInt(-r, 0)
} else {
x = getRandomInt(canvas.width, canvas.width + r)
}
if (rollRight > 0){
y = getRandomInt(-r, 0)
} else {
y = getRandomInt(canvas.height, canvas.height + r)
}
return {x, y}
There are 200,000 possible positions. You can generate just one random number and map it to a valid coordinate. You can specify four valid ranges, defined by top-left and bottom-right corners, then use your random number to get a range (weighted by area) and then convert the number to a point in that range.
function startPos(ranges, totalSize) {
let n = Math.trunc(Math.random() * totalSize);
const {x: j, y: k, w} = ranges.find(r => n < r.size || void(n -= r.size));
const x = n % w, y = (n - x) / w; // remainder/quotient of dividing by width
return [x + j, y + k]; // translate to start of range
}
[x, y] = startPos([
{x: -100, y: -100, w: 600, h: 100, size: 600 * 100},
{x: 500, y: -100, w: 100, h: 400, size: 100 * 400},
{x: 0, y: 300, w: 600, h: 100, size: 600 * 100},
{x: -100, y: 0, w: 100, h: 400, size: 100 * 400},
], 200_000);
The ranges.find(...) predicate is a little hard to read. Could also be written like this:
ranges.find(({size}) => {
if (n < size) return true;
else n -= size;
});
Note that this algorithm gives every pixel equal probability of being the spawn point, in contrast with your solution where each quadrant has equal probability of containing the spawn point, so pixels on the shorter sides have higher probability than pixels on the longer sides.

KonvaJS/Canvas Dynamic fog of war reveal with obstacles

I have a 2D board made with KonvaJS and tokens that can move on a square grid. I can already add fog of war and remove it manually. However, I would like to make it so, when each token moves, it reveals a certain around it, taking into account walls. Most of the work is done, however it's not entirely accurate.
Basically for each wall, I'm checking if the token is on the top/right/bottom/left of it. And then depending on which one it is, I reduce the width/height of the revealing area so it doesn't go beyond the wall. Here is an image explaining what I have and what I need
Legend:
Gray is fog of war
Red area is the wall/obstacle
Token is the movable token
Blue area is the revealed area
Blue lines inside red area is where it intersects
Purple lines are squares that should be revealed (aka, it should be blue)
Basically, in this case, an intersection was detected and the token is on the right side of the obstacle. So I got the right side of the wall (the x coordinate), and made the blue area starting point be that x coordinate and removed from the total width of the blue area the intersection width(the blue lines, so 1 square of width was removed).
However, because of that, the purple lines don't get filled in. Unfortunately, I can't just check the intersection points between blue and red and only remove those, because if the blue area is bigger than the red area, it would reveal the other side of the obstacle(which I don't want).
Here is the code I'm using to iterate the walls, checking if there is an intersection, checking where the token is, and then removing the width or height according to the intersection.
const tokenPosition = { x: 10, y: 10 };
const haveIntersection = (r1, r2) => !(
r2.x > r1.x + r1.width || // Compares top left with top right
r2.x + r2.width < r1.x || // Compares top right with top left
r2.y > r1.y + r1.height || // Compare bottom left with bottom right
r2.y + r2.height < r1.y // Compare bottom right with bottom left
);
walls.forEach(wall => {
const redArea = { x: wall.x, y: wall.y, width: wall.width, height: wall.height };
// blueArea has the same properties as redArea
if (haveIntersection(blueArea, redArea)) {
const tokenToTheRight = tokenPosition.x > wall.x + wall.width;
const tokenToTheLeft = tokenPosition.x < wall.x;
const tokenToTheTop = tokenPosition.y < wall.y;
const tokenToTheBottom = tokenPosition.y > wall.y + wall.height;
if (tokenToTheRight) {
let diff = wall.x + wall.width - blueArea.x;
blueArea.x = wall.x + wall.width;
blueArea.width = blueArea.width - diff;
}
if (tokenToTheLeft) {
let diff = blueArea.x + blueArea.width - wall.x;
blueArea.width = blueArea.width - diff;
}
if (tokenToTheTop) {
let diff = blueArea.y + blueArea.height - wall.y;
blueArea.height = blueArea.height - diff;
}
if (tokenToTheBottom) {
let diff = wall.y + wall.height - blueArea.y;
blueArea.y = wall.y + wall.height;
blueArea.height = blueArea.height - diff;
}
}
});
Any idea on how to fix this or if I should be taking a different approach?
You'll have to do something ray-tracing like to get this to work.
In the snippet below, I:
Loop over each cell in your token's field-of-view
Check for that cell center whether
it is in a box, or
a line between the token and the cell center intersects with a wall of a box
Color the cell based on whether it intersects
Note: the occlusion from the boxes is quite aggressive because we only check the center for quite a large grid cell. You can play around with some of the settings to see if it matches your requirements. Let me know if it doesn't.
Legend:
Red: box
Light blue: in field of view
Orange: blocked field of view because box-overlap
Yellow: blocked field of view because behind box
// Setup
const cvs = document.createElement("canvas");
cvs.width = 480;
cvs.height = 360;
const ctx = cvs.getContext("2d");
document.body.appendChild(cvs);
// Game state
const GRID = 40;
const H_GRID = GRID / 2;
const token = { x: 7.5, y: 3.5, fow: 2 };
const boxes = [
{ x: 2, y: 3, w: 4, h: 4 },
{ x: 8, y: 4, w: 1, h: 1 },
];
const getBoxSides = ({ x, y, w, h }) => [
[ [x + 0, y + 0], [x + w, y + 0]],
[ [x + w, y + 0], [x + w, y + h]],
[ [x + w, y + h], [x + 0, y + h]],
[ [x + 0, y + h], [x + 0, y + 0]],
];
const renderToken = ({ x, y, fow }) => {
const cx = x * GRID;
const cy = y * GRID;
// Render FOV
for (let ix = x - fow; ix <= x + fow; ix += 1) {
for (let iy = y - fow; iy <= y + fow; iy += 1) {
let intersectionFound = false;
for (const box of boxes) {
if (
// Check within boxes
pointInBox(ix, iy, box) ||
// Check walls
// Warning: SLOW
getBoxSides(box).some(
([[ x1, y1], [x2, y2]]) => intersects(x, y, ix, iy, x1, y1, x2, y2)
)
) {
intersectionFound = true;
break;
}
}
if (!intersectionFound) {
renderBox({ x: ix - .5, y: iy - .5, w: 1, h: 1 }, "rgba(0, 255, 255, 0.5)", 0);
ctx.fillStyle = "lime";
ctx.fillRect(ix * GRID - 2, iy * GRID - 2, 4, 4);
} else {
renderBox({ x: ix - .5, y: iy - .5, w: 1, h: 1 }, "rgba(255, 255, 0, 0.5)", 0);
ctx.fillStyle = "red";
ctx.fillRect(ix * GRID - 2, iy * GRID - 2, 4, 4);
}
}
}
ctx.lineWidth = 5;
ctx.fillStyle = "#efefef";
ctx.beginPath();
ctx.arc(cx, cy, GRID / 2, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
const renderBox = ({ x, y, w, h }, color = "red", strokeWidth = 5) => {
ctx.fillStyle = color;
ctx.strokeWidth = strokeWidth;
ctx.beginPath();
ctx.rect(x * GRID, y * GRID, w * GRID, h * GRID);
ctx.closePath();
ctx.fill();
if (strokeWidth) ctx.stroke();
}
const renderGrid = () => {
ctx.lineWidth = 1;
ctx.beginPath();
let x = 0;
while(x < cvs.width) {
ctx.moveTo(x, 0);
ctx.lineTo(x, cvs.height);
x += GRID;
}
let y = 0;
while(y < cvs.height) {
ctx.moveTo(0, y);
ctx.lineTo(cvs.width, y);
y += GRID;
}
ctx.stroke();
}
boxes.forEach(box => renderBox(box));
renderToken(token);
renderGrid();
// Utils
// https://errorsandanswers.com/test-if-two-lines-intersect-javascript-function/
function intersects(a,b,c,d,p,q,r,s) {
var det, gamma, lambda;
det = (c - a) * (s - q) - (r - p) * (d - b);
if (det === 0) {
return false;
} else {
lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det;
gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det;
return (0 <= lambda && lambda <= 1) && (0 <= gamma && gamma <= 1);
}
}
function pointInBox(x, y, box) {
return (
x > box.x &&
x < box.x + box.w &&
y > box.y &&
y < box.bottom
);
}
canvas { border: 1px solid black; }

how to find all points on the circumference of a triangle

I have an algorithm that fills a circle with lines. How it works?
I'm looking for the coordinates of the points lying on the circle on both sides and connecting them with lines.
I look for the coordinates of such points using the following equations.
let p1 = (150 - Math.sqrt(Math.pow(150, 2) - Math.pow((y - 150), 2)));
let p2 = (150 + Math.sqrt(Math.pow(150, 2) - Math.pow((y - 150), 2)));
Where 150 is the coordinates of the center of the circle and the radius.
You can see how it works by running the code.
Now I want to find such coordinates at the edges of the triangle.
Let's say I wrote the following triangle:
context.strokeStyle = "red";
context.lineWidth = 1;
context.beginPath();
context.moveTo(0, 300);
context.lineTo(150, 0);
context.lineTo(300, 300);
context.lineTo(0, 300);
context.stroke();
All I have is the coordinates of its vertices. How to correctly find all points lying on the circle of a triangle?
function func() {
var canvas = document.getElementById("image");
var context = canvas.getContext("2d");
canvas.width = 300;
canvas.height = 300;
context.beginPath();
context.arc(150,150,150,0, 2 * Math.PI, false);
context.closePath();
context.lineWidth = 1;
context.strokeStyle = "rgb(0,0,0)";
context.stroke();
for (let y = 0; y < 300; y++) {
let p1 = (150 - Math.sqrt(Math.pow(150, 2) - Math.pow((y - 150), 2)));
let p2 = (150 + Math.sqrt(Math.pow(150, 2) - Math.pow((y - 150), 2)));
//console.log(p1, y);
context.fillStyle = "red";
context.fillRect(p1, y, 1, 1);
context.fillStyle = "red";
context.fillRect(p2, y, 1, 1);
setTimeout(() => {
context.strokeStyle = "red";
context.lineWidth = 1;
context.beginPath();
context.moveTo(p1, y);
context.lineTo(p2, y);
context.stroke();
}, 5 * y);
}
}
<body onload="func();">
<canvas id="image"></canvas>
</body>
Here are a pair of functions which will aid in your effort. Specifically...
pointsToGeneralForm converts a pair of points (such as the side of a triangle) into the General Form of Ax + By + C = 0. (See How can I find the general form equation of a line from two points?)
intersectOfGeneralForm determines the intersection point of two General Form lines. (See https://mathemerize.com/point-of-intersection-of-two-lines/)
Now, with these two functions, one can proceed by determining the side of the triangle with the greatest x range, and then iterate the x value over each of the two remaining sides. That is, for each of the sides with the shorter x ranges, determine the intersection point for a given x value for both the short range side and long range side, which returns a pair of points defining a vertical line segment within the interior of the triangle...
The code snippet exemplifies the use of the helper functions...
function pointsToGeneralForm( p1, p2 ) {
return {
a: p1.y - p2.y,
b: p2.x - p1.x,
c: ( p1.x - p2.x ) * p1.y + ( p2.y - p1.y ) * p1.x
}
}
function intersectOfGeneralForm( line1, line2 ) {
let den = ( line1.a * line2.b - line2.a * line1.b );
return {
x: ( line1.b * line2.c - line2.b * line1.c ) / den,
y: ( line1.c * line2.a - line2.c * line1.a ) / den,
}
}
// lineA represents the side of the triangle.
let lineA = pointsToGeneralForm( { x: 0, y: 5 },{ x: 5, y: 10 } );
console.log( `LineA from (0,5)-(5,10) in General Form is ${lineA.a}x + ${lineA.b}y + ${lineA.c} = 0\n\n` );
// lineTest1 and lineTest2 represent a vertical line used to determine the
// intersection point for any given X with lineA
let lineTest1 = pointsToGeneralForm( { x: 2, y: 0 },{ x: 2, y: 1 } );
console.log( `LineTest1 from (2,0)-(2,1) in General Form is ${lineTest1.a}x + ${lineTest1.b}y + ${lineTest1.c} = 0` );
let intersectATest1 = intersectOfGeneralForm( lineA, lineTest1 );
console.log( `Intersection of LineA and LineTest1 is ( ${intersectATest1.x}, ${intersectATest1.y} )\n\n` );
let lineTest2 = pointsToGeneralForm( { x: 4, y: 0 },{ x: 4, y: 1 } );
console.log( `LineTest2 from (4,0)-(4,1) in General Form is ${lineTest2.a}x + ${lineTest2.b}y + ${lineTest2.c} = 0` );
let intersectATest2 = intersectOfGeneralForm( lineA, lineTest2 );
console.log( `Intersection of LineA and LineTest2 is ( ${intersectATest2.x}, ${intersectATest2.y} )\n\n` );
let intersectATest3 = intersectOfGeneralForm( lineA, { a: -1, b: 0, c: 2.5 } );
console.log( `Intersection of LineA and x = 2.5 is ( ${intersectATest3.x}, ${intersectATest3.y} )\n\n` );

Canvas Framework for interactive elastic line? [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 6 years ago.
Improve this question
I want to create a interactive elastic line for my webpage, when the user hover the line it will animate like elastic effect or rubber band stretch effect and mouse leave the object will back to the original shape.
Demo Object
I'm the Newbie in HTML5 Canvas, I hope it done with JavaScript canvas library, but when I searching best canvas library I get many more options, so I'm getting confused to which one select to achieving my goal. ThreeJs, fabricJs , PaperJs , etc are the popular canvas libraries.
I would like suggestions of which framework would be the most suitable for my goal.
Thanks and Appreciate your helping mentality.
You will need to use Inverse Kinematic, or more specifically a Kinematic Chain.
There are many approaches more or less complex. Here is a simple approach which will allow you to drag the end point around and the rest will follow. It's not perfect but will probably do for this purpose.
(Inverse) Kinematic Chain
The main function is as follows. It assumes that an array with points is defined as well as a distance variable:
// calculate IK chain (from last to first)
function calc() {
var angle, i, p1, p2;
for(i = points.length - 1; i > 0; i--) {
p1 = points[i]; // current point
p2 = points[i-1]; // previous point
angle = Math.atan2(p2.y - p1.y, p2.x - p1.x); // calc angle
p2.x = p1.x + distance * Math.cos(angle); // update previous point
p2.y = p1.y + distance * Math.sin(angle); // based on a fixed distance
}
}
Notice that the distance variable is set to a fixed length which is the key here.
All we need to do now is to detect the mouse dragging the last point in the chain and the rest will follow.
Chain in action
var c = document.querySelector("canvas"),
ctx = c.getContext("2d"),
// the chain - dragged by the *last* point
points = [
{x: 50, y: 50},
{x: 100, y: 60},
{x: 90, y: 90},
{x: 120, y: 110},
{x: 200, y: 80},
{x: 250, y: 130}
],
distance = 50,
isDown = false;
// set canvas size
resize();
window.onresize = resize;
function resize() {
c.width = window.innerWidth;
c.height = window.innerHeight;
calc();
render()
}
// handle mouse
c.onmousedown = function(e) {
var pos = getXY(e),
p = points[points.length - 1];
isDown = (pos.x > p.x - 7 && pos.x < p.x + 7 && pos.y > p.y - 7 && pos.y < p.y + 7);
};
window.onmousemove = function(e) {
if (!isDown) return;
points[points.length - 1] = getXY(e); // override last point with mouse position
// update chain and canvas
calc();
render();
};
window.onmouseup = function() {isDown = false};
// adjusted mouse position
function getXY(e) {
var rect = c.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
}
}
// IK chain calculations
function calc() {
var angle, i, p1, p2;
for(i = points.length - 1; i > 0; i--) {
p1 = points[i];
p2 = points[i-1];
angle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
p2.x = p1.x + distance * Math.cos(angle);
p2.y = p1.y + distance * Math.sin(angle);
}
}
// render line and handle
function render() {
var lp, radius = 7;
ctx.clearRect(0, 0, c.width, c.height);
// render current chain
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for(var i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
ctx.lineWidth = 3;
ctx.strokeStyle = "#07f";
ctx.stroke();
lp = points[points.length - 1];
// draw handle
ctx.beginPath();
ctx.moveTo(lp.x + radius, lp.y);
ctx.arc(lp.x, lp.y, radius, 0, Math.PI*2);
ctx.lineWidth = 2;
ctx.strokeStyle = "#900";
ctx.stroke();
}
<canvas></canvas>
Going back to the roots
In order for it to bounce back you will need the original coordinates, then interpolate with the corresponding points in the chain.
This would of course happen on the mouse up event. You can use easing functions if you so wish; ease-out would probably be the most suitable in this case.
Bounce back
This example does not intend to solve the entire problem, neither is it optimized but, you should be able to get the gist of what is needed. Modify as needed.
For this to work:
The render function now takes an argument so we can feed it any point array
We need to interpolate between the fixed points (original path) and the IK chain. For this we use a temporary array.
We animate while t is [0, 1]
When done, we reset the IK points to the original and recalc/render it.
I also added min/max for a chain segment to show how you can make the chain more elastic on its own.
var c = document.querySelector("canvas"),
ctx = c.getContext("2d"),
// the fixed point chain
pointsFixed = [
{x: 50, y: 50},
{x: 100, y: 60},
{x: 90, y: 90},
{x: 120, y: 110},
{x: 200, y: 80},
{x: 250, y: 130}
],
// for the IK chain - dragged by the *last* point
points = [
{x: 50, y: 50},
{x: 100, y: 60},
{x: 90, y: 90},
{x: 120, y: 110},
{x: 200, y: 80},
{x: 250, y: 130}
],
min = 40, max = 70,
isDown = false,
// for animation
isPlaying = false,
t, step = 0.1; // t = [0, 1]
// set canvas size
resize();
window.onresize = resize;
function resize() {
c.width = window.innerWidth;
c.height = window.innerHeight;
calc();
render(points)
}
// handle mouse
c.onmousedown = function(e) {
var pos = getXY(e),
p = points[points.length - 1];
isDown = (pos.x > p.x - 7 && pos.x < p.x + 7 && pos.y > p.y - 7 && pos.y < p.y + 7);
};
window.onmousemove = function(e) {
if (!isDown) return;
points[points.length - 1] = getXY(e); // override last point with mouse position
// update chain and canvas
calc();
render(points);
};
window.onmouseup = function() {
if (isDown) {
isDown = false;
t = 0; // reset t for new animation
isPlaying = true; // allow looping
animate(); // start the animation
}
};
// adjusted mouse position
function getXY(e) {
var rect = c.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
}
}
// IK chain calculations
function calc() {
var angle, i, p1, p2, dx, dy, distance;
for(i = points.length - 1; i > 0; i--) {
p1 = points[i];
p2 = points[i-1];
dx = p2.x - p1.x;
dy = p2.y - p1.y;
angle = Math.atan2(dy, dx);
distance = Math.max(min, Math.min(max, Math.sqrt(dx*dx + dy*dy)));
p2.x = p1.x + distance * Math.cos(angle);
p2.y = p1.y + distance * Math.sin(angle);
}
}
// interpolate and animate
function animate() {
if (isPlaying) {
// create a temp. array with interpolated points
for(var i = 0, p, pts = []; i < points.length; i++) {
pts.push(lerp(points[i], pointsFixed[i], t*t)); // t*t for easing
}
// increase t in animation
t += step;
// keep animating?
if (t <= 1) {
render(pts);
requestAnimationFrame(animate)
}
else {
// we're done
isPlaying = false;
points = pts;
calc();
render(points);
}
}
function lerp(p1, p2, t) {
return {
x: p1.x + (p2.x - p1.x) * t,
y: p1.y + (p2.y - p1.y) * t
}
}
}
// render line and handle
function render(points) {
var lp, radius = 7;
ctx.clearRect(0, 0, c.width, c.height);
// render current chain
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for(var i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
ctx.lineWidth = 3;
ctx.strokeStyle = "#07f";
ctx.stroke();
lp = points[points.length - 1];
// draw handle
ctx.beginPath();
ctx.moveTo(lp.x + radius, lp.y);
ctx.arc(lp.x, lp.y, radius, 0, Math.PI*2);
ctx.lineWidth = 2;
ctx.strokeStyle = "#900";
ctx.stroke();
}
<canvas></canvas>
The technique you're looking for is poly line simplification.
Your question borders on being off-topic because it asks for a library recommendation. But there isn't a library that automatically animates your demo path as you describe.
So I guess there's no harm in saying ...
You can define and draw your "wiggly" path as a set of connected points (a polyline). Then when your user moves off the path you can use a path simplification algorithm to remove poly-points until the path "straightens out".
The Ramer-Douglas-Peucker algorithm is one useful path simplification algorithm.
Here's an example of path simplification in action. In the demo, move your mouse rightward to simplify the path and move leftward to show the more complex path (complex==more points on the path).

finding angles 0-360

Need help with a math issue:
i need to get the true angle from 0 degrees using x and y cordinates
im using this at the moment:
Math.atan((x2-x1)/(y1-y2))/(Math.PI/180)
but /(Math.PI/180) limits results from -90 to 90
i need 0-360
note: I'm using the angle to indicate direction:
0=up
90=right
135=45 degree right+down
180=down
270=left
etc
The atan function only gives half the unit circle between -pi/2 and +pi/2 (0 on x axis), there is another library function that can give the whole unit circle between -pi and + pi, atan2
I would think you are better of using atan2 to get the right quadrant rather than branching yourself, then just scale as you have been, something like
Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI + 180
The multiply by 180 over pi is just the scale from radians to degrees as in the question (but with the division by a division simplified), the +180 makes sure its always positive i.e. 0-360 deg rather than -180 to 180 deg
Math.atan limits you to the two rightmost quadrants on the unit circle. To get the full 0-360 degrees:
if x < 0 add 180 to the angle
else if y < 0 add 360 to the angle.
Your coordinate system is rotated and inverted compared to mine (and compared to convention). Positive x is to the right, positive y is up. 0 degrees is to the right (x>0, y=0, 90 degrees is up (x=0,y>0) 135 degrees is up and to the left (y>0, x=-y), etc. Where are your x- and y-axes pointing?
The answers of #jilles de wit and #jk. led me on the right path but for some reason did not provide the right solution for my problem that i think is very similar to the original question.
I wanted to get up = 0°, right = 90°, down = 180°, left = 270° as in aeronautical navigation systems.
Presuming the question was referring to canvas drawing i reached this solution:
I first translated the canvas origin using ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2). I also halved e.offsetX and e.offsedY i got from a mouse event on the canvas to get x and y with the same coordinate system as the canvas.
let radianAngle = Math.atan2(y, x); // x has the range [-canvas.width/2 ... +canvas.width/2], y is similar
let northUpAngle = radianAngle * 180 / PI + 90; // convert to degrees and add 90 to shift the angle counterclockwise from it's default "left" = 0°
if (x < 0 && y < 0) { // check for the top left quadrant
northUpAngle += 360; // add 360 to convert the range of the quadrant from [-90...0] to [270...360] (actual ranges may vary due to the way atan2 handles quadrant boundaries)
}
northUpAngle.toFixed(2) // to avoid getting 360° near the "up" position
There might be a more concise solution using the modulo operation but i could't find it.
Also note:
if (y1==y2) {
if (x1>x2)
angle = 90;
else if (x1<x2)
angle = 270;
else
angle = 0;
}
This should do the trick:
If y2
If < 0, add 360.
Examples:
(x1,y1) = 0
(x2,y2) = (-1,1), atan() = -45, [add 360], 270
(x2,y2) = (1,1), atan() = 45
(x2,y2) = (1,-1), atan() = -45, [add 180], 135
(x2 ,y2) = (-1,-1), atan() = 45, [add 180], 225
angle = Math.atan(this.k) * 180 / Math.PI;
angle = 180 - (angle < 0 ? 180 + angle : angle);
angle = p2.Y > p1.Y || (p2.Y == p1.Y && p2.X > p1.X) ? 180 + angle : angle;
Here's two solutions, one with Math.atan (which takes a FRACTION opposite/adjacent) and one with Math.atan2 (which takes TWO ARGUMENTS)
solutions are written in ES6 (ES2015+) syntax, ironically, because the question predates this javascript.
note that 'to the right' is 0° (=0 Radians); up is 90° (= PI/2); left is 180° (PI), and down is 270° (PI*1.5)
angleGivenCoords(coord1,coord2) {
// given two coords {x,y}, calculate the angle in radians with
// right being 0° (0 radians)
if (coord1.x === coord2.x) return (coord1.y > coord2.y ? Math.PI * 0.5 : Math.PI * 1.5)
if (coord1.y === coord2.y) return (coord1.x > coord2.x ? Math.PI : 0 )
let opposite = coord2.x - coord1.x
let adjacent = coord1.y - coord2.y
let adjustor = ((coord2.x < coord1.x && coord2.y < coord1.y) || (coord2.x < coord1.x && coord2.y > coord1.y)) ? Math.PI : 0
let res = Math.atan(opposite/adjacent) + adjustor
if (res < 0) { res = res + Math.PI*2 }
return res ;
}
now with Math.atan2. notice that with this solution the guard clauses at the top (coord1.x === coord2.x, (coord1.y === coord2.y)) are unneeded
angleGivenCoords(coord1,coord2) {
// given two coords {x,y}, calculate the angle in radians with
// left being 0° (0 radians)
let opposite = coord2.x - coord1.x
let adjacent = coord1.y - coord2.y
let res = Math.atan2(adjacent, opposite)
if (res < 0) { res = res + Math.PI*2 }
return res ;
}
(I tried to keep the 'opposite' and 'adjacent' variable names in deference to the Trigonometry)
please note that here is my test suite written in Jest. Also note my function above returns radians and my code (not shown here) has a simple Trig.degreesToRadians() as you would expect
it('for right 0°', () => {
let coord1 = {x: 500, y: 500},
coord2 = {x: 600, y: 500}
expect(Trig.angleGivenCoords(coord1,coord2)).toEqual(Trig.degreesToRadians(0))
})
it('for up-right 45°', () => {
let coord1 = {x: 500, y: 500},
coord2 = {x: 600, y: 400}
expect(Trig.angleGivenCoords(coord1,coord2)).toEqual(Trig.degreesToRadians(45))
})
it('for 90° up', () => {
let coord1 = {x: 500, y: 500},
coord2 = {x: 500, y: 400}
expect(Trig.angleGivenCoords(coord1,coord2)).toEqual(Trig.degreesToRadians(90))
})
it('for 135° up to left', () => {
let coord1 = {x: 500, y: 500},
coord2 = {x: 400, y: 400}
expect(Trig.angleGivenCoords(coord1,coord2)).toEqual(Trig.degreesToRadians(135))
})
it('for 180° to left', () => {
let coord1 = {x: 500, y: 500},
coord2 = {x: 400, y: 500}
expect(Trig.angleGivenCoords(coord1,coord2)).toEqual(Trig.degreesToRadians(180))
})
it('for 225° to to bottom left', () => {
let coord1 = {x: 500, y: 500},
coord2 = {x: 400, y: 600}
expect(Trig.angleGivenCoords(coord1,coord2)).toEqual(Trig.degreesToRadians(225))
})
it('for 270° to the bottom', () => {
let coord1 = {x: 500, y: 500},
coord2 = {x: 500, y: 600}
expect(Trig.angleGivenCoords(coord1,coord2)).toEqual(Trig.degreesToRadians(270))
})
it('for 315° to the bottom', () => {
let coord1 = {x: 500, y: 500},
coord2 = {x: 600, y: 600}
expect(Trig.angleGivenCoords(coord1,coord2)).toEqual(Trig.degreesToRadians(315))
})
For 0=up,90=right,180=down,270=left etc (x=x2-x1,y=y2-y1)
you can use the formula:
f(x,y)=180-90*(1+sign(y))* (1-sign(x^2))-45*(2+sign(y))*sign(x)
-(180/pi())*sign(x*y)*atan((abs(y)-abs(x))/(abs(y)+abs(x)))
so with a single decesion (i hope this ugly basiclike notation explains it):
IF x = 0 THEN
360degree = 270 - (SIGN(x) + 1) * 90
ELSE
360degree = MOD(180 + (SIGN(y) + 1) * 90 + ATAN(x/y) , 360)
ENDIF
to draw a full circle from north 0degree to 360degree clockwise:
x=SIN(0to360) y=COS(0to360)
cheers, Lev
function angle(x1,y1,x2,y2)
{
eangle = Math.atan((x2-x1)/(y1-y2))/(Math.PI/180)
if ( angle > 0 )
{
if (y1 < y2)
return angle;
else
return 180 + angle;
} else {
if (x1 < x2)
return 180 + angle;
else
return 360 + angle;
}
}

Categories