jax 2.0.4 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG CHANGED
@@ -1,3 +1,21 @@
1
+ * 2.0.5 *
2
+
3
+ * Setting `shadowcaster` to `false` for a light source now has the expected result: it
4
+ disables all shadow mapping for that light, resulting in a significant performance boost.
5
+
6
+ * Updated gl-matrix to the latest version (v1.1), and updated Jax to take advantage of
7
+ it, resulting in a moderate performance boost.
8
+
9
+ * Light sources no longer generate shadow maps for, nor cast shadows upon, objects that
10
+ are beyond their range. This is based on the light's intensity (its ambient,
11
+ specular and diffuse color components), and its attenuation coefficients.
12
+
13
+ * Fixed `Jax.Camera#unproject` -- it had some stupid errors that kept it from working
14
+ at all. This method had also somehow crept into Jax without any tests; it is now
15
+ appropriately tested, of course.
16
+
17
+
18
+
1
19
  * 2.0.4 *
2
20
 
3
21
  * Jax was not differentiating between development mode and production mode, resulting
data/Rakefile CHANGED
@@ -164,9 +164,9 @@ namespace :doc do
164
164
  @hide_links_to_api_docs = true
165
165
  header = ERB.new(File.read(File.expand_path("guides/partials/_top_nav.html.erb", File.dirname(__FILE__)))).result(binding)
166
166
  PDoc.run({
167
- :source_files => (['lib/assets/javascripts/jax.js', 'vendor/assets/javascripts/glMatrix.js'] +
167
+ :source_files => (['lib/assets/javascripts/jax.js', 'vendor/assets/javascripts/gl-matrix-pdoc.js'] +
168
168
  # Dir['vendor/ejs/src/**/*.js'] +
169
- Dir['lib/assets/javascripts/jax/**/*.js']),
169
+ Dir['lib/assets/javascripts/jax/**/*.{js,js.erb}']),
170
170
  :destination => "doc",
171
171
  # :index_page => 'src/README.markdown',
172
172
  :syntax_highlighter => 'coderay',
@@ -8,10 +8,6 @@
8
8
  //= require_everything_matching "shaders"
9
9
  //= require_everything_matching "resources"
10
10
 
11
- <% if Rails.env == "production" %>
12
- Jax.environment = Jax.PRODUCTION;
13
- <% end %>
14
-
15
11
  for (var shaderName in Jax._shader_data) {
16
12
  if (!(Jax.shaders[shaderName] instanceof Jax.Shader)) {
17
13
  var descriptor = Jax._shader_data[shaderName];
@@ -1,5 +1,5 @@
1
1
  //= require "jax/vendor/ejs"
2
- //= require "jax/vendor/glMatrix"
2
+ //= require "jax/vendor/gl-matrix"
3
3
  //= require "jax/prototype/class"
4
4
  //= require "jax/core/helper"
5
5
  //= require "jax/core/math"
@@ -59,6 +59,10 @@ var Jax = {
59
59
  click_speed: 0.2,
60
60
  };
61
61
 
62
+ <% if Rails.env == "production" %>
63
+ Jax.environment = Jax.PRODUCTION;
64
+ <% end %>
65
+
62
66
  // note: the default_shader is used immediately after Jax.Material has been defined. So, most
63
67
  // likely the end user won't be able to customize it with the expected result. Materials created
64
68
  // *after* the default shader has been changed are fine, but materials already existing
@@ -35,6 +35,28 @@ vec3.toQuatRotation = function(first, second, dest) {
35
35
  }
36
36
  };
37
37
 
38
+
39
+ /**
40
+ * vec3.distance(first, second) -> Number
41
+ * - first (vec3): a vector
42
+ * - second (vec3): a vector
43
+ *
44
+ * Returns the scalar distance between the two vectors. This
45
+ * is equivalent to:
46
+ *
47
+ * vec3.length(vec3.subtract(first, second, tmp));
48
+ *
49
+ **/
50
+ vec3.distance = function(a, b) {
51
+ var x = a[0] - b[0];
52
+ var y = a[1] - b[1];
53
+ var z = a[2] - b[2];
54
+
55
+ var sum = x*x + y*y + z*z;
56
+ if (sum > 0) return Math.sqrt(sum);
57
+ return 0;
58
+ };
59
+
38
60
  /**
39
61
  * vec3.multiply(a, b[, dest]) -> vec3
40
62
  * - a (vec3): left operand
@@ -1,4 +1,4 @@
1
- //= require "../vendor/glMatrix"
1
+ //= require "../vendor/gl-matrix"
2
2
  //= require "../prototype/class"
3
3
 
4
4
  /**
@@ -109,6 +109,24 @@
109
109
  **/
110
110
  isShadowCaster: function() { return this.shadow_caster; },
111
111
 
112
+ /** alias of: Jax.Model#isShadowCaster
113
+ * Jax.Model#isShadowcaster() -> Boolean
114
+ *
115
+ * Returns true if this model casts shadows upon other models in the scene. Note that
116
+ * even if true, shadows will only be cast upon models which utilize +Jax.Material+s that support
117
+ * both the +Lighting+ and +ShadowMap+ effects.
118
+ *
119
+ **/
120
+ isShadowcaster: function() { return this.shadow_caster; },
121
+
122
+ /**
123
+ * Jax.Model#disableShadows() -> Boolean
124
+ *
125
+ * Disables shadows cast by this model. Returns whether or not shadows were previously enabled
126
+ * prior to this method call.
127
+ **/
128
+ disableShadows: function() { var was = this.shadow_caster; this.shadow_caster = false; return was; },
129
+
112
130
  /**
113
131
  * Jax.Model#render(context) -> undefined
114
132
  *
@@ -1,4 +1,4 @@
1
- //= require "glMatrix"
1
+ //= require "gl-matrix"
2
2
  //= require "jax/core/glMatrix_ext/mat3.js"
3
3
  //= require "jax/core/glMatrix_ext/mat4.js"
4
4
  //= require "jax/core/glMatrix_ext/quat4.js"
@@ -4,7 +4,7 @@ a new global called GL_METHODS. This will later be used for method delegation
4
4
  within Jax.Context.
5
5
  */
6
6
 
7
- //= require "jax/vendor/glMatrix"
7
+ //= require "jax/vendor/gl-matrix"
8
8
  //= require_self
9
9
  /*
10
10
  all files in the webgl/ subdirectory will have access to a temporary GL context,
@@ -47,7 +47,7 @@ Jax.Camera = (function() {
47
47
  }
48
48
 
49
49
  function tmpRotQuat(self) {
50
- return self._rotquat = self._rotquat || quat4.create();
50
+ return self._rotquat || (self._rotquat = quat4.create());
51
51
  }
52
52
 
53
53
  function storeVecBuf(self, buftype) {
@@ -94,10 +94,7 @@ Jax.Camera = (function() {
94
94
  self.stale = false;
95
95
 
96
96
  var pos = storeVecBuf(self, POSITION);
97
- quat4.toMat4(self.rotation, self.matrices.mv);
98
- mat4.translate(self.matrices.mv, vec3.negate(pos), self.matrices.mv);
99
- mat4.inverse(self.matrices.mv);
100
-
97
+ mat4.fromRotationTranslation(self.rotation, pos, self.matrices.mv);
101
98
  mat4.toInverseMat3(self.matrices.mv, self.matrices.n);
102
99
  mat3.transpose(self.matrices.n);
103
100
 
@@ -316,16 +313,6 @@ Jax.Camera = (function() {
316
313
  }
317
314
 
318
315
  return this.rotateWorld(amount, quat4.multiplyVec3(this.rotation, vec, this._tmp[0]));
319
-
320
- // var axis = storeVecBuf(this, RIGHT);
321
- // this.rotateWorld(amount, axis);
322
- //
323
- // var rotquat = quat4.fromAngleAxis(amount, vec, tmpRotQuat(this));
324
- // quat4.normalize(rotquat);
325
- // quat4.multiply(rotquat, this.rotation, this.rotation);
326
- //
327
- // this.fireEvent('updated');
328
- // return this;
329
316
  },
330
317
 
331
318
  /**
@@ -513,9 +500,9 @@ Jax.Camera = (function() {
513
500
  options = options || {};
514
501
  if (!options.width) throw new Error("Expected a screen width in Jax.Camera#perspective");
515
502
  if (!options.height)throw new Error("Expected a screen height in Jax.Camera#perspective");
516
- options.fov = options.fov || 45;
517
- options.near = options.near || 0.01;
518
- options.far = options.far || 200;
503
+ options.fov || (options.fov = 45);
504
+ options.near || (options.near = 0.01);
505
+ options.far || (options.far = 200);
519
506
 
520
507
  var aspect_ratio = options.width / options.height;
521
508
  mat4.perspective(options.fov, aspect_ratio, options.near, options.far, this.matrices.p);
@@ -587,35 +574,33 @@ Jax.Camera = (function() {
587
574
  // winz is either 0 (near plane), 1 (far plane) or somewhere in between.
588
575
  // if it's not given a value we'll produce coords for both.
589
576
  if (typeof(winz) == "number") {
590
- winx = parseFloat(winx);
591
- winy = parseFloat(winy);
592
- winz = parseFloat(winz);
593
-
594
- var inf = [];
577
+ var inf = vec4.create();
595
578
  var mm = this.getTransformationMatrix(), pm = this.matrices.p;
596
- var viewport = [0, 0, pm.width, pm.height];
579
+ var viewport = [0, 0, this.projection.width, this.projection.height];
597
580
 
598
581
  //Calculation for inverting a matrix, compute projection x modelview; then compute the inverse
599
- var m = mat4.set(mm, mat4.create());
600
-
601
- mat4.inverse(m, m); // WHY do I have to do this? --see Jax.Context#reloadMatrices
582
+ var m;
583
+ mat4.inverse(mm, m = mat4.create()); // WHY do I have to invert first?
602
584
  mat4.multiply(pm, m, m);
603
- mat4.inverse(m, m);
604
-
585
+ if (!mat4.inverse(m, m)) return null;
586
+
605
587
  // Transformation of normalized coordinates between -1 and 1
606
- inf[0]=(winx-viewport[0])/viewport[2]*2.0-1.0;
607
- inf[1]=(winy-viewport[1])/viewport[3]*2.0-1.0;
608
- inf[2]=2.0*winz-1.0;
609
- inf[3]=1.0;
610
-
588
+ inf[0] = (winx-viewport[0])/viewport[2]*2.0-1.0;
589
+ inf[1] = (winy-viewport[1])/viewport[3]*2.0-1.0;
590
+ inf[2] = 2.0*winz-1.0;
591
+ inf[3] = 1.0;
592
+
611
593
  //Objects coordinates
612
- var out = vec3.create();
594
+ var out = vec4.create();
613
595
  mat4.multiplyVec4(m, inf, out);
614
- if(out[3]==0.0)
615
- return null;
596
+ if (out[3] == 0.0) return null;
616
597
 
617
- out[3]=1.0/out[3];
618
- return [out[0]*out[3], out[1]*out[3], out[2]*out[3]];
598
+ var result = vec3.create();
599
+ out[3] = 1.0/out[3];
600
+ result[0] = out[0] * out[3];
601
+ result[1] = out[1] * out[3];
602
+ result[2] = out[2] * out[3];
603
+ return result;
619
604
  }
620
605
  else
621
606
  return [this.unproject(winx, winy, 0), this.unproject(winx, winy, 1)];
@@ -43,6 +43,10 @@ Jax.Scene.LightSource = (function() {
43
43
  spot_exponent: 0,
44
44
  shadowcaster: true
45
45
  });
46
+ // Jax.Model uses `shadow_caster`, but previous versions of LightSource used `shadowcaster`.
47
+ // TODO deprecate both `shadow_caster` and `shadowcaster` in favor of something like boolean `shadows`.
48
+ data.shadow_caster = data.shadowcaster;
49
+ delete data.shadowcaster;
46
50
 
47
51
  if (typeof(data.type) == "string") data.type = Jax[data.type] || data.type;
48
52
  data.color.ambient = Jax.Util.colorize(data.color.ambient);
@@ -56,12 +60,12 @@ Jax.Scene.LightSource = (function() {
56
60
  this.spotExponent = this.spot_exponent;
57
61
  delete this.spot_exponent;
58
62
 
59
- this.shadowMatrix = mat4.create();
60
-
61
- this.framebuffers = [new Jax.Framebuffer({width:1024,height:1024,depth:true,color:GL_RGBA}),
62
- new Jax.Framebuffer({width:1024,height:1024,depth:true,color:GL_RGBA})];
63
-
64
- setupProjection(this);
63
+ if (this.isShadowCaster()) {
64
+ this.shadowMatrix = mat4.create();
65
+ this.framebuffers = [new Jax.Framebuffer({width:1024,height:1024,depth:true,color:GL_RGBA}),
66
+ new Jax.Framebuffer({width:1024,height:1024,depth:true,color:GL_RGBA})];
67
+ setupProjection(this);
68
+ }
65
69
  },
66
70
 
67
71
  format: function() {
@@ -136,8 +140,6 @@ Jax.Scene.LightSource = (function() {
136
140
  /* FIXME remove the shadowmap event listener from object's camera matrix */
137
141
  },
138
142
 
139
- isShadowcaster: function() { return this.shadowcaster; },
140
-
141
143
  getDPShadowNear: function() { setupProjection(this); return this.camera.projection.near; },
142
144
 
143
145
  getDPShadowFar: function() { setupProjection(this); return this.camera.projection.far; },
@@ -146,35 +148,68 @@ Jax.Scene.LightSource = (function() {
146
148
 
147
149
  render: function(context, objects, options) {
148
150
  if (!this.valid) {
151
+ // calculate maximum distance, holding attenuation at 0.001, so we can
152
+ // skip shadowmapping of objects too far away
153
+ // Dmax = R(sqrt(Li/Ic) - 1)
154
+ // where R is light radius (always 1 in Jax)
155
+ // and Li is light intensity
156
+ // and Ic is lowest acceptable attenuation
157
+ this.max_distance = Math.sqrt(this.getIntensity() / 0.001) - 1;
158
+
149
159
  var real_pass = context.current_pass;
150
160
  /* shadowgen pass */
151
161
  context.current_pass = Jax.Scene.SHADOWMAP_PASS;
152
162
  // Look for errors while rendering the shadow map; if any occur, log them, but then
153
163
  // disable shadowmap generation so Jax can continue without shadow support.
154
- try {
155
- if (this.shadowcaster)
164
+ if (this.isShadowCaster())
165
+ try {
156
166
  this.updateShadowMap(context, this.boundingRadius, objects, options);
157
- this.valid = true;
158
- } catch(e) {
159
- if (Jax.getGlobal().console && Jax.getGlobal().console.log)
160
- Jax.getGlobal().console.log(e.toString());
161
- this.shadowcaster = false;
162
- }
167
+ } catch(e) {
168
+ if (Jax.getGlobal().console && Jax.getGlobal().console.log)
169
+ Jax.getGlobal().console.log(e.toString());
170
+ this.disableShadows();
171
+ }
172
+ this.valid = true;
163
173
  context.current_pass = real_pass;
164
174
  }
165
175
 
176
+ this.renderWithinRange(context, objects, options);
177
+ },
178
+
179
+ // Renders all objects within range of this light source, based on its color intensity
180
+ // and attenuation coefficients.
181
+ //
182
+ // Pass force=true in options to render all objects even if #isLit returns false. Even
183
+ // if force=true, the object will be skipped if it is outside of the light's range.
184
+ //
185
+ renderWithinRange: function(context, objects, options) {
166
186
  for (var j = 0; j < objects.length; j++) {
167
187
  options.model_index = j;
168
188
 
169
- /* TODO optimization: see if objects[j] is even affected by this light (based on attenuation) */
170
- if (objects[j].isLit()) // it could be unlit but still in array if it casts a shadow
171
- objects[j].render(context, options);
189
+ if (options.force || objects[j].isLit()) {
190
+ var dist = vec3.distance(this.camera.getPosition(), objects[j].camera.getPosition())
191
+ - objects[j].getBoundingSphereRadius();
192
+
193
+ if (dist < this.max_distance)
194
+ // it could be unlit but still in array if it casts a shadow
195
+ objects[j].render(context, options);
196
+ }
172
197
  }
173
198
  },
174
199
 
200
+ // returns a single float value representing the sum of the average intensities of each color channel
201
+ // of ambient, diffuse and specular components.
202
+ getIntensity: function() {
203
+ var c = this.color;
204
+ return (c.ambient[0] * c.ambient[3] + c.ambient[1] * c.ambient[3] + c.ambient[2] * c.ambient[3] +
205
+ c.diffuse[0] * c.diffuse[3] + c.diffuse[1] * c.diffuse[3] + c.diffuse[2] * c.diffuse[3] +
206
+ c.specular[0]* c.specular[3]+ c.specular[1]* c.specular[3]+ c.specular[2]* c.specular[3]) / 3.0;
207
+ },
208
+
175
209
  updateShadowMap: function(context, sceneBoundingRadius, objects, render_options) {
176
210
  // we can't afford to taint the original options
177
- render_options = Jax.Util.normalizeOptions(render_options, {});
211
+ render_options = Jax.Util.normalizeOptions(render_options, {force: true});
212
+ if (this.max_distance < sceneBoundingRadius) this.max_distance = sceneBoundingRadius;
178
213
 
179
214
  setupProjection(this);
180
215
 
@@ -217,9 +252,7 @@ Jax.Scene.LightSource = (function() {
217
252
  context.loadViewMatrix(self.camera.getTransformationMatrix());
218
253
  mat4.set(context.getInverseViewMatrix(), sm);
219
254
 
220
- for (var i = 0; i < objects.length; i++) {
221
- objects[i].render(context, render_options);
222
- }
255
+ self.renderWithinRange(context, objects, render_options);
223
256
  });
224
257
 
225
258
  render_options.direction = -1;
@@ -229,9 +262,7 @@ Jax.Scene.LightSource = (function() {
229
262
  self.framebuffers[1].viewport(context);
230
263
  context.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
231
264
  context.loadViewMatrix(self.camera.getTransformationMatrix());
232
- for (var i = 0; i < objects.length; i++) {
233
- objects[i].render(context, render_options);
234
- }
265
+ self.renderWithinRange(context, objects, render_options);
235
266
  });
236
267
 
237
268
  context.glDisable(GL_POLYGON_OFFSET_FILL);
@@ -264,9 +295,7 @@ Jax.Scene.LightSource = (function() {
264
295
  context.glDisable(GL_BLEND);
265
296
  context.glEnable(GL_POLYGON_OFFSET_FILL);
266
297
  context.glPolygonOffset(2.0, 2.0);
267
- for (var i = 0; i < objects.length; i++) {
268
- objects[i].render(context, render_options);
269
- }
298
+ self.renderWithinRange(context, objects, render_options);
270
299
  context.glDisable(GL_POLYGON_OFFSET_FILL);
271
300
  context.glEnable(GL_BLEND);
272
301
  context.glCullFace(GL_BACK);
@@ -2,7 +2,7 @@ module Jax
2
2
  module Version
3
3
  MAJOR = 2
4
4
  MINOR = 0
5
- PATCH = 4
5
+ PATCH = 5
6
6
  BUILD = nil
7
7
  STRING = BUILD ? [MAJOR, MINOR, PATCH, BUILD].join(".") : [MAJOR, MINOR, PATCH].join(".")
8
8
  end
@@ -177,6 +177,15 @@ describe("Jax.Util", function() {
177
177
 
178
178
  describe("normalizeOptions", function() {
179
179
  var normalized;
180
+
181
+ describe("with a false value that defaults to true", function() {
182
+ beforeEach(function() { normalized = Jax.Util.normalizeOptions({enabled:false}, {enabled:true}); });
183
+
184
+ it("should not have a truthy value", function() {
185
+ expect(normalized.enabled).toBeFalsy();
186
+ });
187
+ });
188
+
180
189
  describe("with a missing default array", function() {
181
190
  var arr = [1,2,3];
182
191
  beforeEach(function() { normalized = Jax.Util.normalizeOptions(null, {def:arr}); });
@@ -3,6 +3,31 @@ describe("Camera", function() {
3
3
 
4
4
  beforeEach(function() { camera = new Jax.Camera(); });
5
5
 
6
+ it("should unproject properly", function() {
7
+ camera.perspective({width:800,height:500,near:0.1,far:200});
8
+
9
+ camera.setPosition([38.375, 75, 44.25]);
10
+ // camera.setUpVector([0, 0.196116, -0.980580]);
11
+ // camera.setRightVector([1,0,0]);
12
+ camera.setViewVector([0, -0.980580, -0.196116]);
13
+
14
+ // sanity checks
15
+ expect(camera.getPosition()).toEqualVector([38.375, 75, 44.25]);
16
+ expect(camera.getUpVector()).toEqualVector([0, 0.196116, -0.980580]);
17
+ expect(camera.getRightVector()).toEqualVector([1,0,0]);
18
+ expect(camera.getViewVector()).toEqualVector([0, -0.980580, -0.196116]);
19
+
20
+
21
+ var nearest = [ 38.308727, 74.893821, 44.271007 ];
22
+ var farthest = [-94.170333, -137.355712, 86.261283];
23
+
24
+ // all that for this:
25
+ expect(camera.unproject(0, 0, 0)).toEqualVector(nearest);
26
+ expect(camera.unproject(0, 0, 1)).toEqualVector(farthest);
27
+ expect(camera.unproject(0, 0)[0]).toEqualVector(nearest);
28
+ expect(camera.unproject(0, 0)[1]).toEqualVector(farthest);
29
+ });
30
+
6
31
  it("movement with rotations", function() {
7
32
  camera.move(10);
8
33
  camera.yaw(Math.PI/2);