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.
Related
I used quadraticCurveTo to draw the rounded corner rect in canvas
function roundRect(x0, y0, x1, y1, r, color) {
var w = x1 - x0;
var h = y1 - y0;
if (r > w/2) r = w/2;
if (r > h/2) r = h/2;
context.beginPath();
context.moveTo(x1 - r, y0);
context.quadraticCurveTo(x1, y0, x1, y0 + r);
context.lineTo(x1, y1-r);
context.quadraticCurveTo(x1, y1, x1 - r, y1);
context.lineTo(x0 + r, y1);
context.quadraticCurveTo(x0, y1, x0, y1 - r);
context.lineTo(x0, y0 + r);
context.quadraticCurveTo(x0, y0, x0 + r, y0);
context.closePath();
context.fillStyle = color;
context.fill();
}
Now I got the (x1,y1), (x2,y2), (x3,y3), (x4,y4) four points of the rect,
and want to draw the rotated rounded corner rect in canvas without using canvas.rotate()
function roundRect(x1, y1, x2, y2, x3, y3, x4, y4, r, color) {
var w = x4 - x1;
var h = y4 - y1;
if (r > w/2) r = w/2;
if (r > h/2) r = h/2;
context.beginPath();
context.moveTo(x2 - r, y2);
context.quadraticCurveTo(x2, y2, x2, y2 + r);
context.lineTo(x4, y4-r);
context.quadraticCurveTo(x4, y4, x4 - r, y4);
context.lineTo(x3 + r, y3);
context.quadraticCurveTo(x3, y3, x3, y3 - r);
context.lineTo(x1, y1 + r);
context.quadraticCurveTo(x1, y1, x1 + r, y1);
context.closePath();
context.fillStyle = color;
context.fill();
}
The corner was wrong position of this code, any solution to draw the rotated rounded rect using x1-x4, y1-y4 without using canvas.rotate()? I sure that my x1-x4, y1-y4 is works.
Transforming coordinates
To rotate the box you need to apply a rotation matrix on each of the points.
The matrix
The matrix defines the x axis, (top) and y axis, (right side of a pixel, including scale, or how big a pixel is), and where the origin is (coordinate {x:0, y:0})
const xAx = Math.cos(angle) * scale; // scale is the size of a pixel
const xAy = Math.sin(angle) * scale;
const yAx = Math.cos(angle + Math.PI / 2) * scale; // Y axis 90 deg CW from x axis
const yAy = Math.sin(angle + Math.PI / 2) * scale;
matrix[0] = xAx; // x part of x axis
matrix[1] = xAy; // y part of x axis
matrix[2] = yAx; // x part of y axis
matrix[3] = yAy; // y part of y axis
matrix[4] = 0; // origin x
matrix[5] = 0; // origin y
The transformation
When you transform a coordinate x, y to tx, ty...
const x = ?
const y = ?
var tx, ty;
...you first move it alone the x axis...
tx = x * matrix[0]
ty = x * matrix[1]
... which scales it along the x axis at the same time. Then move and scale along the y axis.
tx += y * matrix[2]
ty += y * matrix[3]
Then move to the origin
tx += matrix[4]
ty += matrix[5]
This transformation moves a coordinate from local space to world space (or in 2D world space is often called the view)
Local space
When you rotate a shape you need to pick a point around which you want to rotate it, for example the center, or at one corner.
To do that you define the shape relative to the rotation point (in the shape's local space). If for example you want to rotate the box around the center you define top left and bottom right points to be equal distance from zero eg [-100, -50], [100, 50]
To rotate at a corner you position the box relative to that corner. eg top left the box is [0, 0], [200, 100]
You position the shape in world space, by setting the origin of the matrix (where on the canvas the rotation center will be)
Example
The above matrix calculations can be simplified if we know that the scale is uniform (x and y axis scale the same amount), and that the x, and y axis are always 90 degree from each other.
The example uses an array to hold the matrix, the functions
transformPoint applies the matrix to a point
setOrigin sets the transform origin (where on canvas the rotation point is)
setRotation sets the directions of the x and y axis
setScale not used in example. Sets the scale of the transform. NOTE must call setScale after setRotation in this example
setTransform not used in example. Does the above 3 in one call
roundRect draws the shape, it is given the top left and bottom right coordinates of the box in local space. It constrains the corner radius to the min size that will fit and still maintain (near as beziers are never round) round corners
There are two boxes to demonstrate changing the center of rotation. One rotates about its center the other around the top left corner.
A third box (red) demonstrates that there is no reason to manually transform the box, that using the 2D API transformations is identical but far simpler and a lot quicker
requestAnimationFrame(update);
const ctx = canvas.getContext("2d");
const matrix = [1,0,0,1,0,0];
function transfromPoint(x, y) {
const m = matrix;
return [x * m[0] + y * m[2] + m[4], x * m[1] + y * m[3] + m[5]];
}
function setOrigin(x, y) {
matrix[4] = x;
matrix[5] = y;
}
function setRotation(angle) {
const ax = Math.cos(angle);
const ay = Math.sin(angle);
matrix[0] = ax;
matrix[1] = ay;
matrix[2] = -ay;
matrix[3] = ax;
}
function setScale(scale) {
matrix[0] *= scale;
matrix[1] *= scale;
matrix[2] *= scale;
matrix[3] *= scale;
}
function setTransform(ox, oy, rot, scale) {
const ax = Math.cos(rot) * scale;
const ay = Math.sin(rot) * scale;
matrix[0] = ax;
matrix[1] = ay;
matrix[2] = -ay;
matrix[3] = ax;
matrix[4] = ox;
matrix[5] = oy;
}
function roundRect(x1, y1, x2, y2, r, color = "#000", lineWidth = 2) {
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
const min = Math.min(Math.abs(x1 - x2), Math.abs(y1 - y2));
r = r > min ? min / 2 : r;
ctx.beginPath();
ctx.moveTo(...transfromPoint(x2 - r, y1));
ctx.quadraticCurveTo(...transfromPoint(x2, y1), ...transfromPoint(x2, y1 + r));
ctx.lineTo(...transfromPoint(x2, y2 - r));
ctx.quadraticCurveTo(...transfromPoint(x2, y2), ...transfromPoint(x2 - r, y2));
ctx.lineTo(...transfromPoint(x1 + r, y2));
ctx.quadraticCurveTo(...transfromPoint(x1, y2), ...transfromPoint(x1 , y2 - r));
ctx.lineTo(...transfromPoint(x1, y1 + r));
ctx.quadraticCurveTo(...transfromPoint(x1, y1), ...transfromPoint(x1 + r, y1));
ctx.closePath();
ctx.stroke();
}
function roundRectAPITransform(x1, y1, x2, y2, r, color = "#F00", lineWidth = 2) {
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
const min = Math.min(Math.abs(x1 - x2), Math.abs(y1 - y2));
r = r > min ? min / 2 : r;
ctx.beginPath();
ctx.moveTo(x2 - r, y1);
ctx.quadraticCurveTo(x2, y1, x2, y1 + r);
ctx.lineTo(x2, y2 - r);
ctx.quadraticCurveTo(x2, y2, x2 - r, y2);
ctx.lineTo(x1 + r, y2);
ctx.quadraticCurveTo(x1, y2, x1 , y2 - r);
ctx.lineTo(x1, y1 + r);
ctx.quadraticCurveTo(x1, y1, x1 + r, y1);
ctx.closePath();
ctx.stroke();
}
function update(time) {
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
// around center
setOrigin(100, 100);
setRotation(time * Math.PI / 2000); // one rotation every 4 seconds
roundRect(-60, -35, 60, 35, 15);
// around top right corner
setOrigin(300, 100);
setRotation(-time * Math.PI / 2000); // one rotation every 4 seconds
roundRect(-60, 0, 0, 35, 5);
// red box using API
ctx.setTransform(1,0,0,1,200,100);
ctx.rotate(-time * Math.PI / 4000) // once every 8 seconds;
roundRectAPITransform(-30, -15, 30, 15, 12);
ctx.setTransform(1,0,0,1,0,0); // restore default
requestAnimationFrame(update);
}
<canvas id="canvas" width="400" height="200"></canvas>
Update
Re comments
You don't want to rotate a rectangle, you already have it rotated.
The following function adds rounded corners to a rotate rectangle
// MUST BE RECTANGULAR!!
function roundRectangle(x1, y1, x2, y2, x3, y3, x4, y4, r, color) {
// get top and left edge vectors and lengths
var tx = x2 - x1;
var ty = y2 - y1;
const td = (tx * tx + ty * ty) ** 0.5;
var lx = x3 - x1;
var ly = y3 - y1;
const ld = (lx * lx + ly * ly) ** 0.5;
// Constrain corner radius
const min = Math.min(td, ld) / 2;
r = r > min ? min : r;
// Normalize vectors to length of corner radius
tx *= r / td;
ty *= r / td;
lx *= r / ld;
ly *= r / ld;
// draw rotated retangle
ctx.fillStyle = color;
ctx.beginPath();
ctx.lineTo(x2 - tx, y2 - ty);
ctx.quadraticCurveTo(x2, y2, x2 + lx, y2 + ly);
ctx.lineTo(x4 - lx, y4 - ly);
ctx.quadraticCurveTo(x4, y4, x4 - tx, y4 - ty);
ctx.lineTo(x3 + tx, y3 + ty);
ctx.quadraticCurveTo(x3, y3, x3 - lx, y3 - ly);
ctx.lineTo(x1 + lx, y1 + ly);
ctx.quadraticCurveTo(x1, y1, x1 + tx, y1 + ty);
ctx.fill();
}
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 realize this is a simple Trigonometry question, but my high school is failing me right now.
Given an angle, that I have converted into radians to get the first point. How do I figure the next two points of the triangle to draw on the canvas, so as to make a small triangle always point outwards to the circle. So lets say Ive drawn a circle of a given radius already. Now I want a function to plot a triangle that sits on the edge of the circle inside of it, that points outwards no matter the angle. (follows the edge, so to speak)
function drawPointerTriangle(ctx, angle){
var radians = angle * (Math.PI/180)
var startX = this.radius + this.radius/1.34 * Math.cos(radians)
var startY = this.radius - this.radius/1.34 * Math.sin(radians)
// This gives me my starting point on the outer edge of the circle, plotted at the angle I need
ctx.moveTo(startX, startY);
// HOW DO I THEN CALCULATE x1,y1 and x2, y2. So that no matter what angle I enter into this function, the arrow/triangle always points outwards to the circle.
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
}
Example
You don't say what type of triangle you want to draw so I suppose that it is an equilateral triangle.
Take a look at this image (credit here)
I will call 3 points p1, p2, p3 from top right to bottom right, counterclockwise.
You can easily calculate the coordinate of three points of the triangle in the coordinate system with the origin is coincident with the triangle's centroid.
Given a point belongs to the edge of the circle and the point p1 that we just calculated, we can calculate parameters of the translation from our main coordinate system to the triangle's coordinate system. Then, we just have to translate the coordinate of two other points back to our main coordinate system. That is (x1,y1) and (x2,y2).
You can take a look at the demo below that is based on your code.
const w = 300;
const h = 300;
function calculateTrianglePoints(angle, width) {
let r = width / Math.sqrt(3);
let firstPoint = [
r * Math.cos(angle),
r * Math.sin(angle),
]
let secondPoint = [
r * Math.cos(angle + 2 * Math.PI / 3),
r * Math.sin(angle + 2 * Math.PI / 3),
]
let thirdPoint = [
r * Math.cos(angle + 4 * Math.PI / 3),
r * Math.sin(angle + 4 * Math.PI / 3),
]
return [firstPoint, secondPoint, thirdPoint]
}
const radius = 100
const triangleWidth = 20;
function drawPointerTriangle(ctx, angle) {
var radians = angle * (Math.PI / 180)
var startX = radius * Math.cos(radians)
var startY = radius * Math.sin(radians)
var [pt0, pt1, pt2] = calculateTrianglePoints(radians, triangleWidth);
var delta = [
startX - pt0[0],
startY - pt0[1],
]
pt1[0] = pt1[0] + delta[0]
pt1[1] = pt1[1] + delta[1]
pt2[0] = pt2[0] + delta[0]
pt2[1] = pt2[1] + delta[1]
ctx.beginPath();
// This gives me my starting point on the outer edge of the circle, plotted at the angle I need
ctx.moveTo(startX, startY);
[x1, y1] = pt1;
[x2, y2] = pt2;
// HOW DO I THEN CALCULATE x1,y1 and x2, y2. So that no matter what angle I enter into this function, the arrow/triangle always points outwards to the circle.
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.closePath();
ctx.fillStyle = '#FF0000';
ctx.fill();
}
function drawCircle(ctx, radius) {
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = '#000';
ctx.fill();
}
function clear(ctx) {
ctx.fillStyle = '#fff';
ctx.fillRect(-w / 2, -h / 2, w, h);
}
function normalizeAngle(pointCoordinate, angle) {
const [x, y] = pointCoordinate;
if (x > 0 && y > 0) return angle;
else if (x > 0 && y < 0) return 360 + angle;
else if (x < 0 && y < 0) return 180 - angle;
else if (x < 0 && y > 0) return 180 - angle;
}
function getAngleFromPoint(point) {
const [x, y] = point;
if (x == 0 && y == 0) return 0;
else if (x == 0) return 90 * (y > 0 ? 1 : -1);
else if (y == 0) return 180 * (x >= 0 ? 0: 1);
const radians = Math.asin(y / Math.sqrt(
x ** 2 + y ** 2
))
return normalizeAngle(point, radians / (Math.PI / 180))
}
document.addEventListener('DOMContentLoaded', function() {
const canvas = document.querySelector('canvas');
const angleText = document.querySelector('.angle');
const ctx = canvas.getContext('2d');
ctx.translate(w / 2, h / 2);
drawCircle(ctx, radius);
drawPointerTriangle(ctx, 0);
canvas.addEventListener('mousemove', _.throttle(function(ev) {
let mouseCoordinate = [
ev.clientX - w / 2,
ev.clientY - h / 2
]
let degAngle = getAngleFromPoint(mouseCoordinate)
clear(ctx);
drawCircle(ctx, radius);
drawPointerTriangle(ctx, degAngle)
angleText.innerText = Math.floor((360 - degAngle)*100)/100;
}, 15))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
<canvas width=300 height=300></canvas>
<div class="angle">0</div>
reduce the radius, change the angle and call again cos/sin:
function drawPointerTriangle(ctx, angle)
{
var radians = angle * (Math.PI/180);
var radius = this.radius/1.34;
var startX = this.center.x + radius * Math.cos(radians);
var startY = this.center.y + radius * Math.sin(radians);
ctx.moveTo(startX, startY);
radius *= 0.9;
radians += 0.1;
var x1 = this.center.x + radius * Math.cos(radians);
var y1 = this.center.y + radius * Math.sin(radians);
radians -= 0.2;
var x1 = this.center.x + radius * Math.cos(radians);
var y1 = this.center.y + radius * Math.sin(radians);
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineTo(startX, startY);
}
the resulting triangle's size is proportional to the size of the circle.
in case you need an equilateral, fixed size triangle, use this:
//get h by pythagoras
h = sqrt( a^2 - (a/2)^2 );)
//get phi using arcustangens:
phi = atan( a/2, radius-h );
//reduced radius h by pythagoras:
radius = sqrt( (radius-h)^2 + (a/2)^2 );
radians += phi;
...
radians -= 2*phi;
...
I have program where I click three times, each click creating a point on canvas. I then calculate angle between those three points like this:
function find_angle(A, B, C) {
var AB = Math.sqrt(Math.pow(B.x - A.x, 2) + Math.pow(B.y - A.y, 2));
var BC = Math.sqrt(Math.pow(B.x - C.x, 2) + Math.pow(B.y - C.y, 2));
var AC = Math.sqrt(Math.pow(C.x - A.x, 2) + Math.pow(C.y - A.y, 2));
return Math.acos((BC * BC + AB * AB - AC * AC) / (2 * BC * AB));
}
In example picture above, the calculated angle is 93°. I need to move point 3 by -3° so the points make exactly 90°. I have this function for it:
var angleToCorrect = alpha * (Math.PI / 180) - 90 * (Math.PI / 180);
correct_angle(point2, point3, angleToCorrect)
...
function correct_angle(p2, p3, angle) {
var x = p2.x - p3.x;
var y = p2.y - p3.y;
var r = Math.sqrt(x * x + y * y); //circle radius. origin of the circle is point 2
return {
X: p2.x + (Math.cos(angle) * r),
Y: p2.y + (Math.sin(angle) * r)
};
}
Now, this function should return new x and y for point 3 with corrected angle to 90°. Yet the coordinates don't agree with what I expect. Can someone point out what I'm doing wrong?
To calculate the new position it isn't enough to provide just two of the points since the angle is measured between the three.
So inside this function you have to figure out what the current angle of the vector between point 1 and point 2 is. Javascript offers a nifty built-in function for this Math.atan2()
Now that we know the angle (in radians) we need to add the new angle to it. This makes sure we can place point 3 correctly.
function correct_angle(p1, p2, p3, angle)
{
var currentAngle=Math.atan2(p1.y-p2.y, p1.x-p2.x);
currentAngle+=angle;
var x = p2.x - p3.x;
var y = p2.y - p3.y;
var r = Math.sqrt(x * x + y * y);
return {
X: p2.x + (Math.cos(currentAngle) * r),
Y: p2.y + (Math.sin(currentAngle) * r)
};
}
The angle parameter of the function should be the target angle in radians (90 or 1.5707963267949 in your case)
Here's an interactive example:
Point = function(x, y) {
this.x = x;
this.y = y;
}
var pointA = new Point(162, 39);
var pointB = new Point(105, 161);
var pointC = new Point(211, 242);
var context = document.getElementById("canvas").getContext("2d");
function correct() {
var newPoint = correct_angle(pointA, pointB, pointC, 1.5707963267949);
pointC.x = newPoint.X;
pointC.y = newPoint.Y;
draw();
}
function correct_angle(p1, p2, p3, angle) {
var currentAngle = Math.atan2(p1.y - p2.y, p1.x - p2.x);
currentAngle += angle;
var x = p2.x - p3.x;
var y = p2.y - p3.y;
var r = Math.sqrt(x * x + y * y);
return {
X: p2.x + (Math.cos(currentAngle) * r),
Y: p2.y + (Math.sin(currentAngle) * r)
};
}
function draw() {
context.clearRect(0, 0, 400, 300);
context.fillStyle = "red";
context.beginPath();
context.arc(pointA.x, pointA.y, 10, 0, 2 * Math.PI);
context.fill();
context.beginPath();
context.arc(pointB.x, pointB.y, 10, 0, 2 * Math.PI);
context.fill();
context.beginPath();
context.arc(pointC.x, pointC.y, 10, 0, 2 * Math.PI);
context.fill();
}
draw();
<canvas id="canvas" width="400" height="300" style="background-color:#dddddd;"></canvas>
<button onclick="correct()" style="float:left">
correct me
</button>
A few days ago I came to stackoverflow asking about how to draw an arrow slowly into a canvas. No one was able to give me the correct answer... So I hope this helps someone.
Basically, i wanted to animate the progress of an invasion from one country to another country in a map. To do that I should use canvas and draw an arrow that moved from country A to country B, but not a fixed arrow... An arrow that grows progressively.
The code below draws an arrow, but not progressively. So, I needed to draw this curve like a CSS animation with a 5s transition.
function drawCurve (ctx, x0, y0, x1, y1, x2, y2){
ctx.beginPath();
ctx.moveTo( x0, y0 );
ctx.quadraticCurveTo( x1, y1, x2, y2 );
ctx.stroke();
ctx.closePath();
}
var docCanvas = document.getElementById('canvas');
var ctx = docCanvas.getContext('2d');
drawCurve(ctx, 0, 100, 150, -50, 300, 100);
<canvas id="canvas" width="480" height="320"></canvas>
After some digging I came to this solution that gives me all I wanted.
Basically drawBezierSplit() allow you to draw a section of a quadratic bezier curve.
All the credit to Patrick Galbraith.
/**
* Animates bezier-curve
*
* #param ctx The canvas context to draw to
* #param x0 The x-coord of the start point
* #param y0 The y-coord of the start point
* #param x1 The x-coord of the control point
* #param y1 The y-coord of the control point
* #param x2 The x-coord of the end point
* #param y2 The y-coord of the end point
* #param duration The duration in milliseconds
*/
function animatePathDrawing(ctx, x0, y0, x1, y1, x2, y2, duration) {
var start = null;
var step = function animatePathDrawingStep(timestamp) {
if (start === null)
start = timestamp;
var delta = timestamp - start,
progress = Math.min(delta / duration, 1);
// Clear canvas
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// Draw curve
drawBezierSplit(ctx, x0, y0, x1, y1, x2, y2, 0, progress);
if (progress < 1) {
window.requestAnimationFrame(step);
}
};
window.requestAnimationFrame(step);
}
/**
* Draws a splitted bezier-curve
*
* #param ctx The canvas context to draw to
* #param x0 The x-coord of the start point
* #param y0 The y-coord of the start point
* #param x1 The x-coord of the control point
* #param y1 The y-coord of the control point
* #param x2 The x-coord of the end point
* #param y2 The y-coord of the end point
* #param t0 The start ratio of the splitted bezier from 0.0 to 1.0
* #param t1 The start ratio of the splitted bezier from 0.0 to 1.0
*/
function drawBezierSplit(ctx, x0, y0, x1, y1, x2, y2, t0, t1) {
ctx.beginPath();
if( 0.0 == t0 && t1 == 1.0 ) {
ctx.moveTo( x0, y0 );
ctx.quadraticCurveTo( x1, y1, x2, y2 );
} else if( t0 != t1 ) {
var t00 = t0 * t0,
t01 = 1.0 - t0,
t02 = t01 * t01,
t03 = 2.0 * t0 * t01;
var nx0 = t02 * x0 + t03 * x1 + t00 * x2,
ny0 = t02 * y0 + t03 * y1 + t00 * y2;
t00 = t1 * t1;
t01 = 1.0 - t1;
t02 = t01 * t01;
t03 = 2.0 * t1 * t01;
var nx2 = t02 * x0 + t03 * x1 + t00 * x2,
ny2 = t02 * y0 + t03 * y1 + t00 * y2;
var nx1 = lerp ( lerp ( x0 , x1 , t0 ) , lerp ( x1 , x2 , t0 ) , t1 ),
ny1 = lerp ( lerp ( y0 , y1 , t0 ) , lerp ( y1 , y2 , t0 ) , t1 );
ctx.moveTo( nx0, ny0 );
ctx.quadraticCurveTo( nx1, ny1, nx2, ny2 );
}
ctx.stroke();
ctx.closePath();
}
/**
* Linearly interpolates between two numbers
*/
function lerp(v0, v1, t) {
return ( 1.0 - t ) * v0 + t * v1;
}
var docCanvas = document.getElementById('canvas');
var ctx = docCanvas.getContext('2d');
animatePathDrawing(ctx, 0, 100, 150, -50, 300, 100, 5000);
<canvas id="canvas" width="480" height="320"></canvas>
EDIT:
And if you need a polyfill, you can use this code:
(function() {
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
Link: http://www.pjgalbraith.com/drawing-animated-curves-javascript/