'use strict';
const VertexPackage = require('./VertexPackage');
const VertexBuffer = require('./VertexBuffer');
const IndexBuffer = require('./IndexBuffer');
/**
* Iterates over all vertex buffers and throws an exception if the counts are
* not equal.
*
* @private
*
* @param {Array} vertexBuffers - The array of vertexBuffers.
*/
function checkVertexBufferCounts(vertexBuffers) {
let count = null;
vertexBuffers.forEach(buffer => {
if (count === null) {
count = buffer.count;
} else {
if (count !== buffer.count) {
throw 'VertexBuffers must all have the same count to be ' +
'rendered without an IndexBuffer, mismatch of ' +
`${count} and ${buffer.count} found`;
}
}
});
}
/**
* Iterates over all attribute pointers and throws an exception if an index
* occurs more than once.
*
* @private
*
* @param {Array} vertexBuffers - The array of vertexBuffers.
*/
function checkIndexCollisions(vertexBuffers) {
const indices = new Map();
vertexBuffers.forEach(buffer => {
buffer.pointers.forEach((pointer, index) => {
const count = indices.get(index) || 0;
indices.set(index, count + 1);
});
});
indices.forEach(index => {
if (index > 1) {
throw `More than one attribute pointer exists for index \`${index}\``;
}
});
}
/**
* A container for one or more VertexBuffers and an optional IndexBuffer.
*/
class Renderable {
/**
* Instantiates an Renderable object.
*
* @param {Object} spec - The renderable specification object.
* @param {Array|Float32Array} spec.vertices - The vertices to interleave and buffer.
* @param {VertexBuffer} spec.vertexBuffer - An existing vertex buffer.
* @param {VertexBuffer[]} spec.vertexBuffers - Multiple existing vertex buffers.
* @param {Array|Uint16Array|Uint32Array} spec.indices - The indices to buffer.
* @param {IndexBuffer} spec.indexbuffer - An existing index buffer.
*/
constructor(spec = {}) {
if (spec.vertexBuffer || spec.vertexBuffers) {
// use existing vertex buffer
this.vertexBuffers = spec.vertexBuffers || [spec.vertexBuffer];
} else if (spec.vertices) {
// create vertex package
const vertexPackage = new VertexPackage(spec.vertices);
// create vertex buffer
this.vertexBuffers = [
new VertexBuffer(vertexPackage.buffer, vertexPackage.pointers)
];
} else {
this.vertexBuffers = [];
}
if (spec.indexBuffer) {
// use existing index buffer
this.indexBuffer = spec.indexBuffer;
} else if (spec.indices) {
// create index buffer
this.indexBuffer = new IndexBuffer(spec.indices);
} else {
this.indexBuffer = null;
}
// if there is no index buffer, check that vertex buffers all have
// the same count
if (!this.indexBuffer) {
checkVertexBufferCounts(this.vertexBuffers);
}
// check that no attribute indices clash
checkIndexCollisions(this.vertexBuffers);
}
/**
* Execute the draw command for the underlying buffers.
*
* @param {Object} options - The options to pass to 'drawElements'. Optional.
* @param {string} options.mode - The draw mode / primitive type.
* @param {string} options.byteOffset - The byteOffset into the drawn buffer.
* @param {string} options.indexOffset - The indexOffset into the drawn buffer.
* @param {string} options.count - The number of vertices to draw.
*
* @returns {Renderable} - The renderable object, for chaining.
*/
draw(options = {}) {
// draw the renderable
if (this.indexBuffer) {
// use index buffer to draw elements
// bind vertex buffers and enable attribute pointers
this.vertexBuffers.forEach(vertexBuffer => {
vertexBuffer.bind();
});
// draw primitives using index buffer
this.indexBuffer.draw(options);
// disable attribute pointers
this.vertexBuffers.forEach(vertexBuffer => {
vertexBuffer.unbind();
});
// no advantage to unbinding as there is no stack used
} else {
// no index buffer, use draw arrays
// set all attribute pointers
this.vertexBuffers.forEach(vertexBuffer => {
vertexBuffer.bind();
});
if (this.vertexBuffers.length > 0) {
// draw the buffer
this.vertexBuffers[0].draw(options);
}
// disable all attribute pointers
this.vertexBuffers.forEach(vertexBuffer => {
vertexBuffer.unbind();
});
}
return this;
}
}
module.exports = Renderable;