blaze-2d
Version:
A fast and simple WebGL 2 2D game engine written in TypeScript
343 lines • 12.4 kB
JavaScript
import { vec2 } from "gl-matrix";
import Rect from "../shapes/rect";
import TextureLoader from "../texture/loader";
import Renderer from "./renderer";
import Blaze from "../blaze";
import Circle from "../shapes/circle";
import Triangle from "../shapes/triangle";
/**
* Batch renders instances of shapes and entities.
*
* A texture atlas must be used with the batch renderer.
*/
export default class BatchRenderer extends Renderer {
/**
* Renders all items currently in the batch render queue and clears the queue.
*
* Should be called at the end of each frame.
*
* If there is no camera specified in {@link Renderer} then nothing will be rendered.
*/
static flush(z) {
const min = this.batchQueue.min || 0;
const max = this.batchQueue.max || Blaze.getZLevels();
if (!this.getCamera() || z < min || z > max || !this.batchQueue[z])
return;
const queue = this.batchQueue[z];
for (const type of Object.keys(queue)) {
const count = queue[type].length;
if (count <= 0)
continue;
switch (type) {
case "rect":
this.renderRects(queue[type], z);
break;
case "circle":
this.renderCircles(queue[type], z);
break;
case "triangle":
this.renderTriangles(queue[type], z);
break;
default:
break;
}
}
delete this.batchQueue[z];
}
/**
* Queue an entity to be batch rendered.
*
* @param entity The entity to render
* @param zIndex The z index of the entity
*/
static queueEntity(entity, zIndex = 0) {
const shapes = this.getRenderableShapesFromEntites([entity]);
this.queueRects(shapes.rects, zIndex);
this.queueCircles(shapes.circles, zIndex);
this.queueTriangles(shapes.triangles, zIndex);
}
/**
* Batch render an array of entities.
*
* @param entities The entities to render
* @param zIndex The z position of the rendered rectangle
*/
static queueEntities(entities, zIndex = 0) {
const shapes = this.getRenderableShapesFromEntites(entities);
this.queueRects(shapes.rects, zIndex);
this.queueCircles(shapes.circles, zIndex);
this.queueTriangles(shapes.triangles, zIndex);
}
/**
* Queue a group of rectangles to be batch rendered.
*
* @param rects The rects to queue
* @param zIndex The z index of the rectangles
*/
static queueRects(rects, zIndex = 0) {
const renderable = rects[0] instanceof Rect ? this.getRenderableRectsFromRects(rects) : rects;
this.addRenderablesToQueue(renderable, zIndex, "rect");
}
/**
* Queue a group of circle to be batch rendered.
*
* @param circles The circles to queue
* @param zIndex The z index of the circles
*/
static queueCircles(circles, zIndex = 0) {
const renderable = circles[0] instanceof Circle
? this.getRenderableCirclesFromCircles(circles)
: circles;
this.addRenderablesToQueue(renderable, zIndex, "circle");
}
/**
* Queue a group of triangle to be batch rendered.
*
* @param triangles The triangles to queue
* @param zIndex The z index of the triangles
*/
static queueTriangles(triangles, zIndex = 0) {
const renderable = triangles[0] instanceof Triangle
? this.getRenderableTrianglesfromTriangles(triangles)
: triangles;
this.addRenderablesToQueue(renderable, zIndex, "triangle");
}
static addRenderablesToQueue(renderables, zIndex, type) {
if (!this.batchQueue[zIndex])
this.batchQueue[zIndex] = {};
if (!this.batchQueue[zIndex][type])
this.batchQueue[zIndex][type] = [];
this.batchQueue[zIndex][type].push(...renderables);
if (zIndex >= (this.batchQueue.max ? this.batchQueue.max : 0))
this.batchQueue.max = zIndex;
if (zIndex <= (this.batchQueue.min ? this.batchQueue.min : 0))
this.batchQueue.min = zIndex;
}
/**
* Batch render an array of rectangles.
*
* @param rects The rects to render
* @param zIndex The z index of the rendered rectangles
*/
static renderRects(rects, zIndex = 0) {
const renderable = rects[0] instanceof Rect ? this.getRenderableRectsFromRects(rects) : rects;
const geometry = this.getGeometryFromRenderables(renderable);
this.renderGeometry(geometry, "rect", zIndex);
}
/**
* Batch render an array of circles.
*
* @param circles The circles to render
* @param zIndex The z index of the rendered circles
*/
static renderCircles(circles, zIndex = 0) {
const renderable = circles[0] instanceof Circle
? this.getRenderableCirclesFromCircles(circles)
: circles;
const geometry = this.getGeometryFromRenderables(renderable);
this.renderGeometry(geometry, "circle", zIndex);
}
/**
* Batch render an array of triangles.
*
* @param triangles The triangles to render
* @param zIndex The z index of the rendered triangles
*/
static renderTriangles(triangles, zIndex = 0) {
const renderable = triangles[0] instanceof Triangle
? this.getRenderableTrianglesfromTriangles(triangles)
: triangles;
const geometry = this.getGeometryFromRenderables(renderable);
this.renderGeometry(geometry, "triangle", zIndex);
}
/**
* Renders geometry using a shape shader.
*
* @param geometry The geometry to render
* @param type The type of shape shader to use
* @param zIndex The z position of the rendered rectangle
*/
static renderGeometry(geometry, type, zIndex = 0) {
const gl = this.getGL();
// select shader program
let programInfo;
switch (type) {
case "rect":
programInfo = this.rectProgramInfo;
break;
case "circle":
programInfo = this.circleProgramInfo;
break;
case "triangle":
programInfo = this.triangleProgramInfo;
break;
default:
return;
}
// vertex positions
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, geometry.vertices, gl.STATIC_DRAW);
gl.vertexAttribPointer(programInfo.attribLocations.vertex, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(programInfo.attribLocations.vertex);
// tex coords
gl.bindBuffer(gl.ARRAY_BUFFER, this.texBuffer);
gl.bufferData(gl.ARRAY_BUFFER, geometry.texCoords, gl.STATIC_DRAW);
gl.vertexAttribPointer(programInfo.attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(programInfo.attribLocations.texCoord);
// uv coords
gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
gl.bufferData(gl.ARRAY_BUFFER, geometry.uvs, gl.STATIC_DRAW);
gl.vertexAttribPointer(programInfo.attribLocations.uv, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(programInfo.attribLocations.uv);
// index buffer
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, geometry.indices, gl.STATIC_DRAW);
gl.useProgram(programInfo.program);
gl.uniform1f(programInfo.uniformLocations.zIndex, zIndex / Blaze.getZLevels());
// bind atlas texture
TextureLoader.loadTexture(this.atlas);
const textureUnit = TextureLoader.getUnitOfTexture(this.atlas);
TextureLoader.updateLastUsed(textureUnit.unit);
gl.uniform1i(programInfo.uniformLocations.texture, textureUnit.unit - gl.TEXTURE0);
gl.drawElements(gl[this.getMode()], geometry.indices.length, gl.UNSIGNED_SHORT, 0);
}
/**
* Generates geometry data for an array of {@link Renderable} shapes.
*/
static getGeometryFromRenderables(renderable) {
const vertices = [];
const indices = [];
const texCoords = [];
const uvs = [];
let indicesOffset = 0;
for (const r of renderable) {
const v = r.shape.getVerticesClipSpace(r.pos, this.scale, r.rot, this.getCamera());
const i = r.shape.getIndices(indicesOffset);
indicesOffset += v.length / 2;
const atlasImage = this.atlas.getTexture(r.shape.texture);
if (!atlasImage)
continue;
const uv = r.shape.getUVCoords();
const texCoord = uv.map((uv, i) => {
if (i % 2 === 0) {
// console.log(atlasImage.tl, atlasImage.br);
if (uv === 1)
return atlasImage.br[0] / this.atlas.getSize();
else
return atlasImage.tl[0] / this.atlas.getSize();
}
else {
if (uv === 1)
return atlasImage.br[1] / this.atlas.getSize();
else
return atlasImage.tl[1] / this.atlas.getSize();
}
});
vertices.push(...v);
indices.push(...i);
texCoords.push(...texCoord);
uvs.push(...uv);
}
return {
vertices: new Float32Array(vertices),
indices: new Uint16Array(indices),
texCoords: new Float32Array(texCoords),
uvs: new Float32Array(uvs),
};
}
static getRenderableRectsFromRects(rects) {
const renderable = [];
for (const r of rects) {
renderable.push({
shape: r,
pos: vec2.create(),
rot: 0,
});
}
return renderable;
}
static getRenderableCirclesFromCircles(circles) {
const renderable = [];
for (const c of circles) {
renderable.push({
shape: c,
pos: vec2.create(),
rot: 0,
});
}
return renderable;
}
static getRenderableTrianglesfromTriangles(triangles) {
const renderable = [];
for (const t of triangles) {
renderable.push({
shape: t,
pos: vec2.create(),
rot: 0,
});
}
return renderable;
}
static getRenderableShapesFromEntites(entities) {
const rects = [];
const circles = [];
const triangles = [];
for (const e of entities) {
const pieces = e.getPieces();
for (const p of pieces) {
if (p instanceof Rect) {
rects.push({
shape: p,
pos: e.getPosition(),
rot: e.getRotation(),
});
}
else if (p instanceof Circle) {
circles.push({
shape: p,
pos: e.getPosition(),
rot: e.getRotation(),
});
}
else if (p instanceof Triangle) {
triangles.push({
shape: p,
pos: e.getPosition(),
rot: e.getRotation(),
});
}
}
}
return {
rects,
circles,
triangles,
};
}
/**
* Gets the batch render queue.
*
* @returns the batch render queue
*/
static getBatchQueue() {
return this.batchQueue;
}
/**
* Gets the maximum zIndex used by the queue.
*
* @returns The max zIndex of the queue
*/
static getQueueMax() {
return this.batchQueue.max;
}
/**
* Gets the minimum zIndex used by the queue.
*
* @returns The min zIndex of the queue
*/
static getQueueMin() {
return this.batchQueue.min;
}
}
BatchRenderer.batchQueue = {};
//# sourceMappingURL=batchRenderer.js.map