2D perspective transform in JavaScript

2.1k views Asked by At

Looking for a code example of how to calculate (accurately) the dimensions of a rectangle from a top down view (90 degrees) at a particular angle.

e.g. I have a rectangle of 1200mm x 2000mm

enter image description here

How would i determine the new perspective measurements of the rectangle if it was angled down 30 degrees on one side. (From a 90o viewer perspective, top-down)

enter image description here

As from this perspective it would look like the bottom edge is shorter, even though of course it is not. (Pseudo 3D in a way.. but on 2D shapes and in a 2D view)

edit: this is intended as a reference question, please add alternate answers & solutions

1

There are 1 answers

0
Orren Ravid On BEST ANSWER

The perspective transformation you are describing is called foreshortening and I do suggest not only looking at my answer but also looking up more info on it because it is concept with various applications and can be solved for with varying methods.

Keep in mind Javascript is not my forte so if I make any syntactical errors I apologize and please correct me.

For your specific shape a simple use of similar triangles should give you vertex coordinates which you could then use to structure your shape. You can define vertex coordinates as vectors in space.

I'll begin by defining your original shape in 2d space as a series of 4 verticies. (The bottom left I will define as the origin.) In Javascript you should define a 2d array of 4 3-value arrays. Each array within the 2d array will be a vertex coordinate of form [x,y,z]. The x value will be the horizontal value of the vertex, the y value will be the height value and the z value will be how far out it is in 3d space. The y value can be acquired by multiplying the hypotenuse of the ramp (in this case 2000) by the cosine of the angle of the ramp (in this case 30 degrees). In order to do the cosine operation we must convert the angle from degrees to radians. The conversion is always pi = 180, therefore 30 in radians is pi/6. The x value can be acquired by multiplying the hypotenuse by the sine of the same angle.

var hypotenuse = 2000;
var theta = Math.PI/6;
var x = Math.sin(theta)*hypotenuse;
var y = Math.cos(theta)*hypotenuse;
var z = 1200;
var vertices = [[0,y,0], [0,y,z], [x,0,0], [x,0,z]];

Vertex coordinates on an xyz plane.

Perspective transformations occur due to the perspective of the viewer with respect to the object. Therefore, in order to get the actual perspective transform we have to define the position of the viewer. I will define him arbitrarily at point (x/2,y+y/2,z/2).

var viewer = [x/2,y+y/2,z/2];

Viewer position

We then pick a focal length which will define where our image is defined. The focal length is only a matter of distance from the viewer therefore it only is defined as a single point. I'll put the focal length arbitrarily at (viewer_x,(y+y/2)/2,viewer_z) because it is a distance (y+y/2)/2 below the viewer. You must, however, make sure the focal point is the same x and z coordinate as the viewer and that only the y axis is changed. More information on focal length and perspective transform can be found at http://en.wikipedia.org/wiki/3D_projection.

var focal_length = [viewer[0],(y+y/2)/2,viewer[2]];

Focal Length coordinates

1.Now we get the distance from each vertex of the rectangle to the viewer along the y axis (the axis by which we are applying the perspective transform). 2.Then we get the distance from the y coordinate of the focal length to the viewer. 3.We then get the ratio between the focal length and vertex distances. 4.Then multiply the x and z coordinates of the vertex by that ratio to get their new forshortened coordinates of each vertex.

The distances are found by simply subtracting the y coordinates and taking the absolute value of the result. I will do this all in a for loop in order to get more effective code.

var focal_distance = Math.abs(viewer[1] - focal_length[1]); 
//gets distance between focal point y and viewer y
var vert_distance;
for(var i = 0; i< vertices.length-1; i++){
    vert_distance = Math.abs(viewer[1]-vertices[i][1]); 
    //access each individual vertex and and get the distance between vertex y and viewer y
   for (var j = 0; j<vertices[i].length-1; j++){
       vertices[i][j] = vertices[i][j]*(focal_distance/vert_distance);
       //gets the ratio between focal distance and vertex distance and multiplies each vertex by it
   }
}

Finally, since we did foreshortening along the y axis, the y coordinate of every vertex is no longer necessary, therefore we will now take each vertex and save it to a new array of 2d vertices instead of 3d vertices.

var vertices2d = [[0,0],[0,0],[0,0],[0,0]];
//creates a new 2d vertex array of 4 empty 2d vertex coordinates
for(var i = 0; i<vertices.length-1; i++){
    vertices2d[i][0] = vertices[i][0];
    //sets the x values of the 2d vertices = to the x values of 3d vertices
    vertices2d[i][1] = vertices[i][2];
    //sets the y values of the 2d vertices = to the z values of the 3d vertices
}

Now simply put those 2d vertex coordinates into any graphing program and you will get the desired result.

I'm gonna have the program print out the vertex coordinates anyways:

for (var i = 0; i<vertices2d.length-1; i++){
    document.write("(" + vertices2d[i][0] + "," + vertices2d[i][1] + ")");
}

Feel free to mess around with the viewer coordinates, focal length y value, the length and width of the plane, and the ramp angle to get different results. Cheers and feel free to ask any questions if you don't get it.

The full code uninterrupted is below:

var hypotenuse = 2000;
var theta = Math.PI/6;
var x = Math.Sin(theta)*hypotenuse;
var y = Math.Cos(theta)*hypotenuse;
var z = 1200;
var vertices = [[0,y,0], [0,y,z], [x,0,0], [x,0,z]];
var viewer = [x/2,y+y/2,z/2];
var focal_length = [viewer[0],(y+y/2)/2,viewer[2]];
var focal_distance = Math.abs(viewer[1] - focal_length[1]); 
//gets distance between focal point y and viewer y
var vert_distance;
for(var i = 0; i< vertices.length-1; i++){
    vert_distance = Math.abs(viewer[1]-vertices[i][1]); 
    //access each individual vertex and and get the distance between vertex y and viewer y
   for (var j = 0; j<vertices[i].length-1; j++){
       vertices[i][j] = vertices[i][j]*(focal_distance/vert_distance);
       //gets the ratio between focal distance and vertex distance and multiplies each vertex by it
   }
}
var vertices2d = [[0,0],[0,0],[0,0],[0,0]];
//creates a new 2d vertex array of 4 empty 2d vertex coordinates
for(var i = 0; i<vertices.length-1; i++){
    vertices2d[i][0] = vertices[i][0];
    //sets the x values of the 2d vertices = to the x values of 3d vertices
    vertices2d[i][1] = vertices[i][2];
    //sets the y values of the 2d vertices = to the z values of the 3d vertices
}
for (var i = 0; i<vertices2d.length-1; i++){
  document.write("(" + vertices2d[i][0] + "," + vertices2d[i][1] + ")");
}