Construct a circle of nested squares - javascript

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>

Related

How to add sprites around the perimeter of a circle in phaser-framework

There is a game world. And inside the game world, there is a small circle. How can I add a circle of sprites around the perimeter so that it does not go beyond the circle.
If I understand correctly, your question is more general about how to calculate the coordinates of a circle.
Typically you can calculate circle positions using sine and cosine, and in JavaScript and Phaser you could do it something like this:
addSpritesInCircle: function(xpivot, ypivot, radius, amount) {
// add sprites in a circle
for (var i = 0; i < amount; i++) {
// divide sprites evenly around the circle
var angle = 360.0 * (i / amount);
// calculate circle coordinates
var xpos = xpivot + (Math.cos(2 * Math.PI * angle / 360.0) * radius);
var ypos = ypivot + (Math.sin(2 * Math.PI * angle / 360.0) * radius);
// add phaser sprite
var sprite = this.game.add.sprite(xpos, ypos, 'mysprites', 'alienfoe1');
// optionally also add to a group for handling/updates later
this.myAliensGroup.add(sprite);
};
}

How can I modify the image with text in html5 canvas [duplicate]

How do I create circle text (text in a circle shape) with canvas?
Letters should now be properly oriented:
CanvasRenderingContext2D.prototype.fillTextCircle = function(text,x,y,radius,startRotation){
var numRadsPerLetter = 2*Math.PI / text.length;
this.save();
this.translate(x,y);
this.rotate(startRotation);
for(var i=0;i<text.length;i++){
this.save();
this.rotate(i*numRadsPerLetter);
this.fillText(text[i],0,-radius);
this.restore();
}
this.restore();
}
Sample usage:
var ctx = document.getElementById('canvas').getContext('2d');
ctx.font = "bold 30px Serif";
ctx.fillTextCircle("Circle Text ",150,150,75,Math.PI / 2);
The extra space at the end of the string adds some extra padding.
Sample output:
It can technically be done, but there is no built in way. You'd have to calculate an arc, and draw each letter individually along that arc, figuring out the angle and positioning yourself.
Many people end up making their own methods (like the above) for text. Heck, multiline text can't even be done by default!
EDIT: Here is a working example, piggybacking off of cmptrgeekken's work. If you upvote me, upvote him too :P
http://jsfiddle.net/c3Y8M/1/
What it looks like:
On my blog, I take a fairly close look at creating circular text using HTML5 Canvas:
html5graphics.blogspot.com
In the example, options include rounded text alignment (left, center and right) from a given angle, inward and outward facing text, kerning (adjustable gap between characters) and text inside or outside the radius.
There is also a jsfiddle with a working example.
It is as follows:
document.body.appendChild(getCircularText("ROUNDED TEXT LOOKS BEST IN CAPS!", 250, 0, "center", false, true, "Arial", "18pt", 2));
function getCircularText(text, diameter, startAngle, align, textInside, inwardFacing, fName, fSize, kerning) {
// text: The text to be displayed in circular fashion
// diameter: The diameter of the circle around which the text will
// be displayed (inside or outside)
// startAngle: In degrees, Where the text will be shown. 0 degrees
// if the top of the circle
// align: Positions text to left right or center of startAngle
// textInside: true to show inside the diameter. False to show outside
// inwardFacing: true for base of text facing inward. false for outward
// fName: name of font family. Make sure it is loaded
// fSize: size of font family. Don't forget to include units
// kearning: 0 for normal gap between letters. positive or
// negative number to expand/compact gap in pixels
//------------------------------------------------------------------------
// declare and intialize canvas, reference, and useful variables
align = align.toLowerCase();
var mainCanvas = document.createElement('canvas');
var ctxRef = mainCanvas.getContext('2d');
var clockwise = align == "right" ? 1 : -1; // draw clockwise for aligned right. Else Anticlockwise
startAngle = startAngle * (Math.PI / 180); // convert to radians
// calculate height of the font. Many ways to do this
// you can replace with your own!
var div = document.createElement("div");
div.innerHTML = text;
div.style.position = 'absolute';
div.style.top = '-10000px';
div.style.left = '-10000px';
div.style.fontFamily = fName;
div.style.fontSize = fSize;
document.body.appendChild(div);
var textHeight = div.offsetHeight;
document.body.removeChild(div);
// in cases where we are drawing outside diameter,
// expand diameter to handle it
if (!textInside) diameter += textHeight * 2;
mainCanvas.width = diameter;
mainCanvas.height = diameter;
// omit next line for transparent background
mainCanvas.style.backgroundColor = 'lightgray';
ctxRef.fillStyle = 'black';
ctxRef.font = fSize + ' ' + fName;
// Reverse letters for align Left inward, align right outward
// and align center inward.
if (((["left", "center"].indexOf(align) > -1) && inwardFacing) || (align == "right" && !inwardFacing)) text = text.split("").reverse().join("");
// Setup letters and positioning
ctxRef.translate(diameter / 2, diameter / 2); // Move to center
startAngle += (Math.PI * !inwardFacing); // Rotate 180 if outward
ctxRef.textBaseline = 'middle'; // Ensure we draw in exact center
ctxRef.textAlign = 'center'; // Ensure we draw in exact center
// rotate 50% of total angle for center alignment
if (align == "center") {
for (var j = 0; j < text.length; j++) {
var charWid = ctxRef.measureText(text[j]).width;
startAngle += ((charWid + (j == text.length-1 ? 0 : kerning)) / (diameter / 2 - textHeight)) / 2 * -clockwise;
}
}
// Phew... now rotate into final start position
ctxRef.rotate(startAngle);
// Now for the fun bit: draw, rotate, and repeat
for (var j = 0; j < text.length; j++) {
var charWid = ctxRef.measureText(text[j]).width; // half letter
// rotate half letter
ctxRef.rotate((charWid/2) / (diameter / 2 - textHeight) * clockwise);
// draw the character at "top" or "bottom"
// depending on inward or outward facing
ctxRef.fillText(text[j], 0, (inwardFacing ? 1 : -1) * (0 - diameter / 2 + textHeight / 2));
ctxRef.rotate((charWid/2 + kerning) / (diameter / 2 - textHeight) * clockwise); // rotate half letter
}
// Return it
return (mainCanvas);
}
It's my modification of this: http://jsfiddle.net/Brfp3/3/
But feature allows you to display text clockwise and counterclockwise.
function textCircle(text,x,y,radius,space,top){
space = space || 0;
var numRadsPerLetter = (Math.PI - space * 2) / text.length;
ctx.save();
ctx.translate(x,y);
var k = (top) ? 1 : -1;
ctx.rotate(-k * ((Math.PI - numRadsPerLetter) / 2 - space));
for(var i=0;i<text.length;i++){
ctx.save();
ctx.rotate(k*i*(numRadsPerLetter));
ctx.textAlign = "center";
ctx.textBaseline = (!top) ? "top" : "bottom";
ctx.fillText(text[i],0,-k*(radius));
ctx.restore();
}
ctx.restore();
}
Sample usage:
ctx.font = "bold 30px Courier";
textCircle("Half circle Text",150,150,75,Math.PI/12,1);
textCircle("Half circle Text",150,150,75,Math.PI/12);
A version in which the size of characters is counted. The spaces between the letters are therefore always the same size.
function drawTextAlongArc(context, str, centerX, centerY, radius, angle) {
var len = str.length, s, letterAngle;
context.save();
context.textAlign = 'center';
context.translate(centerX, centerY);
context.rotate(angle + Math.PI / 2);
for (var n = 0; n < len; n++) {
s = str[n];
letterAngle = 0.5*(context.measureText(s).width / radius);
context.rotate(letterAngle);
context.save();
context.translate(0, -radius);
context.fillText(s, 0, 0);
context.restore();
context.rotate(letterAngle);
}
context.restore();
}
CircleType.js doesn't use canvas but achieves the same effect: http://circletype.labwire.ca - also works well in fluid layouts.

Adding perspective to fake 3D animation

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!

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.

Categories