Ops: trying to make vector math look a little better in JS
var a = [1,1,1] ,b = [2,2,2] ,c; // by hand: c = [ a + b, a + b, a + b, ]; // library - notice how assignment reads in opposite direction of JS vec3.add(a, b, c)
This example is trivial, but when calculations become more complex, it becomes harder and harder to read what's actually going on, especially when dealing with 'plus/minus equals' vs a new vector. The assignment begins to read from right to left, instead of the typical left to right.
As an experiment, I created Ops: an attempt at making vector math read more intuitively.
var a = [1,1,1] ,b = [2,2,2] ,c; // vop stands for vector operation c = vop( a, '+', b );
Ops is meant to be used alongside a more powerful library, like glMatrix. It is not a replacement, nor is it meant to be. It's named Ops and not vop because maybe in the future it will handle more than just vector math.
- Hopefully easier to read
- Still a strong indication that these are not ordinary numbers, unlike when using operator overloads
- No custom objects. Still completely interoperable with glMatrix (and others that use arrays)
- Can alias the
vfor even shorter reference
- Sometimes slightly slower than glMatrix (most of the time faster or same: see test/speed.js)
- Strings, Captain! STRINGS!
All vops take the form:
vector1, op, vector2, where
op can be one of the following:
+ : adds the two vectors, returns the new vector
- : subtracts the two vectors, returns the new vector
+= : adds
vector1, and returns
-= : subtracts
vector1, and returns
* : returns the scalar dot product of the two vectors
x : returns a new vector containing the cross product
= : sets
vector1 values equal to the values from
Using only glMatrix, computing the projection of a particle is slightly confusing. The values resulting from the below line are actually being assigned to
this.tpos (in place).
vec3.add( this.pos, vec3.scale(this.vel, this.damping * dt), this.tpos );
Ops allows the statement to read like a typical assignment, but also returns a new array implicitly.
this.tpos = vop( this.pos, '+', vec3.scale(this.vel, this.damping * dt) )
Granted, glMatrix allows you to do something similar, but you are left with the possibly confusing empty array as the third param, which is required to force glMatrix to not perform a
this.tpos = vec3.add( this.pos, vec3.scale(this.vel, this.damping * dt),  );
// glmatrix vec3.add(this.vel, vec3.scale(f, this.invMass)); // vop vop(this.vel, '+=', vec3.scale(f, this.invMass))
Particle Position Update
// glmatrix vec3.subtract(this.tpos, this.pos, this.vel); vec3.scale(this.vel, idt); vec3.set(this.tpos, this.pos); // vop this.vel = vop(this.tpos, '-', this.pos); vec3.scale(this.vel, idt); vop(this.pos, '=', this.tpos);
node test/speed.js. Requires benchmark. Current output:
vec3#add new arrays into new x 3,563,902 ops/sec ±2.55% (80 runs sampled) vop#add new arrays into new x 9,406,491 ops/sec ±1.23% (88 runs sampled) --> Fastest is vop#add new arrays into new vec3#add existing arrays into new x 4,489,732 ops/sec ±2.45% (82 runs sampled) vop#add existing arrays into new x 10,202,485 ops/sec ±2.12% (81 runs sampled) --> Fastest is vop#add existing arrays into new vec3#add existing plusEqual x 13,439,596 ops/sec ±1.73% (86 runs sampled) vop#add existing plusEqual x 13,033,592 ops/sec ±1.63% (91 runs sampled) --> Fastest is vec3#add existing plusEqual,vop#add existing plusEqual vec3#subtract existing arrays into new x 4,662,350 ops/sec ±1.90% (84 runs sampled) vop#subtract existing arrays into new x 11,738,893 ops/sec ±1.52% (90 runs sampled) --> Fastest is vop#subtract existing arrays into new vec3#subtract existing minusEqual x 13,860,887 ops/sec ±1.58% (88 runs sampled) vop#subtract existing minusEqual x 12,581,855 ops/sec ±1.67% (89 runs sampled) --> Fastest is vec3#subtract existing minusEqual vec3#dot x 19,004,653 ops/sec ±1.60% (77 runs sampled) vop#dot x 14,499,943 ops/sec ±1.48% (89 runs sampled) --> Fastest is vec3#dot vec3#cross x 4,354,946 ops/sec ±2.08% (80 runs sampled) vop#cross x 9,147,004 ops/sec ±1.33% (89 runs sampled) --> Fastest is vop#cross vec3#set x 21,830,091 ops/sec ±1.52% (85 runs sampled) vop#set x 15,670,835 ops/sec ±1.45% (87 runs sampled) --> Fastest is vec3#set
Ops is often faster. When it's slower, it's because of the one extra function call from delgating. What you should really read out of this is that Ops is "fast enough, seriously."
This is an experiment, and one that I'm not in love with! But you have to try things to see what works, right?