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.
Related
I'm trying to manipulate a simple rectangle on a HTML5 canvas. The Javascript that does this is here:
var canvas = document.getElementById("mainCanvas");
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, windowWidth, windowHeight);
var halfWidth = (iconWidth / 2);
var halfHeight = (iconHeight / 2);
var centreX = x + halfWidth;
var centreY = y + halfHeight;
ctx.fillStyle = "#FF0000";
ctx.translate(centreX, centreY);
ctx.rotate(rotationDegree * Math.PI / 180);
ctx.fillRect(-halfWidth, -halfHeight, iconWidth, iconHeight);
ctx.translate(-centreX, -centreY);
As I increase y, I can see the rectangle travelling along the screen and, if I rotate the rectangle, it rotates and moves along the new trajectory; however, in order to stop the rectangle leaving the screen, I had a basic boundary check, which was just not working (the rectangle was travelling off the screen, and being "bounced" where it had not reached the edge.
As an experiment, I then tried the following:
var canvas = document.getElementById("mainCanvas");
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, windowWidth, windowHeight);
var halfWidth = (iconWidth / 2);
var halfHeight = (iconHeight / 2);
var centreX = x + halfWidth;
var centreY = y + halfHeight;
ctx.save();
ctx.fillStyle = "#FF0000";
ctx.translate(centreX, centreY);
ctx.rotate(rotationDegree * Math.PI / 180);
ctx.fillRect(-halfWidth, -halfHeight, iconWidth, iconHeight);
// ctx.translate(-centreX, -centreY);
ctx.restore();
This works, but the rotation no longer guides the rectangle. My conclusion is that the rotate function rotates the canvas, but then leaves it in the new, rotated form (like rotating a piece of paper underneath a pen). So, the bug I had was that the rotation was not being reset; however, apart from the boundary check, this "bugged" behaviour was what I was actually aiming for.
Is there a way to get from a canvas 2d context the absolute position, taking into account the rotation so that, even if I leave the canvas in its "rotated" state, I can perform a boundary check?
Here is a fiddle of the site.
To transform a point from local space (the transformed space) to screen space create a matrix that is a shadow (copy) of the context transform then multiply the point with that matrix
function getTransformToScreen(x,y,rotation,posX,posY){
var xAx = Math.cos(rotation); // x axis x
var xAy = Math.sin(rotation); // x axis y
// the equivalent to
// ctx setTransform(xAx, xAy ,-xAy, xAx, posX, posY);
// second two values (y Axis) is at 90 deg of x Axis if it is
// not at 90 (skewed) then you need to calculate the skewed axis (y axis) direction
return {
x : x * xAx - y * xAy + posX,
y : x * xAy + y * xAx + posY
}
}
To use
// your code
ctx.translate(centreX, centreY);
ctx.rotate(rotationDegree * Math.PI / 180);
ctx.fillRect(-halfWidth, -halfHeight, iconWidth, iconHeight);
// get the top left
var topLeft = getTransformToScreen(
-halfWidth, -halfHeight,
rotationDegree * Math.PI / 180,
centreX, centreY
);
I'm working on a canvas-based animation, and I'm trying to get a 3D effect in a 2D canvas.
So far, things are going well! I've got my "orbiting line of triangles" working very well:
var c = document.createElement('canvas');
c.width = c.height = 100;
document.body.appendChild(c);
var ctx = c.getContext("2d");
function Triangles() {
this.rotation = {
x: Math.random()*Math.PI*2,
y: Math.random()*Math.PI*2,
z: Math.random()*Math.PI*2
};
/* Uncomment this for testing perspective...
this.rotation = {
x: Math.PI/2,
y: 0,
z: 0
};
*/
}
Triangles.prototype.draw = function(t) {
this.rotation.z += t/1000;
var i, points;
for( i=0; i<15; i++) {
points = [
this.computeRotation(Math.cos(0.25*i),-Math.sin(0.25*i),0),
this.computeRotation(Math.cos(0.25*(i+1)),-Math.sin(0.25*(i+1)),-0.1),
this.computeRotation(Math.cos(0.25*(i+1)),-Math.sin(0.25*(i+1)),0.1)
];
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(50+40*points[0][0],50+40*points[0][1]);
ctx.lineTo(50+40*points[1][0],50+40*points[1][1]);
ctx.lineTo(50+40*points[2][0],50+40*points[2][1]);
ctx.closePath();
ctx.fill();
}
};
Triangles.prototype.computeRotation = function(x,y,z) {
var rz, ry, rx;
rz = [
Math.cos(this.rotation.z) * x - Math.sin(this.rotation.z) * y,
Math.sin(this.rotation.z) * x + Math.cos(this.rotation.z) * y,
z
];
ry = [
Math.cos(this.rotation.y) * rz[0] + Math.sin(this.rotation.y) * rz[2],
rz[1],
-Math.sin(this.rotation.y) * rz[0] + Math.cos(this.rotation.y) * rz[2]
];
rx = [
ry[0],
Math.cos(this.rotation.x) * ry[1] - Math.sin(this.rotation.x) * ry[2],
Math.sin(this.rotation.x) * ry[1] + Math.cos(this.rotation.x) * ry[2]
];
return rx;
};
var tri = new Triangles();
requestAnimationFrame(function(start) {
function step(t) {
var delta = t-start;
ctx.clearRect(0,0,100,100)
tri.draw(delta);
start = t;
requestAnimationFrame(step);
}
step(start);
});
As you can see it's using rotation matrices for calculating the position of the points after their rotation, and I'm using this to draw the triangles using the output x and y coordinates.
I want to take this a step further by using the z coordinate and adding perspective to this animation, which will make the triangles slightly bigger when in the foreground, and smaller when in the background. However, I'm not sure how to go about doing this.
I guess this is more of a maths question than a programming one, sorry about that!
Define a focal length to control the amount of perspective. The greater the value the less the amount of perspective. Then
var fl = 200; // focal length;
var px = 100; // point in 3D space
var py = 200;
var pz = 500;
Then to get the screen X,Y
var sx = (px * fl) / pz;
var sy = (py * fl) / pz;
The resulting point is relative to the center of the veiw so you need to center it to the canvas.
sx += canvas.width/2;
sy += canvas.height/2;
That is a point.
It assumes that the point being viewed is in front of the view and further than the focal length from the focal point.
I've managed to figure out a basic solution, but I'm sure there's better ones, so if you have a more complete answer feel free to add it! But for now...
Since the coordinate system is already based around the origin with the viewpoint directly on the Z axis looking at the (x,y) plane, it's actually sufficient to just multiply the (x,y) coordinates by a value proportional to z. For example, x * (z+2)/2 will do just fine in this case
There's bound to be a more proper, general solution though!
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'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.