Tweaks to friction constants.
[featherfall2.git] / src / yuu / rdr.js
1 /* Copyright 2014 Yukkuri Games
2 Licensed under the terms of the GNU GPL v2 or later
3 @license http://www.gnu.org/licenses/gpl-2.0.html
4 @source: http://yukkurigames.com/yuu/
5 */
6
7 (function (yuu) {
8 "use strict";
9
10 var yT = this.yT || require("./yT");
11 var yf = this.yf || require("./yf");
12
13 if (!yuu.C) require("./ce");
14 if (!yuu.Material) require("./gfx");
15
16 yuu.Renderable = yT({
17 constructor: function (vbuf, primitive, material, uniforms, z) {
18 this.vbuf = vbuf;
19 this.primitive = primitive || yuu.gl.TRIANGLES;
20 this.material = material || yuu.Material.DEFAULT;
21 this.uniforms = uniforms || {};
22 this.z = z || 0.0;
23 },
24
25 bind: function () {
26 this.material.program.setUniforms(this.uniforms);
27 this.vbuf.bindBuffer();
28 this.material.program.setAttribPointers(this.vbuf);
29 },
30
31 draw: function () {
32 this.bind();
33 yuu.gl.drawArrays(this.primitive, 0, this.vbuf.vertexCount);
34 },
35
36 vertexCount: { alias: "vbuf.vertexCount" }
37 });
38
39 yuu.IndexedRenderable = yT(yuu.Renderable, {
40 constructor: function (vbuf, primitive, material, uniforms, z, ibuf) {
41 yuu.Renderable.call(this, vbuf, primitive, material, uniforms, z);
42 this.ibuf = ibuf;
43 },
44
45 bind: function () {
46 yuu.Renderable.prototype.bind.call(this);
47 this.ibuf.bindBuffer();
48 },
49
50 draw: function () {
51 this.bind();
52 yuu.gl.drawElements(
53 this.primitive, this.ibuf.length, this.ibuf.GL_TYPE, 0);
54 },
55
56 vertexCount: {
57 get: function () {
58 return this.ibuf.length;
59 },
60 set: function (vertexCount) {
61 this.vbuf.vertexCount = vertexCount;
62 this.ibuf.maxIndex = vertexCount;
63 }
64 }
65 });
66
67 yuu.Quad = yT({
68 /** A vertex view containing a 2D quadrilateral
69
70 You probably don't want to use this directly. If you want a
71 simple quad, look at QuadC.
72 */
73 constructor: function (vbuf) {
74 this._vbuf = vbuf;
75 this.anchor = "center";
76 this.position = [0.0, 0.0];
77 this.size = [1.0, 1.0];
78 this.texBounds = [0.0, 0.0, 1.0, 1.0];
79 this.color = [1.0, 1.0, 1.0, 1.0];
80 },
81
82 size: {
83 get: function () {
84 var b = this._vbuf.arrays.position;
85 return [b[6] - b[0], b[4] - b[1]];
86 },
87 set: function(size) {
88 var position = this.position;
89 var b = this._vbuf.arrays.position;
90 b[0] = b[3] = b[1] = b[7] = 0;
91 b[6] = b[9] = size[0];
92 b[4] = b[10] = size[1];
93 this.position = position;
94 this._vbuf.dirty = true;
95 }
96 },
97
98 position: {
99 get: function () {
100 var b = this._vbuf.arrays.position;
101 return yuu.anchorPoint(this.anchor, b[0], b[1], b[6], b[4]);
102 },
103 set: function (position) {
104 var size = this.size;
105 var b = this._vbuf.arrays.position;
106 var bottomLeft = yuu.bottomLeft(
107 this.anchor, position[0], position[1], size[0], size[1]);
108 b[0] = b[3] = bottomLeft[0];
109 b[1] = b[7] = bottomLeft[1];
110 b[6] = b[9] = bottomLeft[0] + size[0];
111 b[4] = b[10] = bottomLeft[1] + size[1];
112 this._vbuf.dirty = true;
113 }
114 },
115
116 x: { synthetic: "position[0]" },
117 y: { synthetic: "position[1]" },
118
119 // Texture coordinate vertices: 12 +...
120 // 2,3 6,7
121 // 0,1 4,5
122
123 texBounds: {
124 get: function() {
125 var b = this._vbuf.arrays.texCoord;
126 return [b[0], b[1], b[6], b[7]];
127 },
128 set: function (uv0uv1) {
129 var b = this._vbuf.arrays.texCoord;
130 b[0] = b[2] = uv0uv1[0];
131 b[1] = b[5] = uv0uv1[1];
132 b[6] = b[4] = uv0uv1[2];
133 b[3] = b[7] = uv0uv1[3];
134 this._vbuf.dirty = true;
135 }
136 },
137
138 // Color vertices: 20 +...
139 // 4,5,6,7 12,13,14,15
140 // 0,1,2,3 8,9,10,11
141
142 color: {
143 get: function () {
144 var b = this._vbuf.arrays.color;
145 return [b[0], b[1], b[2], b[3]];
146 },
147 set: function (rgba) {
148 var b = this._vbuf.arrays.color;
149 var a = rgba[3];
150 b[0] = b[4] = b[8] = b[12] = rgba[0];
151 b[1] = b[5] = b[9] = b[13] = rgba[1];
152 b[2] = b[6] = b[10] = b[14] = rgba[2];
153 if (a !== undefined)
154 b[3] = b[7] = b[11] = b[15] = a;
155 this._vbuf.dirty = true;
156 }
157 },
158
159 luminance: {
160 get: function () {
161 var color = this.color;
162 return 0.2126 * color[0]
163 + 0.7152 * color[1]
164 + 0.0722 * color[2];
165 },
166
167 set: function (v) {
168 this.color = [v, v, v];
169 }
170 },
171
172 alpha: {
173 get: function () { return this._vbuf.arrays.color[3]; },
174 set: function (a) {
175 var b = this._vbuf.arrays.color;
176 b[3] = b[7] = b[11] = b[15] = a;
177 this._vbuf.dirty = true;
178 }
179 }
180 });
181
182 yuu.QuadBatch = yT({
183 constructor: function (capacity) {
184 this.vbuf = new yuu.VertexBuffer(yuu.V3T2C4_F, capacity * 4);
185 this.ibuf = new yuu.IndexBuffer(
186 this.vbuf.vertexCount, capacity * 6);
187 this._capacity = capacity;
188 this._resetAllocations();
189 },
190
191 _vbufSlotFromQuad: function (quad) {
192 if (quad._vbuf.arrays.position.buffer !== this.vbuf.buffer)
193 throw new Error("invalid quad buffer");
194 var offset = quad._vbuf.arrays.position.byteOffset;
195 var bytesPerQuad = (
196 this.vbuf.spec.attribs.position.View.BYTES_PER_ELEMENT
197 * this.vbuf.spec.attribs.position.elements
198 * 4 /* vertices per quad */);
199 return offset / bytesPerQuad;
200 },
201
202 createQuad: function () {
203 var slot = this._freeVbufSlots[this._allocated];
204 if (slot === undefined)
205 throw new Error("out of batch slots");
206 var subdata = this.vbuf.subdata(slot * 4, 4);
207 var index = this._allocated++;
208 var n = 6 * index;
209 this.ibuf.buffer[n + 0] = slot * 4 + 0;
210 this.ibuf.buffer[n + 1] = slot * 4 + 1;
211 this.ibuf.buffer[n + 2] = slot * 4 + 2;
212 this.ibuf.buffer[n + 3] = slot * 4 + 2;
213 this.ibuf.buffer[n + 4] = slot * 4 + 1;
214 this.ibuf.buffer[n + 5] = slot * 4 + 3;
215 this.ibuf.length += 6;
216 this.ibuf.dirty = true;
217 this._vbufToIndex[slot] = index;
218 return new yuu.Quad(subdata);
219 },
220
221 disposeQuad: function (quad) {
222 var slot = this._vbufSlotFromQuad(quad);
223 var index = this._vbufToIndex[slot];
224 this._allocated--;
225 if (index !== this._allocated) {
226 // Unless this was the last index, swap the last index
227 // into the new hole.
228 var n = 6 /* indices per quad */ * index;
229 var m = 6 /* indices per quad */ * this._allocated;
230 var b = this.ibuf.buffer;
231 var lastVbufSlot = b[m] / 4 /* vertices per quad */;
232 if (this._vbufToIndex[lastVbufSlot] !== this._allocated)
233 throw new Error("allocation index mismatch");
234 b[n + 0] = b[m + 0];
235 b[n + 1] = b[m + 1];
236 b[n + 2] = b[m + 2];
237 b[n + 3] = b[m + 3];
238 b[n + 4] = b[m + 4];
239 b[n + 5] = b[m + 5];
240 this.ibuf.dirty = true;
241 this._vbufToIndex[lastVbufSlot] = index;
242 }
243 this._freeVbufSlots[this._allocated] = slot;
244 this.ibuf.length -= 6;
245 },
246
247 _resetAllocations: function () {
248 this.ibuf.length = 0;
249 var Array = yuu.IndexBuffer.Array(this._capacity);
250 this._freeVbufSlots = new Array(this._capacity);
251 yf.transform(yf.counter(), this._freeVbufSlots);
252 this._allocated = 0;
253 this._vbufToIndex = new Array(this._capacity);
254 },
255
256 disposeAll: function () {
257 this._resetAllocations();
258 }
259 });
260
261 yuu.QuadC = yT(yuu.C, {
262 /** A 2D quadrilateral that tracks the entity's transform
263
264 By default, the extents of this quad are [-0.5, -0.5] to
265 [0.5, 0.5], and its model matrix is identical to the
266 entity's transform, i.e. it is centered around [0, 0] in
267 the entity's local space, or the entity's nominal location
268 in world space. This can be changed by adjusting the
269 anchor, position, and size properties.
270 */
271
272 constructor: function (material) {
273 var buffer = new yuu.VertexBuffer(yuu.V3T2C4_F, 4);
274 this._quad = new yuu.Quad(buffer);
275 material = yf.isString(material)
276 ? new yuu.Material(material)
277 : material;
278 this._rdro = new yuu.Renderable(
279 buffer, yuu.gl.TRIANGLE_STRIP, material,
280 { model: mat4.create() }, 0.0);
281 },
282
283 TAPS: ["queueRenderables"],
284
285 queueRenderables: function (rdros) {
286 mat4.copy(this._rdro.uniforms.model,
287 this.entity.transform.matrix);
288 rdros.push(this._rdro);
289 },
290
291 // TODO: yT should offer some way to specify these in two
292 // lists, i.e. the rdro aliases, and the quad aliases.
293
294 material: { alias: "_rdro.material", chainable: true },
295 z: { alias: "_rdro.z", chainable: true },
296 uniforms: { alias: "_rdro.uniforms" },
297 size: { alias: "_quad.size", chainable: true },
298 position: { alias: "_quad.position", chainable: true },
299 anchor: { alias: "_quad.anchor", chainable: true },
300 xy: { alias: "_quad.position", chainable: true },
301 texBounds: { alias: "_quad.texBounds", chainable: true },
302 color: { alias: "_quad.color", chainable: true },
303 alpha: { alias: "_quad.alpha", chainable: true },
304 luminance: { alias: "_quad.luminance", chainable: true },
305 });
306
307 yuu.QuadBatchC = yT(yuu.C, {
308 /** A 2D quadrilateral batch that tracks the entity's transform
309
310 */
311
312 constructor: function (capacity, material) {
313 this._batch = new yuu.QuadBatch(capacity);
314 this._rdro = new yuu.IndexedRenderable(
315 this._batch.vbuf, yuu.gl.TRIANGLES, material,
316 { model: mat4.create() }, 0.0, this._batch.ibuf);
317 },
318
319 TAPS: ["queueRenderables"],
320
321 queueRenderables: function (rdros) {
322 mat4.copy(this._rdro.uniforms.model,
323 this.entity.transform.matrix);
324 rdros.push(this._rdro);
325 },
326
327 material: { alias: "_rdro.material", chainable: true },
328 z: { alias: "_rdro.z", chainable: true },
329 uniforms: { alias: "_rdro.uniforms" },
330 createQuad: { proxy: "_batch.createQuad" },
331 disposeQuad: { proxy: "_batch.disposeQuad" },
332 disposeAll: { proxy: "_batch.disposeAll" },
333 });
334
335 function sortRenderables(a, b) { return a.z - b.z; }
336
337 yuu.Layer = yT({
338 /** List of renderables and per-layer uniforms
339
340 These uniforms usually include the projection and view
341 matrices, set to a [-1, 1] orthographic projection and the
342 identity view by default.
343 */
344
345 // TODO: This is a bad design. Too powerful to be efficient or
346 // a straightforward part of Scene; not enough to abstract
347 // hard things like render passes.
348
349 constructor: function () {
350 this.rdros = [];
351 this.uniforms = {
352 projection: mat4.ortho(mat4.create(), -1, 1, -1, 1, -1, 1),
353 view: mat4.create()
354 };
355 },
356
357 worldFromDevice: yf.argcd(
358 function (p) {
359 var t = this.worldFromDevice(p.x || p.pageX || p[0] || 0,
360 p.y || p.pageY || p[1] || 0);
361 t.inside = p.inside;
362 return t;
363 },
364 function (x, y) {
365 var p = { 0: x, 1: y };
366 var m = mat4.mul(mat4.create(),
367 this.uniforms.projection, this.uniforms.view);
368 m = mat4.invert(m, m);
369 vec2.transformMat4(p, p, m);
370 p.x = p[0]; p.y = p[1];
371 return p;
372 }
373 ),
374
375 worldFromCanvas: yf.argcd(
376 function (p) {
377 return this.worldFromDevice(yuu.deviceFromCanvas(p));
378 },
379 function (x, y) {
380 return this.worldFromDevice(yuu.deviceFromCanvas(x, y));
381 }
382 ),
383
384 resize: function (x, y, w, h) {
385 /** Set a 2D orthographic project with an origin and size
386
387 Arguments:
388 scene.resize(originX, originY, width, height)
389 scene.resize(width, height) // Origin at 0, 0
390 scene.resize(origin, size)
391 scene.resize(size) // Origin at 0, 0
392 */
393 if (y === undefined) {
394 w = x[0]; h = x[1]; x = y = 0;
395 } else if (w === undefined) {
396 if (x.length === undefined) {
397 w = x; h = y; x = y = 0;
398 } else {
399 w = y[0]; h = y[1];
400 y = x[1]; x = x[0];
401 }
402 }
403 mat4.ortho(this.uniforms.projection, x, x + w, y, y + h, -1, 1);
404 },
405
406 render: function () {
407 /** Render all queued renderables */
408 this.rdros.sort(sortRenderables);
409 var mat = null;
410 for (var j = 0; j < this.rdros.length; ++j) {
411 var rdro = this.rdros[j];
412 if (mat !== rdro.material) {
413 if (mat)
414 mat.disable();
415 mat = rdro.material;
416 rdro.material.enable(this.uniforms);
417 }
418 rdro.draw();
419 }
420 },
421
422 clear: function () {
423 this.rdros.length = 0;
424 }
425 });
426
427 }).call(typeof exports === "undefined" ? this : exports,
428 typeof exports === "undefined"
429 ? this.yuu : (module.exports = require('./core')));