I have two lines at a 90 degree angle and I want to make the angle get smaller and larger between 90 and 0 by rotating the vertical line. I'm trying to do this my changing the moveTo parameters I could increase the value but not decrease them. also could you help me make sure that the line that moves is the same length as the horizontal line during the animation. now it looks like it gets smaller then gets larger as it completes.
window.onload = function(){
var canvas =document.getElementById("canvas");
var context = canvas.getContext("2d");
var length = 50;
var x = 50;
var y= 50;
var forward = true;
(function animate(){
if(x <= 201 && y <= 201){
x++
y++
}
if(x > 195){
forward = false;
}
// console.log(x, y)
if(forward == false){
// alert("yo")
x = x - 1
y = y -1
}
console.log(x)
console.log(forward)
context.clearRect(0,0, canvas.width, canvas.height)
context.beginPath();
context.moveTo(x,y)
context.lineTo(50,200);
context.stroke();
context.closePath();
window.requestAnimationFrame(animate)
context.beginPath();
context.moveTo(50, 200);
context.lineTo(200, 200)
context.stroke();
context.closePath();
}())
}
<canvas id="canvas" width="400" height="400"></canvas>
EDIT:::
window.onload = function(){
var canvas =document.getElementById("canvas");
var context = canvas.getContext("2d");
var length = 50;
var x = 50;
var y= 50;
var dlt = 1
var forward = true;
var i = 0;
(function animate(){
if(x >= 50 && x < 200 ){
i++
x += dlt
y += dlt
console.log(i)
$(".display").html(i)
if(i >= 150){
X = 200;
Y = 200;
x -= dlt
y -= dlt
}
}
console.log("x", x)
console.log(forward)
context.clearRect(0,0, canvas.width, canvas.height)
context.beginPath();
context.moveTo(x,y)
context.lineTo(50,200);
context.stroke();
context.closePath();
window.requestAnimationFrame(animate)
context.beginPath();
context.moveTo(50, 200);
context.lineTo(200, 200)
context.stroke();
context.closePath();
}())
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" width="400" height="400"></canvas>
You can use transformations instead of trigonometry to draw your vertically collapsing line:
Here is annotated code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var nextTime=0;
var delay=100;
var angle=-Math.PI/2;
var cx=150;
var cy=150;
var radius=50;
requestAnimationFrame(animate);
function animate(time){
// wait until the desired time elapses
if(time<nextTime){requestAnimationFrame(animate); return;}
nextTime+=delay;
// draw the horizontal line
ctx.clearRect(0,0,cw,ch);
ctx.beginPath();
ctx.moveTo(cx,cy);
ctx.lineTo(cx+radius,cy);
ctx.stroke();
// use transformations to draw the vertical line
// at the desired angle
ctx.translate(cx,cy);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(radius,0);
ctx.stroke();
ctx.setTransform(1,0,0,1,0,0);
// if the vertical line isn't horizontal,
// request another animation frame
if(angle<0){ requestAnimationFrame(animate); }
// adjust the angle
angle+=Math.PI/90;
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>
If you want to use Trig then you can calculate the line endpoint like this:
var lineX1 = lineX0 + radius*Math.cos(angle);
var lineY1 = lineY1 + radius*Math.sin(angle);
[Addition: add trigonometry to questioner's code]
Here is your code refactored to use trigonometry to reposition the vertical line.
var canvas =document.getElementById("canvas");
var context = canvas.getContext("2d");
var length = 50;
var x = 50;
var y= 50;
var dlt = 1
var forward = true;
var i = 0;
var angle=-Math.PI/2;
var direction=1;
(function animate(){
/*
if(x >= 50 && x < 200 ){
i++
x += dlt
y += dlt
console.log(i)
$(".display").html(i)
if(i >= 150){
X = 200;
Y = 200;
x -= dlt
y -= dlt
}
}
*/
var moveX=50+(200-50)*Math.cos(angle);
var moveY=200+(200-50)*Math.sin(angle);
// change the angle
angle+=(Math.PI/120*direction);
// if the angle is beyond vertical or horizontal then
// swing it the other way
if(angle<-Math.PI/2 || angle>0){ direction*=-1;}
context.clearRect(0,0, canvas.width, canvas.height)
context.beginPath();
context.moveTo(moveX,moveY)
context.lineTo(50,200);
context.stroke();
// context.closePath();
context.beginPath();
context.moveTo(50, 200);
context.lineTo(200, 200)
context.stroke();
// context.closePath();
window.requestAnimationFrame(animate)
}())
<canvas id="canvas" width=300 height=300></canvas>
Sin and Cos
All graphics programmers should know these two trigonometric functions thoroughly.
MarkE has given a good answer but I will point out the simple trig method that you can use to find a point that is a desired distance and angle.
Lets say the line you want has a length,
var length = 100;
its starts at,
var posX = 200;
var posY = 200;
the unknown end location will be,
var endX;
var endY;
and the angle you want is
var angle = 0; // in radians
Radians V Degrees
In javascript all Math functions that require angles use angle as radians.
The above angle is in radians, with angle = 0 pointing from left to right across the screen, angle = Math.PI/2 (90deg) from top down the screen, angle = Math.PI (180deg) from right to left across the screen and angle = Math.PI * (3/2) (270deg) from bottom to top up the screen.
If you like to work in degrees then you can convert to radians by multiplying degrees by Math.PI/180.
Function to convert degrees to radians
function degree2Radians(deg){
return deg * (Math.PI/180);
}
Back to getting the line at an angle. We will use the trig functions Math.cos and Math.sin see Wiki trigonometric functions for details.
So the calculation in steps
var x,y; // temp working variables
x = Math.cos(angle); // get the amount of x in the line for angle
y = Math.sin(angle); // get the amount of y in the line for angle
// cos and sin will return values -1 to 1 inclusive.
// now scale the values to the length of the line
x *= length;
y *= length;
// now you have the offset from the start of the line
// to get the end point add the start
endX = x + posX;
endY = y + posY;
Now you can draw the line at the angle you wanted.
ctx.beginPath();
ctx.moveTo(posX,posY);
ctx.lineTo(endX,endY);
ctx.stroke();
Of course that was a long way. It all can be done in two steps.
endX = Math.cos(angle) * length + posX;
endY = Math.sin(angle) * length + posY;
or if you like a function that works in degrees
// add to the current path a line from
// startX,startY of
// length pixels long
// at the angle angleDeg in degrees. Negative anticlockwise positive clockwise
function lineAtAngle(ctx, startX, startY, angleDeg, length){
var angle = angleDeg * (Math.PI / 180);
ctx.moveTo(startX, startY);
ctx.lineTo(
Math.cos(angle) * length + startX,
Math.sin(angle) * length + startY
)
}
And to use it
ctx.beginPath(); // begin the line
lineAtAngle(ctx,200,200,-45,100); // draw a line at -45deg
// (from bottom left to top right)
// 100 pixels long.
ctx.stroke(); // draw the line
Thats how to use sin and cos to draw a line at an angle and length.
Related
I have a code to draw spiral with points
var c = document.getElementById("myCanvas");
var cxt = c.getContext("2d");
var centerX = 400;
var centerY = 400;
cxt.moveTo(centerX, centerY);
var count = 0;
var increment = 3/32;
var distance = 10;
for (theta = 0; theta < 50; theta++) {
var newX = centerX + distance * Math.cos((theta) * 4 * Math.PI * increment );
var newY = centerY + distance * Math.sin(((theta)) * 4 * Math.PI * increment );
cxt.fillText("o", newX, newY);
count++;
if (count % 4 === 0) {
distance = distance + 10;
}
}
cxt.stroke();
<canvas id="myCanvas" width="800" height="800" style="border:1px solid #c3c3c3;"></canvas>
enter image description here
Notice how many time i change the increment value, there're always a line that have more or less points than others
increment = 5/32;
enter image description here
Is this possible to draw a perfect spiral with all lines has the same lenght with each other?
There are a quite a few issues here. Like #Anytech said, you need to first decide how many arms (strings of dots) you want. In your screenshot, it looks like you have 5 arms, but you probably got that by accident. I've replaced the "o" with the distance to help visualize the problem:
var c = document.getElementById("myCanvas");
var cxt = c.getContext("2d");
var centerX = 200;
var centerY = 200;
cxt.moveTo(centerX, centerY);
var count = 0;
var increment = 3/32;
var distance = 10;
for (theta = 0; theta < 50; theta++) {
var newX = centerX + distance * Math.cos((theta) * 4 * Math.PI * increment );
var newY = centerY + distance * Math.sin(((theta)) * 4 * Math.PI * increment );
cxt.fillText(distance, newX, newY);
count++;
if (count % 4 === 0) {
distance = distance + 10;
}
}
cxt.stroke();
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;"></canvas>
As you can see, the first four dots are at distance 10, the 5th one is at distance 20, from there, you've already broken the rhythm.
Assuming you still want 5 arms, increase the distance every 5 dots by checking count % 5 === 0, instead of count % 4 === 0.
var c = document.getElementById("myCanvas");
var cxt = c.getContext("2d");
var centerX = 200;
var centerY = 200;
cxt.moveTo(centerX, centerY);
var count = 0;
var increment = 3/32;
var distance = 10;
for (theta = 0; theta < 50; theta++) {
var newX = centerX + distance * Math.cos((theta) * 4 * Math.PI * increment );
var newY = centerY + distance * Math.sin(((theta)) * 4 * Math.PI * increment );
cxt.fillText(distance, newX, newY);
count++;
if (count % 5 === 0) {
distance = distance + 10;
}
}
cxt.stroke();
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;"></canvas>
Also, a full circle is 2 * Math.PI. If you can use Math.cos((theta) * 2 * Math.PI * increment), increment becomes the arc the dot will travel after each paint. If increment is 1/5, you will get five straight lines. If increment is a little bit more than 1/5, you will get the curve effect you want.
And one final detail, we usually call context ctx, instead of cxt. Combining all of the above, the output looks like this
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var centerX = 200;
var centerY = 200;
ctx.moveTo(centerX, centerY);
var count = 0;
var increment = 1.02/5;
var distance = 10;
for (theta = 0; theta < 50; theta++) {
var newX = centerX + distance * Math.cos((theta) * 2 * Math.PI * increment );
var newY = centerY + distance * Math.sin(((theta)) * 2 * Math.PI * increment );
ctx.fillText('o', newX, newY);
count++;
if (count % 5 === 0) {
distance = distance + 10;
}
}
ctx.stroke();
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;"></canvas>
EDIT
After having a chat with the question raiser, I realize the problem also has to to with fillText using the bottom-left corner of the string as the starting point for the paint. To address the issue, we must plot actual circles, instead of letter 'o'.
Here is the final result (concentric circles added to show perfect symmetry)
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var centerX = 200;
var centerY = 200;
ctx.moveTo(centerX, centerY);
var count = 0;
var increment = 1.05/5;
var distance = 10;
for (theta = 0; theta < 50; theta++) {
var newX = centerX + distance * Math.cos((theta) * 2 * Math.PI * increment );
var newY = centerY + distance * Math.sin(((theta)) * 2 * Math.PI * increment );
ctx.textAlign = "center";
//ctx.fillText('o', newX, newY); <== this will be off-center
ctx.beginPath();
ctx.strokeStyle = "#000";
ctx.arc(newX, newY, 2, 0, Math.PI * 2, true)
ctx.stroke();
count++;
if (count % 5 === 0) {
ctx.strokeStyle = "#cccccc";
ctx.beginPath();
ctx.arc(200, 200, distance, 0, Math.PI * 2, true)
ctx.stroke();
distance = distance + 10;
}
}
ctx.stroke();
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;"></canvas>
The circumference of a circle is 2 * PI * radius.
We can use this to determine what angle step to make to cover a distance on the circumference. Thus the angle that sweeps a given distance along the curve is distance / radius (Note this is not the straight line distance, but the distance ALONG the CURVE)
Though you are creating a spiral and the distance for an angle step is a little longer (as the line moves outward) the approximation will suffice for the human eye.
So changing your code with the following steps
Remove increment
Change distance to radius
Rather than limit the number of turns, we will use the radius to limit the max radius of the spiral. Adding maxRadius = 350 to fit the canvas
Add lineLength to set the approx distance between each point in pixels.
Change the for loop to a while loop as we are going to step angle inside the loop.
Rename theta to angle (we are programmers not mathematicians)
Rather than use a character to draw each point we will create a path of arcs so that the positioning can be precise. That will add some 2D context setup code.
Rather than step out every 4 points (as that no longer will work) we will make the radius a function of angle. With that add some constants to control the radius function.
radiusMin defines the min radius (starting radius)
radiusScale defines the rate per turn in pixel that the spiral moves out.
Remove count as its no longer needed
Inside the loop calculate the radius. As we have difined the radius growth as a rate per turn we divide the radiusScale / (Math.PI * 2) so the radius is radius = angle * (radiusScale / (Math.PI * 2)) + radiusMin;
Inside the loop we step the angle to match the distance we want to move angle += lineLength / radius; which is derived from the circumference formula (and why we use radians for angles)
Change cxt to the more idiomatic ctx
Add moveTo to move to the start of each circle.
Add ctx.arc to define the circle
When all circles are defined, after the loop draw the path with ctx.stroke()
The code below. As I have only approximated your spiral you can play with the constants to make it fit your needs. Also Note that for the inner spiral, longer line distances will not work as well.
const ctx = myCanvas.getContext("2d");
const centerX = 400;
const centerY = 400;
const markRadius = 2; // radius of each circle mark in pixels
const maxRadius = 350; // 50 pixel boarder
const lineLength = 20; // distance between points in pixels
const radiusScale = 80; // how fast the spiral moves outward per turn
const radiusMin = 10; // start radius
var angle = 0, radius = 0;
ctx.lineWidth = 1;
ctx.strokeStye = "black";
ctx.beginPath();
while (radius < maxRadius) {
radius = angle * (radiusScale / (Math.PI * 2)) + radiusMin;
angle += lineLength / radius;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
ctx.moveTo(x + markRadius, y);
ctx.arc(x, y, markRadius, 0, Math.PI * 2);
}
ctx.stroke();
<canvas id="myCanvas" width="800" height="800" style="border:1px solid #c3c3c3;"></canvas>
If you want separate spirals then it is a minor modification of the above
Define the number of arms in the spiral armCount
Define the rate that the arms turn as the move out "spiralRate"
Just for fun animate the spiralRate
requestAnimationFrame(mainLoop);
const ctx = myCanvas.getContext("2d");
const centerX = 200;
const centerY = 200;
const markRadius = 2; // radius of each circle mark in pixels
const maxRadius = 190; // 50 pixel boarder
const armCount = 8; // Number of arms
const radiusScale = 8; // how fast the spiral moves outward per turn
const radiusMin = 10; // start radius
function drawSpiral(spiralRate) { // spiralRate in pixels per point per turn
var angle = 0, radius = radiusMin;
ctx.lineWidth = 1;
ctx.strokeStye = "black";
ctx.beginPath();
while (radius < maxRadius) {
angle += (Math.PI * 2) / armCount + (spiralRate/ radius);
radius = angle * (radiusScale / (Math.PI * 2)) + radiusMin;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
ctx.moveTo(x + markRadius, y);
ctx.arc(x, y, markRadius, 0, Math.PI * 2);
}
ctx.stroke();
}
function mainLoop(time) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
drawSpiral(Math.sin(time / 4000) * 2); // occilate spiral rate every ~ 24 seconds
requestAnimationFrame(mainLoop);
}
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;"></canvas>
After changing the code to increment in number form you can see that the "spiral" is not generated in the expected line.
You will ned to work out how many spiral arms you want and calculate that with the stoplimit.
The increment value is also never used.
// var increment = 6/45;
var stoplimit = 51;
https://jsfiddle.net/Anytech/spzyufev/4/
I am trying to make a circle follow the mouse in HTML Canvas which I am using in a game. I am trying to make the circle move 5px per iteration, but it goes slower when traveling horizontal and faster when it goes vertical. Here's the math that I used:
x=distance between mouse and circle on the x-axis
y=distance between mouse and circle on the y-axis
z=shortest distance between mouse and circle
a=number of units circle should move along the x-axis
b=number of units circle should move along the y axis
x^2 + y^2=z^2
Want the total distance traveled every iteration to be five pixels
a^2 + b^2 = 25
b/a=y/x
b=ay/x
a=sqrt(25-ay/x^2)
a^2+ay/x-25=0
Use Quadratic formula to find both answers
a=(-y/x+-sqrt(y/x)^2+100)/2
I replicated the problem in the code below
$(function(){
let canvas = $("canvas")[0];
let ctx = canvas.getContext("2d");
//Gets position of mouse and stores the value in variables mouseX and mouseY
let mouseX = mouseY = 0;
$("canvas").mousemove(function(e){
mouseX = e.pageX;
mouseY = e.pageY;
}).trigger("mousemove");
let circleX = 0;
let circleY = 0;
function loop(t){
//Background
ctx.fillStyle="blue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
let xFromMouse = mouseX-circleX;
let yFromMouse = mouseY-circleY;
let yxRatio = yFromMouse/xFromMouse;
let xyRatio = xFromMouse/yFromMouse;
let speed = 25;
let possibleXValues = [(-yxRatio+Math.sqrt(Math.pow(yxRatio,2)+(4*speed)))/2,(-yxRatio-Math.sqrt(Math.pow(yxRatio,2)+(4*speed)))/2];
//I use this code as a temporary fix to stop the circle from completely disappearing
if(xFromMouse === 0 || isNaN(yxRatio) || isNaN(possibleXValues[0]) || isNaN(possibleXValues[1])){
possibleXValues = [0,0];
yxRatio = 0;
}
//Uses b=ay/x to calculate for y values
let possibleYValues = [possibleXValues[0]*yxRatio,possibleXValues[1]*yxRatio];
if(xFromMouse >= 0){
circleX += possibleXValues[0];
circleY += possibleYValues[0];
} else {
circleX += possibleXValues[1];
circleY += possibleYValues[1];
}
ctx.beginPath();
ctx.arc(circleX, circleY, 25, 0, 2 * Math.PI,false);
ctx.fillStyle = "red";
ctx.lineWidth = 0;
ctx.fill();
window.requestAnimationFrame(loop);
}
window.requestAnimationFrame(loop);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas width="450" height="250"></canvas>
I think you may be better using a cartesian to polar conversion. Here's an example from something I made previously. This will allow you to have a consistent step per iteration "speed".
//Canvas, context, mouse.
let c, a, m = { x:0, y:0};
//onload.
window.onload = function(){
let circle = {},
w, h,
speed = 5; //step speed = 5 "pixels" (this will be fractional in any one direction depending on direction of travel).
//setup
c = document.getElementById('canvas');
a = c.getContext('2d');
w = c.width = window.innerWidth;
h = c.height = window.innerHeight;
function move(){
//get distance and angle from mouse to circle.
let v1m = circle.x - m.x,
v2m = circle.y - m.y,
vDm = Math.sqrt(v1m*v1m + v2m*v2m),
vAm = Math.atan2(v2m, v1m);
//if distance is above some threshold, to stop jittering, move the circle by 'speed' towards mouse.
if(vDm > speed) {
circle.x -= Math.cos(vAm) * speed;
circle.y -= Math.sin(vAm) * speed;
}
}
function draw(){
//draw it all.
a.fillStyle = "blue";
a.fillRect(0,0,w,h);
a.fillStyle = "red";
a.beginPath();
a.arc(circle.x, circle.y, circle.r, Math.PI * 2, false);
a.closePath();
a.fill();
}
circle = {x:w/2, y:h/2, r:25};
function animate(){
requestAnimationFrame(animate);
move();
draw();
}
c.onmousemove = function(e){
m.x = e.pageX;
m.y = e.pageY;
};
animate();
}
<canvas id="canvas" width="450" height="250"></canvas>
I have two lines that make a 90 degree angle, I hope. I want to make it so that the vertical line moves down to the horizontal line. The angle is the pivot point. so the angle should decrease to 0 I guess. 45 would be half way.
the length of the lines should be the same during the animation.
the animation should be looping. It should go from 90 degree angle to
0 degree angle and back.
1 way I was thinking to figure this out was to change the context.moveTo(50,50) the parameters numbers so the line should begin to be drawn at the new coordinates during the animation. I had problems keeping the line the same size as the horizontal.
another way I was thinking was to change the Math.atan2. I don't know have it start at 90 degrees then go to 0 and have that reflect on the moveto parameters I don't know how to put this together.
I would prefer to use a solution with trigonometry because that is what I'm trying to get good at
for extra help if you could attach a hypotenuse so I could see the angle change the size of the triangle that would be great. That was my original problem. Thanks
window.onload = function(){
var canvas =document.getElementById("canvas");
var context = canvas.getContext("2d");
var length = 50
context.beginPath();
context.moveTo(50,50)
context.lineTo(50,200);
context.stroke();
context.closePath();
context.beginPath();
context.moveTo(50, 200);
context.lineTo(200, 200)
context.stroke();
context.closePath();
var p1 = {
x: 50,
y : 50
}
var p2 = {
x: 50,
y: 200
}
var angleDeg = Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
console.log(angleDeg)
}
<canvas id="canvas" width="400" height="400"></canvas>
This might help.
window.onload = function() {
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var length = 150;
var angle = 270;
var maxAngle = 360;
var minAngle = 270;
var direction = 0;
var p1 = {
x: 50,
y: 200
};
var p2 = {
x: 200,
y: 200
};
context.fillStyle = "rgba( 255, 0, 0, 0.5)";
function draw() {
context.clearRect(0, 0, 400, 400);
context.beginPath();
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y)
if (angle >= maxAngle) {
direction = 1;
} else if (angle <= minAngle) {
direction = 0;
}
if (direction == 0) {
angle++;
} else {
angle--;
}
var x = p1.x + length * Math.cos(angle * Math.PI / 180);
var y = p1.y + length * Math.sin(angle * Math.PI / 180);
context.moveTo(p1.x, p1.y);
context.lineTo(x, y);
context.lineTo(p2.x, p2.y);
context.stroke();
context.fill()
context.closePath();
}
setInterval(draw, 50);
}
<canvas id="canvas" width="400" height="400"></canvas>
To get angle sequence (in degrees) like 90-45-0-45-90-45..., you can use this simple algo (pseudocode):
i = 0
while (drawingNeeded) do
angle = Math.Abs(90 - (i % 180)) * Math.PI / 180;
endPoint.x = centerPoint.x + lineLength * Math.Cos(angle);
endPoint.y = centerPoint.y + lineLength * Math.Sin(angle);
//redraw canvas, draw static objects
drawLine(centerPoint, endPoint);
i++;
I've got a problem with some html5 canvas stuff...
With a lot of patience I've draw a golden spiral with a X number of dots on it (equally distributed).
This dots must become a little perpendicular lines.
Perpendicular to what?
In my mind, the line must be perpendicular to the tangent line of that point of a spiral.
So, that's the questions:
- How can I get the tangent line of a point in a golden spiral?
- Once I find the tangent, how can I draw a line perpendicular to it starting from my point xy?
The main problem is that, in canvas (as far I know), a line must have a start and end already known, while I have only the xystart
This is the jfiddle with the spiral code.
https://jsfiddle.net/MasterFO/bho3v6hs/2/
function spiral_render(){
var c = document.getElementById('c');
var context = c.getContext("2d");
var centerx = (context.canvas.width / 2)+5;
var centery = (context.canvas.height / 2)+100;
context.clearRect(0, 0, 582, 620);
context.moveTo(centerx, centery);
context.beginPath();
var dots_coordinates = [];
a = parseFloat(0.41);
b = parseFloat(0.23);
var dots = 20; //How many dots
rounds = parseFloat(180);
strength = parseFloat(30);
sample = strength * 360;
start = -rounds*(3.14);
end = rounds*(3.14);
step = (end - start) / sample;
var distance = parseInt((sample/dots)/2.6); //Distance between dots
k = 0;
//Draw the golden spiral
for (var i = 1; i <= sample; i++) {
r = start+i*step;
t = (1/b)*Math.log(r/a); //Phi angle of spiral
x = centerx + r* Math.cos(t);
y = centery + r* Math.sin(t);
if (i % distance == 0 && k <=dots && i > 5600) {
if(x && y){
dots_coordinates.push([x,y]);
k++;
}
}
context.lineTo(x, y);
}
context.lineWidth = 0.5;
context.strokeStyle = "#000";
context.stroke();
//Draw the dots in sequential mode
context.moveTo(centerx, centery);
var i = 0;
inter = setInterval(function() {
xp = Math.floor( dots_coordinates[i][0] );
yp = Math.floor( dots_coordinates[i][1] );
context.beginPath();
context.lineTo(xp, yp);
context.arc(xp, yp, 4, 0, 2 * Math.PI , false);
context.fillStyle = '#C4071A';
context.fill();
i++;
if (i == (dots_coordinates.length)) {
clearInterval(inter);
}
}, 50);
}
Someone have any idea?
This is the code to draw a perpendicular line at a dot of your choise. This code will not work on the first and last dot unfortunately. Credits to Spencer Wieczorek who suggested this method in his comment above.
In order to overcome only having a starting point is to use Math.sin() and Math.cos() multiplied with the length that you want your line to have.
var dotIndex = 11;
var lineLength = 50;
var myX = dots_coordinates[dotIndex-1][0]-dots_coordinates[dotIndex+1][0];
var myY = dots_coordinates[dotIndex-1][1]-dots_coordinates[dotIndex+1][1];
var theta = Math.atan2(-myY, myX);
context.beginPath();
context.moveTo(dots_coordinates[dotIndex][0],dots_coordinates[dotIndex][1]);
context.lineTo(dots_coordinates[dotIndex][0]+Math.sin(theta)*lineLength,dots_coordinates[dotIndex][1]+Math.cos(theta)*lineLength);
context.closePath();
context.stroke();
so i'm trying to create a drawing tool in HTML5 canvas where the weight of the stroke increases the faster you move the mouse and decreases the slower you move. I'm using ctx.lineTo() but on my first attempt noticed that if i move too quickly the change in thickness is registered as obvious square increments ( rather than a smooth increase in weight )
so i changed the ctx.lineJoin and ctx.lineCap to "round" and it got a little better
but this is still not as smooth as i'd like. i'm shooting for something like this
any advice on how to make the change in weight a bit smoother would be great! here's a working demo: http://jsfiddle.net/0fhag522/1/
and here' a preview of my "dot" object ( the pen ) and my draw function:
var dot = {
start: false,
weight: 1,
open: function(x,y){
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.beginPath();
ctx.moveTo(x,y);
},
connect: function(x,y){
ctx.lineWidth = this.weight;
ctx.lineTo(x,y);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(x,y);
},
close: function(){
ctx.closePath();
}
}
function draw(){
if(down){
if(!dot.start){
dot.close();
prevx = mx; prevy = my;
dot.open(mx,my);
dot.start=true;
}
else {
var dx = (prevx>mx) ? prevx-mx : mx-prevx;
var dy = (prevy>my) ? prevy-my : my-prevy;
dot.weight = Math.abs(dx-dy)/2;
dot.connect( mx,my );
prevx = mx; prevy = my;
}
}
}
Here is a simple function to create growing lines with a round line cap:
/*
* this function returns a Path2D object
* the path represents a growing line between two given points
*/
function createGrowingLine (x1, y1, x2, y2, startWidth, endWidth) {
// calculate direction vector of point 1 and 2
const directionVectorX = x2 - x1,
directionVectorY = y2 - y1;
// calculate angle of perpendicular vector
const perpendicularVectorAngle = Math.atan2(directionVectorY, directionVectorX) + Math.PI/2;
// construct shape
const path = new Path2D();
path.arc(x1, y1, startWidth/2, perpendicularVectorAngle, perpendicularVectorAngle + Math.PI);
path.arc(x2, y2, endWidth/2, perpendicularVectorAngle + Math.PI, perpendicularVectorAngle);
path.closePath();
return path;
}
const ctx = myCanvas.getContext('2d');
// create a growing line between P1(10, 10) and P2(250, 100)
// with a start line width of 10 and an end line width of 50
let line1 = createGrowingLine(10, 10, 250, 100, 10, 50);
ctx.fillStyle = 'green';
// draw growing line
ctx.fill(line1);
<canvas width="300" height="150" id="myCanvas"></canvas>
Explanation:
The function createGrowingLine constructs a shape between two given points by:
calculating the direction vector of the two points
calculating the angle in radians of the perpendicular vector
creating a semi circle path from the calculated angle to the calculated angle + 180 degree with the center and radius of the start point
creating another semi circle path from the calculated angle + 180 degree to the calculated angle with the center and radius of the end point
closing the path by connecting the start point of the first circle with the end point of the second circle
In case you do not want to have the rounded line cap use the following function:
/*
* this function returns a Path2D object
* the path represents a growing line between two given points
*/
function createGrowingLine (x1, y1, x2, y2, startWidth, endWidth) {
const startRadius = startWidth/2;
const endRadius = endWidth/2;
// calculate direction vector of point 1 and 2
let directionVectorX = x2 - x1,
directionVectorY = y2 - y1;
// calculate vector length
const directionVectorLength = Math.hypot(directionVectorX, directionVectorY);
// normalize direction vector (and therefore also the perpendicular vector)
directionVectorX = 1/directionVectorLength * directionVectorX;
directionVectorY = 1/directionVectorLength * directionVectorY;
// construct perpendicular vector
const perpendicularVectorX = -directionVectorY,
perpendicularVectorY = directionVectorX;
// construct shape
const path = new Path2D();
path.moveTo(x1 + perpendicularVectorX * startRadius, y1 + perpendicularVectorY * startRadius);
path.lineTo(x1 - perpendicularVectorX * startRadius, y1 - perpendicularVectorY * startRadius);
path.lineTo(x2 - perpendicularVectorX * endRadius, y2 - perpendicularVectorY * endRadius);
path.lineTo(x2 + perpendicularVectorX * endRadius, y2 + perpendicularVectorY * endRadius);
path.closePath();
return path;
}
const ctx = myCanvas.getContext('2d');
// create a growing line between P1(10, 10) and P2(250, 100)
// with a start line width of 10 and an end line width of 50
let line1 = createGrowingLine(10, 10, 250, 100, 10, 50);
ctx.fillStyle = 'green';
// draw growing line
ctx.fill(line1);
<canvas width="300" height="150" id="myCanvas"></canvas>
Since canvas does not have a variable width line you must draw closed paths between your line points.
However, this leaves a visible butt-joint.
To smooth the butt-joint, you can draw a circle at each joint.
Here is example code and a Demo:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $canvas.scrollLeft();
var scrollY = $canvas.scrollTop();
var isDown = false;
var startX;
var startY;
var PI = Math.PI;
var halfPI = PI / 2;
var points = [];
$("#canvas").mousedown(function(e) {
handleMouseDown(e);
});
function handleMouseDown(e) {
e.preventDefault();
e.stopPropagation();
mx = parseInt(e.clientX - offsetX);
my = parseInt(e.clientY - offsetY);
var pointsLength = points.length;
if (pointsLength == 0) {
points.push({
x: mx,
y: my,
width: Math.random() * 5 + 2
});
} else {
var p0 = points[pointsLength - 1];
var p1 = {
x: mx,
y: my,
width: Math.random() * 5 + 2
};
addAngle(p0, p1);
p0.angle = p1.angle;
addEndcap(p0);
addEndcap(p1);
points.push(p1);
extendLine(p0, p1);
}
}
function addAngle(p0, p1) {
var dx = p1.x - p0.x;
var dy = p1.y - p0.y;
p1.angle = Math.atan2(dy, dx);
}
function addEndcap(p) {
p.x0 = p.x + p.width * Math.cos(p.angle - halfPI);
p.y0 = p.y + p.width * Math.sin(p.angle - halfPI);
p.x1 = p.x + p.width * Math.cos(p.angle + halfPI);
p.y1 = p.y + p.width * Math.sin(p.angle + halfPI);
}
function extendLine(p0, p1) {
ctx.beginPath();
ctx.moveTo(p0.x0, p0.y0);
ctx.lineTo(p0.x1, p0.y1);
ctx.lineTo(p1.x1, p1.y1);
ctx.lineTo(p1.x0, p1.y0);
ctx.closePath();
ctx.fillStyle = 'blue';
ctx.fill();
// draw a circle to cover the butt-joint
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.arc(p1.x, p1.y, p1.width, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Click to add line segments.</h4>
<canvas id="canvas" width=300 height=300></canvas>