I’ve just uploaded the benchmarks for vecJS in case you want to see them for yourself. You can find them at http://kenshodubs.com/demos/vecJS/benchmark/.

Chrome does ok, but running them in Firefox gets quite funny (if you’re patient…).

I don’t know why, but the arbitrary rotation seems really slow. I’ll have to look into this and check what I did wrong…

/X

Ok so I changed hosting provider, and the site went down a couple of hours. No biggie, everything should be all right by now..

/X

VecJS has reach r1, so maybe it’s time for a little introduction on what it is and how to use it.

First, let’s begin by clearing out what vecJS is not. It’s not another 3D engine, there’s already plenty of them out there. vecJS is a vector maths library, and a fast one at that. You can use it to build your own engine, or to do anything you want as long as it involves 3D maths…

There are 4 type of objects that you can use:

V3
A regular 3 dimensional vector, holds the x, y, and z values, for all your simple vector needs.
V4
A “4 dimensional” vector, holds x, y, z values plus w, you can use it to do weird stuff with homogenous coordinates that a V3 won’t let you do.
M44
A 4×4 matrix used to do affine transformation, mainly used for projection. If you only need rotation, translation and scaling, you can use the faster M34 below
M34
A 4×4 matrix stored with the last row implied as [0, 0, 0, 1]. This is to avoid generally unneeded work, skipping part of the homogeneous coordinates calculations and the homogeneous divide. Good stuff.
Q
A quaternion. Allows you to do strange things that usually involves rotations. Look at Martin Baker’s EuclideanSpace if you have no idea what I’m talking about.

In this introduction, we’ll make the fugly triangles below rotate, with a camera that we can move around. We’ll only be using V3, M34 and M44; V4 and Q will have to wait for something a bit more advanced than hello world.

So let’s begin by the page structure. We’ll go for a really simple one with only a canvas tag in the body:

<!DOCTYPE html>
<html>
<head></head>
<body>
  <canvas id="canvas" width="500" height="500" style="border: 1px solid #000;"></canvas>

  <script src="vec.dbg.js"></script>
  <script>
    /* This is where we'll be writing our script... */
  </script>
</body>
</html>

As you can see, nothing fancy; the canvas, a link to vecJS (we use the debug version here, see this post to know what this means) and the main script tag.

The first thing we do in the script is grab the canvas, get its context, and cache some dimensions we’ll be using later:

var canvas = document.getElementById('canvas'),
    ctx = canvas.getContext('2d'),
    canvasHalfX = canvas.width / 2,
    canvasHalfY = canvas.height / 2,

Then we declare the mesh object: vertices, faces and a color for each face, so that we know which is which when we draw them later.

$V.V3() is shorthand for new vecJS.V3(), you can use whichever you want, or mix them like crazy and end up with stuff like new $V.V3() if you want; your call.

Just remember to always pass arrays. Always. If you don’t, you’ll either get errors with the debug version, or silent fails that’ll drive you mad with the release one.

Btw, since we won’t be z-sorting our faces, we take the easy way out and make them semi-transparent. Since there won’t ever be more than 2 faces overlapping, they will always look “good” regardless.

mesh = {
  vertices: [
    $V.V3([ 1, 0,  1])  // 0
    ,$V.V3([ 1, 0, -1]) // 1
    ,$V.V3([-1, 0,  1]) // 2
    ,$V.V3([-1, 0, -1]) // 3
    ,$V.V3([ 0, 1,  0]) // 4
    ,$V.V3([ 0, -1, 0]) // 5
  ],
  faces: [
    [2, 0, 4]
    ,[2, 0, 5]
    ,[3, 1, 4]
    ,[3, 1, 5]
  ],
  colors:  [
    'rgba(255, 0, 0, .5)'
    ,'rgba(0, 255, 0, .5)'
    ,'rgba(0, 0, 255, .5)'
    ,'rgba(255, 255, 0, .5)'
  ],
  objectMatrix: $V.M34().identity(),
  rotation: 0
},

For good measure, we throw in the mesh transformation matrix and it’s current rotation angle (it only rotates around y, so one number is enough). Actually, we don’t really need to store the transformation matrix with the object, but javascript doesn’t like you when you create a new instance of an object. So we only create it once and just update it in our loop. Note that since there is no projection involved at this stage, it’s enough to have an M34 here.

Oh, and the mesh structure has nothing to to with vecJS, it’s just convenient to have everything in the same place.

So now, on with the camera:

camera = {
  fov: 70,
  aspect: canvas.width / canvas.height,
  pos: $V.V3([2.2, .5, 2]),
  at: $V.V3([0, 0, 0]),

  viewMatrix: $V.M44(),
  projectionMatrix: $V.M44()
},
projectionMatrix = $V.M44();

viewMatrix is the current position of the camera and where it is looking at. We’ll use it later to transform the vertices from world space to camera space. projectionMatrix is used to transform from camera space to screen coordinates.

The last variable is used to combine the view matrix and the projection matrix, and transform our vertices from world space to screen space with one calculation.

Before we begin with the fun stuff, let’s just create some buffers (again, we don’t want to create new instances all the time in the inner loop, so we create them upfront), then tell the canvas that the origin of our coordinate system is at the center, (not in the corner as it is by default) and that we want our strokes to be solid black. We also declare a function to draw a triangle on the canvas from our vertices/faces structure (hi boilerplate!).

// Init the mesh buffers
mesh.worldVertices = [];
mesh.screenVertices = [];
for (vl = mesh.vertices.length; vl; vl--) {
  mesh.worldVertices.push($V.V3());
  mesh.screenVertices.push($V.V3());
}

// Put (0,0) at the center of the canvas
ctx.setTransform(1, 0, 0, 1, canvasHalfX + .5, canvasHalfY + .5);
// Set the default stroke style
ctx.strokeStyle = 'rgba(0, 0, 0, 1)';

// A function to draw a single triangle on the canvas
function drawTriangle(vertices, face) {
  ctx.beginPath();
  ctx.moveTo(vertices[face[0]].v[0], vertices[face[0]].v[1]);
  ctx.lineTo(vertices[face[1]].v[0], vertices[face[1]].v[1]);
  ctx.lineTo(vertices[face[2]].v[0], vertices[face[2]].v[1]);
  ctx.lineTo(vertices[face[0]].v[0], vertices[face[0]].v[1]);
  ctx.closePath();

  ctx.fill();
  ctx.stroke();
}

Ok, so the first thing we’re gonna need is to create the screen projection matrix for the camera:

// Setup the perspective projection matrix
camera.projectionMatrix.perspectiveFov(camera.fov, camera.aspect, 1, 10);

We create a perspective projection, with a field of view of 70 degrees (as declared in our camera object) and the aspect ration of the canvas (width/height).

We are now ready to write our inner loop. We begin by updating the mesh rotation and then call ou render function (which we’ll write next). To make the rotation smoother, we don’t set the new value directly, but rather ease it in gradually with each frame.

function loop() {
  // Schedule the next loop call
  setTimeout(loop, 1000/60);

  mesh.rotation += (targetRotation - mesh.rotation) * 0.05;
  mesh.objectMatrix.setRotateY(mesh.rotation);

  render();
}

Now that we’re done with that, we’re ready for the big one, the render() function:

// The render function refreshing the canvas
function render() {
  var v, vi, vl,
      f, fi, fl;

  // Update the camera view matrix from its current position
  camera.viewMatrix.lookAt(camera.pos.v, camera.at.v, [0, 1, 0]);

  // multiply the projection and the view matrices to get the screen projection matrix
  projectionMatrix.assignMul(camera.projectionMatrix.m,  camera.viewMatrix.m);

  for (vi=0, vl = mesh.vertices.length; vi < vl; vi++) {
    // Calculate the world coordinates of the mesh vertices
    mesh.worldVertices[vi]
      .set(mesh.vertices[vi].v)
      .mulM(mesh.objectMatrix.m);

    // Project the mesh vertices on the screen
    mesh.screenVertices[vi]
      .set(mesh.worldVertices[vi].v)
      .mulCoord(projectionMatrix.m);
    mesh.screenVertices[vi].v[0] *= canvasHalfX;
    mesh.screenVertices[vi].v[1] *= canvasHalfY;
  }

  // Finally, clear the canvas and draw the mesh faces
  ctx.clearRect(-canvasHalfX, -canvasHalfY, canvas.width, canvas.height);
  for (fi=0, fl = mesh.faces.length; fi < fl; fi++) {
    f = mesh.faces[fi];

    ctx.fillStyle = mesh.colors[fi];
    drawTriangle(mesh.screenVertices, f);
  }
}

Ok, let's take that one again (almost) line by line.

We begin by updating the camera view matrix with its current position and where it's looking at. We won't bank the camera so we use an up vector pointing... "up" (really!).

camera.viewMatrix.lookAt(camera.pos.v, camera.at.v, [0, 1, 0]);

Every vector we'll multiply with that matrix will be converted from the world coordinates to a coordinate system where the camera is at the origin.

We then take that matrix and multiply it with the screen projection matrix:

projectionMatrix.assignMul(camera.projectionMatrix.m, camera.viewMatrix.m);

Now we have matrix that can convert directly from world to screen coordinates.

We're done with the camera; let's transform the mesh vertices. For each vertex, we begin by applying the transformation matrix we created before calling render():

// Calculate the world coordinates of the mesh vertices
mesh.worldVertices[vi]
  .set(mesh.vertices[vi].v)
  .mulM(mesh.objectMatrix.m);

We then take those coordinates and project them to the screen:

  // Project the mesh vertices on the screen
  mesh.screenVertices[vi]
    .set(mesh.worldVertices[vi].v)
    .mulCoord(projectionMatrix.m);
  mesh.screenVertices[vi].v[0] *= canvasHalfX;
  mesh.screenVertices[vi].v[1] *= canvasHalfY;
}

mulCoord is the equivalent of multiplying a V4 with an M44, and the dividing the x, y and x components by w. It' just that you can do it with a V3 instead, and in that case, you would have to divide by w for the projection to work anyway.

Since the screen coordinates are expressed in the [-1,1] range, we also have to scale x and y to the size of the canvas once they're projected.

If you're not interested in having the mesh world coordinates lying around, you can combine the two operations and get rid of the worldVertices buffer by writing instead:

mesh.screenVertices[vi]
  .set(mesh.vertices[vi].v)
  .mulM(mesh.objectMatrix.m)
  .mulCoord(projectionMatrix.m);
mesh.screenVertices[vi].v[0] *= canvasHalfX;
mesh.screenVertices[vi].v[1] *= canvasHalfY;

As a side-note, I realize now that having an assignMulM would be a nice addition. Maybe in R2 :)

After that, we have everything we need, so we just clear the canvas and draw our triangles, and we're done!

I haven't written anything about how the camera position and the mesh rotation are actually updated, it's not really relevant here since it's only binding browser events and changing values in the callbacks, but if you really want to see how it's done, just have a look at the source code of the...

Demo

/X

One thing I noticed while writing tests for vecJS, is that

  1. It’s easy to forget to use arrays and pass the wrong type of parameter.
  2. I do it quite often.
  3. It’s really hard to find where I did it, since it fails silently.

First, let me explain why the functions don’t check if the parameters came as an array or as a list of variables.

Usually, you know pretty well what’s in your variables, so checking in the function if you just passed your arguments as an array or as several parameters actually seems a bit stupid, and really it’s time consuming; for that reason, vecJS accepts only arrays. On the good side, you can either pass an array directly, like this:

var v = $V.V3([1, 2, 3]);
v.cross([4, 5, 6]);

or you can pass the content of another vecJS object:

var v1 = $V.V3([1, 2, 3]),
    v2 = $V.V3([4, 5, 6]);
v1.cross(v2.v);

So, all is well, except for the fact that, as mentioned, it’s easy to forget the []s and pass something completely wrong. You won’t get an exception or even an error in the console, but at the end of the line, all your vectors and arrays will be filled with Nan or 0s. Here is an example:

var v1 = $V.V3([1, 2, 3]);
v1.cross(4, 5, 6);

You would assume that v1 contains the cross product of [1, 2, 3] and [4, 5, 6] (which is [-3, 6, -3] btw), but if you check, you’ll see that it’s actually [Nan, Nan, Nan].

While it might be phonetically funny, and easy to spot here, it’s a real pain when you do a lot of stuff. So, in the end, not funny.

To make this easier to spot, I added som “debug code” that check that you really do pass an array (it even check that you have the right array length, so that you don’t mix up your objects), and throws an exception if you don’t. Now you just have to click on the error in the console, and see where you made your mistake.

Since those are the kind of mistakes that are usually the result of typos and only happen when you’re writing the code, but are gone once you’re done, the tests are only present in the vec.dbg.js version of the files. The build process filter them out in the regular and minified versions (thanks sed), and they won’t be there stealing your CPU doing something that is not necessary.

So once you’re sure about your code, just switch vec.dbg.js to vec.min.js, and you’re done! :D

/X

Just had to eat my own dog food when writing the equivalent to DirectX quaternion squad setup for vecJS.

If you look at the MSDN documentation for D3DXQuaternionSquadSetup, it states that given 4 input quaternions (q0, q1, q2, q3), the control points A, B, C for a spherical quadrangle interpolation are calculated like this (some details are omitted, see the original for the full algorithm):

A = q1 * e[-0.25 *( Ln[Exp(q1)*q2] + Ln[Exp(q1)*q0] ) ] 
B = q2 * e[-0.25 *( Ln[Exp(q2)*q3] + Ln[Exp(q2)*q1] ) ]
C = q2

That’s where vecJS chainability really shines, the whole expression for the calculation of A can be expressed on the same line:

_q1
  .set(q1.q)
  .exp()
  .assignAdd(
    _q1.clone().mulQ(q2.q).log().q,
    _q1.clone().mulQ(q0.q).log().q
  )
  .mulScalar(-0.25)
  .exp();
a.set(q1.q).mulQ(_q1.q);

view raw q.js This Gist brought to you by GitHub.
The _q1 variable is a quaternion I have lying around in the closure to avoid calling the instance constructor every time I need a buffer in a calculation. There is another one (_q2) which is used for calculating B, so appart from the calls to clone(), the function has no instantiations or local variables.

Actually, the clones could even be avoided with more buffer variables, but since this function is not likely to be called that often, I felt they would just be polluting the memory.

Now, I haven’t tested the results of the function yet, so I guess I’ll have to check if it really works ;)

/X

Well, nothing’s been going on here in ages, I’ve just been too busy.

But lately, I thought the “math lib” in jsddd is pretty sweet, so I worked on taking it out and make it a lib of its own.

It’s really fast and really easy to use. The philosophy is that you usually know what you’re doing with your code, so you can optimize the hell out of what you do most. Obviously, it’s not the best tool when you’re experimenting, but there are already cool libs out there for that.

Here are some times from the glMatrix benchmark where I added vecJS for comparison:

Multiplication

vecJS glMatrix mjs CanvasMatrix EWGL
Avg 1.41ms 2.36ms 3.28ms 2.39ms 2.17ms
Min 1ms 2ms 2ms 2ms 1ms
Max 14ms 21ms 34ms 15ms 20ms

Translation

vecJS glMatrix mjs CanvasMatrix EWGL
Avg 0.67ms 0.99ms 4.26ms 7.74ms 1.34ms
Min 0ms 0ms 4ms 7ms 1ms
Max 7ms 6ms 6ms 9ms 8ms

Scaling

vecJS glMatrix mjs CanvasMatrix EWGL
Avg 0.6ms 0.96ms 4.18ms 8.97ms 1.31ms
Min 0ms 0ms 3ms 8ms 1ms
Max 4ms 6ms 6ms 27ms 6ms

Rotation (arbitrary axis)

vecJS glMatrix mjs CanvasMatrix EWGL
Avg 1.64ms 2.34ms 6.09ms 10.4ms 2.88ms
Min 1ms 2ms 5ms 10ms 2ms
Max 17ms 20ms 12ms 30ms 20ms

Rotation (X axis)

vecJS glMatrix mjs CanvasMatrix EWGL
Avg 0.86ms 1.18ms ms ms ms
Min 0ms 1ms ms ms ms
Max 4ms 7ms ms ms ms

Transpose

vecJS glMatrix mjs CanvasMatrix EWGL
Avg 0.47ms 0.72ms 1.4ms 0.44ms 0.64ms
Min 0ms 0ms 1ms 0ms 0ms
Max 3ms 4ms 5ms 2ms 3ms

Inverse

vecJS glMatrix mjs CanvasMatrix EWGL
Avg 1.9ms 2.45ms ms 0.04ms 6.51ms
Min 1ms 2ms ms 0ms 4ms
Max 19ms 24ms ms 1ms 57ms

Vector transformation

vecJS glMatrix mjs CanvasMatrix EWGL
Avg 0.7ms 1.29ms ms ms ms
Min 0ms 1ms ms ms ms
Max 5ms 5ms ms ms ms

If you want to check for yourself, download the code at https://github.com/xav/vecJS, build the lib (makefile included), and open the benchmark page.

Right now I’m writing tests, debugging and contemplating writing documentation, so the lib is not really ready for prime time, but it’s coming soon! :)

/X

I’ve juste finished porting Klas Kroon‘s MD2 Loader to Jsddd.

Not sure how I’ll handle the animations yet, but right now I really like this Lost Soul :)

I just fixed some bugs in the lighting functions, and added the possibility to display the light sources in the renderer (apart from the ambient light). It makes it much easier to see how the scene light is actually built.
The new demo has:

  • A slightly red low ambient light
  • A gyrating blueish point light.
  • An oscillating green directional light.

You can press “l” to display the light sources in the scene.

On a side note, I’ve also added code annotations for the closure compiler to most of the code. Hopefully, this will enable the use of “advanced compilation” when used in a project.
The side benefit is that having to comment the code and sort out which datatypes are used where really helped finding bugs or stupid things I hade to rewrite.

jsddd now supports linear, quadratic and cubic bezier curves.

In the demo, the red one is linear, the green one quadratic and the blue one is cubic.
You can press ‘k’ to toggle the control points on and off. Unfortunately, I haven’t implemented reverse mapping; so they’re a bit useless since you can’t modify them…

The z-sorting looks a bit shaky, but the curve centroid and is correct. So it might be a granularity issue since the plane has only two triangles; I’ll have to look into that at some time.

I began working in a javascript 3D engine, heavily inspired by mrdoob’s THREE. Here is a first test of dynamic lighting with 400 triangles.