a03cbec56482b6274583b09a581ec5082e425591
[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 this._rdro = new yuu.Renderable(
276 buffer, yuu.gl.TRIANGLE_STRIP, material,
277 { model: mat4.create() }, 0.0);
278 },
279
280 TAPS: ["queueRenderables"],
281
282 queueRenderables: function (rdros) {
283 mat4.copy(this._rdro.uniforms.model,
284 this.entity.transform.matrix);
285 rdros.push(this._rdro);
286 },
287
288 // TODO: yT should offer some way to specify these in two
289 // lists, i.e. the rdro aliases, and the quad aliases.
290
291 material: { alias: "_rdro.material", chainable: true },
292 z: { alias: "_rdro.z", chainable: true },
293 uniforms: { alias: "_rdro.uniforms" },
294 size: { alias: "_quad.size", chainable: true },
295 position: { alias: "_quad.position", chainable: true },
296 anchor: { alias: "_quad.anchor", chainable: true },
297 xy: { alias: "_quad.position", chainable: true },
298 texBounds: { alias: "_quad.texBounds", chainable: true },
299 color: { alias: "_quad.color", chainable: true },
300 alpha: { alias: "_quad.alpha", chainable: true },
301 luminance: { alias: "_quad.luminance", chainable: true },
302 });
303
304 yuu.QuadBatchC = yT(yuu.C, {
305 /** A 2D quadrilateral batch that tracks the entity's transform
306
307 */
308
309 constructor: function (capacity, material) {
310 this._batch = new yuu.QuadBatch(capacity);
311 this._rdro = new yuu.IndexedRenderable(
312 this._batch.vbuf, yuu.gl.TRIANGLES, material,
313 { model: mat4.create() }, 0.0, this._batch.ibuf);
314 },
315
316 TAPS: ["queueRenderables"],
317
318 queueRenderables: function (rdros) {
319 mat4.copy(this._rdro.uniforms.model,
320 this.entity.transform.matrix);
321 rdros.push(this._rdro);
322 },
323
324 material: { alias: "_rdro.material", chainable: true },
325 z: { alias: "_rdro.z", chainable: true },
326 uniforms: { alias: "_rdro.uniforms" },
327 createQuad: { proxy: "_batch.createQuad" },
328 disposeQuad: { proxy: "_batch.disposeQuad" },
329 disposeAll: { proxy: "_batch.disposeAll" },
330 });
331
332 function sortRenderables(a, b) { return a.z - b.z; }
333
334 yuu.Layer = yT({
335 /** List of renderables and per-layer uniforms
336
337 These uniforms usually include the projection and view
338 matrices, set to a [-1, 1] orthographic projection and the
339 identity view by default.
340 */
341
342 // TODO: This is a bad design. Too powerful to be efficient or
343 // a straightforward part of Scene; not enough to abstract
344 // hard things like render passes.
345
346 constructor: function () {
347 this.rdros = [];
348 this.uniforms = {
349 projection: mat4.ortho(mat4.create(), -1, 1, -1, 1, -1, 1),
350 view: mat4.create()
351 };
352 },
353
354 worldFromDevice: yf.argcd(
355 function (p) {
356 var t = this.worldFromDevice(p.x || p.pageX || p[0] || 0,
357 p.y || p.pageY || p[1] || 0);
358 t.inside = p.inside;
359 return t;
360 },
361 function (x, y) {
362 var p = { 0: x, 1: y };
363 var m = mat4.mul(mat4.create(),
364 this.uniforms.projection, this.uniforms.view);
365 m = mat4.invert(m, m);
366 vec2.transformMat4(p, p, m);
367 p.x = p[0]; p.y = p[1];
368 return p;
369 }
370 ),
371
372 worldFromCanvas: yf.argcd(
373 function (p) {
374 return this.worldFromDevice(yuu.deviceFromCanvas(p));
375 },
376 function (x, y) {
377 return this.worldFromDevice(yuu.deviceFromCanvas(x, y));
378 }
379 ),
380
381 resize: function (x, y, w, h) {
382 /** Set a 2D orthographic project with an origin and size
383
384 Arguments:
385 scene.resize(originX, originY, width, height)
386 scene.resize(width, height) // Origin at 0, 0
387 scene.resize(origin, size)
388 scene.resize(size) // Origin at 0, 0
389 */
390 if (y === undefined) {
391 w = x[0]; h = x[1]; x = y = 0;
392 } else if (w === undefined) {
393 if (x.length === undefined) {
394 w = x; h = y; x = y = 0;
395 } else {
396 w = y[0]; h = y[1];
397 y = x[1]; x = x[0];
398 }
399 }
400 mat4.ortho(this.uniforms.projection, x, x + w, y, y + h, -1, 1);
401 },
402
403 render: function () {
404 /** Render all queued renderables */
405 this.rdros.sort(sortRenderables);
406 var mat = null;
407 for (var j = 0; j < this.rdros.length; ++j) {
408 var rdro = this.rdros[j];
409 if (mat !== rdro.material) {
410 if (mat)
411 mat.disable();
412 mat = rdro.material;
413 rdro.material.enable(this.uniforms);
414 }
415 rdro.draw();
416 }
417 },
418
419 clear: function () {
420 this.rdros.length = 0;
421 }
422 });
423
424 }).call(typeof exports === "undefined" ? this : exports,
425 typeof exports === "undefined"
426 ? this.yuu : (module.exports = require('./core')));