View on GitHub

glm-js

code 3D math in JavaScript using OpenGL Mathematics (GLM) semantics

glm-js working draft

glm-js is an experimental JavaScript implementation of the OpenGL Mathematics (GLM) C++ Library.

Introduction

glm-js is imagined with generative qualities in mind — accessible, easy to learn, easy to master, provides good leverage and adaptable.

Rather than re-inventing the wheel, its lowest-level math functionality is delegated to existing libraries — which makes room to focus on higher-level abstractions like GLM and GLSL.

A limited (but growing) subset of GLM features are currently supported. Several "backend" math vendors have been integrated simultaneously — which makes glm-js one of the most consistent and verifiable ways to access math functionality from JavaScript to date.

Jump Start

To use the latest 'kitchen sink' build of glm-js (everything needed @ ~97k minified):

From the Browser:
<script src='https://git.io/glm-js.min.js'></script>
<script>
  console.log('loaded glm-js version: ', glm.version);
  console.log('vec3 example: ', glm.vec3(1,2,3));
</script>

(note: glm-js is also accessible from the current page you're viewing — just open browser's debug console and see glm global)

From Node.js:
$ npm install glm-js
var glm = require('glm-js');

console.log('glm-js version: ', glm.version);
console.log('glm.vec3 example: ', glm.vec3(1,2,3));

Performance

Comparing performance across different JavaScript math libraries can easily lead to both false positives and false negatives, since testing artificial scenarios tends to differ wildly from later practical applications.

But glm-js offers a unique way to conduct such experiments — instead of hand-crafting test cases three times (across three different backends), you could write your scenarios once and then compare and contrast automatically across multiple, different glm-js backends.

For example, all of the testing (☑) links above go to the same page and differ only in terms of location hash, which just-in-time selects a backend to run the glm-js unit tests live against in your browser.

"GLMenetics"

A significant inspiration for glm-js is the original GLM C++ project, which effectively encourages mindset and code re-use by adopting the GLSL specification with purpose — or in the author's words:

GLM provides classes and functions designed and implemented with the same naming conventions and functionalities than GLSL so that when a programmer knows GLSL, he knows GLM as well which makes it really easy to use.

Similarly, glm-js aims to provide interfaces designed and implemented with the same naming conventions and functionalities as GLM — extending the reach of GLMenetics to JavaScript:

Library Language PU Link
GLSL C (like) GPU OpenGL Shading Language
GLM C++ CPU OpenGL Mathematics
glm-js JavaScript JSPU* glm-js

* JavaScript Processing Unit

By using consistent conventions, math code be crafted in a more portable way across space, time, platform and environment.

Examples

To explore the latest glm-js at the shell prompt / using node:

$ git clone https://github.com/humbletim/glm-js.git
$ cd glm-js
$ node # or maybe: rlwrap -a node
> glm = require("./build/glm-js.min");

You can also open a browser debug console while on this web page — glm-js has been loaded for you, and can be accessed via browser global glm .

Depending on browser you might need to append an .inspect() or .toString() for pretty-printed results — eg: glm.vec2(window.innerWidth,window.innerHeight).inspect() .

And here are some relevant things to try typing :

> glm.vec4(3,2,1,0)
{
  "x": 3,
  "y": 2,
  "z": 1,
  "w": 0
}

> v = glm.vec4(1), v.xyz = [.1,.2,.3], v.toString()
'fvec4(0.100000, 0.200000, 0.300000, 1.000000)'

> v['*='](5) // or v.mul_eq(5)
{
  "x": 0.5,
  "y": 1,
  "z": 1.5,
  "w": 5
}

> q = glm.angleAxis(glm.radians(45.0), glm.vec3(0,1,0));
{
  "w": 0.9238795042037964,
  "x": 0,
  "y": 0.3826834261417389,
  "z": 0
}

> glm.degrees(glm.eulerAngles(q))
{
  "x": 0,
  "y": 44.999996185302734,
  "z": 0
}

> v['*'](q) // or v.mul(q)
{
  "x": 1.4142135381698608,
  "y": 1,
  "z": 0.7071067690849304,
  "w": 5
}

> glm.perspective(glm.radians(45.0), 4.0 / 3.0, 0.1, 100.0).toString()
mat4x4(
    (1.810660, 0.000000, 0.000000, 0.000000), 
    (0.000000, 2.414214, 0.000000, 0.000000), 
    (0.000000, 0.000000, -1.002002, -1.000000), 
    (0.000000, 0.000000, -0.200200, 0.000000)
)

> glm.perspective(glm.radians(45.0), 4.0 / 3.0, 0.1, 100.0)
{
  "0": {
    "x": 1.8106601238250732,
    "y": 0,
    "z": 0,
    "w": 0
  },
  "1": {
    "x": 0,
    "y": 2.4142136573791504,
    "z": 0,
    "w": 0
  },
  "2": {
    "x": 0,
    "y": 0,
    "z": -1.0020020008087158,
    "w": -1
  },
  "3": {
    "x": 0,
    "y": 0,
    "z": -0.20020020008087158,
    "w": 0
  }
}

 

Trans-Porting 3D Math

The following three snippets are roughly the same despite spanning different "host" languages (GLSL, C++ and JavaScript, respectively).


GLSL (typically this would run on your graphics card):

GLSL | GLM C++ | glm-js | three-js


mat4 rotationMatrix(vec3 axis, float angle); // forward declaration

mat4 mrot = rotationMatrix(vec3(0.0,1.0,0.0), radians(45.0));

mat4 m1 = mat4(1.0); 
mat4 m2 = mat4(2.0);

mat4 m3 = m1 * m2;

m3 *= mrot;
 

GLM and C++11 (typically this would run on one of your main processor cores):

GLSL | GLM C++ | glm-js | three-js

#include <glm/glm.hpp>

static auto mrot = glm::angleAxis(glm::radians(45.0f), glm::vec3(0,1,0));

auto m1 = glm::mat4(1.0f); 
auto m2 = glm::mat4(2.0f);

auto m3 = m1 * m2;

m3 *= glm::toMat4(mrot);
 

glm-js and JavaScript (typically this would run in your web browser or on node):

GLSL | GLM C++ | glm-js | three-js

var glm = require('./glm');

this.mrot = this.mrot || glm.angleAxis(glm.radians(45.0), glm.vec3(0,1,0));

var m1 = glm.mat4(1.0); 
var m2 = glm.mat4(2.0);

var m3 = m1['*'](m2);

m3['*='](glm.toMat4(this.mrot));
 

three-js and JavaScript:

GLSL | GLM C++ | glm-js | three-js

... here's the same math as above, implemented here using stock three.js:

var THREE = require('./three');

this.mrot = this.mrot ||
    new THREE.Quaternion().setFromAxisAngle(
        new THREE.Vector3(0,1,0), THREE.Math.degToRad(45.0));  

var m1 = new THREE.Matrix4();  
var m2 = new THREE.Matrix4();
// ... note: we just want a diagonal mat4(2) here, maybe there's a leaner way??
m2.scale(new THREE.Vector3(2.0,2.0,2.0)).elements[15] = 2.0;

var m3 = m1.clone().multiply(m2); 
m3.multiply(new THREE.Matrix4().makeRotationFromQuaternion(this.mrot));  

License