ts-game-engine
Version:
Simple WebGL game/render engine written in TypeScript
203 lines (202 loc) • 9.54 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const Entity_1 = require("./Entity");
const Meshes_1 = require("../Meshes");
const Materials_1 = require("../Materials");
const Constants_1 = require("../Constants");
const Utils_1 = require("../Utils");
const WireBoxMesh_1 = require("../Meshes/WireBoxMesh");
const gl_matrix_1 = require("gl-matrix");
const BoundingBox_1 = require("../Math/BoundingBox");
class MeshRenderer extends Entity_1.Entity {
constructor(scene, name, culling = true) {
super(scene, name);
this.aabbMeshDirty = false;
this.aabbColor = gl_matrix_1.vec4.fromValues(0.1, 1, 0.45, 1);
this.culling = culling;
this.context = scene.Game.GraphicsSystem.Context;
this.pipelineState = scene.Game.GraphicsSystem.PipelineState;
let vao = this.context.createVertexArray();
if (vao === null)
throw new Error("Unable to create Vertex Array Object.");
this.vao = vao;
Utils_1.Utils.DebugName(this.vao, `${this.Name} VAO`);
this.aabb = new BoundingBox_1.BoundingBox();
this.Transform.OnTransformChange = () => this.UpdateAABB();
}
get Mesh() { return this.mesh; }
get Material() { return this.material; }
get IsRenderable() { return this.mesh !== undefined && this.material !== undefined; }
Dispose() {
this.context.deleteVertexArray(this.vao);
this.DisposeBoundsDebug();
}
Render() {
if (this.mesh === undefined)
return;
if (this.material === undefined)
return;
if (this.IsCulled())
return;
this.pipelineState.CurrentShader = this.material.Shader;
const globalUniforms = this.GetGlobalUniformsObject();
this.material.SetUniforms(globalUniforms);
this.pipelineState.CurrentVAO = this.vao;
if (this.mesh.IndexBuffer)
this.context.drawElements(this.mesh.MeshTopology, this.mesh.IndexCount, WebGL2RenderingContext.UNSIGNED_SHORT, 0);
else
this.context.drawArrays(this.mesh.MeshTopology, 0, this.mesh.VertexCount);
this.RenderBoundsDebug(globalUniforms);
}
SetMesh(mesh) {
if (this.mesh === mesh)
return;
if (!mesh.IsLoaded) {
console.error(`Trying to set a mesh into "${this.Name}" MeshRenderer while it still didn't finish loading.`);
return;
}
this.mesh = mesh;
this.UpdateVAO();
this.UpdateAABB();
this.CreateBoundsDebug();
}
SetMaterial(material) {
if (this.material === material)
return;
this.material = material;
this.UpdateVAO();
}
UpdateVAO() {
if (this.mesh === undefined)
return;
if (this.material === undefined)
return;
this.pipelineState.CurrentVAO = this.vao;
this.context.bindBuffer(WebGL2RenderingContext.ARRAY_BUFFER, this.mesh.VertexBuffer);
this.context.bindBuffer(WebGL2RenderingContext.ELEMENT_ARRAY_BUFFER, this.mesh.IndexBuffer !== undefined ? this.mesh.IndexBuffer : null);
const vertexFormat = this.mesh.VertexFormat;
let stride = 0;
for (let f = 0; f < Meshes_1.ATTRIBUTE_INFO.length; f++) {
if ((vertexFormat & Meshes_1.ATTRIBUTE_INFO[f].vertexFormat) !== 0)
stride += Constants_1.FLOAT_SIZE * Meshes_1.ATTRIBUTE_INFO[f].size;
}
let offset = 0;
for (let f = 0; f < Meshes_1.ATTRIBUTE_INFO.length; f++) {
if ((vertexFormat & Meshes_1.ATTRIBUTE_INFO[f].vertexFormat) !== 0) {
this.context.vertexAttribPointer(Meshes_1.ATTRIBUTE_INFO[f].location, Meshes_1.ATTRIBUTE_INFO[f].size, WebGL2RenderingContext.FLOAT, false, stride, offset);
this.context.enableVertexAttribArray(Meshes_1.ATTRIBUTE_INFO[f].location);
offset += Constants_1.FLOAT_SIZE * Meshes_1.ATTRIBUTE_INFO[f].size;
}
}
}
GetGlobalUniformsObject() {
return {
modelMatrix: this.Transform.ModelMatrix,
viewMatrix: this.Scene.Camera.ViewMatrix,
projectionMatrix: this.Scene.Camera.ProjectionMatrix,
viewDirectionProjectionInverseMatrix: this.Scene.Camera.ViewDirectionProjectionInverseMatrix,
normalMatrix: this.Transform.NormalMatrix,
viewPosition: this.Scene.Camera.Transform.Position,
ambientLight: this.Scene.AmbientLight,
pointLightsData: this.Scene.PointLightsData,
pointLightsCount: this.Scene.PointLightsCount
};
}
// Bounds and Culling -----------------------------------------------------------------------------------------------------
UpdateAABB() {
if (!this.culling)
return;
if (this.mesh === undefined)
return;
let points = this.mesh.BoundingBox.GetPoints();
this.aabb.min[0] = Number.MAX_VALUE;
this.aabb.min[1] = Number.MAX_VALUE;
this.aabb.min[2] = Number.MAX_VALUE;
this.aabb.max[0] = -Number.MAX_VALUE;
this.aabb.max[1] = -Number.MAX_VALUE;
this.aabb.max[2] = -Number.MAX_VALUE;
for (let p = 0; p < points.length; p++) {
let point = gl_matrix_1.vec3.transformMat4(points[p], points[p], this.Transform.ModelMatrix);
if (point[0] < this.aabb.min[0])
this.aabb.min[0] = point[0];
else if (point[0] > this.aabb.max[0])
this.aabb.max[0] = point[0];
if (point[1] < this.aabb.min[1])
this.aabb.min[1] = point[1];
else if (point[1] > this.aabb.max[1])
this.aabb.max[1] = point[1];
if (point[2] < this.aabb.min[2])
this.aabb.min[2] = point[2];
else if (point[2] > this.aabb.max[2])
this.aabb.max[2] = point[2];
}
this.aabbMeshDirty = true;
}
IsCulled() {
if (!this.culling)
return false;
return this.Scene.Camera.Frustum.CheckBoundsIntersection(this.aabb) === 1 /* Outside */;
}
CreateBoundsDebug() {
if (!this.culling)
return;
// Create mesh
if (this.aabbMesh !== undefined) {
this.aabbMesh.Dispose();
}
this.aabbMesh = new WireBoxMesh_1.WireBoxMesh(this.Scene, this.aabb, this.aabbColor);
// Create material
if (this.aabbMaterial === undefined) {
this.aabbMaterial = new Materials_1.VertexColoredMaterial(this.Scene);
}
// Create VAO
if (this.aabbVAO !== undefined) {
this.context.deleteVertexArray(this.aabbVAO);
}
let vao = this.context.createVertexArray();
if (vao === null)
throw new Error("Unable to create bounding box Vertex Array Object.");
this.aabbVAO = vao;
Utils_1.Utils.DebugName(this.aabbVAO, `${this.Name} bounding box VAO`);
// Configure VAO
this.pipelineState.CurrentVAO = this.aabbVAO;
this.context.bindBuffer(WebGL2RenderingContext.ARRAY_BUFFER, this.aabbMesh.VertexBuffer);
this.context.bindBuffer(WebGL2RenderingContext.ELEMENT_ARRAY_BUFFER, this.aabbMesh.IndexBuffer !== undefined ? this.aabbMesh.IndexBuffer : null);
let stride = (Constants_1.FLOAT_SIZE * Meshes_1.VERTEX_POSITION_SIZE) + (Constants_1.FLOAT_SIZE * Meshes_1.VERTEX_COLOR_SIZE);
this.context.vertexAttribPointer(Constants_1.POSITION_ATTRIBUTE_LOCATION, Meshes_1.VERTEX_POSITION_SIZE, WebGL2RenderingContext.FLOAT, false, stride, 0);
this.context.enableVertexAttribArray(Constants_1.POSITION_ATTRIBUTE_LOCATION);
this.context.vertexAttribPointer(Constants_1.COLOR_ATTRIBUTE_LOCATION, Meshes_1.VERTEX_COLOR_SIZE, WebGL2RenderingContext.FLOAT, false, stride, Constants_1.FLOAT_SIZE * Meshes_1.VERTEX_POSITION_SIZE);
this.context.enableVertexAttribArray(Constants_1.COLOR_ATTRIBUTE_LOCATION);
}
RenderBoundsDebug(globalUniforms) {
if (!this.culling)
return;
if (!this.Scene.Game.Settings.ShowBounds)
return;
if (this.aabbMesh === undefined)
return;
if (this.aabbMaterial === undefined)
return;
if (this.aabbVAO === undefined)
return;
if (this.aabbMeshDirty) {
this.aabbMesh.UpdateBounds(this.aabb, this.aabbColor);
this.aabbMeshDirty = false;
}
this.pipelineState.CurrentShader = this.aabbMaterial.Shader;
globalUniforms.modelMatrix = gl_matrix_1.mat4.create(); // Override model matrix as we don't use it for AABB
this.aabbMaterial.SetUniforms(globalUniforms);
this.pipelineState.CurrentVAO = this.aabbVAO;
if (this.aabbMesh.IndexBuffer)
this.context.drawElements(this.aabbMesh.MeshTopology, this.aabbMesh.IndexCount, WebGL2RenderingContext.UNSIGNED_SHORT, 0);
else
this.context.drawArrays(this.aabbMesh.MeshTopology, 0, this.aabbMesh.VertexCount);
}
DisposeBoundsDebug() {
if (this.aabbMesh !== undefined)
this.aabbMesh.Dispose();
if (this.aabbVAO !== undefined)
this.context.deleteVertexArray(this.aabbVAO);
}
}
exports.MeshRenderer = MeshRenderer;