import vendor from './core/vendor'; import vec3 from './glmatrix/vec3'; import mat4 from './glmatrix/mat4'; import BoundingBox from './math/BoundingBox'; import GeometryBase from './GeometryBase'; var vec3Create = vec3.create; var vec3Add = vec3.add; var vec3Set = vec3.set; var Attribute = GeometryBase.Attribute; /** * Geometry in ClayGL contains vertex attributes of mesh. These vertex attributes will be finally provided to the {@link clay.Shader}. * Different {@link clay.Shader} needs different attributes. Here is a list of attributes used in the builtin shaders. * * + position: `clay.basic`, `clay.lambert`, `clay.standard` * + texcoord0: `clay.basic`, `clay.lambert`, `clay.standard` * + color: `clay.basic`, `clay.lambert`, `clay.standard` * + weight: `clay.basic`, `clay.lambert`, `clay.standard` * + joint: `clay.basic`, `clay.lambert`, `clay.standard` * + normal: `clay.lambert`, `clay.standard` * + tangent: `clay.standard` * * #### Create a procedural geometry * * ClayGL provides a couple of builtin procedural geometries. Inlcuding: * * + {@link clay.geometry.Cube} * + {@link clay.geometry.Sphere} * + {@link clay.geometry.Plane} * + {@link clay.geometry.Cylinder} * + {@link clay.geometry.Cone} * + {@link clay.geometry.ParametricSurface} * * It's simple to create a basic geometry with these classes. * ```js var sphere = new clay.geometry.Sphere({ radius: 2 }); ``` * * #### Create the geometry data by yourself * * Usually the vertex attributes data are created by the {@link clay.loader.GLTF} or procedural geometries like {@link clay.geometry.Sphere}. * Besides these, you can create the data manually. Here is a simple example to create a triangle. ```js var TRIANGLE_POSITIONS = [ [-0.5, -0.5, 0], [0.5, -0.5, 0], [0, 0.5, 0] ]; var geometry = new clay.StaticGeometryBase(); // Add triangle vertices to position attribute. geometry.attributes.position.fromArray(TRIANGLE_POSITIONS); ``` * Then you can use the utility methods like `generateVertexNormals`, `generateTangents` to create the remaining necessary attributes. * * * #### Use with custom shaders * * If you wan't to write custom shaders. Don't forget to add SEMANTICS to these attributes. For example * ```glsl uniform mat4 worldViewProjection : WORLDVIEWPROJECTION; uniform mat4 worldInverseTranspose : WORLDINVERSETRANSPOSE; uniform mat4 world : WORLD; attribute vec3 position : POSITION; attribute vec2 texcoord : TEXCOORD_0; attribute vec3 normal : NORMAL; ``` * These `POSITION`, `TEXCOORD_0`, `NORMAL` are SEMANTICS which will map the attributes in shader to the attributes in the GeometryBase * * Available attributes SEMANTICS includes `POSITION`, `TEXCOORD_0`, `TEXCOORD_1` `NORMAL`, `TANGENT`, `COLOR`, `WEIGHT`, `JOINT`. * * * @constructor clay.Geometry * @extends clay.GeometryBase */ var Geometry = GeometryBase.extend(function () { return /** @lends clay.Geometry# */ { /** * Attributes of geometry. Including: * + `position` * + `texcoord0` * + `texcoord1` * + `normal` * + `tangent` * + `color` * + `weight` * + `joint` * + `barycentric` * * @type {Object.} */ attributes: { position: new Attribute('position', 'float', 3, 'POSITION'), texcoord0: new Attribute('texcoord0', 'float', 2, 'TEXCOORD_0'), texcoord1: new Attribute('texcoord1', 'float', 2, 'TEXCOORD_1'), normal: new Attribute('normal', 'float', 3, 'NORMAL'), tangent: new Attribute('tangent', 'float', 4, 'TANGENT'), color: new Attribute('color', 'float', 4, 'COLOR'), // Skinning attributes // Each vertex can be bind to 4 bones, because the // sum of weights is 1, so the weights is stored in vec3 and the last // can be calculated by 1-w.x-w.y-w.z weight: new Attribute('weight', 'float', 3, 'WEIGHT'), joint: new Attribute('joint', 'float', 4, 'JOINT'), // For wireframe display // http://codeflow.org/entries/2012/aug/02/easy-wireframe-display-with-barycentric-coordinates/ barycentric: new Attribute('barycentric', 'float', 3, null), }, /** * Calculated bounding box of geometry. * @type {clay.BoundingBox} */ boundingBox: null }; }, /** @lends clay.Geometry.prototype */ { mainAttribute: 'position', /** * Update boundingBox of Geometry */ updateBoundingBox: function () { var bbox = this.boundingBox; if (!bbox) { bbox = this.boundingBox = new BoundingBox(); } var posArr = this.attributes.position.value; if (posArr && posArr.length) { var min = bbox.min; var max = bbox.max; var minArr = min.array; var maxArr = max.array; vec3.set(minArr, posArr[0], posArr[1], posArr[2]); vec3.set(maxArr, posArr[0], posArr[1], posArr[2]); for (var i = 3; i < posArr.length;) { var x = posArr[i++]; var y = posArr[i++]; var z = posArr[i++]; if (x < minArr[0]) { minArr[0] = x; } if (y < minArr[1]) { minArr[1] = y; } if (z < minArr[2]) { minArr[2] = z; } if (x > maxArr[0]) { maxArr[0] = x; } if (y > maxArr[1]) { maxArr[1] = y; } if (z > maxArr[2]) { maxArr[2] = z; } } min._dirty = true; max._dirty = true; } }, /** * Generate normals per vertex. */ generateVertexNormals: function () { if (!this.vertexCount) { return; } var indices = this.indices; var attributes = this.attributes; var positions = attributes.position.value; var normals = attributes.normal.value; if (!normals || normals.length !== positions.length) { normals = attributes.normal.value = new vendor.Float32Array(positions.length); } else { // Reset for (var i = 0; i < normals.length; i++) { normals[i] = 0; } } var p1 = vec3Create(); var p2 = vec3Create(); var p3 = vec3Create(); var v21 = vec3Create(); var v32 = vec3Create(); var n = vec3Create(); var len = indices ? indices.length : this.vertexCount; var i1, i2, i3; for (var f = 0; f < len;) { if (indices) { i1 = indices[f++]; i2 = indices[f++]; i3 = indices[f++]; } else { i1 = f++; i2 = f++; i3 = f++; } vec3Set(p1, positions[i1*3], positions[i1*3+1], positions[i1*3+2]); vec3Set(p2, positions[i2*3], positions[i2*3+1], positions[i2*3+2]); vec3Set(p3, positions[i3*3], positions[i3*3+1], positions[i3*3+2]); vec3.sub(v21, p1, p2); vec3.sub(v32, p2, p3); vec3.cross(n, v21, v32); // Already be weighted by the triangle area for (var i = 0; i < 3; i++) { normals[i1*3+i] = normals[i1*3+i] + n[i]; normals[i2*3+i] = normals[i2*3+i] + n[i]; normals[i3*3+i] = normals[i3*3+i] + n[i]; } } for (var i = 0; i < normals.length;) { vec3Set(n, normals[i], normals[i+1], normals[i+2]); vec3.normalize(n, n); normals[i++] = n[0]; normals[i++] = n[1]; normals[i++] = n[2]; } this.dirty(); }, /** * Generate normals per face. */ generateFaceNormals: function () { if (!this.vertexCount) { return; } if (!this.isUniqueVertex()) { this.generateUniqueVertex(); } var indices = this.indices; var attributes = this.attributes; var positions = attributes.position.value; var normals = attributes.normal.value; var p1 = vec3Create(); var p2 = vec3Create(); var p3 = vec3Create(); var v21 = vec3Create(); var v32 = vec3Create(); var n = vec3Create(); if (!normals) { normals = attributes.normal.value = new Float32Array(positions.length); } var len = indices ? indices.length : this.vertexCount; var i1, i2, i3; for (var f = 0; f < len;) { if (indices) { i1 = indices[f++]; i2 = indices[f++]; i3 = indices[f++]; } else { i1 = f++; i2 = f++; i3 = f++; } vec3Set(p1, positions[i1*3], positions[i1*3+1], positions[i1*3+2]); vec3Set(p2, positions[i2*3], positions[i2*3+1], positions[i2*3+2]); vec3Set(p3, positions[i3*3], positions[i3*3+1], positions[i3*3+2]); vec3.sub(v21, p1, p2); vec3.sub(v32, p2, p3); vec3.cross(n, v21, v32); vec3.normalize(n, n); for (var i = 0; i < 3; i++) { normals[i1*3 + i] = n[i]; normals[i2*3 + i] = n[i]; normals[i3*3 + i] = n[i]; } } this.dirty(); }, /** * Generate tangents attributes. */ generateTangents: function () { if (!this.vertexCount) { return; } var nVertex = this.vertexCount; var attributes = this.attributes; if (!attributes.tangent.value) { attributes.tangent.value = new Float32Array(nVertex * 4); } var texcoords = attributes.texcoord0.value; var positions = attributes.position.value; var tangents = attributes.tangent.value; var normals = attributes.normal.value; if (!texcoords) { console.warn('Geometry without texcoords can\'t generate tangents.'); return; } var tan1 = []; var tan2 = []; for (var i = 0; i < nVertex; i++) { tan1[i] = [0.0, 0.0, 0.0]; tan2[i] = [0.0, 0.0, 0.0]; } var sdir = [0.0, 0.0, 0.0]; var tdir = [0.0, 0.0, 0.0]; var indices = this.indices; var len = indices ? indices.length : this.vertexCount; var i1, i2, i3; for (var i = 0; i < len;) { if (indices) { i1 = indices[i++]; i2 = indices[i++]; i3 = indices[i++]; } else { i1 = i++; i2 = i++; i3 = i++; } var st1s = texcoords[i1 * 2], st2s = texcoords[i2 * 2], st3s = texcoords[i3 * 2], st1t = texcoords[i1 * 2 + 1], st2t = texcoords[i2 * 2 + 1], st3t = texcoords[i3 * 2 + 1], p1x = positions[i1 * 3], p2x = positions[i2 * 3], p3x = positions[i3 * 3], p1y = positions[i1 * 3 + 1], p2y = positions[i2 * 3 + 1], p3y = positions[i3 * 3 + 1], p1z = positions[i1 * 3 + 2], p2z = positions[i2 * 3 + 2], p3z = positions[i3 * 3 + 2]; var x1 = p2x - p1x, x2 = p3x - p1x, y1 = p2y - p1y, y2 = p3y - p1y, z1 = p2z - p1z, z2 = p3z - p1z; var s1 = st2s - st1s, s2 = st3s - st1s, t1 = st2t - st1t, t2 = st3t - st1t; var r = 1.0 / (s1 * t2 - t1 * s2); sdir[0] = (t2 * x1 - t1 * x2) * r; sdir[1] = (t2 * y1 - t1 * y2) * r; sdir[2] = (t2 * z1 - t1 * z2) * r; tdir[0] = (s1 * x2 - s2 * x1) * r; tdir[1] = (s1 * y2 - s2 * y1) * r; tdir[2] = (s1 * z2 - s2 * z1) * r; vec3Add(tan1[i1], tan1[i1], sdir); vec3Add(tan1[i2], tan1[i2], sdir); vec3Add(tan1[i3], tan1[i3], sdir); vec3Add(tan2[i1], tan2[i1], tdir); vec3Add(tan2[i2], tan2[i2], tdir); vec3Add(tan2[i3], tan2[i3], tdir); } var tmp = vec3Create(); var nCrossT = vec3Create(); var n = vec3Create(); for (var i = 0; i < nVertex; i++) { n[0] = normals[i * 3]; n[1] = normals[i * 3 + 1]; n[2] = normals[i * 3 + 2]; var t = tan1[i]; // Gram-Schmidt orthogonalize vec3.scale(tmp, n, vec3.dot(n, t)); vec3.sub(tmp, t, tmp); vec3.normalize(tmp, tmp); // Calculate handedness. vec3.cross(nCrossT, n, t); tangents[i * 4] = tmp[0]; tangents[i * 4 + 1] = tmp[1]; tangents[i * 4 + 2] = tmp[2]; // PENDING can config ? tangents[i * 4 + 3] = vec3.dot(nCrossT, tan2[i]) < 0.0 ? -1.0 : 1.0; } this.dirty(); }, /** * If vertices are not shared by different indices. */ isUniqueVertex: function () { if (this.isUseIndices()) { return this.vertexCount === this.indices.length; } else { return true; } }, /** * Create a unique vertex for each index. */ generateUniqueVertex: function () { if (!this.vertexCount || !this.indices) { return; } if (this.indices.length > 0xffff) { this.indices = new vendor.Uint32Array(this.indices); } var attributes = this.attributes; var indices = this.indices; var attributeNameList = this.getEnabledAttributes(); var oldAttrValues = {}; for (var a = 0; a < attributeNameList.length; a++) { var name = attributeNameList[a]; oldAttrValues[name] = attributes[name].value; attributes[name].init(this.indices.length); } var cursor = 0; for (var i = 0; i < indices.length; i++) { var ii = indices[i]; for (var a = 0; a < attributeNameList.length; a++) { var name = attributeNameList[a]; var array = attributes[name].value; var size = attributes[name].size; for (var k = 0; k < size; k++) { array[cursor * size + k] = oldAttrValues[name][ii * size + k]; } } indices[i] = cursor; cursor++; } this.dirty(); }, /** * Generate barycentric coordinates for wireframe draw. */ generateBarycentric: function () { if (!this.vertexCount) { return; } if (!this.isUniqueVertex()) { this.generateUniqueVertex(); } var attributes = this.attributes; var array = attributes.barycentric.value; var indices = this.indices; // Already existed; if (array && array.length === indices.length * 3) { return; } array = attributes.barycentric.value = new Float32Array(indices.length * 3); for (var i = 0; i < (indices ? indices.length : this.vertexCount / 3);) { for (var j = 0; j < 3; j++) { var ii = indices ? indices[i++] : (i * 3 + j); array[ii * 3 + j] = 1; } } this.dirty(); }, /** * Apply transform to geometry attributes. * @param {clay.Matrix4} matrix */ applyTransform: function (matrix) { var attributes = this.attributes; var positions = attributes.position.value; var normals = attributes.normal.value; var tangents = attributes.tangent.value; matrix = matrix.array; // Normal Matrix var inverseTransposeMatrix = mat4.create(); mat4.invert(inverseTransposeMatrix, matrix); mat4.transpose(inverseTransposeMatrix, inverseTransposeMatrix); var vec3TransformMat4 = vec3.transformMat4; var vec3ForEach = vec3.forEach; vec3ForEach(positions, 3, 0, null, vec3TransformMat4, matrix); if (normals) { vec3ForEach(normals, 3, 0, null, vec3TransformMat4, inverseTransposeMatrix); } if (tangents) { vec3ForEach(tangents, 4, 0, null, vec3TransformMat4, inverseTransposeMatrix); } if (this.boundingBox) { this.updateBoundingBox(); } }, /** * Dispose geometry data in GL context. * @param {clay.Renderer} renderer */ dispose: function (renderer) { var cache = this._cache; cache.use(renderer.__uid__); var chunks = cache.get('chunks'); if (chunks) { for (var c = 0; c < chunks.length; c++) { var chunk = chunks[c]; for (var k = 0; k < chunk.attributeBuffers.length; k++) { var attribs = chunk.attributeBuffers[k]; renderer.gl.deleteBuffer(attribs.buffer); } if (chunk.indicesBuffer) { renderer.gl.deleteBuffer(chunk.indicesBuffer.buffer); } } } if (this.__vaoCache) { var vaoExt = renderer.getGLExtension('OES_vertex_array_object'); for (var id in this.__vaoCache) { var vao = this.__vaoCache[id].vao; if (vao) { vaoExt.deleteVertexArrayOES(vao); } } } this.__vaoCache = {}; cache.deleteContext(renderer.__uid__); } }); Geometry.STATIC_DRAW = GeometryBase.STATIC_DRAW; Geometry.DYNAMIC_DRAW = GeometryBase.DYNAMIC_DRAW; Geometry.STREAM_DRAW = GeometryBase.STREAM_DRAW; Geometry.AttributeBuffer = GeometryBase.AttributeBuffer; Geometry.IndicesBuffer = GeometryBase.IndicesBuffer; Geometry.Attribute = Attribute; export default Geometry;