Add anchorAtPosition to preserve position when setting anchor.
[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 anchorAtPosition: {
182 get: function () { return this.anchor; },
183 set: function (a) {
184 var position = this.position;
185 this.anchor = a;
186 this.position = position;
187 }
188 }
189 });
190
191 yuu.QuadBatch = yT({
192 constructor: function (capacity) {
193 this.vbuf = new yuu.VertexBuffer(yuu.V3T2C4_F, capacity * 4);
194 this.ibuf = new yuu.IndexBuffer(
195 this.vbuf.vertexCount, capacity * 6);
196 this._capacity = capacity;
197 this._resetAllocations();
198 },
199
200 _vbufSlotFromQuad: function (quad) {
201 if (quad._vbuf.arrays.position.buffer !== this.vbuf.buffer)
202 throw new Error("invalid quad buffer");
203 var offset = quad._vbuf.arrays.position.byteOffset;
204 var bytesPerQuad = (
205 this.vbuf.spec.attribs.position.View.BYTES_PER_ELEMENT
206 * this.vbuf.spec.attribs.position.elements
207 * 4 /* vertices per quad */);
208 return offset / bytesPerQuad;
209 },
210
211 createQuad: function () {
212 var slot = this._freeVbufSlots[this._allocated];
213 if (slot === undefined)
214 throw new Error("out of batch slots");
215 var subdata = this.vbuf.subdata(slot * 4, 4);
216 var index = this._allocated++;
217 var n = 6 * index;
218 this.ibuf.buffer[n + 0] = slot * 4 + 0;
219 this.ibuf.buffer[n + 1] = slot * 4 + 1;
220 this.ibuf.buffer[n + 2] = slot * 4 + 2;
221 this.ibuf.buffer[n + 3] = slot * 4 + 2;
222 this.ibuf.buffer[n + 4] = slot * 4 + 1;
223 this.ibuf.buffer[n + 5] = slot * 4 + 3;
224 this.ibuf.length += 6;
225 this.ibuf.dirty = true;
226 this._vbufToIndex[slot] = index;
227 return new yuu.Quad(subdata);
228 },
229
230 disposeQuad: function (quad) {
231 var slot = this._vbufSlotFromQuad(quad);
232 var index = this._vbufToIndex[slot];
233 this._allocated--;
234 if (index !== this._allocated) {
235 // Unless this was the last index, swap the last index
236 // into the new hole.
237 var n = 6 /* indices per quad */ * index;
238 var m = 6 /* indices per quad */ * this._allocated;
239 var b = this.ibuf.buffer;
240 var lastVbufSlot = b[m] / 4 /* vertices per quad */;
241 if (this._vbufToIndex[lastVbufSlot] !== this._allocated)
242 throw new Error("allocation index mismatch");
243 b[n + 0] = b[m + 0];
244 b[n + 1] = b[m + 1];
245 b[n + 2] = b[m + 2];
246 b[n + 3] = b[m + 3];
247 b[n + 4] = b[m + 4];
248 b[n + 5] = b[m + 5];
249 this.ibuf.dirty = true;
250 this._vbufToIndex[lastVbufSlot] = index;
251 }
252 this._freeVbufSlots[this._allocated] = slot;
253 this.ibuf.length -= 6;
254 },
255
256 _resetAllocations: function () {
257 this.ibuf.length = 0;
258 var Array = yuu.IndexBuffer.Array(this._capacity);
259 this._freeVbufSlots = new Array(this._capacity);
260 yf.transform(yf.counter(), this._freeVbufSlots);
261 this._allocated = 0;
262 this._vbufToIndex = new Array(this._capacity);
263 },
264
265 disposeAll: function () {
266 this._resetAllocations();
267 }
268 });
269
270 yuu.QuadC = yT(yuu.C, {
271 /** A 2D quadrilateral that tracks the entity's transform
272
273 By default, the extents of this quad are [-0.5, -0.5] to
274 [0.5, 0.5], and its model matrix is identical to the
275 entity's transform, i.e. it is centered around [0, 0] in
276 the entity's local space, or the entity's nominal location
277 in world space. This can be changed by adjusting the
278 anchor, position, and size properties.
279 */
280
281 constructor: function (material) {
282 var buffer = new yuu.VertexBuffer(yuu.V3T2C4_F, 4);
283 this._quad = new yuu.Quad(buffer);
284 material = yf.isString(material)
285 ? new yuu.Material(material)
286 : material;
287 this._rdro = new yuu.Renderable(
288 buffer, yuu.gl.TRIANGLE_STRIP, material,
289 { model: mat4.create() }, 0.0);
290 },
291
292 TAPS: ["queueRenderables"],
293
294 queueRenderables: function (rdros) {
295 mat4.copy(this._rdro.uniforms.model,
296 this.entity.transform.matrix);
297 rdros.push(this._rdro);
298 },
299
300 // TODO: yT should offer some way to specify these in two
301 // lists, i.e. the rdro aliases, and the quad aliases.
302
303 material: { alias: "_rdro.material", chainable: true },
304 z: { alias: "_rdro.z", chainable: true },
305 uniforms: { alias: "_rdro.uniforms" },
306 size: { alias: "_quad.size", chainable: true },
307 position: { alias: "_quad.position", chainable: true },
308 anchor: { alias: "_quad.anchor", chainable: true },
309 anchorAtPosition: { alias: "_quad.anchorAtPosition", chainable: true },
310 xy: { alias: "_quad.position", chainable: true },
311 texBounds: { alias: "_quad.texBounds", chainable: true },
312 color: { alias: "_quad.color", chainable: true },
313 alpha: { alias: "_quad.alpha", chainable: true },
314 luminance: { alias: "_quad.luminance", chainable: true },
315 });
316
317 yuu.QuadBatchC = yT(yuu.C, {
318 /** A 2D quadrilateral batch that tracks the entity's transform
319
320 */
321
322 constructor: function (capacity, material) {
323 this._batch = new yuu.QuadBatch(capacity);
324 this._rdro = new yuu.IndexedRenderable(
325 this._batch.vbuf, yuu.gl.TRIANGLES, material,
326 { model: mat4.create() }, 0.0, this._batch.ibuf);
327 },
328
329 TAPS: ["queueRenderables"],
330
331 queueRenderables: function (rdros) {
332 mat4.copy(this._rdro.uniforms.model,
333 this.entity.transform.matrix);
334 rdros.push(this._rdro);
335 },
336
337 material: { alias: "_rdro.material", chainable: true },
338 z: { alias: "_rdro.z", chainable: true },
339 uniforms: { alias: "_rdro.uniforms" },
340 createQuad: { proxy: "_batch.createQuad" },
341 disposeQuad: { proxy: "_batch.disposeQuad" },
342 disposeAll: { proxy: "_batch.disposeAll" },
343 });
344
345 function sortRenderables(a, b) { return a.z - b.z; }
346
347 yuu.Layer = yT({
348 /** List of renderables and per-layer uniforms
349
350 These uniforms usually include the projection and view
351 matrices, set to a [-1, 1] orthographic projection and the
352 identity view by default.
353 */
354
355 // TODO: This is a bad design. Too powerful to be efficient or
356 // a straightforward part of Scene; not enough to abstract
357 // hard things like render passes.
358
359 constructor: function () {
360 this.rdros = [];
361 this.uniforms = {
362 projection: mat4.ortho(mat4.create(), -1, 1, -1, 1, -1, 1),
363 view: mat4.create()
364 };
365 },
366
367 worldFromDevice: yf.argcd(
368 function (p) {
369 var t = this.worldFromDevice(p.x || p.pageX || p[0] || 0,
370 p.y || p.pageY || p[1] || 0);
371 t.inside = p.inside;
372 return t;
373 },
374 function (x, y) {
375 var p = { 0: x, 1: y };
376 var m = mat4.mul(mat4.create(),
377 this.uniforms.projection, this.uniforms.view);
378 m = mat4.invert(m, m);
379 vec2.transformMat4(p, p, m);
380 p.x = p[0]; p.y = p[1];
381 return p;
382 }
383 ),
384
385 worldFromCanvas: yf.argcd(
386 function (p) {
387 return this.worldFromDevice(yuu.deviceFromCanvas(p));
388 },
389 function (x, y) {
390 return this.worldFromDevice(yuu.deviceFromCanvas(x, y));
391 }
392 ),
393
394 resize: function (x, y, w, h) {
395 /** Set a 2D orthographic project with an origin and size
396
397 Arguments:
398 scene.resize(originX, originY, width, height)
399 scene.resize(width, height) // Origin at 0, 0
400 scene.resize(origin, size)
401 scene.resize(size) // Origin at 0, 0
402 */
403 if (y === undefined) {
404 w = x[0]; h = x[1]; x = y = 0;
405 } else if (w === undefined) {
406 if (x.length === undefined) {
407 w = x; h = y; x = y = 0;
408 } else {
409 w = y[0]; h = y[1];
410 y = x[1]; x = x[0];
411 }
412 }
413 mat4.ortho(this.uniforms.projection, x, x + w, y, y + h, -1, 1);
414 },
415
416 render: function () {
417 /** Render all queued renderables */
418 this.rdros.sort(sortRenderables);
419 var mat = null;
420 for (var j = 0; j < this.rdros.length; ++j) {
421 var rdro = this.rdros[j];
422 if (mat !== rdro.material) {
423 if (mat)
424 mat.disable();
425 mat = rdro.material;
426 rdro.material.enable(this.uniforms);
427 }
428 rdro.draw();
429 }
430 },
431
432 clear: function () {
433 this.rdros.length = 0;
434 }
435 });
436
437 }).call(typeof exports === "undefined" ? this : exports,
438 typeof exports === "undefined"
439 ? this.yuu : (module.exports = require('./core')));