ObjToSchematic/src/render_buffer.ts
2022-09-12 01:07:42 +01:00

230 lines
7.9 KiB
TypeScript

import * as twgl from 'twgl.js';
import { Renderer } from './renderer';
import { ASSERT } from './util/error_util';
export interface Attribute {
name: string,
numComponents: number
}
interface BottomlessBufferData {
indices: BottomlessAttributeData,
[name: string]: BottomlessAttributeData
}
interface BottomlessAttributeData {
numComponents: number,
data: Array<number>
}
export interface AttributeData {
indices: Uint32Array
custom: {
[name: string]: Array<number>
}
}
export function MergeAttributeData(...data: AttributeData[]): AttributeData {
if (data.length === 0) {
return {
indices: new Uint32Array(),
custom: {},
};
}
// Check custom attributes match
const requiredAttributes = Object.keys(data[0].custom);
for (let i = 1; i < data.length; ++i) {
const customAttributes = Object.keys(data[i].custom);
const isAllRequiredInCustom = requiredAttributes.every((attr) => {
return customAttributes.includes(attr);
});
const isAllCustomInRequired = customAttributes.every((attr) => {
return requiredAttributes.includes(attr);
});
ASSERT(isAllRequiredInCustom && isAllCustomInRequired, 'Attributes to merge do not match');
}
// Merge data
const indices = Array.from(data[0].indices);
const custom = data[0].custom;
for (let i = 1; i < data.length; ++i) {
const nextIndex = Math.max(...indices) + 1;
const d = data[i];
const newIndices = d.indices.map((index) => index + nextIndex);
indices.push(...Array.from(newIndices));
for (const attr of requiredAttributes) {
const attrData = d.custom[attr];
custom[attr].push(...attrData);
}
}
return {
indices: new Uint32Array(indices),
custom: custom,
};
}
export class RenderBuffer {
private _WebGLBuffer?: {
buffer: twgl.BufferInfo,
numElements: number
};
private _buffer!: BottomlessBufferData;
private _attributes: {[name: string]: Attribute};
private _maxIndex: number;
private _compiled: boolean;
private _needsCompiling: boolean;
public constructor(attributes: Array<Attribute>) {
this._attributes = {};
for (const attr of attributes) {
this._attributes[attr.name] = {
name: attr.name,
numComponents: attr.numComponents,
};
}
this._needsCompiling = false;
this._compiled = false;
this._maxIndex = 0;
this._getNewBuffer();
}
public add(data: AttributeData) {
const mappedIndicesToAdd = new Array<number>(data.indices.length);
let maxMapped = -1;
data.indices.forEach((index, i) => {
const newIndex = index + this._maxIndex;
maxMapped = Math.max(maxMapped, newIndex);
mappedIndicesToAdd[i] = newIndex;
});
this._buffer.indices.data.push(...mappedIndicesToAdd);
this._maxIndex = 1 + maxMapped;
for (const attr in this._attributes) {
this._buffer[attr].data.push(...data.custom[attr]);
}
this._needsCompiling = true;
}
public static from(other: RenderBuffer): RenderBuffer {
const buffer = new RenderBuffer([]);
buffer._buffer = other._buffer;
buffer._attributes = other._attributes;
buffer._maxIndex = other._maxIndex;
buffer._compiled = other._compiled;
buffer._needsCompiling = other._needsCompiling;
return buffer;
}
public attachNewAttribute(attribute: Attribute, data: Array<number>) {
ASSERT(this._buffer[attribute.name] === undefined, 'Attribute already exists in buffer');
ASSERT(this._attributes[attribute.name] === undefined, 'Attribute already exists in attributes');
const expectedDataLength = this._maxIndex * attribute.numComponents;
ASSERT(data.length === expectedDataLength, `Data length expected to be ${expectedDataLength}, got ${data.length}`);
this._buffer[attribute.name] = {
numComponents: attribute.numComponents,
data: data,
};
this._attributes[attribute.name] = attribute;
this._needsCompiling = true;
}
public removeAttribute(attributeName: string) {
delete this._buffer[attributeName];
delete this._attributes[attributeName];
this._needsCompiling = true;
}
public getWebGLBuffer() {
this._compile();
ASSERT(this._WebGLBuffer !== undefined);
return this._WebGLBuffer;
}
private _compile() {
if (this._compiled && !this._needsCompiling) {
return;
}
const newBuffer: { indices: { data: Uint32Array, numComponents: number }, [arr: string]: { data: (Float32Array | Uint32Array), numComponents: number }} = {
indices: { data: Uint32Array.from(this._buffer.indices.data), numComponents: this._buffer.indices.numComponents },
};
for (const key in this._buffer) {
if (key !== 'indices') {
newBuffer[key] = {
data: Float32Array.from(this._buffer[key].data),
numComponents: this._buffer[key].numComponents,
};
}
}
this._WebGLBuffer = {
buffer: twgl.createBufferInfoFromArrays(Renderer.Get._gl, newBuffer),
numElements: this._buffer.indices.data.length,
};
this._compiled = true;
this._needsCompiling = false;
}
private _getNewBuffer() {
this._buffer = {
indices: {numComponents: 1, data: []},
};
for (const attr in this._attributes) {
this._buffer[attr] = {
numComponents: this._attributes[attr].numComponents,
data: [],
};
}
}
private _checkDataMatchesAttributes(data: AttributeData) {
if (!('indices' in data)) {
throw Error('Given data does not have indices data');
}
const setsRequired = data.indices.reduce((a, v) => Math.max(a, v)) + 1;
for (const attr in this._attributes) {
if (!(attr in data)) {
throw Error(`Given data does not have ${attr} data`);
}
if (data.custom[attr].length % this._attributes[attr].numComponents != 0) {
throw Error(`Not enough/too much ${attr} data given`);
}
const numSets = data.custom[attr].length / this._attributes[attr].numComponents;
if (numSets != setsRequired) {
// throw `Number of indices does not match number of ${attr} components given`;
throw Error(`Expected ${setsRequired * this._attributes[attr].numComponents} values for ${attr}, got ${data.custom[attr].length}`);
}
}
}
public copy(): RenderBuffer {
const copiedBuffer = new RenderBuffer([]);
copiedBuffer._buffer = {
indices: {
numComponents: this._buffer.indices.numComponents,
data: Array.from(this._buffer.indices.data),
},
};
for (const key in this._buffer) {
if (key !== 'indices') {
copiedBuffer._buffer[key] = {
numComponents: this._buffer[key].numComponents,
data: Array.from(this._buffer[key].data),
};
}
}
copiedBuffer._attributes = JSON.parse(JSON.stringify(this._attributes));
copiedBuffer._maxIndex = this._maxIndex;
copiedBuffer._compiled = false;
copiedBuffer._needsCompiling = true;
return copiedBuffer;
}
}