Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

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.

share|improve this question
6  
Why can't you use matrices? –  Byte56 Mar 14 '12 at 20:13
    
I don't think javascript supports matrices or matrix transformations by default. You'd have to write classes etc to do this. Creating a basic matrix class I can do, but I don't know how to implement rotation and world to view matrix transformations. –  Thomas Mar 14 '12 at 20:16
1  
You can do everything without matrices. A rotation matrix is just a series of sines and cosines. A translation matrix is just a addition. A scale matrix is just a few multiplications. For the perspective projection matrix the easiest way is just to divide by Z. You can do any of these calculations manually. Just look up online the usual contents of these matrices, on paper take a generic (x,y,z) and multiply that by each of the matrices to figure out how they affect each of the coordinates. With that said, I've done this in Javascript before, and I used some Matrix library from the web. –  David Gouveia Mar 14 '12 at 22:09
7  
"Since I cannot use matrices or built in transformation methods I need another way." No, you don't. All you need to do is implement matrices in JavaScript. This isn't hard; a 4x4 matrix is just an array of 16 values, just like a 4-element vector is an array of 4 values. You then need some functions to implement matrix operations like matrix multiplication and so forth. It's not particularly hard. –  Nicol Bolas Mar 14 '12 at 23:32
1  
@NicolBolas Yes I know that a "real" perspective projection matrix does a lot more than that e.g. mapping to normalized device coordinates, taking into account the near and far planes, distorting the scene to account for the aspect ratio. I should have put more emphasis on the "easiest way" and been more clear about it but I was running low on characters for the comment. But basically, by easiest I mean it's a good educational starting point in order to get some perspective happening in your scene. A full understanding of projection matrices was outside the scope of my comment. –  David Gouveia Mar 14 '12 at 23:47

4 Answers 4

up vote 6 down vote accepted

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.

share|improve this answer
    
Finally found some time to try and implement this. Your example code is absolutely perfect, exacly what I need! I've managed to get this working with all kinds of javascript classes for the world, camera and objects in it. I've got only one question left, I noticed you have a world and camera matrix, now I want to be able to rotate an object itself without rotating the world or camera. From XNA I remember something called an Identity matrix. I can create this exactle like the camera and world in your example, but how do I use this in the draw method? –  Thomas Apr 8 '12 at 12:31
    
my draw code is now: var comboMatrix = world.WorldMatrix.x(camera.ViewMatrix); How do I add the identity matrix to this? Do I first transform this with the view or world matrix or with comboMatrix? –  Thomas Apr 8 '12 at 12:35
    
After playing around some more, i also find it difficult to implement the projection matrix, ill post some code below. –  Thomas Apr 8 '12 at 14:06
    
@Thomas You are mistaken about something. The world matrix is actually what you use to transform individual objects. You should have a different world matrix for each object, and that is where the rotation goes. An identity matrix is just a matrix with ones in the main diagonal and zeros everywhere else, and it represents the concept if not being transformed at all. –  David Gouveia Apr 8 '12 at 21:13
    
@Thomas As for the projection matrix, you need to convert all your other code to work with 4x4 matrices and 4D vectors. Then you add the projection matrix to the end of the combo matrix, same way as the view matrix. Finally within the loop instead of dividing x and y by z, you need to divide x y and z by w instead. You should not need any change to the viewpor transformation at the end, unless the size of the viewport changes. I recommend researching the subject a bit. Do not have time now to provide a full example, and hate typing on a tablet :-P –  David Gouveia Apr 8 '12 at 21:19

Another alternative other then Sylvester which is already mentioned is gl-matrix which is meant to be a fast implementation of matrices vectors and quaternions in javascript. That should be most of what you need.

share|improve this answer

Two alternatives I'd recommend:

A) Calculate all the matrices by hand. It doesn't require any matrix4 classes or anything, just a lot of math. You'd have to calculate something like

NDC_Coordinates=PerspectiveDivide(Projection*Cameraview*Modelview*Vector)

The Modelview one is optional, so it'll be the CameraView that will probably contain all the annoying rotation and translation stuff.

B) Make a Doom-style renderer. I won't elaborate much here because there are tons of resources online:

share|improve this answer
    
Woah, that's a good looking post now. Thanks Aku. –  NeuroFuzzy Mar 17 '12 at 4:36

Well those are for 3D transformations/rotations, to convert from 3D to 2D (to draw on a screen), xp = x * scale / (z + scale) yp = y * scale / (z + scale)

where scale is a depth scaling factor (500 for example) after that, generally you would add that to half the screen dimensions to center it

read above for in 3D manipulation

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.