Drawing along an equation of a line in HTML5 Canvas - javascript

I have a line [from (x1, y1) to (x2, y2)] on the canvas that acts like a gun. I want the bullet to travel in the direction of the line (gun). Let the bullet also be a line. I know that from x1, y1 and x2, y2 I can find the slope of the line m and the y-intercept b. I'm also aware that the equation of a line is y = mx + b. I want the bullet to travel along the equation y = mx + b.
I do not want my bullet to look like a long line that starts from the end of my gun all the way to the boundary of the canvas. I want it to be a small line redrawn multiple times along the equation y = mx + b.
Can someone please guide me on how to draw my bullet's movement? Thanks in advance!

You can use a simple interpolation formula where you animate it by adjusting the factor f.
The formula is (shown only for x):
x = x1 + (x2 - x1) * f
An example on how to implement -
AN ONLINE DEMO
/// add click callback for canvas (id = demo)
demo.onclick = function(e) {
/// get mouse coordinate
var rect = demo.getBoundingClientRect(),
/// gun at center bottom
x1 = demo.width * 0.5,
y1 = demo.height,
/// target is where we click on canvas
x2 = e.clientX - rect.left,
y2 = e.clientY - rect.top,
/// factor [0, 1] is where we are at the line
f = 0,
/// our bullet
x, y;
loop();
}
Then we provide the following code for the loop
function loop() {
/// clear previous bullet (for demo)
ctx.clearRect(x - 2, y - 2, 6, 6);
/// HERE we calculate the position on the line
x = x1 + (x2 - x1) * f;
y = y1 + (y2 - y1) * f;
/// draw some bullet
ctx.fillRect(x, y, 3, 3);
/// increment f until it's 1
if (f < 1) {
f += 0.05;
requestAnimationFrame(loop);
} else {
ctx.clearRect(x - 2, y - 2, 6, 6);
}
}
To draw a "longer" bullet that follows the line you can either store an older value of the x/y pair and draw a line between that and current, or less optimal, calculate the position separately or even calculate the angle and use a fixed length.
Also worth to be aware of: the longer the line is the faster the bullet goes. You can calculate a delta value for f based on length (not shown in demo) to get around this.

Related

Draw text above a line on P5js

I'm doing a graph theory project, and I need to show the edge weight above each of the edges.
currently I'm using this method:
var x1; //starting point
var x2; //ending point
function setup() {
createCanvas(640, 480);
x1 = createVector(random(0, width/2), random(0, height/2)); //random position to the upper left
x2 = createVector(random(width/2, width), random(height/2, height)); //random position to the lower right
}
function draw() {
background(200);
stroke(0);
line(x1.x, x1.y, x2.x, x2.y); //draw a line beetween the points
d = dist(x1.x, x1.y, x2.x, x2.y);
var angle = atan2(x1.y - x2.y, x1.x - x2.x); // gets the angle of the line
textAlign(CENTER);
text("X1", x1.x + 5, x1.y + 5); // just to show where is the begining
text("X2", x2.x - 5, x2.y - 5); // just to show there is the end
fill(0);
signalx = x1.x > x2.x ? -1 : 1; // if the start is to the right of the end
signaly = x1.y > x2.y ? -1 : 1; // if the start is below the end
// I think i need to use the angle here
text(42, (x1.x + (d / 2) * signalx), (x1.y + (d / 2) * signaly));
}
the problem is that the result, well, is not as expected:
The idea is that the text I'm showing (42, the edge weight) is a little bit above the middle of the line, what is currently not happening.
I know that I have to take the angle of the line into consideration, but not sure where.
Thanks for any help, and if there's any need of more information let me know.
What you want to do is use linear interpolation. First, find the equation of the line in slope-intercept form, so you can solve for y (when you know x). (I'm just going to rename x1 to p1 and x2 to p2 for clarity.)
(math)
// with points (x1, y1) and (x2, y2)
y - y1 = m*(x - x1) // point-slope form (just a step)
y - y1 = m*x - m*x1
y = m*x - m*x1 + y1 // slope-intercept
Then, since x is the midpoint of the line, x equals the average of the two endpoints. And then calculate y, based on the above equation:
(code)
float m = (p2.y - p1.y) / (p2.x - p1.x);
int x = (x2 + x1) / 2;
int y = m*x - m*p1.x + p1.y;

Draw a line with two different sized ends

How can I have a line be two different sizes with canvas?
I have a line I am drawing with canvas that I would like to start out with a width of 30 and gradually(proportionally) reduce down to a size of 15, so that it reaches 15 right at the end of the line.
I though that perhaps if I set context.lineWidth in two places (start and end) it would work.
<!DOCTYPE HTML>
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="578" height="200"></canvas>
<script>
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.beginPath();
context.moveTo(100, 150);
context.lineWidth = 30;
context.lineTo(450, 50);
context.lineWidth = 15;
context.stroke();
</script>
</body>
</html>
I once wondered about building such a variable-width line, and i ended building my own solution, and wrote a blog post out of it.
I'll copy the first part of it here, the rounded version can also be found here :
https://gamealchemist.wordpress.com/2013/08/28/variable-width-lines-in-html5-canvas/
Variable width lines in html5
Drawing such a [variable width] line is quite easy once we realize that what we need to draw is not a line : in fact it is a polygon.
If the line segment we want to draw is (A,B), the situation looks like this :
What we want to draw in fact is the A1,A2,B2,B1 polygon.
If we call N the normal vector (drawn on the scheme), and w1 and w2 the width in A and B respectively, we have :
A1 = A + N * w1/2
A2 = A – N * w1/2
B1 = B + N * w2/2
B2 = B – N * w2/2
So how do we find this normal vector N ?
Maths says that if (x,y) defines a vector V , its normal vector coordinates are (-y, x).
N, the vector normal to AB will hence have ( – ( yB – yA ) , ( xB – xA ) ) as coordinates.
But there is an annoying thing about this vector : it depends on AB length, which is not
what we want : we need to normalize this vector, i.e. have it to a standard length of 1, so when we later multiply this vector by w1/2, we get the right length vector added.
Vector normalisation is done by dividing the x and y of the vector by the vector length.
Since the length is found using phytagore’s theorem, that makes 2 squares, one square root, and finally 2 divides to find the normalized vector N :
// computing the normalized vector normal to AB
length = Math.sqrt( sq (xB-xA) + sq (yB - yA) ) ;
Nx = - (yB - yA) / length ;
Ny = (xB - xA) / length ;
So now that we can compute the four points, let us link them by a poly-line, and fill the resulting shape : here comes our variable width segment !
Here is the javascript code :
// varLine : draws a line from A(x1,y1) to B(x2,y2)
// that starts with a w1 width and ends with a w2 width.
// relies on fillStyle for its color.
// ctx is a valid canvas's context2d.
function varLine(ctx, x1, y1, x2, y2, w1, w2) {
var dx = (x2 - x1);
var dy = (y2 - y1);
w1 /= 2; w2 /= 2; // we only use w1/2 and w2/2 for computations.
// length of the AB vector
var length = Math.sqrt(sq(dx) + sq(dy));
if (!length) return; // exit if zero length
dx /= length ; dy /= length ;
var shiftx = - dy * w1 // compute AA1 vector's x
var shifty = dx * w1 // compute AA1 vector's y
ctx.beginPath();
ctx.moveTo(x1 + shiftx, y1 + shifty);
ctx.lineTo(x1 - shiftx, y1 - shifty); // draw A1A2
shiftx = - dy * w2 ; // compute BB1 vector's x
shifty = dx * w2 ; // compute BB1 vector's y
ctx.lineTo(x2 - shiftx, y2 - shifty); // draw A2B1
ctx.lineTo(x2 + shiftx, y2 + shifty); // draw B1B2
ctx.closePath(); // draw B2A1
ctx.fill();
}
So let us see the result on a small example : drawing variable width segments within a circle with nice hsl colors :
(About #MarkE's (interesting) remark on chaining line segments, i fear this is a quite difficult goal, since there are many specific cases depending on line length/ w1 /w2 / angle in between segments. I quite solved it using force fields and marching cubes, but i fear this is completely off-topic !! :-) )

HTML5 Perspective Grid

I was trying to do a perspective grid on my canvas and I've changed the function from another website with this result:
function keystoneAndDisplayImage(ctx, img, x, y, pixelHeight, scalingFactor) {
var h = img.height,
w = img.width,
numSlices = Math.abs(pixelHeight),
sliceHeight = h / numSlices,
polarity = (pixelHeight > 0) ? 1 : -1,
heightScale = Math.abs(pixelHeight) / h,
widthScale = (1 - scalingFactor) / numSlices;
for(var n = 0; n < numSlices; n++) {
var sy = sliceHeight * n,
sx = 0,
sHeight = sliceHeight,
sWidth = w;
var dy = y + (sliceHeight * n * heightScale * polarity),
dx = x + ((w * widthScale * n) / 2),
dHeight = sliceHeight * heightScale,
dWidth = w * (1 - (widthScale * n));
ctx.drawImage(img, sx, sy, sWidth, sHeight,
dx, dy, dWidth, dHeight);
}
}
It creates almost-good perspective grid, but it isn't scaling the Height, so every square has got the same height. Here's a working jsFiddle and how it should look like, just below the canvas. I can't think of any math formula to distort the height in proportion to the "perspective distance" (top).
I hope you understand. Sorry for language errors. Any help would be greatly appreciatedRegards
There is sadly no proper way besides using a 3D approach. But luckily it is not so complicated.
The following will produce a grid that is rotatable by the X axis (as in your picture) so we only need to focus on that axis.
To understand what goes on: We define the grid in Cartesian coordinate space. Fancy word for saying we are defining our points as vectors and not absolute coordinates. That is to say one grid cell can go from 0,0 to 1,1 instead of for example 10,20 to 45, 45 just to take some numbers.
At the projection stage we project these Cartesian coordinates into our screen coordinates.
The result will be like this:
ONLINE DEMO
Ok, lets dive into it - first we set up some variables that we need for projection etc:
fov = 512, /// Field of view kind of the lense, smaller values = spheric
viewDist = 22, /// view distance, higher values = further away
w = ez.width / 2, /// center of screen
h = ez.height / 2,
angle = -27, /// grid angle
i, p1, p2, /// counter and two points (corners)
grid = 10; /// grid size in Cartesian
To adjust the grid we don't adjust the loops (see below) but alter the fov and viewDist as well as modifying the grid to increase or decrease the number of cells.
Lets say you want a more extreme view - by setting fov to 128 and viewDist to 5 you will get this result using the same grid and angle:
The "magic" function doing all the math is as follows:
function rotateX(x, y) {
var rd, ca, sa, ry, rz, f;
rd = angle * Math.PI / 180; /// convert angle into radians
ca = Math.cos(rd);
sa = Math.sin(rd);
ry = y * ca; /// convert y value as we are rotating
rz = y * sa; /// only around x. Z will also change
/// Project the new coords into screen coords
f = fov / (viewDist + rz);
x = x * f + w;
y = ry * f + h;
return [x, y];
}
And that's it. Worth to mention is that it is the combination of the new Y and Z that makes the lines smaller at the top (at this angle).
Now we can create a grid in Cartesian space like this and rotate those points directly into screen coordinate space:
/// create vertical lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(i, -grid);
p2 = rotateX(i, grid);
ez.strokeLine(p1[0], p1[1], p2[0], p2[1]); //from easyCanvasJS, see demo
}
/// create horizontal lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(-grid, i);
p2 = rotateX(grid, i);
ez.strokeLine(p1[0], p1[1], p2[0], p2[1]);
}
Also notice that position 0,0 is center of screen. This is why we use negative values to get out on the left side or upwards. You can see that the two center lines are straight lines.
And that's all there is to it. To color a cell you simply select the Cartesian coordinate and then convert it by calling rotateX() and you will have the coordinates you need for the corners.
For example - a random cell number is picked (between -10 and 10 on both X and Y axis):
c1 = rotateX(cx, cy); /// upper left corner
c2 = rotateX(cx + 1, cy); /// upper right corner
c3 = rotateX(cx + 1, cy + 1); /// bottom right corner
c4 = rotateX(cx, cy + 1); /// bottom left corner
/// draw a polygon between the points
ctx.beginPath();
ctx.moveTo(c1[0], c1[1]);
ctx.lineTo(c2[0], c2[1]);
ctx.lineTo(c3[0], c3[1]);
ctx.lineTo(c4[0], c4[1]);
ctx.closePath();
/// fill the polygon
ctx.fillStyle = 'rgb(200,0,0)';
ctx.fill();
An animated version that can help see what goes on.

create beziers curve with only start and endpoint

I'm trying to create a html canvas where the user can define a start- and endpoint, between the start and endpoint I want to draw a waved line, I'm doing this by drawing bezierCurveTo.
a sample:
the code I use to draw this is the following:
var wave = new Kinetic.Shape({
drawFunc: function (canvas) {
var ctx = canvas.getContext();
ctx.beginPath();
ctx.moveTo(50, 50);
var waveCount = 0;
var controlPoint1X = 55;
var controlPoint2X = 60;
var endPointX = 65;
while(waveCount < 10) {
ctx.bezierCurveTo(controlPoint1X, 35, controlPoint2X, 65, endPointX, 50);
controlPoint1X += 20;
controlPoint2X += 20;
endPointX += 20;
waveCount++;
}
ctx.stroke(_this);
},
stroke: '#000000',
strokeWidth: 2
});
I can make this work as long as only the x or only the y coordinate changes. Now I want to be able to create a waved line like shown above but with a different x,y coordinate. For example startpoint x: 50 y: 50 and endpoint x: 100 y: 100. I know I have to calculate the controlpoints, but I can't find out what formula I have to use. Can someone help me out?
Let's simulate a circle and sinewave on a straight line. For a semi-circle, each "period" consists of two segments, with segment one being:
cDist = 4/3 * amplitude
(we know this from http://pomax.github.com/bezierinfo/#circles_cubic)
S = (x1, 0),
C1 = (x1, cDist)
C2 = (x2, cDist)
E = (x2, 0)
and segment two being:
S = (x2, 0),
C1 = (x2, -cDist)
C2 = (x3, -cDist)
E = (x3, 0)
For a sine wave, the control points are almost the same; the y coordinate stays at the same height, but we need to shift the x coordinates so that the shape has the corrected angle at the start and end points (for a circle they're vertical, for a sine wave they're diagonal):
S = (x1, 0),
C1 = (x1 + cDist/2, cDist)
C2 = (x2 - cDist/2, cDist)
E = (x2, 0)
and segment two is:
S = (x2, 0),
C1 = (x2+cDist, -cDist)
C2 = (x3-cDist, -cDist)
E = (x3, 0)
I put up a demonstrator of this at: http://jsfiddle.net/qcUyC/6
If you want these lines to be at a fixed angle, my advice is: rotate your context. Don't actually change your coordinates. Just use context.rotate(...) and you're done. See http://jsfiddle.net/qcUyC/7
But, if you absolutely need coordinates that aren't just drawn in the right place, but have coordinates that represent a real angled line, then start with your angle:
angle = some value you picked, in radians (somewhere between 0 and 2*pi)
with that angle, we can place our points:
dx = some fixed value we pick
dy = some fixed value we pick
ox = the x-offset w.r.t. 0 for the first coordinate in our line
oy = the y-offset w.r.t. 0 for the first coordinate in our line
x1 = ox
y1 = oy
x2 = (dx * cos(angle) - dy * sin(angle)) + ox
y2 = (dx * sin(angle) + dy * cos(angle)) + oy
x3 = (2*dx * cos(angle) - 2*dy * sin(angle)) + ox
y3 = (2*dx * sin(angle) + 2*dy * cos(angle)) + oy
...
xn = ((n-1)*dx * cos(angle) - (n-1)*dy * sin(angle)) + ox
yn = ((n-1)*dx * sin(angle) + (n-1)*dy * cos(angle)) + oy
you then have to treat your control points as vectors relative to the start point in your segments, so C1' = C1-S, and C2' = C2-S, and then you rotate those with the same transformation. You then add those vectors back up to your starting point and you now have the correctly rotated control point.
That said, don't do that. Let the canvas2d API do the rotation for you and just draw straight lines. It makes life so much easier.

How to calculate rotation angle from rectangle points?

I have 4 points 1,2,3,4 that closes a rectangle.
The points are in a array in this following way: x1 y1 x2 y2 x3 y3 x4 y4
The problem I have is that the rectangle can be rotated in a angle.
How can I calculate the original points (gray outline), and the angle?
I'm trying to reproduce this effect in javascript+css3-transform, so I need to first know the straight dimensions and then rotate with the css.
I just know if the rectangle is straight by comparing points e.g. y1==y2
if(x1==x4 && x2==x3 && y1==y2 && y4==y3){
rectangle.style.top = y1;
rectangle.style.left = x1;
rectangle.style.width = x2-x1;
rectangle.style.height = y4-y1;
rectangle.style.transform = "rotate(?deg)";
}
You can use any coordinate pair on the same side to calculate the rotation angle. Note that mathematic angles normally assume 0 as long the +ve X axis and increase by rotating anti–clockwise (so along the +ve Y axis is 90°, -ve X axis is 180° and so on).
Also, javascript trigonometry functions return values in radians that must be converted to degrees before being used in a CSS transform.
If the shape is not rotated more than 90°, then life is fairly simple and you can use the tanget ratio of a right angle triangle:
tan(angle) = length of opposite side / length of adjacent side
For the OP, the best corners to use are 1 and 4 so that rotation is kept in the first quadrant and clockwise (per the draft CSS3 spec). In javascript terms:
var rotationRadians = Math.atan((x1 - x4) / (y1 - y4));
To convert to degrees:
var RAD2DEG = 180 / Math.PI;
var rotationDegrees = rotationRadians * RAD2DEG;
If the rotation is more than 90°, you will need to adjust the angle. e.g. where the angle is greater than 90° but less than 180°, you'll get a -ve result from the above and need to add 180°:
rotationDegrees += 180;
Also, if you are using page dimentions, y coordinates increase going down the page, which is the opposite of the normal mathetmatic sense so you need to reverse the sense of y1 - y4 in the above.
Edit
Based on the orientation of points in the OP, the following is a general function to return the center and clockwise rotation of the rectangle in degrees. That's all you should need, though you can rotate the corners to be "level" yourself if you wish. You can apply trigonometric functions to calculate new corners or just do some averages (similar to Ian's answer).
/** General case solution for a rectangle
*
* Given coordinages of [x1, y1, x2, y2, x3, y3, x4, y4]
* where the corners are:
* top left : x1, y1
* top right : x2, y2
* bottom right: x3, y3
* bottom left : x4, y4
*
* The centre is the average top left and bottom right coords:
* center: (x1 + x3) / 2 and (y1 + y3) / 2
*
* Clockwise rotation: Math.atan((x1 - x4)/(y1 - y4)) with
* adjustment for the quadrant the angle is in.
*
* Note that if using page coordinates, y is +ve down the page which
* is the reverse of the mathematic sense so y page coordinages
* should be multiplied by -1 before being given to the function.
* (e.g. a page y of 400 should be -400).
*
* #see https://stackoverflow.com/a/13003782/938822
*/
function getRotation(coords) {
// Get center as average of top left and bottom right
var center = [(coords[0] + coords[4]) / 2,
(coords[1] + coords[5]) / 2];
// Get differences top left minus bottom left
var diffs = [coords[0] - coords[6], coords[1] - coords[7]];
// Get rotation in degrees
var rotation = Math.atan(diffs[0]/diffs[1]) * 180 / Math.PI;
// Adjust for 2nd & 3rd quadrants, i.e. diff y is -ve.
if (diffs[1] < 0) {
rotation += 180;
// Adjust for 4th quadrant
// i.e. diff x is -ve, diff y is +ve
} else if (diffs[0] < 0) {
rotation += 360;
}
// return array of [[centerX, centerY], rotation];
return [center, rotation];
}
The center of the rectangle is right between two opposite corners:
cx = (x1 + x3) / 2
cy = (y1 + y3) / 2
The size of the rectangle is the distance between two points:
w = sqrt(pow(x2-x1, 2) + pow(y2-y1, 2))
h = sqrt(pow(x3-x2, 2) + pow(y3-y2, 2))
The corners of the gray rectangle can be calculated from the center and the size, for example the top left corner:
x = cx - w / 2
y = cy - h / 2
The angle is the arctangent of a side of the square:
a = arctan2(y4 - y1, x4 - x1)
(I'm not sure exactly which angle it returns, or what angle you expect for that matter, so you get to test a bit.)
This is how you get the angle between the vertical pink line and the black line starting at the pink line intersection:
var deg = 90 - Math.arctan((x2-x1) / (y2-y1));
The dimensions can be calculated with the help of the Pythagoras theorem:
var width = Math.sqrt((x2-x1)^2 / (y2-y1)^2));
var height = Math.sqrt((x1-x4)^2) / (y4-y1)^2));
The positional coordinates (left and top) are the averages of x1 and x3 and y1 and y3 respectively.
var left = Math.floor((x1 + x3) / 2);
var top = Math.floor((y1 + y3) / 2);
You want to use the negative-margin trick.
var marginLeft = -Math.ceil(width / 2);
var marginTop = -Math.ceil(height / 2);

Categories