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 +18 -0
- data/Rakefile +2 -2
- data/lib/assets/javascripts/jax/{application.js.erb → application.js} +0 -4
- data/lib/assets/javascripts/jax/core.js +1 -1
- data/lib/assets/javascripts/jax/core/base.js.erb +4 -0
- data/lib/assets/javascripts/jax/core/glMatrix_ext/vec3.js +22 -0
- data/lib/assets/javascripts/jax/core/matrix_stack.js +1 -1
- data/lib/assets/javascripts/jax/mvc/model.js +18 -0
- data/lib/assets/javascripts/jax/vendor/{glMatrix.js → gl-matrix.js} +1 -1
- data/lib/assets/javascripts/jax/webgl.js +1 -1
- data/lib/assets/javascripts/jax/webgl/camera.js +24 -39
- data/lib/assets/javascripts/jax/webgl/scene/light_source.js +58 -29
- data/lib/jax/version.rb +1 -1
- data/spec/javascripts/jax/core/utils_spec.js +9 -0
- data/spec/javascripts/jax/webgl/camera_spec.js +25 -0
- data/spec/javascripts/jax/webgl/lighting_spec.js +17 -0
- data/spec/javascripts/vendor/gl_matrix_spec.js +90 -0
- data/vendor/assets/javascripts/gl-matrix-pdoc.js +705 -0
- data/vendor/assets/javascripts/gl-matrix.js +1924 -0
- metadata +48 -45
- data/vendor/assets/javascripts/glMatrix.js +0 -1772
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/
|
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];
|
@@ -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
|
@@ -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
|
*
|
@@ -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/
|
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
|
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
|
-
|
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
|
517
|
-
options.near
|
518
|
-
options.far
|
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
|
-
|
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,
|
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
|
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 =
|
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
|
-
|
618
|
-
|
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.
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
155
|
-
|
164
|
+
if (this.isShadowCaster())
|
165
|
+
try {
|
156
166
|
this.updateShadowMap(context, this.boundingRadius, objects, options);
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
-
|
170
|
-
|
171
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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);
|
data/lib/jax/version.rb
CHANGED
@@ -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);
|