Thursday, February 9, 2017

javascript - Transform coordinates from 3d to 2d without matrix or built in methods


Not to long ago i started to create a small 3D engine in javascript to combine this with an html5 canvas.


One of the issues I run into is how can you transform 3d to 2d coords. Since I cannot use matrices or built in transformation methods I need another way.


I've tried implementing the next explanation + pseudo code: http://freespace.virgin.net/hugo.elias/routines/3d_to_2d.htm


Unfortunately no luck there. I've replace all the input variables with data from my own camera and object classes.


I have the following data: An object with a rotation, position vector and an array of 4 3d coords (its just a plane) a camera with a position and rotation vector the viewport -> a square 600 x 600 surface. The example uses a zoom factor which I've set as 1



Most hits on google use either matrix calculations or don't implement camera rotation. Basic transformation should be like this: screen.x = x / z * zoom screen.y = y / z * zoom


Can anyone point me in the right direction or explain to me howto achieve this?


edit: Thanks for all your posts, I haven't been able to apply all this to my project yet but I hope to do this soon.




Ok after playing around a while some issues still remain. I've created a new projection matrix:


    var w = (2 * nearPlane) / viewportWidth;
var h = (2 * nearPlane) / viewportHeight;
var q = farPlane / (farPlane - nearPlane);

this.ProjectionMatrix = $M([

[w, 0, 0, 0],
[0, h, 0, 0],
[0, 0, q, 1],
[0, 0, (nearPlane * farPlane) / (nearPlane - farPlane), 0]
]);

where viewport = 600 * 450


and near plane = 0.5 and far plane = 500


the original code for drawing the vertices is this:


 var comboMatrix = world.WorldMatrix.x(camera.ViewMatrix);

for (i = 0; i != this.NumberOfPolygons; i++) {
var transformedVertices = new Array(verticesPerPolygon);
var valid = new Boolean(1);
for (j = 0; j != verticesPerPolygon; j++) {

currentVertex = this.Mesh[i][j].x(comboMatrix);

currentVertex.elements[0][0] = currentVertex.elements[0][0] / currentVertex.elements[0][2];
currentVertex.elements[0][1] = currentVertex.elements[0][1] / currentVertex.elements[0][2];


if (currentVertex.elements[0][0] < -1 || currentVertex.elements[0][0] > 1 || currentVertex.elements[0][1] < -1 || currentVertex.elements[0][1] > 1) {
valid = new Boolean(0);
break;
}

// Process Viewport Mapping Transformation
currentVertex.elements[0][0] = currentVertex.elements[0][0] * 300 + 300;
currentVertex.elements[0][1] = -currentVertex.elements[0][1] * 300 + 300;

transformedVertices[j] = currentVertex;

}

// Draw the polygon
if (valid == true) {
this.DrawQuad(context, transformedVertices, true, new Boolean(0), new Boolean(1));
}
}

which kinda works since the letter H is drawn on my screen. Moving the camera however makes parts of the letter stop being drawn whenever one of the vertices is outside a 300 * 300 region. I'm assuming this has something to do with a very basic implementation of a projection in these lines:


            currentVertex.elements[0][0] = currentVertex.elements[0][0] * 300 + 300;

currentVertex.elements[0][1] = -currentVertex.elements[0][1] * 300 + 300;

Can anybody help me out howto change this piece of code so it works with my projection matrix?


I've tried removing the last 2 lines and changing transformedVertices[j] = currentVertex; into transformedVertices[j] = currentVertex.x(projection); with no result.



Answer



Linear Algebra Library


Although you can do all the calculations without using matrices, using them makes everything significantly easier. For starters, if you don't want to implement matrices and vectors yourself, I've successfully used the following library before:


Sylvester - Vector and Matrix Math for Javascript


You can just copy and paste the packed version of the library at the top of your code - it's a one liner.


If after this you still intend to do this without matrices, the process is pretty much the same as I'm going to show, but you'll have to unroll all of the matrices multiplications yourself into their own separate expressions, and run them separately.



Example


As for an example of how to use it, I've digged up some old code of mine which you can try below:


http://jsfiddle.net/JMa9b/


I'm not sure how much that will be helpful to you though. The code is a mess - it was done many years ago when I was just starting to learn about 3D, and it was pretty much just me messing around trying to get some 3D image rendered from scratch. Also, I didn't even know Javascript, so I was just making it up as I progressed, so disregard anything that looks wrong language wise.


More Info


The most relevant is the way I calculate the world matrix that is rotating the object:


var cx = Math.cos(rotX);
var sx = Math.sin(rotX);
var cy = Math.cos(rotY);
var sy = Math.sin(rotY);

var cz = Math.cos(rotZ);
var sz = Math.sin(rotZ);

var rotXMatrix =$M([
[1,0,0,0],
[0,cx,sx,0],
[0,-sx,cx,0],
[0,0,0,1]
]);


var rotYMatrix = $M([
[cy,0,-sy,0],
[0,1,0,0],
[sy,0,cy,0],
[0,0,0,1]
]);

var rotZMatrix = $M([
[cz,sz,0,0],
[-sz,cz,0,0],

[0,0,1,0],
[0,0,0,1]
]);

var translationMatrix = $M([
[1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[posX,posY,posZ,1]
]);


worldMatrix = Matrix.I(4);
worldMatrix = worldMatrix.x(rotXMatrix);
worldMatrix = worldMatrix.x(rotYMatrix);
worldMatrix = worldMatrix.x(rotZMatrix);
worldMatrix = worldMatrix.x(translationMatrix);

Adding scaling would have been pretty easy too. For instance, for a uniform scale, it would be something like:


var scaleMatrix=$M([
[scale,0,0,0],

[0,scale,0,0],
[0,0,scale,0],
[0,0,0,1]
]);

The most important thing is that when creating the World Matrix for your objects, you must multiply these individual matrices in the correct order, which is scale, rotation and translation.


As for the View Matrix for your camera, it's somewhat similar to the World Matrix, but everything is done the other way around. You negate the rotation angles and the positions , and multiply the matrices together in the inverse order as the one above.


I got the formulas from here by the way (they can be slightly different in OpenGL though).


Notes on Perspective


For the perspective projection I'm simply taking the view space X and Y components and dividing by Z. That works in a simple "toy" case like this, but if you want to deal with other parameters such as the aspect ratio, field of view, near and far planes, etc, you'll want to do it with a matrix too. For example, a matrix such as this (taken from DX documentation):



2*zn/w  0       0              0
0 2*zn/h 0 0
0 0 zf/(zf-zn) 1
0 0 zn*zf/(zn-zf) 0

The way to use such a matrix is that you take your coordinates in view space (after applying the world and view matrix) with an added 1 as the fourth component, i.e. (x,y,z,1), multiply that by the projection matrix, and then homogenize the result back into 3D by dividing everything by W, i.e. (x,y,z,w)=(x/w,y/w,z/w).


And by the way, the simplest perspective matrix possible is something like:


1 0 0 0
0 1 0 0
0 0 1 1

0 0 0 0

Which basically discards the W component and stores Z on it. Then when you homogenize the result is the same as dividing by Z, which is what I did directly in my code.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...