import {mergeOptions} from "Util";
import {mat4} from "gl-matrix";
import cfg from "Config"
import Drawable from "Drawable";
"use strict";
export default Geometry;
/**
* @constructor
* @extends Drawable
*
* @description Base class for objects describing webGl geometry. All geometry is stored in a circular buffer.
* Buffer size is fixed on construction
*
* Handy-Dandy Glossary:
* A vertex is composed of three coordinates
* A coordinate is the component of a vertex in a single axis
* Length refers to the number of elements in an array
* Size refers to byte length of a buffer
* Index refers to the index of an element in an array
* Offset refers to byte location in a buffer
*
* Note that for 8bit buffers, index and offset, size and length, will both refer to the same location and capacity,
* but the terminology should remain consistent regardless.
*/
function Geometry(userOptions) {
/**
* @typedef Geometry.Options
*/
let options = mergeOptions({
origin:[0,0], /* Fraction of the drawable to start rendering the lines in */
size:[1,1], /* Size of the drawable */
coordBufferLength: 512,
}, userOptions);
this._coordBuffer = null;
this._coordBufferLength = options.coordBufferLength;
this._coordCache = new Float32Array(120);
this._numCoords = 0;
this._buffersDirty = true;
this._mvMatrix = mat4.create();
this._pMatrix = mat4.create();
this.numVertices = 0;
this._lastCoordIndex = 0;
this._unAllocated = true;
Drawable.call(this, options);
this.setCamera(options.origin, options.size);
}
Geometry.prototype = Object.assign( Object.create(Drawable.prototype) , {
setCamera(origin, size, near=0, far=1) {
mat4.ortho(this._pMatrix,
origin[0], /* left */
origin[0] + size[0], /* right */
origin[1], /* bottom */
origin[1] + size[1], /* top */
0, /* near */
1 /* far */
);
},
/**
* @memberof Geometry
* @instance
* @description
* Add vertices to this geometry object
* @param {Float[]} coords Packed array of floating point coordinats. (e.g. [x1,y1,z1,x2,y2,z2,...xn,yn,zn])
*/
addVertices : function (coords) {
if (this._numCoords >= (this._coordCache.length - coords.length)) {
let oldCache = this._coordCache;
let newSize = (this._coordCache.length + coords.length) * 1.125;
newSize = newSize + newSize % cfg.COORDS_PER_VERT;
this._coordCache = new Float32Array(newSize);
this._coordCache.set(oldCache);
}
this._coordCache.set(coords, this._numCoords);
this._numCoords += coords.length;
this._buffersDirty = true;
},
initGl : function (gl) {
if (this._unAllocated) {
this._coordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this._coordBuffer);
let initialSize = Math.max(this._numCoords, this._coordBufferLength);
this._coordBufferLength = initialSize;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(initialSize), gl.STATIC_DRAW);
this._unAllocated = false;
}
if (this._numCoords) {
gl.bindBuffer(gl.ARRAY_BUFFER, this._coordBuffer);
let remainingLength = this._coordBufferLength - this._lastCoordIndex;
if (remainingLength < this._numCoords) {
/* new verts would overflow buffer */
if (remainingLength) {
gl.bufferSubData(gl.ARRAY_BUFFER, this._lastCoordIndex * Float32Array.BYTES_PER_ELEMENT, this._coordCache.subarray(0, remainingLength));
}
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._coordCache.subarray(remainingLength, this._numCoords));
this._lastCoordIndex = (this._numCoords - remainingLength);
} else {
gl.bufferSubData(gl.ARRAY_BUFFER, this._lastCoordIndex * Float32Array.BYTES_PER_ELEMENT, this._coordCache.subarray(0, this._numCoords));
this._lastCoordIndex += this._numCoords;
}
this.numVertices = Math.min(this.numVertices + this._numCoords / cfg.COORDS_PER_VERT, parseInt(this._coordBufferLength / cfg.COORDS_PER_VERT));
this._numCoords = 0;
}
},
/**
* @memberof Geometry
* @instance
* @description
* Base draw method, which should always be called by subclasses. This method will perform any updates to dirty
* geometry buffers, but does not actually perform any drawing itself.
*/
draw : function (gl) {
if (this._buffersDirty) {
this.initGl(gl);
this._buffersDirty = false;
}
},
/**
* @memberof Geometry
* @instance
* @description
* Draws the circular buffer as a line strip,
*/
drawCircular(gl) {
if (this._lastCoordIndex && (this.numVertices == parseInt(this._coordBufferLength / cfg.COORDS_PER_VERT))) {
let lastVertexIndex = this._lastCoordIndex / cfg.COORDS_PER_VERT;
let vertsToDraw = this.numVertices - lastVertexIndex;
if (vertsToDraw) {
gl.drawArrays(gl.LINE_STRIP, lastVertexIndex, vertsToDraw);
}
gl.drawArrays(gl.LINE_STRIP, 0, lastVertexIndex);
} else {
gl.drawArrays(gl.LINE_STRIP, 0, this.numVertices);
}
},
/**
* @memberof Geometry
* @instance
* @description
* Set up a vertex shader's modelView and projection matrixes, and assign the position of the vertex buffer for this
* geometry.
*
* @param {WebGl} gl Webgl context
* @param {Shader} shader Shader instance
*/
setShaderPosition : function (gl, shader) {
shader.use(gl, this._mvMatrix, this._pMatrix);
gl.bindBuffer(gl.ARRAY_BUFFER, this._coordBuffer);
gl.enableVertexAttribArray(shader.vertexPositionAttribute);
gl.vertexAttribPointer(shader.vertexPositionAttribute, cfg.COORDS_PER_VERT, gl.FLOAT, false, 0, 0);
},
})