Related
What I am going to do is to change the curve of a circle.
If I click one point in a circle and drag it to other point, that arc of the circle should be extended or contracted accordingly.
I was going to use beizer curve but there's no guarantee that the new beizer curve will pass the dragged point.
Attached is the image showing a new curve when mouse dragged which I can not solve.
can anyone help me on this matter?
I will be looking forward to your reply
Fit circle to points
Maybe this will help.
The function at the top of the example fitCircleToPoints(x1, y1, x2, y2, x3, y3) will fit a circle to 3 points.
It returns an object
{
x, y, // center of circle
radius, // radius of circle
CCW, // true if circle segment is counter clockwise
}
If the 3 points are all on the same line then there is no circle that can fit (radius Infinity is not valid) so the function returns undefined.
function fitCircleToPoints(x1, y1, x2, y2, x3, y3) {
var x, y, u;
const slopeA = (x2 - x1) / (y1 - y2); // slope of vector from point 1 to 2
const slopeB = (x3 - x2) / (y2 - y3); // slope of vector from point 2 to 3
if (slopeA === slopeB) { return } // Slopes are same thus 3 points form striaght line. No circle can fit.
if (y1 === y2) { // special case with points 1 and 2 have same y
x = ((x1 + x2) / 2);
y = slopeB * x + (((y2 + y3) / 2) - slopeB * ((x2 + x3) / 2));
} else if(y2 === y3) { // special case with points 2 and 3 have same y
x = ((x2 + x3) / 2);
y = slopeA * x + (((y1 + y2) / 2) - slopeA * ((x1 + x2) / 2));
} else {
x = ((((y2 + y3) / 2) - slopeB * ((x2 + x3) / 2)) - (u = ((y1 + y2) / 2) - slopeA * ((x1 + x2) / 2))) / (slopeA - slopeB);
y = slopeA * x + u;
}
return {
x, y,
radius: ((x1 - x) ** 2 + (y1 - y) ** 2) ** 0.5,
CCW: ((x3 - x1) * (y2 - y1) - (y3 - y1) * (x2 - x1)) >= 0,
};
}
requestAnimationFrame(update);
Math.TAU = Math.PI * 2;
const ctx = canvas.getContext("2d");
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
const bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
var w = canvas.width, h = canvas.height, cw = w / 2, ch = h / 2;
var nearest, ox, oy, dragging, dragIdx;
const points = [10,110,200,100,400,110];
function drawPoint(x, y, rad, col = "black") {
ctx.strokeStyle = col;
ctx.beginPath();
ctx.arc(x, y, rad, 0, Math.TAU);
ctx.stroke();
}
function drawLines(idx, col = "black") {
ctx.strokeStyle = col;
ctx.beginPath();
ctx.lineTo(points[idx++], points[idx++]);
ctx.lineTo(points[idx++], points[idx++]);
ctx.lineTo(points[idx++], points[idx++]);
ctx.stroke();
}
function drawPoints() {
var i = 0, x, y;
nearest = - 1;
var minDist = 20;
while (i < points.length) {
drawPoint(x = points[i++], y = points[i++], 4);
const dist = (x - mouse.x) ** 2 + (y - mouse.y) ** 2;
if (dist < minDist) {
minDist = dist;
nearest = i - 2;
}
}
}
function update(){
ctx.setTransform(1,0,0,1,0,0); // reset transform
if (w !== innerWidth || h !== innerHeight) {
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
} else {
ctx.clearRect(0,0,w,h);
}
canvas.style.cursor = "default";
drawPoints();
if (nearest > -1) {
if (mouse.button) {
if (!dragging) {
dragging = true;
ox = points[nearest] - mouse.x;
oy = points[nearest+1] - mouse.y;
dragIdx = nearest;
}
} else {
canvas.style.cursor = "move";
}
drawPoint(points[nearest], points[nearest + 1], 6, "red")
}
if (dragging) {
if (!mouse.button) {
dragging = false;
} else {
points[dragIdx] = mouse.x + ox;
points[dragIdx + 1] = mouse.y + oy
canvas.style.cursor = "none";
}
}
drawLines(0, "#0002");
const circle = fitCircleToPoints(points[0], points[1], points[2], points[3], points[4], points[5]);
if (circle) {
ctx.strokeStyle = "#000";
const ang1 = Math.atan2(points[1] - circle.y, points[0]- circle.x);
const ang2 = Math.atan2(points[5] - circle.y, points[4]- circle.x);
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.radius, ang1, ang2, circle.CCW);
ctx.stroke();
}
requestAnimationFrame(update);
}
canvas { position : absolute; top : 0px; left : 0px; }
<canvas id="canvas"></canvas>
Use mouse to move points.
You could draw two curves but make sure the control points are in line so you get a smooth transition. Using this tool I've made an example. His is however not a circle and not an ellipse.
I am working on this script where I have x-number bouncing balls (in this case 20 balls) in a canvas.
My question is, how do I make them bounce off each other when they hit, as well as bounce off the yellow rectangle when they hit it?
var mycanvas =document.getElementById("mycanvas");
var ctx=mycanvas.getContext("2d");
var w=500,h=500;
mycanvas.height=h;
mycanvas.width=w;
var ball=[];
function Ball(x,y,r,c,vx,vy){
this.x=x; //starting x coordinate
this.y=y; //starting x coordinate
this.r=r; //radius
this.c=c; //color
this.vx=vx; // x direction speed
this.vy=vy; // y direction speed
this.update=function(){
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI*2, false);
ctx.fillStyle = this.c;
ctx.fill();
ctx.closePath();
this.x += this.vx;
this.y += this.vy;
//changing direction on hitting wall
if(this.y>=(w-10)||this.y<=10){
this.vy=-this.vy;
}
if(this.x>=(h-10)||this.x<=10){
this.vx=-this.vx;
}
}
}
function clearCanvas(){
ctx.clearRect(0, 0, w, h);
}
var count;
for (count = 0; count < 20; count++) {
var rndColor=Math.floor((Math.random() * 9) + 1); //random color
ball[count]= new Ball(Math.floor((Math.random() * 490) + 1),Math.floor((Math.random() * 490)+1),5,'red',5,5);
}
function update(){
var i;
clearCanvas();
//draw rectangle
ctx.rect(250, 200, 10, 100);
ctx.fillStyle = 'yellow';
ctx.fill();
for(i=0;i<count;i++){
ball[i].update();
}
}
setInterval(update, 1000/60);
There are several methods you can use. The following methods are about the simplest.
Update
I have added an example that uses the second method. See snippet at the bottom.
Defining the balls
Each example is as an object called Ball.
// x,y position of center,
// vx,vy is velocity,
// r is radius defaults 45,
// m is mass defaults to the volume of the sphere of radius r
function Ball(x, y, vx, vy, r = 45, m = (4 / 3 * Math.PI * (r ** 3)) {
this.r = r;
this.m = m;
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
}
Ball.prototype = {
// add collision functions here
};
The code assumes the balls are touching.
Elastic collisions
The logic used can be found at wikis elastic collision page
The calculation splits the forces into two parts for each ball. (4 in total for 2 balls)
The transfer of energy along the line between the balls,
The adjustment of energy per ball along the tangent of the collision point
Equal mass
Each ball has the same mass which means that the transfer of energy is balanced and can be ignored
After the function is called each ball has a new velocity vector.
Note that if you call collision and the velocities mean that the balls are moving away from each other (collision paradox) then the result will have the balls moving into each other (resolution paradox)
To keep the math simple the vector magnitudes u1, u2, u3, and u4 are converted into a unit that is the length of the line between the ball centers (square root of d)
collide(b) { // b is the ball that the collision is with
const a = this;
const x = a.x - b.x;
const y = a.y - b.y;
const d = x * x + y * y;
const u1 = (a.vx * x + a.vy * y) / d; // From this to b
const u2 = (x * a.vy - y * a.vx) / d; // Adjust self along tangent
const u3 = (b.vx * x + b.vy * y) / d; // From b to this
const u4 = (x * b.vy - y * b.vx) / d; // Adjust b along tangent
// set new velocities
b.vx = x * u1 - y * u4;
b.vy = y * u1 + x * u4;
a.vx = x * u3 - y * u2;
a.vy = y * u3 + x * u2;
},
Different masses
Each ball has its own mass and thus the transfer needs to calculate the amount of energy related to the mass that is transferred.
Only the energy transferred along the line between the balls is effect by the mass differences
collideMass(b) {
const a = this;
const m1 = a.m;
const m2 = b.m;
const x = a.x - b.x;
const y = a.y - b.y;
const d = x * x + y * y;
const u1 = (a.vx * x + a.vy * y) / d;
const u2 = (x * a.vy - y * a.vx) / d;
const u3 = (b.vx * x + b.vy * y) / d;
const u4 = (x * b.vy - y * b.vx) / d;
const mm = m1 + m2;
const vu3 = (m1 - m2) / mm * u1 + (2 * m2) / mm * u3;
const vu1 = (m2 - m1) / mm * u3 + (2 * m1) / mm * u1;
b.vx = x * vu1 - y * u4;
b.vy = y * vu1 + x * u4;
a.vx = x * vu3 - y * u2;
a.vy = y * vu3 + x * u2;
},
Example
Simple ball collision example. Balls bound by lines (Note lines have an outside and inside, if looking from the start to the end the inside is on the right)
Collisions are fully resolved in chronological order between frames. The time used is a frame where 0 is the previous frame and 1 is the current frame.
canvas.width = innerWidth -20;
canvas.height = innerHeight -20;
const ctx = canvas.getContext("2d");
const GRAVITY = 0;
const WALL_LOSS = 1;
const BALL_COUNT = 20; // approx as will not add ball if space can not be found
const MIN_BALL_SIZE = 13;
const MAX_BALL_SIZE = 20;
const VEL_MIN = 1;
const VEL_MAX = 5;
const MAX_RESOLUTION_CYCLES = 100;
Math.TAU = Math.PI * 2;
Math.rand = (min, max) => Math.random() * (max - min) + min;
Math.randI = (min, max) => Math.random() * (max - min) + min | 0; // only for positive numbers 32bit signed int
Math.randItem = arr => arr[Math.random() * arr.length | 0]; // only for arrays with length < 2 ** 31 - 1
// contact points of two circles radius r1, r2 moving along two lines (a,e)-(b,f) and (c,g)-(d,h) [where (,) is coord (x,y)]
Math.circlesInterceptUnitTime = (a, e, b, f, c, g, d, h, r1, r2) => { // args (x1, y1, x2, y2, x3, y3, x4, y4, r1, r2)
const A = a * a, B = b * b, C = c * c, D = d * d;
const E = e * e, F = f * f, G = g * g, H = h * h;
var R = (r1 + r2) ** 2;
const AA = A + B + C + F + G + H + D + E + b * c + c * b + f * g + g * f + 2 * (a * d - a * b - a * c - b * d - c * d - e * f + e * h - e * g - f * h - g * h);
const BB = 2 * (-A + a * b + 2 * a * c - a * d - c * b - C + c * d - E + e * f + 2 * e * g - e * h - g * f - G + g * h);
const CC = A - 2 * a * c + C + E - 2 * e * g + G - R;
return Math.quadRoots(AA, BB, CC);
}
Math.quadRoots = (a, b, c) => { // find roots for quadratic
if (Math.abs(a) < 1e-6) { return b != 0 ? [-c / b] : [] }
b /= a;
var d = b * b - 4 * (c / a);
if (d > 0) {
d = d ** 0.5;
return [0.5 * (-b + d), 0.5 * (-b - d)]
}
return d === 0 ? [0.5 * -b] : [];
}
Math.interceptLineBallTime = (x, y, vx, vy, x1, y1, x2, y2, r) => {
const xx = x2 - x1;
const yy = y2 - y1;
const d = vx * yy - vy * xx;
if (d > 0) { // only if moving towards the line
const dd = r / (xx * xx + yy * yy) ** 0.5;
const nx = xx * dd;
const ny = yy * dd;
return (xx * (y - (y1 + nx)) - yy * (x -(x1 - ny))) / d;
}
}
const balls = [];
const lines = [];
function Line(x1,y1,x2,y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
Line.prototype = {
draw() {
ctx.moveTo(this.x1, this.y1);
ctx.lineTo(this.x2, this.y2);
},
reverse() {
const x = this.x1;
const y = this.y1;
this.x1 = this.x2;
this.y1 = this.y2;
this.x2 = x;
this.y2 = y;
return this;
}
}
function Ball(x, y, vx, vy, r = 45, m = 4 / 3 * Math.PI * (r ** 3)) {
this.r = r;
this.m = m
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
}
Ball.prototype = {
update() {
this.x += this.vx;
this.y += this.vy;
this.vy += GRAVITY;
},
draw() {
ctx.moveTo(this.x + this.r, this.y);
ctx.arc(this.x, this.y, this.r, 0, Math.TAU);
},
interceptLineTime(l, time) {
const u = Math.interceptLineBallTime(this.x, this.y, this.vx, this.vy, l.x1, l.y1, l.x2, l.y2, this.r);
if(u >= time && u <= 1) {
return u;
}
},
checkBallBallTime(t, minTime) {
return t > minTime && t <= 1;
},
interceptBallTime(b, time) {
const x = this.x - b.x;
const y = this.y - b.y;
const d = (x * x + y * y) ** 0.5;
if(d > this.r + b.r) {
const times = Math.circlesInterceptUnitTime(
this.x, this.y,
this.x + this.vx, this.y + this.vy,
b.x, b.y,
b.x + b.vx, b.y + b.vy,
this.r, b.r
);
if(times.length) {
if(times.length === 1) {
if(this.checkBallBallTime(times[0], time)) { return times[0] }
return;
}
if(times[0] <= times[1]) {
if(this.checkBallBallTime(times[0], time)) { return times[0] }
if(this.checkBallBallTime(times[1], time)) { return times[1] }
return
}
if(this.checkBallBallTime(times[1], time)) { return times[1] }
if(this.checkBallBallTime(times[0], time)) { return times[0] }
}
}
},
collideLine(l, time) {
const x1 = l.x2 - l.x1;
const y1 = l.y2 - l.y1;
const d = (x1 * x1 + y1 * y1) ** 0.5;
const nx = x1 / d;
const ny = y1 / d;
const u = (this.vx * nx + this.vy * ny) * 2;
this.x += this.vx * time;
this.y += this.vy * time;
this.vx = (nx * u - this.vx) * WALL_LOSS;
this.vy = (ny * u - this.vy) * WALL_LOSS;
this.x -= this.vx * time;
this.y -= this.vy * time;
},
collide(b, time) {
const a = this;
const m1 = a.m;
const m2 = b.m;
const x = a.x - b.x
const y = a.y - b.y
const d = (x * x + y * y);
const u1 = (a.vx * x + a.vy * y) / d
const u2 = (x * a.vy - y * a.vx ) / d
const u3 = (b.vx * x + b.vy * y) / d
const u4 = (x * b.vy - y * b.vx ) / d
const mm = m1 + m2;
const vu3 = (m1 - m2) / mm * u1 + (2 * m2) / mm * u3;
const vu1 = (m2 - m1) / mm * u3 + (2 * m1) / mm * u1;
a.x = a.x + a.vx * time;
a.y = a.y + a.vy * time;
b.x = b.x + b.vx * time;
b.y = b.y + b.vy * time;
b.vx = x * vu1 - y * u4;
b.vy = y * vu1 + x * u4;
a.vx = x * vu3 - y * u2;
a.vy = y * vu3 + x * u2;
a.x = a.x - a.vx * time;
a.y = a.y - a.vy * time;
b.x = b.x - b.vx * time;
b.y = b.y - b.vy * time;
},
doesOverlap(ball) {
const x = this.x - ball.x;
const y = this.y - ball.y;
return (this.r + ball.r) > ((x * x + y * y) ** 0.5);
}
}
function canAdd(ball) {
for(const b of balls) {
if (ball.doesOverlap(b)) { return false }
}
return true;
}
function create(bCount) {
lines.push(new Line(-10, 10, ctx.canvas.width + 10, 5));
lines.push((new Line(-10, ctx.canvas.height - 2, ctx.canvas.width + 10, ctx.canvas.height - 10)).reverse());
lines.push((new Line(10, -10, 4, ctx.canvas.height + 10)).reverse());
lines.push(new Line(ctx.canvas.width - 3, -10, ctx.canvas.width - 10, ctx.canvas.height + 10));
while (bCount--) {
let tries = 100;
debugger
while (tries--) {
const dir = Math.rand(0, Math.TAU);
const vel = Math.rand(VEL_MIN, VEL_MAX);
const ball = new Ball(
Math.rand(MAX_BALL_SIZE + 10, canvas.width - MAX_BALL_SIZE - 10),
Math.rand(MAX_BALL_SIZE + 10, canvas.height - MAX_BALL_SIZE - 10),
Math.cos(dir) * vel,
Math.sin(dir) * vel,
Math.rand(MIN_BALL_SIZE, MAX_BALL_SIZE),
);
if (canAdd(ball)) {
balls.push(ball);
break;
}
}
}
}
function resolveCollisions() {
var minTime = 0, minObj, minBall, resolving = true, idx = 0, idx1, after = 0, e = 0;
while(resolving && e++ < MAX_RESOLUTION_CYCLES) { // too main ball may create very lone resolution cycle. e limits this
resolving = false;
minObj = undefined;
minBall = undefined;
minTime = 1;
idx = 0;
for(const b of balls) {
idx1 = idx + 1;
while(idx1 < balls.length) {
const b1 = balls[idx1++];
const time = b.interceptBallTime(b1, after);
if(time !== undefined) {
if(time <= minTime) {
minTime = time;
minObj = b1;
minBall = b;
resolving = true;
}
}
}
for(const l of lines) {
const time = b.interceptLineTime(l, after);
if(time !== undefined) {
if(time <= minTime) {
minTime = time;
minObj = l;
minBall = b;
resolving = true;
}
}
}
idx ++;
}
if(resolving) {
if (minObj instanceof Ball) {
minBall.collide(minObj, minTime);
} else {
minBall.collideLine(minObj, minTime);
}
after = minTime;
}
}
}
create(BALL_COUNT);
mainLoop();
function mainLoop() {
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
resolveCollisions();
for(const b of balls) { b.update() }
ctx.strokeStyle = "#000";
ctx.beginPath();
for(const b of balls) { b.draw() }
for(const l of lines) { l.draw() }
ctx.stroke();
requestAnimationFrame(mainLoop);
}
<canvas id="canvas"></canvas>
To bounce balls off of one another, he's what you need to know
Have the balls collided? The way to determine is to measure the distance between the centers of the two circles. If this is less than the combined radiuses, the balls have collided
What direction should they have after colliding? Use use atan2 to calculate the angle between the centers of the two balls. Then set them in opposite directions on that angle, in a way that they don't end up deeper within each other. Of course, this simple solution ignores existing momentum that the balls may have. But doing the momentum calculation (which involves mass, speed, and current angle) is more complicated.
With the help of this forum I wrote some code to make an animation of a ball that goes into a basket.
My idea is that the user has to write the result of a mathematical operation within a time limit. If he gets it right, the ball goes into the basket.
The only problem is that I can't find out how to delay the start of the animation. My goal is to have a fixed canvas with a ball and a basket, and then at the end of the time limit I want the ball to start moving. I've tried to use a sleep() function that I got from the internet, but soon I learnt that it gave me more trouble than anything else. Here is a part of my code:
const tabx = 800, taby = 100; //backboard position
var canvas, ctx, i=0;
function init(z) { //z is the state of the function, at the start is 0 then goes to 1
canvas = document.getElementById("mycanvas");
ctx = canvas.getContext("2d");
//backboard
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.rect(tabx, taby, 180, 100);
ctx.stroke();
//basket
ctx.scale(1, 0.5);
ctx.beginPath();
ctx.arc(890, 320, 35, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.moveTo(855, 320);
ctx.lineTo(865, 445);
ctx.stroke();
ctx.moveTo(925, 320);
ctx.lineTo(915, 445);
ctx.stroke();
ctx.scale(1, 0.15);
ctx.beginPath();
ctx.arc(890, 2980, 25, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.setTransform(1, 0, 0, 1, 0, 0); //setting transformation to default
if (z == 0){
start();
}
}
function start() {
var x1, y1, a, b, c, denom,x3,y3;
//generating ball position
x1 = (Math.random() * 500) + 100;
y1 = (Math.random() * 200) + 300;
canvas = document.getElementById("mycanvas");
ctx = canvas.getContext("2d");
//setting the end point of the parabola
x3 = tabx + 90;
y3 = taby + 90;
//setting the medium point of the parabola
x2 = (x1 + (x3+90))/ 2;
y2 = ((y1 + (y3+220)) / 2) - 300;
//calculating the equation of the parabola
denom = (x1 - x2) * (x1 - x3) * (x2 - x3);
a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom;
b = (x3 * x3 * (y1 - y2) + x2 * x2 * (y3 - y1) + x1 * x1 * (y2 - y3)) / denom;
c = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3) / denom;
drawball(a, b, c, x1, y1,x3);
}
function drawball(a, b, c, x1, y1, x3){
var ris;
canvas = document.getElementById("mycanvas");
ctx = canvas.getContext("2d");
//ball drawing
ctx.beginPath();
ctx.arc(x1, y1, 25, 0, 2 * Math.PI);
ctx.stroke();
if(i==1){
sleep(3000);
ris=genmat();
}
i++;
if (x1 < x3) {
window.requestAnimationFrame(function() {
ctx.clearRect(0, 0, 1000, 600);
init(1); //calling the init function to redraw everything
y1 = a * (x1 * x1) + b * x1 + c;
x1 += 10;
//drawing the next ball
drawpalla(a, b, c, x1, y1,x3)
});
}
}
function sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
}
//function that generates the operation
function genmat(){
var n1,n2,op,ris;
n1=Math.trunc(Math.random()*10)
n2=Math.trunc(Math.random()*10)
op=Math.trunc((Math.random()*(4-1))+1)
if(op==1){
op="+";
ris=n1+n2;
}
if(op==2){
op="-";
ris=n1-n2;
}
if(op==3){
op="*";
ris=n1*n2;
}
if(op==4){
op="/";
ris=n1/n2;
}
document.getElementById("mate").innerHTML=n1+op+n2;
return ris;
}
Thank you for your help.
I'm trying to learn some basic vector math, but I can't seem to get this method for rotating a point to work. The magnitude of the rotated vectors is scaling up and I don't know what's up with the angle.
Here's the relevant function. I'm working in javascript/HTML canvas.
function rotate(point, center, angle) {
var theta = (Math.PI / 180) * angle,
cX = center.pos.x,
cY = center.pos.y,
pX = point.pos.x,
pY = point.pos.y,
pCos = Math.cos(theta),
pSin = Math.sin(theta),
x = pX - cX,
y = pY - cY;
x = (x * pCos - y * pSin) + cX;
y = (x * pSin + y * pCos) + cY;
return {x: Math.floor(x), y: Math.floor(y)};
}
Here's a jsbin of the weird result
The function is almost right but you are just using the modified x value to calculate y
function rotate(point, center, angle) {
var theta = (Math.PI / 180) * angle,
cX = center.pos.x,
cY = center.pos.y,
pX = point.pos.x,
pY = point.pos.y,
pCos = Math.cos(theta),
pSin = Math.sin(theta),
x = pX - cX,
y = pY - cY;
/* You had
x = (x * pCos - y * pSin) + cX; // you change x on this line
y = (x * pSin + y * pCos) + cY; /// then used the modified x to get y
*/
// this will fix the problem
var xx = (x * pCos - y * pSin) + cX;
var yy = (x * pSin + y * pCos) + cY;
return {x: Math.floor(xx), y: Math.floor(yy)};
}
I have two points(x1,y1 and x2,y2) on a circle and the center (c1,c2) of a circle
and need javascript code to calcuate the intersection point of two tangent lines thru points x1,y1 and x2,y2.
I am using it to convert from a circle (really an arc defined by the above points) to a quadratic bezier curve.
The normals of the tangents are:
n1x = x1 - c1
n1y = y1 - c2
n2x = x2 - c1
n2y = y2 - c2
Using the following parameters:
d1 = n1x * x1 + n1y * y1
d2 = n2x * x2 + n2y * y2
the equations of the tangents can be written as:
x * n1x + y * n1y = d1
x * n2x + y * n2y = d2
Solving the linear equation system yields the following result in general case:
x = (d2 * n1y - d1 * n2y) / (n1y * n2x - n1x * n2y)
y = (d1 * n2x - d2 * n1x) / (n1y * n2x - n1x * n2y)
In javascript:
var x1,y1,x2,y2,c1,c2; // inputs
var x, y; // outputs
... get the parameters somehow
var n1x = x1 - c1;
var n1y = y1 - c2;
var n2x = x2 - c1;
var n2y = y2 - c2;
var d1 = n1x * x1 + n1y * y1;
var d2 = n2x * x2 + n2y * y2;
var det = n1y * n2x - n1x * n2y;
if (det === 0) {
// The lines are parallel
} else {
x = (d2 * n1y - d1 * n2y) / det;
y = (d1 * n2x - d2 * n1x) / det;
}
The vector from the center (c₁, c₂) to a point (xᵢ, yᵢ) on a circumference is (xᵢ-c₁, yᵢ-c₂).
That means the line through (c₁, c₂) and (xᵢ, yᵢ) has slope sᵢ = (yᵢ-c₂)/(xᵢ-c₁).
Let tᵢ be the slope of the tangent line on (xᵢ, yᵢ). tᵢ = -1/sᵢ = (c₁-xᵢ)/(yᵢ-c₂).
Let oᵢ = yᵢ-tᵢxᵢ. Then the tangent line through (xᵢ, yᵢ) is
y = tᵢ(x-xᵢ) + yᵢ = tᵢx + oᵢ
Doing this for i = 1, i = 2 produces a linear equation system.
y = t₁x + o₁
y = t₂x + o₂
Solving it gives the intersection of the tangent lines
o₁-o₂
x = ─────
t₂-t₁
o₁-o₂
y = t₁ ───── + o₁
t₂-t₁