How to find a middle point of a beizer curve? - javascript

I want to make a 'named' bezier curve. I want it to be one-word named so I don't have to worry about word-wrap.
I make bezier curve via P5 bezier(sx,sy,c1x,c1y,c2x,c2y,ex,ey) function and I want a string to be shown in the middle of bezier curve. But I don't know how to find 'the middle' of curve.
For now my result looks like this
(I don't know where to start picking up this problem, so I went with the easier way of just printing text on a start of curve):
But I want it to look like this:
This means that I need P1 and P2 coordinates:
Sorry for paint, but I don't have my code yet. As soon as I will have my hands on it I will add it here.
Here is code that draws a curve:
let
b = dest.inTriangle.middle, // destination triangle
g = this.outTriangle.p3, // tip of out triangle
c = {x:b.x-g.x,y:b.y-g.y}, // distance between objects
r1 = {}, // bezier point 1
r2 = {}; // bezier point 2
if(c.x > 0){
// b is on left
r1 = {
x: g.x + c.x/2,
y: g.y
};
r2 = {
x: b.x - c.x/2,
y: b.y
};
}
else {
// b is on right
r1 = {
x: g.x - c.x/2,
y: g.y + c.y
};
r2 = {
x: b.x + c.x/2,
y: b.y - c.y
};
}
noFill();
stroke(0);
bezier(
g.x, g.y,
r1.x, r1.y,
r2.x, r2.y,
b.x, b.y
);
noStroke();
fill(0);
text(this.name, g.x, (g.y-this.h/2))

You can use the bezierPoint() function that comes with P5.js.
From the reference:
noFill();
var x1 = 85,
x2 = 10,
x3 = 90,
x4 = 15;
var y1 = 20,
y2 = 10,
y3 = 90,
y4 = 80;
bezier(x1, y1, x2, y2, x3, y3, x4, y4);
fill(255);
var steps = 10;
for (var i = 0; i <= steps; i++) {
var t = i / steps;
var x = bezierPoint(x1, x2, x3, x4, t);
var y = bezierPoint(y1, y2, y3, y4, t);
ellipse(x, y, 5, 5);
}
You'd probably want to use a value of 0.5 for t to get the midpoint.

So, the formula to translate the 4 points in a function over time is the following (image from wikipedia):
Since you want the middle, and t ranges from 0 to 1, you just have to set t = 1/2
So
B(1/2) = 1/8 P0 + 3/8 P1 + 3/8 P2 + 1/8 P3

Related

How to draw parallel edges (arrows) between vertices with canvas?

I'm working on a flow-network visualization with Javascript.
Vertices are represented as circles and edges are represented as arrows.
Here is my Edge class:
function Edge(u, v) {
this.u = u; // start vertex
this.v = v; // end vertex
this.draw = function() {
var x1 = u.x;
var y1 = u.y;
var x2 = v.x;
var y2 = v.y;
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
var dx = x1 - x2;
var dy = y1 - y2;
var length = Math.sqrt(dx * dx + dy * dy);
x1 = x1 - Math.round(dx / ((length / (radius))));
y1 = y1 - Math.round(dy / ((length / (radius))));
x2 = x2 + Math.round(dx / ((length / (radius))));
y2 = y2 + Math.round(dy / ((length / (radius))));
// calculate the angle of the edge
var deg = (Math.atan(dy / dx)) * 180.0 / Math.PI;
if (dx < 0) {
deg += 180.0;
}
if (deg < 0) {
deg += 360.0;
}
// calculate the angle for the two triangle points
var deg1 = ((deg + 25 + 90) % 360) * Math.PI * 2 / 360.0;
var deg2 = ((deg + 335 + 90) % 360) * Math.PI * 2 / 360.0;
// calculate the triangle points
var arrowx = [];
var arrowy = [];
arrowx[0] = x2;
arrowy[0] = y2;
arrowx[1] = Math.round(x2 + 12 * Math.sin(deg1));
arrowy[1] = Math.round(y2 - 12 * Math.cos(deg1));
arrowx[2] = Math.round(x2 + 12 * Math.sin(deg2));
arrowy[2] = Math.round(y2 - 12 * Math.cos(deg2));
context.beginPath();
context.moveTo(arrowx[0], arrowy[0]);
context.lineTo(arrowx[1], arrowy[1]);
context.lineTo(arrowx[2], arrowy[2]);
context.closePath();
context.stroke();
context.fillStyle = "black";
context.fill();
};
}
Given the code
var canvas = document.getElementById('canvas'); // canvas element
var context = canvas.getContext("2d");
context.lineWidth = 1;
context.strokeStyle = "black";
var radius = 20; // vertex radius
var u = {
x: 50,
y: 80
};
var v = {
x: 150,
y: 200
};
var e = new Edge(u, v);
e.draw();
The draw() function will draw an edge between two vertices like this:
If we add the code
var k = new Edge(v, u);
k.draw();
We will get:
but I want to draw edges both directions as following:
(sorry for my bad paint skills)
Of course the vertices and the edge directions are not fixed.
A working example (with drawing vertex fucntion) on JSFiddle:
https://jsfiddle.net/Romansko/0fu01oec/18/
Aligning axis to a line.
It can make everything a little easier if you rotate the rendering to align with the line. Once you do that it is then easy to draw above or below the line as that is just in the y direction and along the line is the x direction.
Thus if you have a line
const line = {
p1 : { x : ? , y : ? },
p2 : { x : ? , y : ? },
};
Convert it to a vector and normalise that vector
// as vector from p1 to p2
var nx = line.p2.x - line.p1.x;
var ny = line.p2.y - line.p1.y;
// then get length
const len = Math.sqrt(nx * nx + ny * ny);
// use the length to normalise the vector
nx /= len;
ny /= len;
The normalised vector represents the new x axis we want to render along, and the y axis is at 90 deg to that. We can use setTransform to set both axis and the origin (0,0) point at the start of the line.
ctx.setTransform(
nx, ny, // the x axis
-ny, nx, // the y axis at 90 deg to the x axis
line.p1.x, line.p1.y // the origin (0,0)
)
Now rendering the line and arrow heads is easy as they are axis aligned
ctx.beginPath();
ctx.lineTo(0,0); // start of line
ctx.lineTo(len,0); // end of line
ctx.stroke();
// add the arrow head
ctx.beginPath();
ctx.lineTo(len,0); // tip of arrow
ctx.lineTo(len - 10, 10);
ctx.lineTo(len - 10, -10);
ctx.fill();
To render two lines offset from the center
var offset = 10;
ctx.beginPath();
ctx.lineTo(0,offset); // start of line
ctx.lineTo(len,offset); // end of line
ctx.moveTo(0,-offset); // start of second line
ctx.lineTo(len,-offset); // end of second line
ctx.stroke();
// add the arrow head
ctx.beginPath();
ctx.lineTo(len,offset); // tip of arrow
ctx.lineTo(len - 10, offset+10);
ctx.lineTo(len - 10, offset-10);
ctx.fill();
offset = -10;
// add second arrow head
ctx.beginPath();
ctx.lineTo(0,offset); // tip of arrow
ctx.lineTo(10, offset+10);
ctx.lineTo(10, offset-10);
ctx.fill();
And you can reset the transform with
ctx.setTransform(1,0,0,1,0,0); // restore default transform

Line of sight from point

Need to create simple line of sight from point. Length of this line would be adapt to the size of canvas. If line directed to any object (circle, rectangle etc) it must be interrupted after this. I don't know exactly how to describe this, but behavior should be something like this. It's like laser aim in video-games.
Demo jsfiddle. Target line has red color. I think that line must have dynamic length depending on where I will direct it.
var canvas = document.querySelector("canvas");
canvas.width = 500;
canvas.height = 300;
var ctx = canvas.getContext("2d"),
line = {
x1: 190, y1: 170,
x2: 0, y2: 0,
x3: 0, y3: 0
};
var length = 100;
var circle = {
x: 400,
y: 70
};
window.onmousemove = function(e) {
//get correct mouse pos
var rect = ctx.canvas.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top;
// calc line angle
var dx = x - line.x1,
dy = y - line.y1,
angle = Math.atan2(dy, dx);
//Then render the line using 100 pixel radius:
line.x2 = line.x1 - length * Math.cos(angle);
line.y2 = line.y1 - length * Math.sin(angle);
line.x3 = line.x1 + canvas.width * Math.cos(angle);
line.y3 = line.y1 + canvas.width * Math.sin(angle);
// render
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.moveTo(line.x1, line.y1);
ctx.lineTo(line.x2, line.y2);
ctx.strokeStyle = '#333';
ctx.stroke();
ctx.beginPath();
ctx.moveTo(line.x1, line.y1);
ctx.lineTo(line.x3, line.y3);
ctx.strokeStyle = 'red';
ctx.stroke();
ctx.beginPath();
ctx.arc(circle.x, circle.y, 20, 0, Math.PI * 2, true);
ctx.fillStyle = '#333';
ctx.fill();
}
<canvas></canvas>
Ray casting
The given answer is a good answer but this problem is better suited to a ray casting like solution where we are only interested in the distance to an intercept rather than the actual point of interception. We only need one point per cast ray so not calculating points will reduce the math and hence the CPU load giving more rays and objects per second.
A ray is a point that defines the start and a normalised vector that represents the direction of the ray. Because the ray uses a normalised vector that is a unit length many calculations are simplified because 1 * anything changes nothing.
Also the problem is about looking for the closest intercept so the intercept functions return a distance from the ray's origin. If no intercept is found then Infinity is returned to allow a valid distance comparison to be made. Every number is less than Infinity.
A nice feature of JavaScript is that it allows divide by zero and returns Infinity if that happens, this further reduces the complexity of the solution. Also if the intercept finds a negative intercept that means the object is behind that raycast origin and thus will return infinity as well.
So first let's define our objects by creating functions to make them. They are all ad hoc objects.
The Ray
// Ad Hoc method for ray to set the direction vector
var updateRayDir = function(dir){
this.nx = Math.cos(dir);
this.ny = Math.sin(dir);
return this;
}
// Creates a ray objects from
// x,y start location
// dir the direction in radians
// len the rays length
var createRay = function(x,y,dir,len){
return ({
x : x,
y : y,
len : len,
setDir : updateRayDir, // add function to set direction
}).setDir(dir);
}
A circle
// returns a circle object
// x,y is the center
// radius is the you know what..
// Note r2 is radius squared if you change the radius remember to set r2 as well
var createCircle = function(x , y, radius){
return {
x : x,
y : y,
rayDist : rayDist2Circle, // add ray cast method
radius : radius,
r2 : radius * radius, // ray caster needs square of radius may as well do it here
};
}
A wall
Note I changed the wall code in the demo
// Ad Hoc function to change the wall position
// x1,y1 are the start coords
// x2,y2 are the end coords
changeWallPosition = function(x1, y1, x2, y2){
this.x = x1;
this.y = y1;
this.vx = x2 - x1;
this.vy = y2 - y1;
this.len = Math.hypot(this.vx,this.vy);
this.nx = this.vx / this.len;
this.ny = this.vy / this.len;
return this;
}
// returns a wall object
// x1,y1 are the star coords
// x2,y2 are the end coords
var createWall = function(x1, y1, x2, y2){
return({
x : x1, y : y1,
vx : x2 - x1,
vy : y2 - y1,
rayDist : rayDist2Wall, // add ray cast method
setPos : changeWallPosition,
}).setPos(x1, y1, x2, y2);
}
So those are the objects, they can be static or moving through the circle should have a setRadius function because I have added a property that holds the square of the radius but I will leave that up to you if you use that code.
Now the intercept functions.
Ray Intercepts
The stuff that matters. In the demo these functions are bound to the objects so that the ray casting code need not have to know what type of object it is checking.
Distance to circle.
// Self evident
// returns a distance or infinity if no valid solution
var rayDist2Circle = function(ray){
var vcx, vcy, v;
vcx = ray.x - this.x; // vector from ray to circle
vcy = ray.y - this.y;
v = -2 * (vcx * ray.nx + vcy * ray.ny);
v -= Math.sqrt(v * v - 4 * (vcx * vcx + vcy * vcy - this.r2)); // this.r2 is the radius squared
// If there is no solution then Math.sqrt returns NaN we should return Infinity
// Not interested in intercepts in the negative direction so return infinity
return isNaN(v) || v < 0 ? Infinity : v / 2;
}
Distance to wall
// returns the distance to the wall
// if no valid solution then return Infinity
var rayDist2Wall = function(ray){
var x,y,u;
rWCross = ray.nx * this.ny - ray.ny * this.nx;
if(!rWCross) { return Infinity; } // Not really needed.
x = ray.x - this.x; // vector from ray to wall start
y = ray.y - this.y;
u = (ray.nx * y - ray.ny * x) / rWCross; // unit distance along normalised wall
// does the ray hit the wall segment
if(u < 0 || u > this.len){ return Infinity;} /// no
// as we use the wall normal and ray normal the unit distance is the same as the
u = (this.nx * y - this.ny * x) / rWCross;
return u < 0 ? Infinity : u; // if behind ray return Infinity else the dist
}
That covers the objects. If you need to have a circle that is inside out (you want the inside surface then change the second last line of the circle ray function to v += rather than v -=
The ray casting
Now it is just a matter of iterating all the objects against the ray and keeping the distant to the closest object. Set the ray to that distance and you are done.
// Does a ray cast.
// ray the ray to cast
// objects an array of objects
var castRay = function(ray,objects)
var i,minDist;
minDist = ray.len; // set the min dist to the rays length
i = objects.length; // number of objects to check
while(i > 0){
i -= 1;
minDist = Math.min(objects[i].rayDist(ray),minDist);
}
ray.len = minDist;
}
A demo
And a demo of all the above in action. THere are some minor changes (drawing). The important stuff is the two intercept functions. The demo creates a random scene each time it is resized and cast 16 rays from the mouse position. I can see in your code you know how to get the direction of a line so I made the demo show how to cast multiple rays that you most likely will end up doing
const COLOUR = "BLACK";
const RAY_COLOUR = "RED";
const LINE_WIDTH = 4;
const RAY_LINE_WIDTH = 2;
const OBJ_COUNT = 20; // number of object in the scene;
const NUMBER_RAYS = 16; // number of rays
const RAY_DIR_SPACING = Math.PI / (NUMBER_RAYS / 2);
const RAY_ROTATE_SPEED = Math.PI * 2 / 31000;
if(typeof Math.hypot === "undefined"){ // poly fill for Math.hypot
Math.hypot = function(x, y){
return Math.sqrt(x * x + y * y);
}
}
var ctx, canvas, objects, ray, w, h, mouse, rand, ray, rayMaxLen, screenDiagonal;
// create a canvas and add to the dom
var canvas = document.createElement("canvas");
canvas.width = w = window.innerWidth;
canvas.height = h = window.innerHeight;
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
document.body.appendChild(canvas);
// objects to ray cast
objects = [];
// mouse object
mouse = {x :0, y: 0};
//========================================================================
// random helper
rand = function(min, max){
return Math.random() * (max - min) + min;
}
//========================================================================
// Ad Hoc draw line method
// col is the stroke style
// width is the storke width
var drawLine = function(col,width){
ctx.strokeStyle = col;
ctx.lineWidth = width;
ctx.beginPath();
ctx.moveTo(this.x,this.y);
ctx.lineTo(this.x + this.nx * this.len, this.y + this.ny * this.len);
ctx.stroke();
}
//========================================================================
// Ad Hoc draw circle method
// col is the stroke style
// width is the storke width
var drawCircle = function(col,width){
ctx.strokeStyle = col;
ctx.lineWidth = width;
ctx.beginPath();
ctx.arc(this.x , this.y, this.radius, 0 , Math.PI * 2);
ctx.stroke();
}
//========================================================================
// Ad Hoc method for ray to set the direction vector
var updateRayDir = function(dir){
this.nx = Math.cos(dir);
this.ny = Math.sin(dir);
return this;
}
//========================================================================
// Creates a ray objects from
// x,y start location
// dir the direction in radians
// len the rays length
var createRay = function(x,y,dir,len){
return ({
x : x,
y : y,
len : len,
draw : drawLine,
setDir : updateRayDir, // add function to set direction
}).setDir(dir);
}
//========================================================================
// returns a circle object
// x,y is the center
// radius is the you know what..
// Note r2 is radius squared if you change the radius remember to set r2 as well
var createCircle = function(x , y, radius){
return {
x : x,
y : y,
draw : drawCircle, // draw function
rayDist : rayDist2Circle, // add ray cast method
radius : radius,
r2 : radius * radius, // ray caster needs square of radius may as well do it here
};
}
//========================================================================
// Ad Hoc function to change the wall position
// x1,y1 are the start coords
// x2,y2 are the end coords
changeWallPosition = function(x1, y1, len, dir){
this.x = x1;
this.y = y1;
this.len = len;
this.nx = Math.cos(dir);
this.ny = Math.sin(dir);
return this;
}
//========================================================================
// returns a wall object
// x1,y1 are the star coords
// len is the length
// dir is the direction
var createWall = function(x1, y1, len, dir){
return({
x : x1, y : y1,
rayDist : rayDist2Wall, // add ray cast method
draw : drawLine,
setPos : changeWallPosition,
}).setPos(x1, y1, len, dir);
}
//========================================================================
// Self evident
// returns a distance or infinity if no valid solution
var rayDist2Circle = function(ray){
var vcx, vcy, v;
vcx = ray.x - this.x; // vector from ray to circle
vcy = ray.y - this.y;
v = -2 * (vcx * ray.nx + vcy * ray.ny);
v -= Math.sqrt(v * v - 4 * (vcx * vcx + vcy * vcy - this.r2)); // this.r2 is the radius squared
// If there is no solution then Math.sqrt returns NaN we should return Infinity
// Not interested in intercepts in the negative direction so return infinity
return isNaN(v) || v < 0 ? Infinity : v / 2;
}
//========================================================================
// returns the distance to the wall
// if no valid solution then return Infinity
var rayDist2Wall = function(ray){
var x,y,u;
rWCross = ray.nx * this.ny - ray.ny * this.nx;
if(!rWCross) { return Infinity; } // Not really needed.
x = ray.x - this.x; // vector from ray to wall start
y = ray.y - this.y;
u = (ray.nx * y - ray.ny * x) / rWCross; // unit distance along normal of wall
// does the ray hit the wall segment
if(u < 0 || u > this.len){ return Infinity;} /// no
// as we use the wall normal and ray normal the unit distance is the same as the
u = (this.nx * y - this.ny * x) / rWCross;
return u < 0 ? Infinity : u; // if behind ray return Infinity else the dist
}
//========================================================================
// does a ray cast
// ray the ray to cast
// objects an array of objects
var castRay = function(ray,objects){
var i,minDist;
minDist = ray.len; // set the min dist to the rays length
i = objects.length; // number of objects to check
while(i > 0){
i -= 1;
minDist = Math.min(objects[i].rayDist(ray), minDist);
}
ray.len = minDist;
}
//========================================================================
// Draws all objects
// objects an array of objects
var drawObjects = function(objects){
var i = objects.length; // number of objects to check
while(i > 0){
objects[--i].draw(COLOUR, LINE_WIDTH);
}
}
//========================================================================
// called on start and resize
// creats a new scene each time
// fits the canvas to the avalible realestate
function reMakeAll(){
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
screenDiagonal = Math.hypot(window.innerWidth,window.innerHeight);
if(ray === undefined){
ray = createRay(0,0,0,screenDiagonal);
}
objects.length = 0;
var i = OBJ_COUNT;
while( i > 0 ){
if(Math.random() < 0.5){ // half circles half walls
objects.push(createWall(rand(0, w), rand(0, h), rand(screenDiagonal * 0.1, screenDiagonal * 0.2), rand(0, Math.PI * 2)));
}else{
objects.push(createCircle(rand(0, w), rand(0, h), rand(screenDiagonal * 0.02, screenDiagonal * 0.05)));
}
i -= 1;
}
}
//========================================================================
function mouseMoveEvent(event){
mouse.x = event.clientX;
mouse.y = event.clientY;
}
//========================================================================
// updates all that is needed when needed
function updateAll(time){
var i;
ctx.clearRect(0,0,w,h);
ray.x = mouse.x;
ray.y = mouse.y;
drawObjects(objects);
i = 0;
while(i < NUMBER_RAYS){
ray.setDir(i * RAY_DIR_SPACING + time * RAY_ROTATE_SPEED);
ray.len = screenDiagonal;
castRay(ray,objects);
ray.draw(RAY_COLOUR, RAY_LINE_WIDTH);
i ++;
}
requestAnimationFrame(updateAll);
}
// add listeners
window.addEventListener("resize",reMakeAll);
canvas.addEventListener("mousemove",mouseMoveEvent);
// set it all up
reMakeAll();
// start the ball rolling
requestAnimationFrame(updateAll);
An alternative use of above draws a polygon using the end points of the cast rays can be seen at codepen
For this you would need a line to circle intersection algorithm for the balls as well as line to line intersection for the walls.
For the ball you can use this function - I made this to return arrays being empty if no intersection, one point if tangent or two points if secant.
Simply feed it start of line, line of sight end-point as well as the ball's center position and radius. In your case you will probably only need the first point:
function lineIntersectsCircle(x1, y1, x2, y2, cx, cy, r) {
x1 -= cx;
y1 -= cy;
x2 -= cx;
y2 -= cy;
// solve quadrant
var a = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1),
b = 2 * ((x2 - x1) * x1 + (y2 - y1) * y1),
c = x1 * x1 + y1 * y1 - r * r,
d = b * b - 4 * a * c,
dq, p1, p2, t1, t2;
if (d <= 0 || !a) return [];
dq = Math.sqrt(d);
t1 = (-b - dq) / (2 * a);
t2 = (-b + dq) / (2 * a);
// calculate actual intersection points
if (t1 >= 0 && t1 <= 1)
p1 = {
x: x1 + t1 * (x2 - x1) + cx,
y: y1 + t1 * (y2 - y1) + cy
};
if (t2 >= 0 && t2 <= 1)
p2 = {
x: x1 + t2 * (x2 - x1) + cx,
y: y1 + t2 * (y2 - y1) + cy
};
return p1 && p2 ? [p1, p2] : p1 ? [p1] : [p2]
};
Then for the walls you would need a line to line intersection - define one line for each side of the rectangle. If there is line overlap you may get hit for two intersection, just ignore the second.
This will return a single point for the intersection or null if no intersection:
function getLineIntersection(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
var d1x = p1x - p0x,
d1y = p1y - p0y,
d2x = p3x - p2x,
d2y = p3y - p2y,
d = d1x * d2y - d2x * d1y,
px, py, s, t;
if (Math.abs(d) < 1e-14) return null;
px = p0x - p2x;
py = p0y - p2y;
s = (d1x * py - d1y * px) / d;
if (s >= 0 && s <= 1) {
t = (d2x * py - d2y * px) / d;
if (t >= 0 && t <= 1) {
return {
x: p0x + (t * d1x),
y: p0y + (t * d1y)
}
}
}
return null
}
Then just iterate with the line through the ball array, if no hit, iterate through the wall array.
Modified fiddle
To utilize these you will have to run the line through these each time it is moved (or per frame update).
Tip: You can make the function recursive so that you can find the intersection point, calculate reflected vector based on the hit angle, then find next intersection for n number of times (or total length the shot can move) using the last intersecting point and new angle as start of next line. This way you can build the path the shot will follow.
var canvas = document.querySelector("canvas");
canvas.width = 500;
canvas.height = 300;
var ctx = canvas.getContext("2d"),
line = {
x1: 190, y1: 170,
x2: 0, y2: 0,
x3: 0, y3: 0
};
var length = 100;
var circle = {
x: 400,
y: 70
};
var wall = {
x1: 440, y1: 0,
x2: 440, y2: 100
};
window.onmousemove = function(e) {
//get correct mouse pos
var rect = ctx.canvas.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top;
// calc line angle
var dx = x - line.x1,
dy = y - line.y1,
angle = Math.atan2(dy, dx);
//Then render the line using length as pixel radius:
line.x2 = line.x1 - length * Math.cos(angle);
line.y2 = line.y1 - length * Math.sin(angle);
line.x3 = line.x1 + canvas.width * Math.cos(angle);
line.y3 = line.y1 + canvas.width * Math.sin(angle);
// does it intersect?
var pts = lineIntersectsCircle(line.x1, line.y1, line.x3, line.y3, circle.x, circle.y, 20);
if (pts.length) {
line.x3 = pts[0].x;
line.y3 = pts[0].y
}
else {
pts = getLineIntersection(line.x1, line.y1, line.x3, line.y3, wall.x1, wall.y1, wall.x2, wall.y2);
if (pts) {
line.x3 = pts.x;
line.y3 = pts.y
}
}
// render
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.moveTo(line.x1, line.y1);
ctx.lineTo(line.x2, line.y2);
ctx.strokeStyle = '#333';
ctx.stroke();
ctx.beginPath();
ctx.moveTo(line.x1, line.y1);
ctx.lineTo(line.x3, line.y3);
ctx.strokeStyle = 'red';
ctx.stroke();
ctx.beginPath();
ctx.arc(circle.x, circle.y, 20, 0, Math.PI * 2, true);
ctx.fillStyle = '#333';
ctx.fill();
// render example wall:
ctx.fillRect(wall.x1, wall.y1, 4, wall.y2-wall.y1);
}
function lineIntersectsCircle(x1, y1, x2, y2, cx, cy, r) {
x1 -= cx;
y1 -= cy;
x2 -= cx;
y2 -= cy;
// solve quadrant
var a = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1),
b = 2 * ((x2 - x1) * x1 + (y2 - y1) * y1),
c = x1 * x1 + y1 * y1 - r * r,
d = b * b - 4 * a * c,
dq, p1, p2, t1, t2;
if (d <= 0 || !a) return [];
dq = Math.sqrt(d);
t1 = (-b - dq) / (2 * a);
t2 = (-b + dq) / (2 * a);
// calculate actual intersection points
if (t1 >= 0 && t1 <= 1)
p1 = {
x: x1 + t1 * (x2 - x1) + cx,
y: y1 + t1 * (y2 - y1) + cy
};
if (t2 >= 0 && t2 <= 1)
p2 = {
x: x1 + t2 * (x2 - x1) + cx,
y: y1 + t2 * (y2 - y1) + cy
};
return p1 && p2 ? [p1, p2] : p1 ? [p1] : [p2]
};
function getLineIntersection(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
var d1x = p1x - p0x,
d1y = p1y - p0y,
d2x = p3x - p2x,
d2y = p3y - p2y,
d = d1x * d2y - d2x * d1y,
px, py, s, t;
if (Math.abs(d) < 1e-14) return null;
px = p0x - p2x;
py = p0y - p2y;
s = (d1x * py - d1y * px) / d;
if (s >= 0 && s <= 1) {
t = (d2x * py - d2y * px) / d;
if (t >= 0 && t <= 1) {
return {
x: p0x + (t * d1x),
y: p0y + (t * d1y)
}
}
}
return null
}
<canvas></canvas>
I don't have enough reputation to add this as a comment to Blindman67's solution, so i have to resort to adding this as an answer.
Blindman67's answer is great, but i needed support for polygons as well.
I am no math wizard so there may be a much better solution for polygons than this, but what i did was loop over all pairs of points from a polygon (so all sides of a polygon, really) and treat them as walls based on the code from Blindman67, then check the ray distance in the new rayDist2Polygon:
var rayDist2Polygon = function(ray){
let u,lineU;
const polLength = this.points.length;
const startX = this.x;
const startY = this.y;
// Loop over all lines of the polygon
for (i = 0; i < polLength; i++) {
const nextPoint = i === polLength - 1 ? this.points[0] : this.points[i + 1];
const x1 = startX + this.points[i].x;
const x2 = startX + nextPoint.x;
const y1 = startY + this.points[i].y;
const y2 = startY + nextPoint.y;
this.setupWall(x1, y1, x2, y2);
lineU = rayDist2Wall.bind(this)(ray);
if (!u) {
// If it's the first hit, assign it to `u`
u = lineU;
} else if (lineU < u) {
// If the current hit is smaller than anything we have so far, then this is the closest one, assign it to `u`
u = lineU;
}
}
// Reset positions after running this.setupWall;
this.x = startX;
this.y = startY;
return (!u || u < 0) ? Infinity : u; // if behind ray return Infinity else the dist
}
Then used the same logic to also support squares by converting a square's dimension/shape to points.
You can view it below, or fiddle with it at my codepen.
// Forked from https://stackoverflow.com/a/36566360/16956030
// All credits go to Blindman67
// All i did was add support for Polygons and Squares based on code from
// Blindman67, by treating each side of a polyon/square as a line/wall,
// then loop over each side and get the smallest result in rayDist2Polygon.
// I'm no math wizard and there may be a much better solution for these shapes,
// but this'll do for now.
console.clear();
const COLOUR = "BLACK";
const RAY_COLOUR = "RED";
const LINE_WIDTH = 4;
const RAY_LINE_WIDTH = 2;
const OBJ_COUNT = 20; // number of object in the scene;
const NUMBER_RAYS = 16; // number of rays
const RAY_DIR_SPACING = Math.PI / (NUMBER_RAYS / 2);
const RAY_ROTATE_SPEED = Math.PI * 2 / 31000;
if(typeof Math.hypot === "undefined"){ // poly fill for Math.hypot
Math.hypot = function(x, y){
return Math.sqrt(x * x + y * y);
}
}
var ctx, canvas, objects, ray, w, h, mouse, rand, ray, rayMaxLen, screenDiagonal;
// create a canvas and add to the dom
var canvas = document.createElement("canvas");
canvas.width = w = window.innerWidth;
canvas.height = h = window.innerHeight;
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
document.body.appendChild(canvas);
// objects to ray cast
objects = [];
// mouse object
mouse = {x :0, y: 0};
//========================================================================
// random helper
rand = function(min, max){
return Math.random() * (max - min) + min;
}
//========================================================================
// Ad Hoc draw line method
// col is the stroke style
// width is the storke width
var drawLine = function(col,width){
ctx.strokeStyle = col;
ctx.lineWidth = width;
ctx.beginPath();
ctx.moveTo(this.x,this.y);
ctx.lineTo(this.x + this.nx * this.len, this.y + this.ny * this.len);
ctx.stroke();
}
//========================================================================
// Ad Hoc draw circle method
// col is the stroke style
// width is the storke width
var drawCircle = function(col,width){
ctx.strokeStyle = col;
ctx.lineWidth = width;
ctx.beginPath();
ctx.arc(this.x , this.y, this.radius, 0 , Math.PI * 2);
ctx.stroke();
}
//========================================================================
// Ad Hoc draw square method
var drawSquare = function(){
ctx.beginPath();
ctx.rect(this.x, this.y, this.width, this.height);
ctx.stroke();
// Create array of points like a polygon based on the position & dimensions
// from this square, necessary for rayDist2Polygon
this.points = [
{ x: 0, y: 0},
{ x: this.width, y: 0},
{ x: this.width, y: this.height},
{ x: 0, y: this.height}
];
}
//========================================================================
// Ad Hoc draw [poligon] method
var drawPolygon = function(){
ctx.beginPath();
ctx.moveTo(this.x,this.y);
var polLength = this.points.length;
for(var i=0; i < polLength; ++i) {
ctx.lineTo(this.x + this.points[i].x, this.y + this.points[i].y);
}
ctx.closePath();
ctx.stroke();
}
//========================================================================
// Ad Hoc method for ray to set the direction vector
var updateRayDir = function(dir){
this.nx = Math.cos(dir);
this.ny = Math.sin(dir);
return this;
}
//========================================================================
// Creates a ray objects from
// x,y start location
// dir the direction in radians
// len the rays length
var createRay = function(x,y,dir,len){
return ({
x : x,
y : y,
len : len,
draw : drawLine,
setDir : updateRayDir, // add function to set direction
}).setDir(dir);
}
//========================================================================
// returns a circle object
// x,y is the center
// radius is the you know what..
// Note r2 is radius squared if you change the radius remember to set r2 as well
var createCircle = function(x , y, radius){
return {
x : x,
y : y,
draw : drawCircle, // draw function
rayDist : rayDist2Circle, // add ray cast method
radius : radius,
r2 : radius * radius, // ray caster needs square of radius may as well do it here
};
}
// Ad Hoc function to set the wall information
// x1,y1 are the start coords
// x2,y2 are the end coords
setupWallInformation = function(x1, y1, x2, y2){
this.x = x1;
this.y = y1;
this.vx = x2 - x1;
this.vy = y2 - y1;
this.len = Math.hypot(this.vx,this.vy);
this.nx = this.vx / this.len;
this.ny = this.vy / this.len;
return this;
}
//========================================================================
// returns a polygon object
// x,y are the start coords
// In this example the polygon always has the same shape
var createPolygon = function(x , y){
return {
x : x,
y : y,
points: [
{ x: 0, y: 0},
{ x: 100, y: 50},
{ x: 50, y: 100},
{ x: 0, y: 90}
],
draw : drawPolygon, // draw function
setupWall : setupWallInformation,
rayDist : rayDist2Polygon, // add ray cast method
};
}
//========================================================================
// returns a square object
// x,y are the start coords
// In this example the polygon always has the same shape
var createSquare = function(x , y, width, height){
return {
x : x,
y : y,
width: width,
height: height,
draw : drawSquare, // draw function
setupWall : setupWallInformation,
rayDist : rayDist2Polygon, // add ray cast method
};
}
//========================================================================
// Ad Hoc function to change the wall position
// x1,y1 are the start coords
// x2,y2 are the end coords
changeWallPosition = function(x1, y1, len, dir){
this.x = x1;
this.y = y1;
this.len = len;
this.nx = Math.cos(dir);
this.ny = Math.sin(dir);
return this;
}
//========================================================================
// returns a wall object
// x1,y1 are the star coords
// len is the length
// dir is the direction
var createWall = function(x1, y1, len, dir){
return({
x : x1, y : y1,
rayDist : rayDist2Wall, // add ray cast method
draw : drawLine,
setPos : changeWallPosition,
}).setPos(x1, y1, len, dir);
}
//========================================================================
// Self evident
// returns a distance or infinity if no valid solution
var rayDist2Circle = function(ray){
var vcx, vcy, v;
vcx = ray.x - this.x; // vector from ray to circle
vcy = ray.y - this.y;
v = -2 * (vcx * ray.nx + vcy * ray.ny);
v -= Math.sqrt(v * v - 4 * (vcx * vcx + vcy * vcy - this.r2)); // this.r2 is the radius squared
// If there is no solution then Math.sqrt returns NaN we should return Infinity
// Not interested in intercepts in the negative direction so return infinity
return isNaN(v) || v < 0 ? Infinity : v / 2;
}
//========================================================================
// returns the distance to the wall
// if no valid solution then return Infinity
var rayDist2Wall = function(ray){
var x,y,u;
rWCross = ray.nx * this.ny - ray.ny * this.nx;
if(!rWCross) { return Infinity; } // Not really needed.
x = ray.x - this.x; // vector from ray to wall start
y = ray.y - this.y;
u = (ray.nx * y - ray.ny * x) / rWCross; // unit distance along normal of wall
// does the ray hit the wall segment
if(u < 0 || u > this.len){ return Infinity;} /// no
// as we use the wall normal and ray normal the unit distance is the same as the
u = (this.nx * y - this.ny * x) / rWCross;
return u < 0 ? Infinity : u; // if behind ray return Infinity else the dist
}
//========================================================================
// returns the distance to the polygon
// if no valid solution then return Infinity
var rayDist2Polygon = function(ray){
let u,lineU;
const polLength = this.points.length;
const startX = this.x;
const startY = this.y;
// Loop over all lines of the polygon
for (i = 0; i < polLength; i++) {
const nextPoint = i === polLength - 1 ? this.points[0] : this.points[i + 1];
const x1 = startX + this.points[i].x;
const x2 = startX + nextPoint.x;
const y1 = startY + this.points[i].y;
const y2 = startY + nextPoint.y;
this.setupWall(x1, y1, x2, y2);
lineU = rayDist2Wall.bind(this)(ray);
if (!u) {
// If it's the first hit, assign it to `u`
u = lineU;
} else if (lineU < u) {
// If the current hit is smaller than anything we have so far, then this is the closest one, assign it to `u`
u = lineU;
}
}
// Reset positions after running this.setupWall;
this.x = startX;
this.y = startY;
return (!u || u < 0) ? Infinity : u; // if behind ray return Infinity else the dist
}
//========================================================================
// does a ray cast
// ray the ray to cast
// objects an array of objects
var castRay = function(ray,objects){
var i,minDist;
minDist = ray.len; // set the min dist to the rays length
i = objects.length; // number of objects to check
while(i > 0){
i -= 1;
minDist = Math.min(objects[i].rayDist(ray), minDist);
}
ray.len = minDist;
}
//========================================================================
// Draws all objects
// objects an array of objects
var drawObjects = function(objects){
var i = objects.length; // number of objects to check
while(i > 0){
objects[--i].draw(COLOUR, LINE_WIDTH);
}
}
//========================================================================
// called on start and resize
// creats a new scene each time
// fits the canvas to the avalible realestate
function reMakeAll(){
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
screenDiagonal = Math.hypot(window.innerWidth,window.innerHeight);
if(ray === undefined){
ray = createRay(0,0,0,screenDiagonal);
}
objects.length = 0;
var i = OBJ_COUNT;
while( i > 0 ){
var objectRandom = Math.floor(rand(0, 4));
if(objectRandom === 1){
objects.push(createWall(rand(0, w), rand(0, h), rand(screenDiagonal * 0.1, screenDiagonal * 0.2), rand(0, Math.PI * 2)));
}else if(objectRandom === 2){
objects.push(createPolygon(rand(0, w), rand(0, h)));
}else if(objectRandom === 3){
objects.push(createSquare(rand(0, w), rand(0, h), rand(screenDiagonal * 0.02, screenDiagonal * 0.05), rand(screenDiagonal * 0.02, screenDiagonal * 0.05)));
}else{
objects.push(createCircle(rand(0, w), rand(0, h), rand(screenDiagonal * 0.02, screenDiagonal * 0.05)));
}
i -= 1;
}
}
//========================================================================
function mouseMoveEvent(event){
mouse.x = event.clientX;
mouse.y = event.clientY;
}
//========================================================================
// updates all that is needed when needed
function updateAll(time){
var i;
ctx.clearRect(0,0,w,h);
ray.x = mouse.x;
ray.y = mouse.y;
drawObjects(objects);
i = 0;
while(i < NUMBER_RAYS){
ray.setDir(i * RAY_DIR_SPACING + time * RAY_ROTATE_SPEED);
ray.len = screenDiagonal;
castRay(ray,objects);
ray.draw(RAY_COLOUR, RAY_LINE_WIDTH);
i ++;
}
requestAnimationFrame(updateAll);
}
// add listeners
window.addEventListener("resize",reMakeAll);
canvas.addEventListener("mousemove",mouseMoveEvent);
// set it all up
reMakeAll();
// start the ball rolling
requestAnimationFrame(updateAll);

How to find the control point of a quadratic curve using a parabola?

I can't figure out how to draw a parabola which is having a equation as y^2 = 4ax
So I have both end points i.e. P0, P2, however I can't figure out how to find control point to put in quadraticCurveTo() function.
To match a quadratic Bezier to this parabola formula and assuming origin is 0, you can use place the control point at -y0 or -y1 from one of the end points.
Example
First, lets rearrange the formula:
y2 = 4ax
to:
x = y2 / 4a
so we can plot from bottom down.
In this case we can simply boil down everything and use the inverse of y and mid x as control point.
The general principle though, is to find the tangents of the endpoints. Then where the lines from those intersect the control-point should be placed. If you want the mathematical steps on how to find the intersection I would recommend taking a look at Erik Man's answer here (which happened to be posted today but breaks down the math in much more details).
So, if we plot it within the window of a canvas (black is parabola, red is quadratic curve):
var ctx = document.querySelector("canvas").getContext("2d"),
w = ctx.canvas.width, h = ctx.canvas.height;
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
ctx.translate(0, 6);
// formula
function f(y, a) {return y * y / (a * 4)};
var a = 80;
plotWindow();
function plotWindow() {
ctx.clearRect(0, -6, w, h);
ctx.fillStyle = "#000";
// plot parabola using formula
for(var i = 0; i < w; i++) {
var y = f(i - w * 0.5, a);
ctx.fillRect(i - 2, y - 2, 4, 4);
}
// plot parabola using quadratic curve:
var x0 = 0;
var y0 = f(-w * 0.5, a);
var x1 = w;
var y1 = f( w * 0.5, a);
var cx = x1 * 0.5; // control point is center for x
var cy = -y0; // control point is -y0 for y assuming top of parabola = 0
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.quadraticCurveTo(cx, cy, x1, y1);
ctx.stroke();
// plot a
ctx.fillStyle = "blue";
ctx.fillRect(cx - 3, a - 3, 6, 6);
ctx.fillText("a=" + a, cx + 6, a + 5)
}
// slider
document.querySelector("input").onchange = function() {
a = +this.value;
plotWindow();
};
canvas {border:1px solid #777}
<script src="https://cdn.rawgit.com/epistemex/slider-feedback/master/sliderfeedback.min.js"></script>
<label>a: <input type="range" min=10 max=172 value=80></label><br>
<canvas width=600 height=190></canvas>

Better line selection than using Bresenham algorithm?

I'm drawing lines on an HTML canvas, and use a less precise 2d-array (representing blocks of 10x10 pixels) in which I 'draw' lines with Bresenham's algorithm to store line-ids, so I can use that array to see which line is selected.
This works, but I would like it to be more accurate - not in the 10x10 size that I use (I like that I don't exactly have to click on the line), but when I draw a representation of that array over my actual canvas, I see that there are a lot of the 10x10 blocks not filled, even though the line is crossing them:
Is there a better solution to this? What I want is to catch ALL grid blocks that the actual line passes through.
Without seeing your code, I think you made a rounding error while filling the lookup table using the Bresenham algorithm or you scaled the coordinates before running the algorithm.
This jsFiddle shows what I came up with and the squares are perfectly aligned.
HTML
<canvas id="myCanvas"></canvas>
CSS
#myCanvas {
width: 250px;
height: 250px;
}
JavaScript
var $canvas = $("#myCanvas"),
ctx = $canvas[0].getContext("2d");
ctx.canvas.width = $canvas.width();
ctx.canvas.height = $canvas.height();
function Grid(ctx) {
this._ctx = ctx;
this._lines = [];
this._table = [];
this._tableScale = 10;
this._createLookupTable();
}
Grid.prototype._createLookupTable = function() {
this._table = [];
for (var y = 0; y < Math.ceil(ctx.canvas.height / this._tableScale); y++) {
this._table[y] = [];
for (var x = 0; x < Math.ceil(ctx.canvas.width / this._tableScale); x++)
this._table[y][x] = null;
}
};
Grid.prototype._updateLookupTable = function(line) {
var x0 = line.from[0],
y0 = line.from[1],
x1 = line.to[0],
y1 = line.to[1],
dx = Math.abs(x1 - x0),
dy = Math.abs(y1 - y0),
sx = (x0 < x1) ? 1 : -1,
sy = (y0 < y1) ? 1 : -1,
err = dx - dy;
while(true) {
this._table[Math.floor(y0 / 10)][Math.floor(x0 / 10)] = line;
if ((x0 == x1) && (y0 == y1)) break;
var e2 = 2 * err;
if (e2 >- dy) { err -= dy; x0 += sx; }
if (e2 < dx) { err += dx; y0 += sy; }
}
};
Grid.prototype.hitTest = function(x, y) {
var ctx = this._ctx,
hoverLine = this._table[Math.floor(y / 10)][Math.floor(x / 10)];
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
this._lines.forEach(function(line) {
line.draw(ctx, line === hoverLine ? "red" : "black");
});
};
Grid.prototype.drawLookupTable = function() {
ctx.beginPath();
for (var y = 0; y < this._table.length; y++)
for (var x = 0; x < this._table[y].length; x++) {
if (this._table[y][x])
ctx.rect(x * 10, y * 10, 10, 10);
}
ctx.strokeStyle = "rgba(0, 0, 0, 0.2)";
ctx.stroke();
};
Grid.prototype.addLine = function(line) {
this._lines.push(line);
this._updateLookupTable(line);
};
Grid.prototype.draw = function() {
var ctx = this._ctx;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
this._lines.forEach(function(line) {
line.draw(ctx);
});
};
function Line(x0, y0, x1, y1) {
this.from = [ x0, y0 ];
this.to = [ x1, y1];
}
Line.prototype.draw = function(ctx, style) {
ctx.beginPath();
ctx.moveTo(this.from[0], this.from[1]);
ctx.lineTo(this.to[0], this.to[1]);
ctx.strokeStyle = style || "black";
ctx.stroke();
};
var grid = new Grid(ctx);
grid.addLine(new Line(80, 10, 240, 75));
grid.addLine(new Line(150, 200, 50, 45));
grid.addLine(new Line(240, 10, 20, 150));
grid.draw();
grid.drawLookupTable();
$canvas.on("mousemove", function(e) {
grid.hitTest(e.offsetX, e.offsetY);
grid.drawLookupTable();
});
Your best option is to treat the mouse-cursor-position as a small circle (f.e. with a 5px radius) and check if the line intersects with the circle.
Use the math as explained in this Q&A
JavaScript
A simple function to detect intersection would be:
function lineCircleIntersects(x1, y1, x2, y2, cx, cy, cr) {
var dx = x2 - x1,
dy = y2 - y1,
a = dx * dx + dy * dy,
b = 2 * (dx * (x1 - cx) + dy * (y1 - cy)),
c = cx * cx + cy * cy,
bb4ac;
c += x1 * x1 + y1 * y1;
c -= 2 * (cx * x1 + cy * y1);
c -= cr * cr;
bb4ac = b * b - 4 * a * c;
return bb4ac >= 0; // true: collision, false: no collision
}
See it working in this jsFiddle, but note that this function will also return true if the cursor is on the slope of the line outside [x1, y1], [x2, y2]. I'll leave this up to you :)
You can also try line-circle-collision library on github which should give you what you want.

Draw Arc initial point, radius and final point in JavaScript Canvas

I need to draw an arc having initial point, radius and final point.
I am using the HTML5 canvas arc function (x, y, radius, startAngle, endAngle, anticlockwise) in JavaScript.
context.arc(x, y, radius, startAngle, endAngle, anticlockwise)
Have:
var initialPoint = { x: 20, y: 40 };
var radius = 40;
var finalPoint = { x: 180, y: 40 };
The expected results:
please some help
You have to do a little math to find the center of a circle that matches your 3 constraints :
• intersects with initialPoints
• intersects with finalPoint
• has provided radius
Notice that there might be no result : if the points are further from twice the radius from one another no circle can match.
If points are < 2 * radius away, we have two results in fact, i don't know how you'd like your user to choose.
The math uses a few properties :
• the circles center are on the line perpendicular to p1, p2.
• pm, the middle point of (p1, p2) is also the middle point of (c1, c2).
• the triangles (p1, pm, c1) and (p1, pm, c2) have a 90° angle in pm (called 'triangle rectangle' in french, donno in english).
Here's a screenshot with the two possible arcs in green/red :
http://jsbin.com/jutidigepeta/1/edit?js,output
var initialPoint = { x: 100, y: 160 };
var radius = 90;
var finalPoint = { x: 240, y: 190 };
var centers = findCenters(radius,initialPoint, finalPoint );
Core function :
//
function findCenters(r, p1, p2) {
// pm is middle point of (p1, p2)
var pm = { x : 0.5 * (p1.x + p2.x) , y: 0.5*(p1.y+p2.y) } ;
drawPoint(pm, 'PM (middle)');
// compute leading vector of the perpendicular to p1 p2 == C1C2 line
var perpABdx= - ( p2.y - p1.y );
var perpABdy = p2.x - p1.x;
// normalize vector
var norm = Math.sqrt(sq(perpABdx) + sq(perpABdy));
perpABdx/=norm;
perpABdy/=norm;
// compute distance from pm to p1
var dpmp1 = Math.sqrt(sq(pm.x-p1.x) + sq(pm.y-p1.y));
// sin of the angle between { circle center, middle , p1 }
var sin = dpmp1 / r ;
// is such a circle possible ?
if (sin<-1 || sin >1) return null; // no, return null
// yes, compute the two centers
var cos = Math.sqrt(1-sq(sin)); // build cos out of sin
var d = r*cos;
var res1 = { x : pm.x + perpABdx*d, y: pm.y + perpABdy*d };
var res2 = { x : pm.x - perpABdx*d, y: pm.y - perpABdy*d };
return { c1 : res1, c2 : res2} ;
}
utilities :
function sq(x) { return x*x ; }
function drawPoint(p, name) {
ctx.fillRect(p.x - 1,p.y - 1,2, 2);
ctx.textAlign = 'center';
ctx.fillText(name, p.x, p.y+10);
}
function drawCircle(c, r) {
ctx.beginPath();
ctx.arc(c.x, c.y, r, 0, 6.28);
ctx.strokeStyle='#000';
ctx.stroke();
}
function drawCircleArc(c, r, p1, p2, col) {
var ang1 = Math.atan2(p1.y-c.y, p1.x-c.x);
var ang2 = Math.atan2(p2.y-c.y, p2.x-c.x);
ctx.beginPath();
var clockwise = ( ang1 > ang2);
ctx.arc(c.x, c.y, r, ang1, ang2, clockwise);
ctx.strokeStyle=col;
ctx.stroke();
}
Edit :
Here a fiddle using 'side', a boolean that states which side of the arc we should choose.
http://jsbin.com/jutidigepeta/3/edit
If anyone is looking for the equivalent in SVG (using D3.js), using the answer from opsb:
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
return {
x: centerX + (radius * Math.cos(angleInRadians)),
y: centerY + (radius * Math.sin(angleInRadians))
};
}
function describeArc(x, y, radius, startAngle, endAngle){
var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);
var arcSweep = endAngle - startAngle <= 180 ? "0" : "1";
var d = [
"M", start.x, start.y,
"A", radius, radius, 0, arcSweep, 0, end.x, end.y
].join(" ");
return d;
}
function sq(x) { return x*x ; }
function drawCircleArcSVG(c, r, p1, p2, col) {
var ang1 = Math.atan2(p1.y-c.y, p1.x-c.x)*180/Math.PI+90;
var ang2 = Math.atan2(p2.y-c.y, p2.x-c.x)*180/Math.PI+90;
var clockwise = side;
var path = describeArc(c.x, c.y, r, ang1, ang2)
console.log(path)
svg.append("path").attr("d", path).attr("fill", "none").attr("stroke-width", 3).attr("stroke", col)
}
function drawPointSVG(p, name) {
svg.append("circle").attr("cx", p.x).attr("cy", p.y).attr("r", 2)
svg.append("text").attr("x", p.x).attr("y", p.y+10).style("font-size", 10).text(name).style("font-family", "Arial")
}
function drawCircleSVG(c, r) {
svg.append("circle").attr("cx", c.x).attr("cy", c.y).attr("r", r).style("fill", "none").attr("stroke", "#000")
}
Working example: https://jsbin.com/qawenekesi/edit

Categories