gl-sprite-batch
Version:
a high level quad batcher for stackgl
233 lines (190 loc) • 5.96 kB
JavaScript
var colorToFloat = require('./pack-rgba-float')
var mixes = require('mixes')
var premult = require('premultiplied-rgba')
var WhiteTex = require('gl-white-texture')
var vertNumFloats = require('./common').floatsPerVertex
//Temporary arrays to avoid GC thrashing
var position = [0, 0],
shape = [0, 0],
texcoord = [0, 0, 0, 0],
color = [0, 0, 0, 0]
var tmp4 = [0, 0, 0, 0],
rotOrigin = [0, 0],
tmp2 = [0, 0]
function SpriteBatch(gl, opt) {
if (!(this instanceof SpriteBatch))
return new SpriteBatch(gl, opt)
if (!gl)
throw new Error("must specify gl context")
this.gl = gl
opt = opt || {}
this._bound = false
this.idx = 0
//no transform means identity
this.transform = null
//white texture is akin to "no texture" (without switching shaders)
this._defaultTexture = opt.defaultTexture || WhiteTex(gl)
this._ownsDefault = !opt.defaultTexture
this._lastTexture = this._defaultTexture
this._texture = this._defaultTexture
this.texture = null
this.mode = typeof opt.mode === 'number' ? opt.mode : gl.TRIANGLES
this.premultiplied = opt.premultiplied || false
this._dirty = true
this.create(opt)
//set default attributes
this.defaults()
}
//mix in create() and ensureCapacity() functions
mixes(SpriteBatch, require('./common').mixins)
mixes(SpriteBatch, {
capacity: {
get: function() {
return this._capacity
}
},
texture: {
get: function() {
return this._texture
},
set: function(tex) {
this._texture = tex || this._defaultTexture
}
},
dispose: function() {
if (this.vertexBuffer)
this.vertexBuffer.dispose()
if (this.indexBuffer)
this.indexBuffer.dispose()
if (this.vao)
this.vao.dispose()
if (this._ownsDefault)
this._defaultTexture.dispose()
},
clear: function() {
this.idx = 0
return this
},
bind: function(shader) {
shader.bind()
this.vao.bind(shader)
this._bound = true
},
unbind: function() {
this.vao.unbind()
this._bound = false
},
defaults: function() {
this.position = copy2(position, 0, 0)
this.texcoord = copy4(texcoord, 0, 0, 1, 1)
this.color = copy4(color, 1, 1, 1, 1)
this.shape = copy2(shape, 0, 0)
return this
},
push: function(sprite) {
//if we are defining attributes on the fly
if (sprite) {
this.texture = sprite.texture
this.position = sprite.position || copy2(position, 0, 0)
this.texcoord = sprite.texcoord || copy4(texcoord, 0, 0, 1, 1)
this.color = sprite.color || copy4(color, 1, 1, 1, 1)
this.shape = sprite.shape || copy2(shape, 0, 0)
}
if (this.texture !== this._lastTexture) {
//new texture, flush previous data
if (this._bound)
this.flush()
this._lastTexture = this.texture
} else if (this.idx === this.vertices.length) {
//if we AREN'T bound, we need to stop pushing vertex data!
if (!this._bound)
return this
//if we ARE bound, we can flush the batch and continue drawing
this.flush()
}
this._dirty = true
//get RGBA components and pack into a single float
var colorRGBA = this.premultiplied ? premult(this.color, tmp4) : this.color
var c = colorToFloat(colorRGBA)
var u1 = this.texcoord[0],
v1 = this.texcoord[1],
u2 = this.texcoord[2],
v2 = this.texcoord[3]
var x = this.position[0],
y = this.position[1],
width = this.shape[0],
height = this.shape[1]
this._vert(x, y, u1, v1, c)
this._vert(x+width, y, u2, v1, c)
this._vert(x+width, y+height, u2, v2, c)
this._vert(x, y+height, u1, v2, c)
return this
},
_vert: function(x1, y1, u1, v1, c) {
var idx = this.idx,
verts = this.vertices,
transform = this.transform
if (transform) {
var x = x1, y = y1
x1 = transform[0] * x + transform[4] * y + transform[12]
y1 = transform[1] * x + transform[5] * y + transform[13]
}
//xy
verts[idx++] = x1
verts[idx++] = y1
//uv
verts[idx++] = u1
verts[idx++] = v1
//color
verts[idx++] = c
this.idx = idx
},
flush: function() {
this.draw()
return this.clear()
},
draw: function() {
//If we've reached a new texture or capacity
//while not bound, then we will just clear the batch
//to zero and draw nothing
if (this.idx === 0 || !this._bound)
return this
var gl = this.gl
if (this._dirty) {
var view = this.vertices.subarray(0, this.idx)
this.vertexBuffer.update(view, 0)
this._dirty = false
}
if (this._lastTexture)
this._lastTexture.bind()
this._lastTexture = this.texture
var sprites = (this.idx / (vertNumFloats * 4))
if (sprites > 0)
this.vao.draw(this.mode, sprites * 6, 0)
return this
},
})
module.exports = SpriteBatch
//TODO: will use modular gl-matrix for these...
function copy2(out, x, y) {
out[0] = x
out[1] = y
return out
}
function copy4(out, x, y, z, w) {
out[0] = x
out[1] = y
out[2] = z
out[3] = w
return out
}
function copyVec2(out, vec) {
return copy2(out, vec[0], vec[1])
}
function transformMat4(out, a, m) {
var x = a[0],
y = a[1]
out[0] = m[0] * x + m[4] * y + m[12]
out[1] = m[1] * x + m[5] * y + m[13]
return out
}