From a348ba4e232a8b20e245c1025409fd054dd4aa65 Mon Sep 17 00:00:00 2001 From: Lucas Dower Date: Mon, 19 Jul 2021 15:53:33 +0100 Subject: [PATCH] Added buffer.js --- src/buffer.js | 143 +++++++++++++++ src/client.js | 14 +- src/geometry.js | 92 ++++++++++ src/math.js | 34 ++-- src/renderer.js | 468 ++++++++++++------------------------------------ 5 files changed, 370 insertions(+), 381 deletions(-) create mode 100644 src/buffer.js create mode 100644 src/geometry.js diff --git a/src/buffer.js b/src/buffer.js new file mode 100644 index 0000000..9060f75 --- /dev/null +++ b/src/buffer.js @@ -0,0 +1,143 @@ +const twgl = require('twgl.js'); + +class SegmentedBuffer { + + constructor(bufferSize, attributes) { + this._bufferSize = bufferSize; + this._completeBuffers = []; + + this._compiled = false; + this.compiledBuffers = []; + + this._attributes = {}; + for (const attr of attributes) { + this._attributes[attr.name] = { + numComponents: attr.numComponents + }; + } + + this._insertIndex = 0; + this._maxIndex = 0; + + this._getNewBuffer(); + } + + _getNewBuffer() { + this._buffer = { + indices: { + numComponents: 1, + data: new Uint16Array(this._bufferSize), + } + }; + for (const attr in this._attributes) { + this._buffer[attr] = { + numComponents: this._attributes[attr].numComponents, + data: new Float32Array(this._bufferSize * this._attributes[attr].numComponents) + }; + } + } + + _cycle() { + + this._completeBuffers.push({ + buffer: this._buffer, + numElements: this._insertIndex, + }); + this._getNewBuffer(); + this._maxIndex = 0; + this._insertIndex = 0; + } + + _willOverflow(data) { + // Check for indices Uint16 overflow + const dataMaxIndex = Math.max(...data.indices); + let willOverflow = (this._maxIndex + dataMaxIndex) > 65535; + if ((this._maxIndex + dataMaxIndex) > 65535) { + console.log("index overflow"); + } + + const sizeAdding = data.indices.length; + willOverflow |= (this._insertIndex + sizeAdding > this._bufferSize); + if (this._insertIndex + sizeAdding > this._bufferSize) { + console.log("length overflow"); + } + + if (sizeAdding > this._bufferSize) { + throw "Data length too large, add in chunks smaller than buffer size"; + } + + return willOverflow; + } + + _checkDataMatchesAttributes(data) { + if (!('indices'in data)) { + throw `Given data does not have indices data`; + } + const setsRequired = Math.max(...data.indices) + 1; + for (const attr in this._attributes) { + if (!(attr in data)) { + throw `Given data does not have ${attr} data`; + } + if (data[attr].length % this._attributes[attr].numComponents != 0) { + throw `Not enough/too much ${attr} data given`; + } + const numSets = data[attr].length / this._attributes[attr].numComponents; + if (numSets != setsRequired) { + //throw `Number of indices does not match number of ${attr} components given`; + throw `Expected ${setsRequired * this._attributes[attr].numComponents} values for ${attr}, got ${data[attr].length}`; + } + } + } + + _addDataToAttribute(attr, attr_data) { + const indexOffset = this._insertIndex * this._attributes[attr].numComponents; + for (let i = 0; i < attr_data.length; ++i) { + this._buffer[attr].data[i + indexOffset] = attr_data[i]; + } + } + + add(data) { + this._checkDataMatchesAttributes(data); + + if (this._willOverflow(data)) { + this._cycle(); + } + + if (this._compiled) { + throw "Buffer already compiled, cannot add more data"; + } + + for (let i = 0; i < data.indices.length; ++i) { + this._buffer.indices.data[i + this._insertIndex] = data.indices[i] + this._maxIndex; + } + //this._insertIndex += data.indices.length; + const dataMaxIndex = Math.max(...data.indices); + this._maxIndex += 1 + dataMaxIndex; + + for (const attr in this._attributes) { + this._addDataToAttribute(attr, data[attr]); + } + this._insertIndex += data.indices.length; + } + + compile(gl) { + if (this._compiled) { + return; + } + + this._cycle(); + + this.compiledBuffers = new Array(this._completeBuffers.length); + for (let i = 0; i < this._completeBuffers.length; ++i) { + this.compiledBuffers[i] = { + buffer: twgl.createBufferInfoFromArrays(gl, this._completeBuffers[i].buffer), + numElements: this._completeBuffers[i].numElements + }; + } + + this._compiled = true; + } + +} + +module.exports.SegmentedBuffer = SegmentedBuffer; diff --git a/src/client.js b/src/client.js index be73ee2..51feac3 100644 --- a/src/client.js +++ b/src/client.js @@ -7,9 +7,16 @@ const { Schematic } = require('./src/schematic.js'); const dialog = require('electron').remote.dialog; const voxelSize = document.querySelector("#voxelInput").value; -let renderer = new Renderer(voxelSize); -const voxelManager = new VoxelManager(voxelSize); +let renderer = new Renderer(30, new Vector3(0.1, 0.1, 0.1)); +const mesh = new Mesh('./resources/suzanne.obj'); + +renderer.setDebug(true); +renderer.registerMesh(mesh); +renderer.compile(); + + +const voxelManager = new VoxelManager(voxelSize); const canvas = document.querySelector("#c"); let loadedMesh = null; @@ -172,8 +179,7 @@ $(document).resize(function() { }); function render(time) { - renderer.begin(); - renderer.end(); + renderer.draw(); requestAnimationFrame(render); } diff --git a/src/geometry.js b/src/geometry.js new file mode 100644 index 0000000..3c1d676 --- /dev/null +++ b/src/geometry.js @@ -0,0 +1,92 @@ +const twgl = require('twgl.js'); +const { Vector3 } = require('./vector.js'); + +const default_cube = twgl.primitives.createCubeVertices(1.0); + +class GeometryTemplates { + + static getTriangleBufferData(triangle, debug) { + const a = triangle.v0; + const b = triangle.v1; + const c = triangle.v2; + const n = triangle.normal; + + if (debug) { + return { + position: [ + a.x, a.y, a.z, + b.x, b.y, b.z, + c.x, c.y, c.z, + ], + indices: [ + 0, 1, + 1, 2, + 2, 0 + ] + }; + } else { + return { + position: [ + a.x, a.y, a.z, + b.x, b.y, b.z, + c.x, c.y, c.z, + ], + normal: [ + n.x, n.y, n.z, + n.x, n.y, n.z, + n.x, n.y, n.z + ], + indices: [ + 0, 1, 2 + ] + }; + } + } + + static getBoxBufferData(centre, size, debug) { + const a = Vector3.sub(centre, Vector3.mulScalar(size, 0.5)); + const b = Vector3.add(centre, Vector3.mulScalar(size, 0.5)); + + if (debug) { + return { + position: [ + a.x, a.y, a.z, + b.x, a.y, a.z, + b.x, b.y, a.z, + a.x, b.y, a.z, + a.x, a.y, b.z, + b.x, a.y, b.z, + b.x, b.y, b.z, + a.x, b.y, b.z + ], + indices: [ + 0, 1, 1, 2, 2, 3, 3, 0, + 4, 5, 5, 6, 6, 7, 7, 4, + 0, 4, 1, 5, 2, 6, 3, 7 + ] + }; + } else { + + let cube = { + position: new Float32Array(72), + normal: new Float32Array(72), + indices: new Float32Array(72) + }; + + cube.position.set(default_cube.position); + cube.normal.set(default_cube.normal); + cube.indices.set(default_cube.indices); + + for (let i = 0; i < 72; i += 3) { + cube.position[i + 0] = (cube.position[i + 0] * size.x) + centre.x; + cube.position[i + 1] = (cube.position[i + 1] * size.y) + centre.y; + cube.position[i + 2] = (cube.position[i + 2] * size.z) + centre.z; + } + + return cube; + } + } + +} + +module.exports.GeometryTemplates = GeometryTemplates; \ No newline at end of file diff --git a/src/math.js b/src/math.js index fd0874f..8378e5b 100644 --- a/src/math.js +++ b/src/math.js @@ -1,32 +1,16 @@ // Not apart of rendering, SIMD optimisation not necessary const { Vector3 } = require('./vector.js'); -/* -function roundTo(value, base) { - return Math.round(value / base) * base; +/** + * Retrieve the array key corresponding to the largest element in the array. + * + * @param {Array.} array Input array + * @return {number} Index of array element with largest value + */ +function argMax(array) { + return array.map((x, i) => [x, i]).reduce((r, a) => (a[0] > r[0] ? a : r))[1]; } -function floorTo(value, base) { - return Math.floor(value / base) * base; -} - -function ceilTo(value, base) { - return Math.ceil(value / base) * base; -} - -function fastDotXAxis(vec) { - return vec[0]; -} - -function fastDotYAxis(vec) { - return vec[1]; -} - -function fastDotZAxis(vec) { - return vec[2]; -} -*/ - function fastCrossXAxis(vec) { return new Vector3(0.0, -vec.z, vec.y); } @@ -65,4 +49,6 @@ module.exports.xAxis = new Vector3(1.0, 0.0, 0.0); module.exports.yAxis = new Vector3(0.0, 1.0, 0.0); module.exports.zAxis = new Vector3(0.0, 0.0, 1.0); +module.exports.argMax = argMax; + //module.exports.roundVector3To = roundVector3To; \ No newline at end of file diff --git a/src/renderer.js b/src/renderer.js index b9c404b..8372028 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -4,53 +4,133 @@ const { Vector3 } = require('./vector.js'); const { ArcballCamera } = require('./camera.js'); const mouseManager = require('./mouse.js'); const shaderManager = require('./shaders.js'); +const { SegmentedBuffer } = require('./buffer.js'); +const { GeometryTemplates } = require('./geometry.js'); class Renderer { - constructor(voxelSize) { - this._gl = document.querySelector("#c").getContext("webgl"); - - this._fov = 30; - this._backgroundColour = new Vector3(0.1, 0.1, 0.1); + constructor(fov, backgroundColour) { + this._backgroundColour = backgroundColour; this._strokeColour = new Vector3(1.0, 1.0, 1.0); - - this._camera = new ArcballCamera(this._fov, this._gl.canvas.clientWidth / this._gl.canvas.clientHeight, 0.5, 100.0); + + this._gl = document.querySelector("#c").getContext("webgl"); + this._camera = new ArcballCamera(fov, this._gl.canvas.clientWidth / this._gl.canvas.clientHeight, 0.5, 100.0); this._registerEvents(); - this._debugRegister = this._getEmptyDebugRegister(); - this._register = this._getEmptyRegister(); + this._debugRegister = new SegmentedBuffer(2048, [{name: 'position', numComponents: 3}, {name: 'colour', numComponents: 3}]); + this._register = new SegmentedBuffer(2048, [{name: 'position', numComponents: 3}, {name: 'normal', numComponents: 3}]); - this._filledDebugRegisters = []; - this._filledRegisters = []; - - this._registerBuffers = []; - this._debugRegisterBuffers = []; - - this._debugMaxIndex = 0; - this._maxIndex = 0; - - this._voxelSize = voxelSize; - this._voxelSizeVector = new Vector3(voxelSize, voxelSize, voxelSize); - this._cube = twgl.primitives.createCubeVertices(1.0); - - this._registersOpen = true; + this._debug = false; + this._compiled = false; } - _getEmptyDebugRegister() { - return { - position: {numComponents: 3, data: []}, - colour: {numComponents: 3, data: []}, - indices: {numComponents: 3, data: []} - }; + + compile() { + this._debugRegister.compile(this._gl); + this._register.compile(this._gl); + this._compiled = true; } - _getEmptyRegister() { - return { - position: {numComponents: 3, data: []}, - normal: {numComponents: 3, data: []}, - indices: {numComponents: 3, data: []}, + setStroke(colour) { + this._strokeColour = colour; + } + + setDebug(debug) { + this._debug = debug; + } + + draw() { + if (!this._compiled) { + this.compile(); + return; + } + + this._setupScene(); + + this._drawDebugRegisters(); + this._drawRegisters(); + } + + + + + _drawDebugRegisters() { + const debugUniforms = { + u_worldViewProjection: this._camera.getWorldViewProjection(), }; + + for (const buffer of this._debugRegister.compiledBuffers) { + this._drawBuffer(this._gl.LINES, buffer, shaderManager.debugProgram, debugUniforms); + } + } + + _drawRegisters() { + const uniforms = { + u_lightWorldPos: this._camera.getCameraPosition(0.0, 0.0), + u_worldViewProjection: this._camera.getWorldViewProjection(), + u_worldInverseTranspose: this._camera.getWorldInverseTranspose() + }; + + for (const buffer of this._register.compiledBuffers) { + this._drawBuffer(this._gl.TRIANGLES, buffer, shaderManager.shadedProgram, uniforms); + } + } + + + + + + + registerBox(centre, size) { + const data = GeometryTemplates.getBoxBufferData(centre, size, this._debug); + this._registerData(data); + } + + registerTriangle(triangle) { + const data = GeometryTemplates.getTriangleBufferData(triangle, this._debug); + this._registerData(data); + } + + registerMesh(mesh) { + for (const triangle of mesh.triangles) { + this.registerTriangle(triangle); + } + } + + registerVoxelMesh(voxelManager) { + const mesh = voxelManager.buildMesh(); + for (const box of mesh) { + this.registerBox(box.centre, box.size, false); + } + } + + + + + _registerData(data) { + if (this._debug) { + const numVertices = data.position.length / 3; + data.colour = [].concat(...new Array(numVertices).fill(this._strokeColour.toArray())); + this._debugRegister.add(data); + } else { + this._register.add(data); + } + } + + _setupScene() { + twgl.resizeCanvasToDisplaySize(this._gl.canvas); + this._gl.viewport(0, 0, this._gl.canvas.width, this._gl.canvas.height); + this._camera.aspect = this._gl.canvas.width / this._gl.canvas.height; + //this._gl.blendFuncSeparate(this._gl.SRC_ALPHA, this._gl.ONE_MINUS_SRC_ALPHA, this._gl.ONE, this._gl.ONE_MINUS_SRC_ALPHA); + + this._gl.enable(this._gl.DEPTH_TEST); + this._gl.enable(this._gl.CULL_FACE); + //this._gl.enable(this._gl.BLEND); + this._gl.clearColor(this._backgroundColour.x, this._backgroundColour.y, this._backgroundColour.z, 1); + this._gl.clear(this._gl.COLOR_BUFFER_BIT | this._gl.DEPTH_BUFFER_BIT); + + this._camera.updateCameraPosition(); } _registerEvents() { @@ -72,331 +152,13 @@ class Renderer { }); } - begin() { - twgl.resizeCanvasToDisplaySize(this._gl.canvas); - this._gl.viewport(0, 0, this._gl.canvas.width, this._gl.canvas.height); - this._camera.aspect = this._gl.canvas.width / this._gl.canvas.height; - this._gl.blendFuncSeparate(this._gl.SRC_ALPHA, this._gl.ONE_MINUS_SRC_ALPHA, this._gl.ONE, this._gl.ONE_MINUS_SRC_ALPHA); - - this._gl.enable(this._gl.DEPTH_TEST); - this._gl.enable(this._gl.CULL_FACE); - //this._gl.enable(this._gl.BLEND); - this._gl.clearColor(this._backgroundColour.x, this._backgroundColour.y, this._backgroundColour.z, 1); - this._gl.clear(this._gl.COLOR_BUFFER_BIT | this._gl.DEPTH_BUFFER_BIT); - - this._camera.updateCameraPosition(); - } - - compileRegister() { - this._cycleRegister(); - this._cycleDebugRegister(); - - //console.log(this._debug); - - for (const register of this._filledRegisters) { - this._registerBuffers.push(twgl.createBufferInfoFromArrays(this._gl, register)); - } - for (const debugRegister of this._filledDebugRegisters) { - this._debugRegisterBuffers.push(twgl.createBufferInfoFromArrays(this._gl, debugRegister)); - } - //this._debugRegisterBuffer = twgl.createBufferInfoFromArrays(this._gl, this._debugRegister); - //this._registerBuffer = twgl.createBufferInfoFromArrays(this._gl, this._register); - this._registersOpen = false; - } - - setStroke(colour) { - this._strokeColour = colour; - } - - end() { - if (this._registersOpen) { - //console.error("Trying to draw register objects before register is closed. Call compileRegister() first."); - return; - } - - this._drawDebugRegisters(); - this._drawRegisters(); - } - - clear() { - //console.log("clearing"); - - this._debugRegister = this._getEmptyDebugRegister(); - this._register = this._getEmptyRegister(); - - this._filledDebugRegisters = []; - this._filledRegisters = []; - - this._registerBuffers = []; - this._debugRegisterBuffers = []; - - this._debugMaxIndex = 0; - this._maxIndex = 0; - - this._registersOpen = true; - } - - setVoxelSize(voxelSize) { - this._voxelSize = voxelSize; - this._voxelSizeVector = new Vector3(voxelSize, voxelSize, voxelSize); - } - - _drawDebugRegisters() { - const uniforms = { - u_worldViewProjection: this._camera.getWorldViewProjection() - }; - - for (const debugBuffer of this._debugRegisterBuffers) { - this._drawBuffer(this._gl.LINES, debugBuffer, shaderManager.debugProgram, uniforms); - } - } - - _drawRegisters() { - const uniforms = { - u_lightWorldPos: this._camera.getCameraPosition(0.0, 0.0), - //u_lightWorldPos: new Vector3(4, 2, 1).normalise().toArray(), - u_worldViewProjection: this._camera.getWorldViewProjection(), - u_worldInverseTranspose: this._camera.getWorldInverseTranspose() - }; - - for (const buffer of this._registerBuffers) { - this._drawBuffer(this._gl.TRIANGLES, buffer, shaderManager.shadedProgram, uniforms); - } - } - _drawBuffer(drawMode, buffer, shader, uniforms) { this._gl.useProgram(shader.program); - twgl.setBuffersAndAttributes(this._gl, shader, buffer); + twgl.setBuffersAndAttributes(this._gl, shader, buffer.buffer); twgl.setUniforms(shader, uniforms); this._gl.drawElements(drawMode, buffer.numElements, this._gl.UNSIGNED_SHORT, 0); } - _getBoxData(centre, size, debug) { - const a = Vector3.sub(centre, Vector3.mulScalar(size, 0.5)); - const b = Vector3.add(centre, Vector3.mulScalar(size, 0.5)); - - if (debug) { - return { - position: [ - a.x, a.y, a.z, - b.x, a.y, a.z, - b.x, b.y, a.z, - a.x, b.y, a.z, - a.x, a.y, b.z, - b.x, a.y, b.z, - b.x, b.y, b.z, - a.x, b.y, b.z - ], - indices: [ - 0, 1, 1, 2, 2, 3, 3, 0, - 4, 5, 5, 6, 6, 7, 7, 4, - 0, 4, 1, 5, 2, 6, 3, 7 - ] - }; - } else { - - let cube = { - position: new Float32Array(72), - normal: new Float32Array(72), - indices: new Float32Array(72) - }; - - cube.position.set(this._cube.position); - cube.normal.set(this._cube.normal); - cube.indices.set(this._cube.indices); - - for (let i = 0; i < 72; i += 3) { - cube.position[i + 0] = (cube.position[i + 0] * size.x) + centre.x; - cube.position[i + 1] = (cube.position[i + 1] * size.y) + centre.y; - cube.position[i + 2] = (cube.position[i + 2] * size.z) + centre.z; - } - - return cube; - } - } - - _getTriangleData(triangle, debug) { - - const a = triangle.v0; - const b = triangle.v1; - const c = triangle.v2; - const n = triangle.normal; - - if (debug) { - return { - position: [ - a.x, a.y, a.z, - b.x, b.y, b.z, - c.x, c.y, c.z, - ], - indices: [ - 0, 1, - 1, 2, - 2, 0 - ] - }; - } else { - return { - position: [ - a.x, a.y, a.z, - b.x, b.y, b.z, - c.x, c.y, c.z, - ], - normal: [ - n.x, n.y, n.z, - n.x, n.y, n.z, - n.x, n.y, n.z - ], - indices: [ - 0, 1, 2 - ] - }; - } - } - - /* - // Use when immediate drawing - drawBox(centre, size) { - const data = this._getBoxData(centre, size); - this._drawData(data); - } - - // Use when immediate drawing - drawTriangle(a, b, c) { - const data = this._getTriangleData(a, b, c); - this._drawData(data); - } - */ - - - // Use when drawing the same thing each frame - registerBox(centre, size, debug) { - const data = this._getBoxData(centre, size, debug); - //console.log(data); - this._addDataToRegister(data, debug); - } - - // Use when drawing the same triangle each frame - /* - registerTriangle(a, b, c) { - const data = this._getTriangleData(a, b, c); - this._addDataToRegister(data); - }*/ - - registerMesh(mesh) { - for (const triangle of mesh.triangles) { - this.registerTriangle(triangle, false); - } - } - - registerVoxelMesh(voxelManager, useMeshing) { - if (useMeshing) { - const mesh = voxelManager.buildMesh(); - for (const box of mesh) { - this.registerBox(box.centre, box.size, false); - } - } else { - for (const voxel of voxelManager.voxels) { - this.registerVoxel(voxel, false); - } - } - } - - registerTriangle(triangle, debug) { - const data = this._getTriangleData(triangle, debug); - this._addDataToRegister(data, debug); - } - - registerVoxel(centre, debug) { - const data = this._getBoxData(centre, this._voxelSizeVector, debug); - this._addDataToRegister(data, debug); - } - - registerVoxels(voxelCentres, debug) { - /* - for (let i = 0; i < voxelCentres.length; ++i) { - console.log(i / voxelCentres.length); - this.registerVoxel(voxelCentres[i], debug); - } - */ - for (const voxelCentre of voxelCentres) { - this.registerVoxel(voxelCentre, debug); - } - - } - - _cycleDebugRegister() { - this._filledDebugRegisters.push(this._debugRegister); - this._debugRegister = this._getEmptyDebugRegister(); - //this._debugMaxIndex = 0; - } - - _cycleRegister() { - this._filledRegisters.push(this._register); - this._register = this._getEmptyRegister(); - //this._maxIndex = 0; - //console.log("Cycling Registers"); - } - - _willDataOverflowBuffer(data) { - - } - - _addDataToRegister(data, debug) { - if (!this._registersOpen) { - console.error("Trying to register object when register is closed. Register before calling compileRegister()"); - return; - } - - if (debug) { - let newMaxIndex = this._debugMaxIndex + 1 + Math.max(...data.indices); - if (newMaxIndex >= 65535) { - this._cycleDebugRegister(); - newMaxIndex = 0; - } - - this._debugRegister.position.data.push(...data.position); - this._debugRegister.indices.data.push(...data.indices.map(x => x + this._debugMaxIndex)); - - const numVertices = data.position.length / 3; - const vertexColours = [].concat(...new Array(numVertices).fill(this._strokeColour.toArray())); - this._debugRegister.colour.data.push(...vertexColours); - - this._debugMaxIndex = newMaxIndex; - } else { - let newMaxIndex = this._maxIndex + 1 + Math.max(...data.indices); - if (newMaxIndex >= 65535) { - this._cycleRegister(); - newMaxIndex = 0; - } - - this._register.position.data.push(...data.position); - this._register.normal.data.push(...data.normal); - this._register.indices.data.push(...data.indices.map(x => x + this._maxIndex)); - - this._maxIndex = newMaxIndex; - } - - - } - - /* - _drawData(data) { - const buffer = twgl.createBufferInfoFromArrays(this._gl, data); - - const uniforms = { - u_fillColour: this._strokeColour.toArray(), - u_worldViewProjection: this._camera.getWorldViewProjection() - }; - - const shader = shaderManager.unshadedProgram; - this._gl.useProgram(shader.program); - twgl.setBuffersAndAttributes(this._gl, shader, buffer); - twgl.setUniforms(shader, uniforms); - this._gl.drawElements(this._gl.LINES, buffer.numElements, this._gl.UNSIGNED_SHORT, 0); - } - */ - } module.exports.Renderer = Renderer; \ No newline at end of file