//= require "jax/webgl/core" //= require_self //= require "jax/webgl/mesh/tangent_space" //= require "jax/webgl/mesh/support" //= require "jax/webgl/mesh/normals" /** * class Jax.Mesh * * Example: * * var mesh = new Jax.Mesh({ * init: function(vertices, colors, textureCoords, normals, indices) { * // all of the arguments are arrays. If you don't intend to use one, * // simply don't populate it with data. For instance, if your mesh * // does not use vertex indices, don't add any data to the indices * // array. * * // Colors will default to white if they are not populated. * * // A simple, red, opaque quad: * vertices.push(-1, -1, 0); colors.push(1, 0, 0, 1); * vertices.push(-1, 1, 0); colors.push(1, 0, 0, 1); * vertices.push( 1, 1, 0); colors.push(1, 0, 0, 1); * vertices.push( 1, -1, 0); colors.push(1, 0, 0, 1); * } * }); * * You can also subclass Mesh directly: * * var Quad = Jax.Class.create(Jax.Mesh, { * init: function(vertices, colors, textureCoords, normals, indices) { * // ... * } * }); **/ Jax.Mesh = (function() { var BUFFERS = { /** * Jax.Mesh#vertices -> Array * * A subgroup of Jax.Mesh#vertexData. * * This is essentially an array of arrays, each inner array * containing 3 elements (an X, Y, Z value). This shares the same memory as the raw data it is * based on, so the memory footprint is negligible, though it does take some time to construct * the data group the first time it is called. **/ vertices:['vertexData',3], /** * Jax.Mesh#normals -> Array * * A subgroup of Jax.Mesh#normalData. * * This is essentially an array of arrays, each inner array * containing 3 elements (an X, Y, Z value). This shares the same memory as the raw data it is * based on, so the memory footprint is negligible, though it does take some time to construct * the data group the first time it is called. * * You can modify the inner arrays of this object, but you should then call refresh() on * Jax.Mesh#getNormalBuffer(). **/ normals:['normalData',3], /** * Jax.Mesh#colors -> Array * * A subgroup of Jax.Mesh#colorData. * * This is essentially an array of arrays, each inner array * containing 4 elements (an R, G, B, A value). This shares the same memory as the raw data it is * based on, so the memory footprint is negligible, though it does take some time to construct * the data group the first time it is called. * * You can modify the inner arrays of this object, but you should then call refresh() on * Jax.Mesh#getColorBuffer(). **/ colors:['colorData',4], /** * Jax.Mesh#textureCoords -> Array * * A subgroup of Jax.Mesh#textureCoordsData. * * This is essentially an array of arrays, each inner array * containing 2 elements (a U, V value). This shares the same memory as the raw data it is * based on, so the memory footprint is negligible, though it does take some time to construct * the data group the first time it is called. * * You can modify the inner arrays of this object, but you should then call refresh() on * Jax.Mesh#getTextureCoordsBuffer(). **/ textureCoords:['textureCoordsData',2] }; return Jax.Class.create({ initialize: function(options) { this.buffers = {}; this.bounds = { left: 0, right: 0, front: 0, back: 0, top: 0, bottom: 0, width: 0, height: 0, depth: 0 }; this.triangles = []; var self = this; for (var i in BUFFERS) { Object.defineProperty(self, i, (function() { var j = "_"+i, k = BUFFERS[i]; return { configurable: true, enumerable: true, get: function() { if (!self[j]) // if (!self._vertices) self[j] = self.validate()[k[0]].group(k[1]); // self._vertices = self.validate().vertexData.group(3); return self[j]; // return self._vertices; }, // set: function(v) { } }; })()); } /** * Jax.Mesh#material -> String | Jax.Material * This property represents the material that will be used to render this mesh. If * it is a string, Jax will find the material with this name in the material registry * using: * * Jax.Material.find(...). * * If not specified, Jax.Mesh#default_material will be used instead. **/ /** * Jax.Mesh#default_material -> String | Jax.Material * This property represents the material that will be used to render this mesh if #material * isn't given a value and the render options don't override the material. If * it is a string, Jax will find the material with this name in the material registry * using: * * Jax.Material.find(...). * * This property can also be specified as a render option in order to specify a default * for a particular pass. **/ this.default_material = "default"; for (var i in options) this[i] = options[i]; if (this.draw_mode == undefined) this.draw_mode = GL_TRIANGLES; }, /** * Jax.Mesh#getBounds() -> Object * * Returns a generic object containing the following properties describing * an axis-aligned bounding box (AABB): * * * * * * *
left, rightX-axis coordinates of the left-most and right-most vertices
top, bottomY-axis coordinates of the top-most and bottom-most vertices
front, back1Z-axis coordinates of the closest and furthest vertices
width, height, depthnon-negative dimensions of the cube
* * 1 as with most units in WebGL, the front is the greatest value, while the back * is the lowest. * * If the mesh has not been built yet (e.g. +Jax.Mesh#isValid+ returns false), these properties will * all be zero, or they will be whatever their previous values were if the mesh has been invalidated. * * These properties will also all be zero if the mesh has been built but has no vertices. **/ getBounds: function() { return this.bounds; }, /** * Jax.Mesh#getTriangles() -> Array * * Returns an array of +Jax.Geometry.Triangle+ built from the vertices in this mesh. * If the draw mode for the mesh is not one of +GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN+, * then the array will be empty. * * Also, it is up to the +init+ method to make sure that the number of vertices produced * matches the selected draw mode. If, for instance, the draw mode is +GL_TRIANGLES+ but * the number of vertices is not divisible by 3, this method will simply return an incomplete * or empty triangle array rather than raise an error. **/ getTriangles: function() { this.validate(); if (this.triangles.length == 0 && this.vertices.length != 0) this.buildTriangles(); return this.triangles; }, /** * Jax.Mesh#setColor(red, green, blue, alpha) -> Jax.Mesh * Sets the color of this mesh. This will set the color at each vertex, regardless * of the original color of that vertex. The result will be that the entire mesh * takes on the specified color (not just a particular vertex). **/ setColor: function(red, green, blue, alpha) { var colorBuffer = this.getColorBuffer(); for (var i = 0; i < this.colors.length; i++) { if (arguments.length == 4) { this.colors[i].array[0] = red; this.colors[i].array[1] = green; this.colors[i].array[2] = blue; this.colors[i].array[3] = alpha; } else { for (var j = 0; j < 4; j++) { this.colors[i].array[j] = arguments[0][j]; } } } colorBuffer.refresh(); return this; }, /** * Jax.Mesh#dispose() -> undefined * Frees the various WebGL buffers used by this mesh. **/ dispose: function() { for (var i in this.buffers) this.buffers[i].dispose(); this.built = false; }, /** * Jax.Mesh#render(context[, options]) -> undefined * - context (Jax.Context): the Jax context to render this object to * - options (Object): a set of custom render options to override the defaults for this Mesh. * * Options include: * * *draw_mode* : a GL rendering enum, such as GL_TRIANGLES or GL_LINE_STRIP. * * *material* : an instance of Jax.Material, or the name of a registered Jax material, to override * the material associated with this mesh. **/ render: function(context, options) { if (!this.isValid()) this.rebuild(); options = this.getNormalizedRenderOptions(options); options.material.render(context, this, options); }, getNormalizedRenderOptions: function(options) { var result = Jax.Util.normalizeOptions(options, { material: this.material, default_material: this.default_material, draw_mode: this.draw_mode == undefined ? GL_TRIANGLES : this.draw_mode }); if (!result.material) result.material = result.default_material; result.material = Jax.Util.findMaterial(result.material); return result; }, /** * Jax.Mesh#getVertexBuffer() -> Jax.DataBuffer **/ getVertexBuffer: function() { this.validate(); if (this.buffers.vertex_buffer.length == 0) return null; return this.buffers.vertex_buffer; }, /** * Jax.Mesh#getColorBuffer() -> Jax.DataBuffer **/ getColorBuffer: function() { this.validate(); if (this.buffers.color_buffer.length == 0) return null; return this.buffers.color_buffer; }, /** * Jax.Mesh#getIndexBuffer() -> Jax.DataBuffer **/ getIndexBuffer: function() { this.validate(); if (this.buffers.index_buffer.length == 0) return null; return this.buffers.index_buffer; }, /** * Jax.Mesh#getNormalBuffer() -> Jax.DataBuffer **/ getNormalBuffer: function() { this.validate(); if (this.buffers.normal_buffer.length == 0 && this.buffers.vertex_buffer.length > 0) this.recalculateNormals(); if (this.buffers.normal_buffer.length == 0) return null; return this.buffers.normal_buffer; }, /** * Jax.Mesh#getTextureCoordsBuffer() -> Jax.DataBuffer **/ getTextureCoordsBuffer: function() { this.validate(); if (this.buffers.texture_coords.length == 0) return null; return this.buffers.texture_coords; }, /** * Jax.Mesh#getTangentBuffer() -> Jax.DataBuffer * Returns tangent normals for each normal in this Mesh. Used for normal / bump mapping. **/ getTangentBuffer: function() { if (!this.buffers.tangent_buffer) return this.rebuildTangentBuffer(); if (this.buffers.tangent_buffer.length == 0) return null; return this.buffers.tangent_buffer; }, /** * Jax.Mesh#rebuildTangentBuffer() -> Jax.NormalBuffer * Forces an immediate rebuild of the tangent buffer for this Mesh. Use this if you've changed * the vertex, normal or texture information to update the tangent vectors. If this step is * skipped, you'll notice strange artifacts when using bump mapping (because the tangents will * be pointing in the wrong direction). **/ rebuildTangentBuffer: function() { return this.makeTangentBuffer(); }, /** * Jax.Mesh#validate() -> Jax.Mesh * * If this mesh is not valid (its #init method hasn't been called or needs to be called again), * the mesh will be rebuilt per +Jax.Mesh#rebuild+. This mesh is returned. **/ validate: function() { if (!this.isValid()) this.rebuild(); return this; }, /** * Jax.Mesh#isValid() -> Boolean * * Returns true if this mesh is valid. If the mesh is invalid, it will be rebuilt during the next call to * Jax.Mesh#render(). **/ isValid: function() { return !!this.built; }, /** * Jax.Mesh#recalculateNormals() -> Jax.Mesh * * Recalculates all vertex normals based on the vertices themselves (and vertex indices, if present), * replacing all current values. This is a very expensive operation and should be avoided if at all * possible by populating the normals directly within this mesh's +init+ method. **/ recalculateNormals: function() { this.calculateNormals(); }, /** * Jax.Mesh#rebuild() -> undefined * * Forces Jax to rebuild this mesh immediately. This will dispose of any WebGL buffers * and reinitialize them with a new call to this mesh's data init method. Note that this * is a very expensive operation and is *usually not* what you want. * * If, for instance, you want to update the mesh with new vertex positions (say, for animation) * then you'd be much better off doing something like this: * * var vbuf = mesh.getVertexBuffer(); * vbuf.js.clear(); * for (var i = 0; i < newVertexData.length; i++) * vbuf.push(newVertexData[i]); * vbuf.refresh(); * **/ rebuild: function() { this.dispose(); // we are about to recalculate vertices, that means triangles will (maybe) be inaccurate while (this.triangles.length > 0) this.triangles.pop(); var vertices = [], colors = [], textureCoords = [], normals = [], indices = []; if (this.init) this.init(vertices, colors, textureCoords, normals, indices); this.built = true; // mesh builder didn't set colors...default to this.color || white. if (colors.length == 0 || this.color) { if (!this.color) this.color = [1,1,1,1]; for (var i = 0; i < vertices.length / 3; i++) { for (var j = 0; j < 4; j++) colors[i*4+j] = this.color[j]; } } if (this.dataRegion) { // we don'y simply call data.set(vertices) because the data count may // have changed. Remapping will reallocate memory as needed. this.dataRegion.remap(this.vertexData, vertices); this.dataRegion.remap(this.colorData, colors); this.dataRegion.remap(this.textureCoordsData, textureCoords); this.dataRegion.remap(this.normalData, normals); this.dataRegion.remap(this.indices, indices); } else { // it's faster to preallocate a known number of bytes than it is to // let the data region figure it out incrementally. We can be conservative here. // If the number is too low, dataRegion will adapt. this.dataRegion = new Jax.DataRegion( (vertices.length+colors.length+textureCoords.length+normals.length) * Float32Array.BYTES_PER_ELEMENT + indices.length * Uint16Array.BYTES_PER_ELEMENT ); this.vertexData = this.dataRegion.map(Float32Array, vertices); this.colorData = this.dataRegion.map(Float32Array, colors); this.textureCoordsData = this.dataRegion.map(Float32Array, textureCoords); this.normalData = this.dataRegion.map(Float32Array, normals); this.indices = this.dataRegion.map(Uint16Array, indices); } this._vertices = null; this._colors = null; this._textureCoords = null; this._normals = null; this.calculateBounds(vertices); this.buffers.vertex_buffer = new Jax.DataBuffer(GL_ARRAY_BUFFER, this.vertexData, 3); this.buffers.color_buffer = new Jax.DataBuffer(GL_ARRAY_BUFFER, this.colorData, 4); this.buffers.normal_buffer = new Jax.DataBuffer(GL_ARRAY_BUFFER, this.normalData, 3); this.buffers.texture_coords = new Jax.DataBuffer(GL_ARRAY_BUFFER, this.textureCoordsData, 2); this.buffers.index_buffer = new Jax.DataBuffer(GL_ELEMENT_ARRAY_BUFFER, this.indices); if (this.after_initialize) this.after_initialize(); } }); })();