UNPKG

blaze-2d

Version:

A fast and simple WebGL 2 2D game engine written in TypeScript

343 lines 12.4 kB
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