var merge = require('merge');
var geom = require('pex-geom')
var Context = require('./Context');
var RenderableGeometry = require('./RenderableGeometry');
var Vec3 = geom.Vec3
var Quat = geom.Quat
var Mat4 = geom.Mat4
var BoundingBox = geom.BoundingBox;
function Mesh(geometry, material, options) {
this.gl = Context.currentContext;
this.geometry = merge(geometry, RenderableGeometry);
this.material = material;
options = options || {};
this.primitiveType = options.primitiveType;
if (this.primitiveType == null) {
this.primitiveType = this.gl.TRIANGLES;
}
if (options.lines) {
this.primitiveType = this.gl.LINES;
}
if (options.triangles) {
this.primitiveType = this.gl.TRIANGLES;
}
if (options.points) {
this.primitiveType = this.gl.POINTS;
}
this.position = Vec3.create(0, 0, 0);
this.rotation = Quat.create();
this.scale = Vec3.create(1, 1, 1);
this.projectionMatrix = Mat4.create();
this.viewMatrix = Mat4.create();
this.invViewMatrix = Mat4.create();
this.modelWorldMatrix = Mat4.create();
this.modelViewMatrix = Mat4.create();
this.rotationMatrix = Mat4.create();
this.normalMatrix = Mat4.create();
}
Mesh.extensions = {};
Mesh.prototype.draw = function(camera) {
if (this.geometry.isDirty()) {
this.geometry.compile();
}
if (camera) {
this.updateMatrices(camera);
this.updateMatricesUniforms(this.material);
}
this.material.use();
var numInstances = this.bindAttribs();
if (numInstances > 0) {
var drawElementsInstanced;
if (this.gl.drawElementsInstanced) {
drawElementsInstanced = this.gl.drawElementsInstanced.bind(this.gl);
}
if (!drawElementsInstanced) {
if (!Mesh.extensions.instancedArrays) {
Mesh.extensions.instancedArrays = this.gl.getExtension("ANGLE_instanced_arrays");
if (!Mesh.extensions.instancedArrays) {
throw 'Mesh has instanced geometry but ANGLE_instanced_arrays is not available';
}
}
drawElementsInstanced = Mesh.extensions.instancedArrays.drawElementsInstancedANGLE.bind(Mesh.extensions.instancedArrays);
}
if (this.geometry.faces && this.geometry.faces.length > 0 && this.primitiveType !== this.gl.LINES && this.primitiveType !== this.gl.POINTS) {
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.geometry.faces.buffer.handle);
drawElementsInstanced(this.primitiveType, this.geometry.faces.buffer.dataBuf.length, this.gl.UNSIGNED_SHORT, 0, numInstances);
}
else if (this.geometry.edges && this.geometry.edges.length > 0 && this.primitiveType === this.gl.LINES) {
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.geometry.edges.buffer.handle);
drawElementsInstanced(this.primitiveType, this.geometry.edges.buffer.dataBuf.length, this.gl.UNSIGNED_SHORT, 0, numInstances);
}
}
else {
if (this.geometry.faces && this.geometry.faces.length > 0 && this.primitiveType !== this.gl.LINES && this.primitiveType !== this.gl.POINTS) {
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.geometry.faces.buffer.handle);
this.gl.drawElements(this.primitiveType, this.geometry.faces.buffer.dataBuf.length, this.gl.UNSIGNED_SHORT, 0);
}
else if (this.geometry.edges && this.geometry.edges.length > 0 && this.primitiveType === this.gl.LINES) {
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.geometry.edges.buffer.handle);
this.gl.drawElements(this.primitiveType, this.geometry.edges.buffer.dataBuf.length, this.gl.UNSIGNED_SHORT, 0);
}
else if (this.geometry.vertices) {
var num = this.geometry.vertices.length;
this.gl.drawArrays(this.primitiveType, 0, num);
}
}
this.unbindAttribs();
};
Mesh.prototype.drawInstances = function(camera, instances) {
if (this.geometry.isDirty()) {
this.geometry.compile();
}
if (camera) {
this.updateMatrices(camera);
this.updateMatricesUniforms(this.material);
}
this.material.use();
this.bindAttribs();
if (this.geometry.faces && this.geometry.faces.length > 0 && !this.useEdges) {
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.geometry.faces.buffer.handle);
for (var i = 0; i < instances.length; i++) {
var instance = instances[i];
if (camera) {
this.updateMatrices(camera, instance);
this.updateMatricesUniforms(this.material);
this.updateUniforms(this.material, instance);
this.material.use();
}
this.gl.drawElements(this.primitiveType, this.geometry.faces.buffer.dataBuf.length, this.gl.UNSIGNED_SHORT, 0);
}
}
else if (this.geometry.edges && this.useEdges) {
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.geometry.edges.buffer.handle);
for (var i = 0; i < instances.length; i++) {
var instance = instances[i];
if (camera) {
this.updateMatrices(camera, instance);
this.updateMatricesUniforms(this.material);
this.updateUniforms(this.material, instance);
this.material.use();
}
this.gl.drawElements(this.primitiveType, this.geometry.edges.buffer.dataBuf.length, this.gl.UNSIGNED_SHORT, 0);
}
}
else if (this.geometry.vertices) {
var num = this.geometry.vertices.length;
for (var i = 0; i < instances.length; i++) {
var instance = instances[i];
if (camera) {
this.updateMatrices(camera, instance);
this.updateMatricesUniforms(this.material);
this.updateUniforms(this.material, instance);
this.material.use();
}
this.gl.drawArrays(this.primitiveType, 0, num);
}
}
return this.unbindAttribs();
};
Mesh.prototype.bindAttribs = function() {
var numInstances = 0;
var program = this.material.program;
for (name in this.geometry.attribs) {
var attrib = this.geometry.attribs[name];
attrib.location = this.gl.getAttribLocation(program.handle, attrib.name);
if (attrib.location >= 0) {
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, attrib.buffer.handle);
this.gl.vertexAttribPointer(attrib.location, attrib.buffer.elementSize, this.gl.FLOAT, false, 0, 0);
this.gl.enableVertexAttribArray(attrib.location);
if (attrib.instanced) {
this.vertexAttribDivisor(attrib.location, 1);
numInstances = attrib.length;
}
}
}
return numInstances;
}
Mesh.prototype.unbindAttribs = function() {
for (name in this.geometry.attribs) {
var attrib = this.geometry.attribs[name];
if (attrib.location >= 0) {
if (attrib.instanced) {
this.vertexAttribDivisor(attrib.location, 0);
}
this.gl.disableVertexAttribArray(attrib.location);
}
}
};
Mesh.prototype.vertexAttribDivisor = function(location, divisor) {
if (this.gl.vertexAttribDivisor) {
this.gl.vertexAttribDivisor(location, divisor);
}
else {
if (!Mesh.extensions.instancedArrays) {
Mesh.extensions.instancedArrays = this.gl.getExtension("ANGLE_instanced_arrays");
if (!Mesh.extensions.instancedArrays) {
throw 'Mesh has instanced geometry but ANGLE_instanced_arrays is not available';
}
}
Mesh.extensions.instancedArrays.vertexAttribDivisorANGLE(location, divisor);
}
}
Mesh.prototype.resetAttribLocations = function() {
for (name in this.geometry.attribs) {
var attrib = this.geometry.attribs[name];
attrib.location = -1;
}
};
Mesh.prototype.updateMatrices = function(camera, instance) {
var position = instance && instance.position ? instance.position : this.position;
var rotation = instance && instance.rotation ? instance.rotation : this.rotation;
var scale = instance && instance.scale ? instance.scale : this.scale;
rotation.toMat4(this.rotationMatrix);
this.modelWorldMatrix.identity().translate(position.x, position.y, position.z).mul(this.rotationMatrix).scale(scale.x, scale.y, scale.z);
if (camera) {
this.projectionMatrix.copy(camera.getProjectionMatrix());
this.viewMatrix.copy(camera.getViewMatrix());
this.invViewMatrix.copy(camera.getViewMatrix().dup().invert());
this.modelViewMatrix.copy(camera.getViewMatrix()).mul(this.modelWorldMatrix);
return this.normalMatrix.copy(this.modelViewMatrix).invert().transpose();
}
};
Mesh.prototype.updateUniforms = function(material, instance) {
for (uniformName in instance.uniforms) {
var uniformValue = instance.uniforms[uniformName];
material.uniforms[uniformName] = uniformValue;
}
};
Mesh.prototype.updateMatricesUniforms = function(material) {
var materialUniforms, programUniforms;
programUniforms = this.material.program.uniforms;
materialUniforms = this.material.uniforms;
if (programUniforms.projectionMatrix) {
materialUniforms.projectionMatrix = this.projectionMatrix;
}
if (programUniforms.viewMatrix) {
materialUniforms.viewMatrix = this.viewMatrix;
}
if (programUniforms.invViewMatrix) {
materialUniforms.invViewMatrix = this.invViewMatrix;
}
if (programUniforms.modelWorldMatrix) {
materialUniforms.modelWorldMatrix = this.modelWorldMatrix;
}
if (programUniforms.modelViewMatrix) {
materialUniforms.modelViewMatrix = this.modelViewMatrix;
}
if (programUniforms.normalMatrix) {
return materialUniforms.normalMatrix = this.normalMatrix;
}
};
Mesh.prototype.getMaterial = function() {
return this.material;
};
Mesh.prototype.setMaterial = function(material) {
this.material = material;
return this.resetAttribLocations();
};
Mesh.prototype.getProgram = function() {
return this.material.program;
};
Mesh.prototype.setProgram = function(program) {
this.material.program = program;
return this.resetAttribLocations();
};
Mesh.prototype.dispose = function() {
return this.geometry.dispose();
};
Mesh.prototype.getBoundingBox = function() {
if (!this.boundingBox) {
this.updateBoundingBox();
}
return this.boundingBox;
};
Mesh.prototype.updateBoundingBox = function() {
this.updateMatrices();
return this.boundingBox = BoundingBox.fromPoints(this.geometry.vertices.map((function(_this) {
return function(v) {
return v.dup().transformMat4(_this.modelWorldMatrix);
};
})(this)));
};
module.exports = Mesh;