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