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 !! :-) )
Related
What I'm trying to do is this:
I have two (x, y)points, p1 and p2, and a rotation value from p1 (angle in radians).
P2 has also two other variables, a width and height, that I will call p2w and p2h. I want to check if the angle from p1 intersects the bounds of p2, in a radius of the width and/or height.
In other words, if the angle "cuts through" the square of center p2, width p2w and height p2h.
Here's a graph for better understanding:
http://i.imgur.com/Y7WFD36.png
What I've been trying to do is this:
if( p1.rot > (Math.atan2(p2.y-p2h, p2.x-p2w))
&& p1.rot < (Math.atan2(p2.y+p2h, p2.x+p2w)) )
//There's an intersection
But as you guess, it doesn't work as intended; is there another way to do this?
Captain Math to the rescue!
You are asking whether a ray intersects a rectangle. Here's what we need to do.
Firstly, a ray is defined using either a point and a vector or a point and angle. Since working with vectors is much easier, let's convert your angle to a vector. Using the Pythagorean theorem, your angle phi is identical to the vector n = {x: Math.cos(phi), y: Math.sin(phi)}.
I'll rename your variables to make notation easier. I'll denote your p1 with p and your implicitly defined rectangle with r.
Now, for any random point M lying on the ray, the following equations must hold:
M.x = p.x + n.x * alpha (1)
M.y = p.y + n.y * alpha
for some real alpha. Similarly, for any random point M lying within the rectangle, the following inequalities must hold:
M.x >= r.x
M.x <= r.x + r.w
M.y >= r.y
M.y <= r.y + r.h
For a point to lie on both the ray and within the the rectangle both the equations and the inequalities must hold. Substituting the values above, we get:
p.x + n.x * alpha >= r.x
p.x + n.x * alpha <= r.x + r.w
p.y + n.y * alpha >= r.y
p.y + n.y * alpha <= r.y + r.h
Solve for alpha and we get:
alpha >= (r.x - p.x) / n.x
alpha <= (r.x + r.w - p.x) / n.x
alpha >= (r.y - p.y) / n.y
alpha <= (r.y + r.h - p.y) / n.y
The system above has a solution if and only if:
var lowerLimitX = (r.x - p.x) / n.x;
var lowerLimitY = (r.y - p.y) / n.y;
var upperLimitX = (r.x + r.w - p.x) / n.x;
var upperLimitY = (r.y + r.h - p.y) / n.y;
var minAlpha = Math.max(lowerLimitX, lowerLimitY);
var maxAlpha = Math.min(upperLimitX, upperLimitY);
var hasSolution = minAlpha<= maxAlpha;
Now, if the system above has a solution, it must be the case that at least one point lies on both the ray and the rectangle, in other words, they intersect.
Edit: Here's a working demo. Move the mouse around to see the results. Note that, because of the fact that the Y axis grows downwards in the HTML canvas API, one must swap the lower and upper limits of the Y axis.
Edit 2: If you care about the intersection segment as suggested by #pfannkuchen_gesicht (note that generally, the intersection will be a line segment, not a point), well that's easy as well. As we already know, for the points on the intersection, the ray equations must hold. To find the points themselves, simply substitute alpha with a value within the range [minAlpha; maxAlpha] in (1). For example, the closest point is p + minAlpha * n, the farthest is p + maxAlpha * n and a random point in between is p +(minAlpha + Math.random() * (maxAlpha - minAlpha)) * n.
Here's one way to test if your line segment (ray?) and rectangle intersect.
Just test if the line segment / ray intersects either of the 2 diagonals of the rectangle.
Example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
ctx.lineWidth=3;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
var ray1={x:30,y:250,angle:-Math.PI/3.5};
var ray2={x:30,y:250,angle:-Math.PI/6};
var r={x:100,y:100,w:40,h:40};
ctx.strokeStyle='black';
ctx.strokeRect(r.x,r.y,r.w,r.h);
// this ray intersects the rect
drawRay(ray1,r);
// this ray doesn't intersect the rect
drawRay(ray2,r);
function drawRay(ray,rect){
var intersects=rayRectIntersect(ray,rect);
ctx.beginPath();
ctx.moveTo(ray.x,ray.y);
ctx.lineTo(ray.x+1000*Math.cos(ray.angle),ray.y+1000*Math.sin(ray.angle));
ctx.strokeStyle=(intersects)?'red':'green';
ctx.stroke();
}
function rayRectIntersect(ray,rect){
d0={x:rect.x,y:rect.y};
d1={x:rect.x+rect.w,y:rect.y+rect.h};
d2={x:rect.x,y:rect.y+rect.h};
d3={x:rect.x+rect.w,y:rect.y};
ray0={x:ray.x,y:ray.y};
ray1={x:ray.x+1000*Math.cos(ray.angle),y:ray.y+1000*Math.sin(ray.angle)};
var diag1Test=line2lineIntersection(ray0,ray1,d0,d1);
var diag2Test=line2lineIntersection(ray0,ray1,d2,d3);
return(diag1Test || diag2Test);
}
// Get interseting point of 2 line segments (if any)
// Attribution: http://paulbourke.net/geometry/pointlineplane/
function line2lineIntersection(p0,p1,p2,p3) {
var unknownA = (p3.x-p2.x) * (p0.y-p2.y) - (p3.y-p2.y) * (p0.x-p2.x);
var unknownB = (p1.x-p0.x) * (p0.y-p2.y) - (p1.y-p0.y) * (p0.x-p2.x);
var denominator = (p3.y-p2.y) * (p1.x-p0.x) - (p3.x-p2.x) * (p1.y-p0.y);
// Test if Coincident
// If the denominator and numerator for the ua and ub are 0
// then the two lines are coincident.
if(unknownA==0 && unknownB==0 && denominator==0){return(true);}
// Test if Parallel
// If the denominator for the equations for ua and ub is 0
// then the two lines are parallel.
if (denominator == 0) return false;
// If the intersection of line segments is required
// then it is only necessary to test if ua and ub lie between 0 and 1.
// Whichever one lies within that range then the corresponding
// line segment contains the intersection point.
// If both lie within the range of 0 to 1 then
// the intersection point is within both line segments.
unknownA /= denominator;
unknownB /= denominator;
var isIntersecting=(unknownA>=0 && unknownA<=1 && unknownB>=0 && unknownB<=1)
if(!isIntersecting){return(false);}
return({
x: p0.x + unknownA * (p1.x-p0.x),
y: p0.y + unknownA * (p1.y-p0.y)
});
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<h4>Ray is red if intersecting, green if not</h4>
<canvas id="canvas" width=300 height=300></canvas>
I want to construct a circle of nested squares like this:
In the moment, I am programming in JavaScript/HTML5 canvas. This is my code:
<html>
<head>
<title>Circle of squares</title>
<script type="text/javascript">
var r = 150, u = 20, nests = 200; //radius in pixels, circumference in squares, nests in squares
var w = r; //any number != 0
function getNewW()
{
if(u < 3)
alert("Error: u < 3 (" + u + " < 3)!");
var tangents = new Array(new Array(0, w/2), new Array(Math.sin((1/u*360)*(Math.PI/180))*(w/2), -Math.cos((1/u*360)*(Math.PI/180))*(w/2)));
var sta = new Array(new Array(r, 0), new Array(Math.cos((1/u*360)*(Math.PI/180))*r, Math.sin((1/u*360)*(Math.PI/180))*r));
var end = new Array(new Array(sta[0][0]+tangents[0][0], sta[0][1]+tangents[0][1]), new Array(sta[1][0]+tangents[1][0], sta[1][1]+tangents[1][1]));
var pts = new Array(sta[0], end[0], sta[1], end[1]);
var intersect = new Array(((pts[0][0]*pts[1][1]-pts[0][1]*pts[1][0])*(pts[2][0]-pts[3][0]) - (pts[0][0]-pts[1][0])*(pts[2][0]*pts[3][1]-pts[2][1]*pts[3][0])) / ((pts[0][0]-pts[1][0])*(pts[2][1]-pts[3][1]) - (pts[0][1]-pts[1][1])*(pts[2][0]-pts[3][0])), ((pts[0][0]*pts[1][1]-pts[0][1]*pts[1][0])*(pts[2][1]-pts[3][1]) - (pts[0][1]-pts[1][1])*(pts[2][0]*pts[3][1]-pts[2][1]*pts[3][0])) / ((pts[0][0]-pts[1][0])*(pts[2][1]-pts[3][1]) - (pts[0][1]-pts[1][1])*(pts[2][0]-pts[3][0]))); //Formula from http://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
//distTo0 should be equal to distTo1
var distTo0 = Math.sqrt(Math.pow(sta[0][0]-intersect[0], 2) + Math.pow(sta[0][1]-intersect[1], 2));
var distTo1 = Math.sqrt(Math.pow(sta[1][0]-intersect[0], 2) + Math.pow(sta[1][1]-intersect[1], 2));
if(Math.round(distTo0*100)/100 != Math.round(distTo1*100)/100)
alert("Error: distTo0 != distTo1 (" + distTo0 + " != " + distTo1 + ")!");
return distTo0*2;
}
function start()
{
var canvas = document.getElementById("outputCanvas");
canvas.setAttribute("width", 600);
canvas.setAttribute("height", 600);
if(canvas.getContext)
{
var ctx = canvas.getContext("2d");
ctx.translate(300, 300);
w = getNewW();
for(var i=0; i<u; i++)
{
ctx.rotate((1/u*360)*(Math.PI/180));
ctx.fillRect(r, -w/2, w, w);
}
for(var j=1; j<nests; j++)
{
var oldr = r;
var temp1 = 1/(10*j+1);
while(r+w > oldr) //This is the while-loop that makes the program slow
{
r -= temp1;
w = getNewW();
}
if(r < 0) //When the radius gets smaller than 0, the center is reached -> no new squares have to be drawn
break;
var temp2 = (1/u*360)*(Math.PI/180);
for(var i=0; i<u; i++)
{
ctx.rotate(temp2);
ctx.fillRect(r, -w/2, w, w);
}
}
}
}
</script>
</head>
<body id="main" onload="start()">
<canvas style="border:1px #000000 solid;" width="0" height="0" id="outputCanvas">Canvas not supported...</canvas>
<div id="info"> </div>
</body>
</html>
But because I don't have a formula for the solution, I use a while-loop to get closer and closer to the solution (until it has reached zero because of float-inaccuracy), that's why it's quite slow.
So, what formula can be used to calculate the width of the next square inside the (thought) circle and, if necessary, how could the code be optimized elsewhere?
Near the center of the circle, where the squares are small enough, you can approximate the length of the side (w) by the arc length - that is, how long one uth of the inner circle would be if you drew it as an actual circle. That's just the angle in radians (2 π/u) times the radius of the circle that goes through the inner corners of the square. Since you have r varying in your code, I'll call the specific radius value under consideration at a single moment r2; that makes the arc length this:
w_approx = (2 * Math.PI / u) * r2
But for most of the squares in your picture, the difference between that and the actual value of w is too great; if you use that as the side length, you'll get overlapping squares. Fortunately, we can calculate the true value of w directly, too; it just requires a little trigonometry.
If you draw lines from the inner corners of the square to the center of the circle, those two lines plus the inner side of the square form a triangle. We know how long those two lines we just drew are; they're equal to the inner radius. We don't know how long the third side is - that's the value of w we're looking for - but we do know the angle opposite it. Those three pieces of information are enough to calculate w.
Here's a picture to show what I'm talking about:
The angle at the center of the circle, labeled α (alpha) in the picture, is just one uth of a full circle, which is 2 π /u radians (or 360/u degrees, but the trig functions all expect radians):
alpha = 2 * Math.PI / u
The other two angles of the triangle are equal (they have to be, because they're opposite sides that are of equal length), so they're both labeled β. Since the three angles of a triangle always add up to π radians (or 180º), we can calculate β; it's equal to (π - α)/2 radians:
beta = (Math.PI - alpha)/2
By the Law of Sines, if you divide the length of any side of any triangle by the sine of the angle opposite that side, the result is the same no matter which of the three sides you picked. That tells us that w/sin α must be the same as r2/sin β. Solving that equation for w gets us this:
w = r2 * Math.sin(alpha) / Math.sin(beta)
Solution is quite easy :
What are the parameters ?
• The start radius of your circle.
• The end radius of your circle.
• The number of square per circle.
Then what do you need to compute ?
• The rotation to be performed between two circles : easy ,that's just a full rotation divided by the number of square per circle :
var angle = 2 * Math.PI / squaresPerCircle;
• The size of each square, given the current radius. Easy also : compute the circumference of the current circle (2*PI*radius), then the size of one square is approximately this circumference divided by the number of squares (since you want to fill the circle) :
squareSize = 2 * Math.PI * currentRadius / squaresPerCircle;
approximation is good enough even for like 10 squares per circles.
(
Otherwise the 'real' way to get the height when you have radius and angle is done with :
squareSize = 2 * currentRadius * Math.tan(angle/2);
)
Snippet :
// parameters
var startRadius = 5;
var maxRadius = 200;
var squaresPerCircle = 20;
function start() {
// boilerplate
var canvas = document.getElementById("outputCanvas");
var ctx = canvas.getContext("2d");
canvas.width = 600;
canvas.height = 600;
//
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
var currentRadius = startRadius;
var angle = 2 * Math.PI / squaresPerCircle;
// loop on each ring
do {
squareSize = 2 * Math.PI * currentRadius / squaresPerCircle;
// squareSize = 2 * currentRadius * Math.tan(angle/2);
ctx.save();
// loop on every square of a single ring
for (var cIndex = 0; cIndex < squaresPerCircle; cIndex++) {
ctx.fillRect(currentRadius, -squareSize / 2,
squareSize, squareSize);
ctx.rotate(angle);
};
ctx.restore();
currentRadius += squareSize;
} while (currentRadius < maxRadius);
ctx.restore();
}
onload = start;
<canvas style="border:1px #000000 solid;" width="0" height="0" id="outputCanvas">Canvas not supported...</canvas>
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.
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.
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.