Skip to content

Commit 8d1b670

Browse files
committed
Merge remote-tracking branch 'origin/webgl-tilemap' into dev
2 parents b2c0b3d + 48ea3ec commit 8d1b670

9 files changed

Lines changed: 2048 additions & 42 deletions

File tree

docs/_phaser_tilemap_GL_progress.txt

Lines changed: 419 additions & 0 deletions
Large diffs are not rendered by default.

src/pixi/extras/Tilemap.js

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
2+
/**
3+
* Tilemap - constructor
4+
*
5+
* @param {Array} layer - layer data from the map, arranged in mapheight lists of mapwidth Phaser.Tile objects (2d array)
6+
*
7+
*/
8+
PIXI.Tilemap = function(texture, mapwidth, mapheight, tilewidth, tileheight, layer)
9+
{
10+
PIXI.DisplayObjectContainer.call(this);
11+
12+
/**
13+
* The texture of the Tilemap
14+
*
15+
* @property texture
16+
* @type Texture
17+
*/
18+
this.texture = texture;
19+
20+
// faster access to the tile dimensions
21+
this.tileWide = tilewidth;
22+
this.tileHigh = tileheight;
23+
this.mapWide = mapwidth;
24+
this.mapHigh = mapheight;
25+
26+
// TODO: switch here to create DisplayObjectContainer at correct size for the render mode
27+
this.width = this.mapWide * this.tileWide;
28+
this.height = this.mapHigh * this.tileHigh;
29+
30+
this.layer = layer;
31+
32+
// store the list of batch drawing instructions (for use with WebGL rendering)
33+
this.glBatch = null;
34+
35+
/**
36+
* Remember last tile drawn to avoid unnecessary set-up
37+
*
38+
* @type Integer
39+
*/
40+
this.lastTile = -1;
41+
42+
/**
43+
* Whether the Tilemap is dirty or not
44+
*
45+
* @property dirty
46+
* @type Boolean
47+
*/
48+
this.dirty = true;
49+
50+
/**
51+
* The blend mode to be applied to the tilemap. Set to PIXI.blendModes.NORMAL to remove any blend mode.
52+
*
53+
* @property blendMode
54+
* @type Number
55+
* @default PIXI.blendModes.NORMAL;
56+
*/
57+
this.blendMode = PIXI.blendModes.NORMAL;
58+
59+
/**
60+
* The size of a single data element in the batch drawing.
61+
* Each tile requires two triangles, each specified as:
62+
* float left, bottom, right, top - screen coordinates
63+
* float u, v, wide, high - source texture coordinates
64+
*
65+
* @type {Number}
66+
*/
67+
this.batchDataElement = 16;
68+
69+
// calculate total batch data size
70+
var dataSize = mapwidth * mapheight * this.batchDataElement;
71+
72+
// create buffer data for the webgl rendering of this tile
73+
this.buffer = new PIXI.Float32Array( dataSize );
74+
};
75+
76+
77+
// constructor, this class extends PIXI.DisplayObjectContainer
78+
PIXI.Tilemap.prototype = Object.create(PIXI.DisplayObjectContainer.prototype);
79+
PIXI.Tilemap.prototype.constructor = PIXI.Tilemap;
80+
81+
// unused methods overridden to prevent default behaviour
82+
PIXI.Tilemap.prototype.update = function() {};
83+
PIXI.Tilemap.prototype.postUpdate = function() {};
84+
85+
86+
// override PIXI.DisplayObjectContainer _renderWebGL
87+
PIXI.Tilemap.prototype._renderWebGL = function(renderSession)
88+
{
89+
// if the sprite is not visible or the alpha is 0 then no need to render this element
90+
if(!this.visible || this.alpha <= 0)
91+
{
92+
return;
93+
}
94+
95+
// stop current render session batch drawing
96+
renderSession.spriteBatch.stop();
97+
98+
if (!this._vertexBuffer)
99+
{
100+
this._initWebGL(renderSession);
101+
}
102+
103+
renderSession.shaderManager.setShader(renderSession.shaderManager.tilemapShader);
104+
105+
this._renderWholeTilemap(renderSession);
106+
107+
// restart batch drawing now that this Tile layer has been rendered
108+
renderSession.spriteBatch.start();
109+
};
110+
111+
112+
PIXI.Tilemap.prototype._initWebGL = function(renderSession)
113+
{
114+
var gl = renderSession.gl;
115+
116+
this._vertexBuffer = gl.createBuffer();
117+
this._indexBuffer = gl.createBuffer();
118+
this._uvBuffer = gl.createBuffer();
119+
this._colorBuffer = gl.createBuffer();
120+
121+
// create a GL buffer to transfer all the vertex position data through
122+
this.positionBuffer = gl.createBuffer();
123+
124+
// bind the buffer to the RAM resident positionBuffer
125+
gl.bindBuffer( gl.ARRAY_BUFFER, this.positionBuffer );
126+
gl.bufferData( gl.ARRAY_BUFFER, this.buffer, gl.STATIC_DRAW );
127+
};
128+
129+
130+
PIXI.Tilemap.prototype._renderBatch = function( renderSession )
131+
{
132+
if ( this.glBatch )
133+
{
134+
var gl = renderSession.gl;
135+
136+
// TODO: should probably use destination buffer dimensions (halved)
137+
var screenWide2 = this.game.width * 0.5;
138+
var screenHigh2 = this.game.height * 0.5;
139+
140+
// size of one pixel in the source texture
141+
var iTextureWide = 1.0 / this.texture.width;
142+
var iTextureHigh = 1.0 / this.texture.height;
143+
144+
// size of one tile in the source texture
145+
var srcWide = this.tileWide * iTextureWide;
146+
var srcHigh = this.tileHigh * iTextureHigh;
147+
148+
// pre-calculate inverse half-buffer dimensions
149+
var iWide = 1.0 / screenWide2;
150+
var iHigh = 1.0 / screenHigh2;
151+
152+
var wide = this.tileWide * 0.5 / screenWide2;
153+
var high = this.tileHigh * 0.5 / screenHigh2;
154+
155+
var buffer = this.buffer;
156+
var oldR, oldT, uvl, uvt;
157+
158+
// process entire glBatch into a single webGl draw buffer for a TRIANGLE_STRIP blit
159+
var c = 0;
160+
var degenerate = false;
161+
for(var i = 0, l = this.glBatch.length; i < l; i++)
162+
{
163+
// sx: this.drawCoords[coordIndex],
164+
// sy: this.drawCoords[coordIndex + 1],
165+
// sw: this.tileWidth,
166+
// sh: this.tileHeight,
167+
// dx: x,
168+
// dy: y,
169+
// dw: this.tileWidth,
170+
// dh: this.tileHeight
171+
var t = this.glBatch[i];
172+
173+
if ( !t )
174+
{
175+
// insert a degenerate triangle when null is found in the list of batch objects
176+
degenerate = true;
177+
// skip to end of loop, degenerate will be inserted when no more null objects are found
178+
continue;
179+
}
180+
181+
var x = t.dx * iWide - 1;
182+
var y = 1 - t.dy * iHigh;
183+
184+
var lft = x - wide;
185+
var bot = y + high;
186+
187+
var uvl = t.sx * iTextureWide;
188+
var uvt = t.sy * iTextureHigh;
189+
190+
// insert a degenerate triangle to separate the tiles
191+
if ( degenerate )
192+
{
193+
// add a degenerate triangle: repeat the last vertex
194+
buffer[ c ] = oldR;
195+
buffer[ c + 1 ] = oldT;
196+
// then repeat the next vertex
197+
buffer[ c + 4 ] = lft;
198+
buffer[ c + 5 ] = bot;
199+
// pad with texture coordinates (probably not needed)
200+
buffer[ c + 2 ] = buffer[ c + 6 ] = uvl;
201+
buffer[ c + 3 ] = buffer[ c + 7 ] = uvt;
202+
203+
// advance the buffer index for one single degenerate triangle
204+
c += 8;
205+
degenerate = false;
206+
}
207+
208+
// calculate the destination location of the tile in screen units (-1..1)
209+
buffer[ c ] = buffer[ c + 4 ] = lft;
210+
buffer[ c + 1 ] = buffer[ c + 9 ] = bot;
211+
buffer[ c + 8 ] = buffer[ c + 12] = oldR = x + wide;
212+
buffer[ c + 5 ] = buffer[ c + 13] = oldT = y - high;
213+
214+
// calculate the uv coordinates of the tile source image
215+
buffer[ c + 2 ] = buffer[ c + 6 ] = uvl;
216+
buffer[ c + 3 ] = buffer[ c + 11] = uvt;
217+
buffer[ c + 10] = buffer[ c + 14] = uvl + srcWide;
218+
buffer[ c + 7 ] = buffer[ c + 15] = uvt + srcHigh;
219+
220+
// advance the buffer index
221+
c += 16;
222+
}
223+
224+
// if there's anything to draw...
225+
if ( c > 0 )
226+
{
227+
var shader = renderSession.shaderManager.tilemapShader;
228+
229+
// upload the VBO
230+
gl.bufferData( gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW );
231+
232+
// prepare the shader attributes
233+
gl.vertexAttribPointer( shader.aPosition, 4, gl.FLOAT, false, 0, 0 );
234+
235+
// draw the entire VBO in one call
236+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, Math.floor(c / 4));
237+
}
238+
}
239+
};
240+
241+
242+
/**
243+
* render the entire tilemap using a fast webgl batched tile render
244+
*
245+
* @param {[type]} renderSession [description]
246+
*/
247+
PIXI.Tilemap.prototype._renderWholeTilemap = function(renderSession)
248+
{
249+
var gl = renderSession.gl;
250+
var shader = renderSession.shaderManager.tilemapShader;
251+
252+
renderSession.blendModeManager.setBlendMode(this.blendMode);
253+
254+
// set the uniforms and texture
255+
256+
// set the offset in screen units to the centre of the screen
257+
// and flip the GL y coordinate to be zero at the top
258+
gl.uniform2f( shader.uCentreOffset, 1, -1 );
259+
// alpha value for whole batch
260+
gl.uniform1f( shader.uAlpha, this.alpha );
261+
// scale factors for whole batch
262+
gl.uniform2f( shader.uScale, this.worldScale.x, this.worldScale.y );
263+
// source texture unit
264+
gl.activeTexture(gl.TEXTURE0);
265+
266+
// check if a texture is dirty..
267+
if(this.texture.baseTexture._dirty[gl.id])
268+
{
269+
renderSession.renderer.updateTexture(this.texture.baseTexture);
270+
}
271+
else
272+
{
273+
// bind the current texture
274+
gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id]);
275+
}
276+
277+
// bind the source buffer
278+
gl.bindBuffer( gl.ARRAY_BUFFER, this.positionBuffer );
279+
280+
// draw the batched tile list
281+
this._renderBatch( renderSession );
282+
};
283+
284+
285+
/**
286+
* When the texture is updated, this event will fire to update the scale and frame
287+
*
288+
* @method onTextureUpdate
289+
* @param event
290+
* @private
291+
*/
292+
293+
PIXI.Tilemap.prototype.onTextureUpdate = function()
294+
{
295+
this.updateFrame = true;
296+
};
297+
298+
/**
299+
* Returns the bounds of the map as a rectangle. The bounds calculation takes the worldTransform into account.
300+
*
301+
* @method getBounds
302+
* @param matrix {Matrix} the transformation matrix of the sprite
303+
* @return {Rectangle} the framing rectangle
304+
*/
305+
PIXI.Tilemap.prototype.getBounds = function(matrix)
306+
{
307+
var worldTransform = matrix || this.worldTransform;
308+
309+
var a = worldTransform.a;
310+
var b = worldTransform.b;
311+
var c = worldTransform.c;
312+
var d = worldTransform.d;
313+
var tx = worldTransform.tx;
314+
var ty = worldTransform.ty;
315+
316+
var maxX = -Infinity;
317+
var maxY = -Infinity;
318+
319+
var minX = Infinity;
320+
var minY = Infinity;
321+
322+
var vertices = [
323+
0, 0,
324+
this.mapWide * this.tileWide, 0,
325+
this.mapWide * this.tileWide, this.mapHigh * this.tileHigh,
326+
0, this.mapHigh * this.tileHigh
327+
];
328+
for (var i = 0, n = vertices.length; i < n; i += 2)
329+
{
330+
var rawX = vertices[i], rawY = vertices[i + 1];
331+
var x = (a * rawX) + (c * rawY) + tx;
332+
var y = (d * rawY) + (b * rawX) + ty;
333+
334+
minX = x < minX ? x : minX;
335+
minY = y < minY ? y : minY;
336+
337+
maxX = x > maxX ? x : maxX;
338+
maxY = y > maxY ? y : maxY;
339+
}
340+
341+
if (minX === -Infinity || maxY === Infinity)
342+
{
343+
return PIXI.EmptyRectangle;
344+
}
345+
346+
var bounds = this._bounds;
347+
348+
bounds.x = minX;
349+
bounds.width = maxX - minX;
350+
351+
bounds.y = minY;
352+
bounds.height = maxY - minY;
353+
354+
// store a reference so that if this function gets called again in the render cycle we do not have to recalculate
355+
this._currentBounds = bounds;
356+
357+
return bounds;
358+
};
359+

0 commit comments

Comments
 (0)