Added buffer.js

This commit is contained in:
Lucas Dower 2021-07-19 15:53:33 +01:00
parent 7aa6e82f3d
commit a348ba4e23
5 changed files with 370 additions and 381 deletions

143
src/buffer.js Normal file
View File

@ -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;

View File

@ -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);
}

92
src/geometry.js Normal file
View File

@ -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;

View File

@ -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.<number>} 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;

View File

@ -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;