jax 2.0.4 → 2.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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);
|