Source: scene/procedural.js

/**
 * @function
 * @name pc.calculateNormals
 * @description Generates normal information from the specified positions and triangle indices. See {@link pc.createMesh}.
 * @param {Number[]} positions An array of 3-dimensional vertex positions.
 * @param {Number[]} indices An array of triangle indices.
 * @returns {Number[]} An array of 3-dimensional vertex normals.
 * @example
 * var normals = pc.calculateNormals(positions, indices);
 * var tangents = pc.calculateTangents(positions, normals, uvs, indices);
 * var mesh = pc.createMesh(positions, normals, tangents, uvs, indices);
 */

var primitiveUv1Padding = 4.0 / 64;
var primitiveUv1PaddingScale = 1.0 - primitiveUv1Padding * 2;

pc.calculateNormals = function (positions, indices) {
    var triangleCount = indices.length / 3;
    var vertexCount   = positions.length / 3;
    var i1, i2, i3;
    var i; // Loop counter
    var p1 = new pc.Vec3();
    var p2 = new pc.Vec3();
    var p3 = new pc.Vec3();
    var p1p2 = new pc.Vec3();
    var p1p3 = new pc.Vec3();
    var faceNormal = new pc.Vec3();

    var normals = [];

    // Initialize the normal array to zero
    for (i = 0; i < positions.length; i++) {
        normals[i] = 0;
    }

    // Accumulate face normals for each vertex
    for (i = 0; i < triangleCount; i++) {
        i1 = indices[i * 3];
        i2 = indices[i * 3 + 1];
        i3 = indices[i * 3 + 2];

        p1.set(positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2]);
        p2.set(positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2]);
        p3.set(positions[i3 * 3], positions[i3 * 3 + 1], positions[i3 * 3 + 2]);

        p1p2.sub2(p2, p1);
        p1p3.sub2(p3, p1);
        faceNormal.cross(p1p2, p1p3).normalize();

        normals[i1 * 3]     += faceNormal.x;
        normals[i1 * 3 + 1] += faceNormal.y;
        normals[i1 * 3 + 2] += faceNormal.z;
        normals[i2 * 3]     += faceNormal.x;
        normals[i2 * 3 + 1] += faceNormal.y;
        normals[i2 * 3 + 2] += faceNormal.z;
        normals[i3 * 3]     += faceNormal.x;
        normals[i3 * 3 + 1] += faceNormal.y;
        normals[i3 * 3 + 2] += faceNormal.z;
    }

    // Normalize all normals
    for (i = 0; i < vertexCount; i++) {
        var nx = normals[i * 3];
        var ny = normals[i * 3 + 1];
        var nz = normals[i * 3 + 2];
        var invLen = 1 / Math.sqrt(nx * nx + ny * ny + nz * nz);
        normals[i * 3] *= invLen;
        normals[i * 3 + 1] *= invLen;
        normals[i * 3 + 2] *= invLen;
    }

    return normals;
};

/**
 * @function
 * @name pc.calculateTangents
 * @description Generates tangent information from the specified positions, normals, texture coordinates
 * and triangle indices. See {@link pc.createMesh}.
 * @param {Number[]} positions An array of 3-dimensional vertex positions.
 * @param {Number[]} normals An array of 3-dimensional vertex normals.
 * @param {Number[]} uvs An array of 2-dimensional vertex texture coordinates.
 * @param {Number[]} indices An array of triangle indices.
 * @returns {Number[]} An array of 3-dimensional vertex tangents.
 * @example
 * var tangents = pc.calculateTangents(positions, normals, uvs, indices);
 * var mesh = pc.createMesh(positions, normals, tangents, uvs, indices);
 */
pc.calculateTangents = function (positions, normals, uvs, indices) {
    var triangleCount = indices.length / 3;
    var vertexCount   = positions.length / 3;
    var i1, i2, i3;
    var x1, x2, y1, y2, z1, z2, s1, s2, t1, t2, r;
    var sdir = new pc.Vec3();
    var tdir = new pc.Vec3();
    var v1   = new pc.Vec3();
    var v2   = new pc.Vec3();
    var v3   = new pc.Vec3();
    var w1   = new pc.Vec2();
    var w2   = new pc.Vec2();
    var w3   = new pc.Vec2();
    var i; // Loop counter
    var tan1 = new Float32Array(vertexCount * 3);
    var tan2 = new Float32Array(vertexCount * 3);

    var tangents = [];
    var area = 0.0;

    for (i = 0; i < triangleCount; i++) {
        i1 = indices[i * 3];
        i2 = indices[i * 3 + 1];
        i3 = indices[i * 3 + 2];

        v1.set(positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2]);
        v2.set(positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2]);
        v3.set(positions[i3 * 3], positions[i3 * 3 + 1], positions[i3 * 3 + 2]);

        w1.set(uvs[i1 * 2], uvs[i1 * 2 + 1]);
        w2.set(uvs[i2 * 2], uvs[i2 * 2 + 1]);
        w3.set(uvs[i3 * 2], uvs[i3 * 2 + 1]);

        x1 = v2.x - v1.x;
        x2 = v3.x - v1.x;
        y1 = v2.y - v1.y;
        y2 = v3.y - v1.y;
        z1 = v2.z - v1.z;
        z2 = v3.z - v1.z;

        s1 = w2.x - w1.x;
        s2 = w3.x - w1.x;
        t1 = w2.y - w1.y;
        t2 = w3.y - w1.y;

        area = s1 * t2 - s2 * t1;

        // Area can 0.0 for degenerate triangles or bad uv coordinates
        if (area == 0.0) {
            // Fallback to default values
            sdir.set(0.0, 1.0, 0.0);
            tdir.set(1.0, 0.0, 0.0);
        } else {
            r = 1.0 / area;
            sdir.set((t2 * x1 - t1 * x2) * r,
                     (t2 * y1 - t1 * y2) * r,
                     (t2 * z1 - t1 * z2) * r);
            tdir.set((s1 * x2 - s2 * x1) * r,
                     (s1 * y2 - s2 * y1) * r,
                     (s1 * z2 - s2 * z1) * r);
        }

        tan1[i1 * 3 + 0] += sdir.x;
        tan1[i1 * 3 + 1] += sdir.y;
        tan1[i1 * 3 + 2] += sdir.z;
        tan1[i2 * 3 + 0] += sdir.x;
        tan1[i2 * 3 + 1] += sdir.y;
        tan1[i2 * 3 + 2] += sdir.z;
        tan1[i3 * 3 + 0] += sdir.x;
        tan1[i3 * 3 + 1] += sdir.y;
        tan1[i3 * 3 + 2] += sdir.z;

        tan2[i1 * 3 + 0] += tdir.x;
        tan2[i1 * 3 + 1] += tdir.y;
        tan2[i1 * 3 + 2] += tdir.z;
        tan2[i2 * 3 + 0] += tdir.x;
        tan2[i2 * 3 + 1] += tdir.y;
        tan2[i2 * 3 + 2] += tdir.z;
        tan2[i3 * 3 + 0] += tdir.x;
        tan2[i3 * 3 + 1] += tdir.y;
        tan2[i3 * 3 + 2] += tdir.z;
    }

    t1 = new pc.Vec3();
    t2 = new pc.Vec3();
    var n = new pc.Vec3();
    var temp = new pc.Vec3();

    for (i = 0; i < vertexCount; i++) {
        n.set(normals[i * 3], normals[i * 3 + 1], normals[i * 3 + 2]);
        t1.set(tan1[i * 3], tan1[i * 3 + 1], tan1[i * 3 + 2]);
        t2.set(tan2[i * 3], tan2[i * 3 + 1], tan2[i * 3 + 2]);

        // Gram-Schmidt orthogonalize
        var ndott = n.dot(t1);
        temp.copy(n).scale(ndott);
        temp.sub2(t1, temp).normalize();

        tangents[i * 4]     = temp.x;
        tangents[i * 4 + 1] = temp.y;
        tangents[i * 4 + 2] = temp.z;

        // Calculate handedness
        temp.cross(n, t1);
        tangents[i * 4 + 3] = (temp.dot(t2) < 0.0) ? -1.0 : 1.0;
    }

    return tangents;
};

/**
 * @function
 * @name pc.createMesh
 * @description Creates a new mesh object from the supplied vertex information and topology.
 * @param {pc.GraphicsDevice} device The graphics device used to manage the mesh.
 * @param {Number[]} positions An array of 3-dimensional vertex positions.
 * @param {Object} opts An object that specifies optional inputs for the function as follows:
 * @param {Number[]} opts.normals An array of 3-dimensional vertex normals.
 * @param {Number[]} opts.tangents An array of 3-dimensional vertex tangents.
 * @param {Number[]} opts.colors An array of 4-dimensional vertex colors.
 * @param {Number[]} opts.uvs An array of 2-dimensional vertex texture coordinates.
 * @param {Number[]} opts.uvs1 Same as opts.uvs, but for additional UV set
 * @param {Number[]} opts.indices An array of triangle indices.
 * @returns {pc.Mesh} A new Geometry constructed from the supplied vertex and triangle data.
 * @example
 * // Create a new mesh supplying optional parameters using object literal notation
 * var mesh = pc.createMesh(
 *     graphicsDevice,
 *     positions,
 *     {
 *         normals: treeNormals,
 *         uvs: treeUvs,
 *         indices: treeIndices
 *     });
 */
pc.createMesh = function (device, positions, opts) {
    // Check the supplied options and provide defaults for unspecified ones
    var normals      = opts && opts.normals !== undefined ? opts.normals : null;
    var tangents     = opts && opts.tangents !== undefined ? opts.tangents : null;
    var colors       = opts && opts.colors !== undefined ? opts.colors : null;
    var uvs          = opts && opts.uvs !== undefined ? opts.uvs : null;
    var uvs1         = opts && opts.uvs1 !== undefined ? opts.uvs1 : null;
    var indices      = opts && opts.indices !== undefined ? opts.indices : null;
    var blendIndices = opts && opts.blendIndices !== undefined ? opts.blendIndices : null;
    var blendWeights = opts && opts.blendWeights !== undefined ? opts.blendWeights : null;

    var vertexDesc = [
        { semantic: pc.SEMANTIC_POSITION, components: 3, type: pc.TYPE_FLOAT32 }
    ];
    if (normals !== null) {
        vertexDesc.push({ semantic: pc.SEMANTIC_NORMAL, components: 3, type: pc.TYPE_FLOAT32 });
    }
    if (tangents !== null) {
        vertexDesc.push({ semantic: pc.SEMANTIC_TANGENT, components: 4, type: pc.TYPE_FLOAT32 });
    }
    if (colors !== null) {
        vertexDesc.push({ semantic: pc.SEMANTIC_COLOR, components: 4, type: pc.TYPE_UINT8, normalize: true });
    }
    if (uvs !== null) {
        vertexDesc.push({ semantic: pc.SEMANTIC_TEXCOORD0, components: 2, type: pc.TYPE_FLOAT32 });
    }
    if (uvs1 !== null) {
        vertexDesc.push({ semantic: pc.SEMANTIC_TEXCOORD1, components: 2, type: pc.TYPE_FLOAT32 });
    }
    if (blendIndices !== null) {
        vertexDesc.push({ semantic: pc.SEMANTIC_BLENDINDICES, components: 2, type: pc.TYPE_UINT8 });
    }
    if (blendWeights !== null) {
        vertexDesc.push({ semantic: pc.SEMANTIC_BLENDWEIGHT, components: 2, type: pc.TYPE_FLOAT32 });
    }

    var vertexFormat = new pc.VertexFormat(device, vertexDesc);

    // Create the vertex buffer
    var numVertices  = positions.length / 3;
    var vertexBuffer = new pc.VertexBuffer(device, vertexFormat, numVertices);

    // Write the vertex data into the vertex buffer
    var iterator = new pc.VertexIterator(vertexBuffer);
    for (var i = 0; i < numVertices; i++) {
        iterator.element[pc.SEMANTIC_POSITION].set(positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]);
        if (normals !== null) {
            iterator.element[pc.SEMANTIC_NORMAL].set(normals[i * 3], normals[i * 3 + 1], normals[i * 3 + 2]);
        }
        if (tangents !== null) {
            iterator.element[pc.SEMANTIC_TANGENT].set(tangents[i * 4], tangents[i * 4 + 1], tangents[i * 4 + 2], tangents[i * 4 + 3]);
        }
        if (colors !== null) {
            iterator.element[pc.SEMANTIC_COLOR].set(colors[i * 4], colors[i * 4 + 1], colors[i * 4 + 2], colors[i * 4 + 3]);
        }
        if (uvs !== null) {
            iterator.element[pc.SEMANTIC_TEXCOORD0].set(uvs[i * 2], uvs[i * 2 + 1]);
        }
        if (uvs1 !== null) {
            iterator.element[pc.SEMANTIC_TEXCOORD1].set(uvs1[i * 2], uvs1[i * 2 + 1]);
        }
        if (blendIndices !== null) {
            iterator.element[pc.SEMANTIC_BLENDINDICES].set(blendIndices[i * 2], blendIndices[i * 2 + 1]);
        }
        if (blendWeights !== null) {
            iterator.element[pc.SEMANTIC_BLENDWEIGHT].set(blendWeights[i * 2], blendWeights[i * 2 + 1]);
        }
        iterator.next();
    }
    iterator.end();

    // Create the index buffer
    var indexBuffer = null;
    var indexed = (indices !== null);
    if (indexed) {
        indexBuffer = new pc.IndexBuffer(device, pc.INDEXFORMAT_UINT16, indices.length);

        // Read the indicies into the index buffer
        var dst = new Uint16Array(indexBuffer.lock());
        dst.set(indices);
        indexBuffer.unlock();
    }

    var aabb = new pc.BoundingBox();
    aabb.compute(positions);

    var mesh = new pc.Mesh();
    mesh.vertexBuffer = vertexBuffer;
    mesh.indexBuffer[0] = indexBuffer;
    mesh.primitive[0].type = pc.PRIMITIVE_TRIANGLES;
    mesh.primitive[0].base = 0;
    mesh.primitive[0].count = indexed ? indices.length : numVertices;
    mesh.primitive[0].indexed = indexed;
    mesh.aabb = aabb;
    return mesh;
};

/**
 * @function
 * @name pc.createTorus
 * @description Creates a procedural torus-shaped mesh.
 * The size, shape and tesselation properties of the torus can be controlled via function parameters.
 * By default, the function will create a torus in the XZ-plane with a tube radius of 0.2, a ring radius
 * of 0.3, 20 segments and 30 sides.<br />
 * Note that the torus is created with UVs in the range of 0 to 1. Additionally, tangent information
 * is generated into the vertex buffer of the torus's mesh.<br />
 * @param {pc.GraphicsDevice} device The graphics device used to manage the mesh.
 * @param {Object} opts An object that specifies optional inputs for the function as follows:
 * @param {Number} opts.tubeRadius The radius of the tube forming the body of the torus (defaults to 0.2).
 * @param {Number} opts.ringRadius The radius from the centre of the torus to the centre of the tube (defaults to 0.3).
 * @param {Number} opts.segments The number of radial divisions forming cross-sections of the torus ring (defaults to 20).
 * @param {Number} opts.sides The number of divisions around the tubular body of the torus ring (defaults to 30).
 * @returns {pc.Mesh} A new torus-shaped mesh.
 */
pc.createTorus = function (device, opts) {
    // Check the supplied options and provide defaults for unspecified ones
    var rc = opts && opts.tubeRadius !== undefined ? opts.tubeRadius : 0.2;
    var rt = opts && opts.ringRadius !== undefined ? opts.ringRadius : 0.3;
    var segments = opts && opts.segments !== undefined ? opts.segments : 30;
    var sides = opts && opts.sides !== undefined ? opts.sides : 20;
    var calculateTangents = opts && opts.calculateTangents !== undefined ? opts.calculateTangents : false;

    // Variable declarations
    var i, j;
    var x, y, z, nx, ny, nz, u, v;
    var positions = [];
    var normals = [];
    var uvs = [];
    var indices = [];

    for (i = 0; i <= sides; i++) {
        for (j = 0; j <= segments; j++) {
            x  = Math.cos(2.0 * Math.PI * j / segments) * (rt + rc * Math.cos(2.0 * Math.PI * i / sides));
            y  = Math.sin(2.0 * Math.PI * i / sides) * rc;
            z  = Math.sin(2.0 * Math.PI * j / segments) * (rt + rc * Math.cos(2.0 * Math.PI * i / sides));

            nx = Math.cos(2.0 * Math.PI * j / segments) * Math.cos(2.0 * Math.PI * i / sides);
            ny = Math.sin(2.0 * Math.PI * i / sides);
            nz = Math.sin(2.0 * Math.PI * j / segments) * Math.cos(2.0 * Math.PI * i / sides);

            u = i / sides;
            v = 1.0 - j / segments;

            positions.push(x, y, z);
            normals.push(nx, ny, nz);
            uvs.push(u, v);

            if ((i < sides) && (j < segments)) {
                var first, second, third, fourth;
                first   = ((i))     * (segments + 1) + ((j));
                second  = ((i + 1)) * (segments + 1) + ((j));
                third   = ((i))     * (segments + 1) + ((j + 1));
                fourth  = ((i + 1)) * (segments + 1) + ((j + 1));

                indices.push(first, second, third);
                indices.push(second, fourth, third);
            }
        }
    }

    var options = {
        normals: normals,
        uvs: uvs,
        indices: indices
    };

    if (calculateTangents) {
        options.tangents = pc.calculateTangents(positions, normals, uvs, indices);
    }

    return pc.createMesh(device, positions, options);
};

pc._createConeData = function (baseRadius, peakRadius, height, heightSegments, capSegments, roundedCaps) {
    // Variable declarations
    var i, j;
    var x, y, z, u, v;
    var pos = new pc.Vec3();
    var bottomToTop = new pc.Vec3();
    var norm = new pc.Vec3();
    var top, bottom, tangent;
    var positions = [];
    var normals = [];
    var uvs = [];
    var uvs1 = [];
    var indices = [];
    var theta, cosTheta, sinTheta;
    var phi, sinPhi, cosPhi;
    var first, second, third, fourth;
    var offset;

    // Define the body of the cone/cylinder
    if (height > 0) {
        for (i = 0; i <= heightSegments; i++) {
            for (j = 0; j <= capSegments; j++) {
                // Sweep the cone body from the positive Y axis to match a 3DS Max cone/cylinder
                theta = (j / capSegments) * 2.0 * Math.PI - Math.PI;
                sinTheta = Math.sin(theta);
                cosTheta = Math.cos(theta);
                bottom = new pc.Vec3(sinTheta * baseRadius, -height / 2.0, cosTheta * baseRadius);
                top    = new pc.Vec3(sinTheta * peakRadius,  height / 2.0, cosTheta * peakRadius);
                pos.lerp(bottom, top, i / heightSegments);
                bottomToTop.sub2(top, bottom).normalize();
                tangent = new pc.Vec3(cosTheta, 0.0, -sinTheta);
                norm.cross(tangent, bottomToTop).normalize();

                positions.push(pos.x, pos.y, pos.z);
                normals.push(norm.x, norm.y, norm.z);
                u = j / capSegments;
                v = i / heightSegments;
                uvs.push(u, v);

                // Pack UV1 to 1st third
                var _v = v;
                v = u;
                u = _v;
                u /= 3;
                u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
                v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
                uvs1.push(u, v);

                if ((i < heightSegments) && (j < capSegments)) {
                    first   = ((i))     * (capSegments + 1) + ((j));
                    second  = ((i))     * (capSegments + 1) + ((j + 1));
                    third   = ((i + 1)) * (capSegments + 1) + ((j));
                    fourth  = ((i + 1)) * (capSegments + 1) + ((j + 1));

                    indices.push(first, second, third);
                    indices.push(second, fourth, third);
                }
            }
        }
    }

    if (roundedCaps) {
        var lat, lon;
        var latitudeBands = Math.floor(capSegments / 2);
        var longitudeBands = capSegments;
        var capOffset = height / 2;

        // Generate top cap
        for (lat = 0; lat <= latitudeBands; lat++) {
            theta = (lat * Math.PI * 0.5) / latitudeBands;
            sinTheta = Math.sin(theta);
            cosTheta = Math.cos(theta);

            for (lon = 0; lon <= longitudeBands; lon++) {
                // Sweep the sphere from the positive Z axis to match a 3DS Max sphere
                phi = lon * 2 * Math.PI / longitudeBands - Math.PI / 2.0;
                sinPhi = Math.sin(phi);
                cosPhi = Math.cos(phi);

                x = cosPhi * sinTheta;
                y = cosTheta;
                z = sinPhi * sinTheta;
                u = 1.0 - lon / longitudeBands;
                v = 1.0 - lat / latitudeBands;

                positions.push(x * peakRadius, y * peakRadius + capOffset, z * peakRadius);
                normals.push(x, y, z);
                uvs.push(u, v);

                // Pack UV1 to 2nd third
                u /= 3;
                v /= 3;
                u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
                v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
                u += 1.0 / 3;
                uvs1.push(u, v);
            }
        }

        offset = (heightSegments + 1) * (capSegments + 1);
        for (lat = 0; lat < latitudeBands; ++lat) {
            for (lon = 0; lon < longitudeBands; ++lon) {
                first  = (lat * (longitudeBands + 1)) + lon;
                second = first + longitudeBands + 1;

                indices.push(offset + first + 1, offset + second, offset + first);
                indices.push(offset + first + 1, offset + second + 1, offset + second);
            }
        }

        // Generate bottom cap
        for (lat = 0; lat <= latitudeBands; lat++) {
            theta = Math.PI * 0.5 + (lat * Math.PI * 0.5) / latitudeBands;
            sinTheta = Math.sin(theta);
            cosTheta = Math.cos(theta);

            for (lon = 0; lon <= longitudeBands; lon++) {
                // Sweep the sphere from the positive Z axis to match a 3DS Max sphere
                phi = lon * 2 * Math.PI / longitudeBands - Math.PI / 2.0;
                sinPhi = Math.sin(phi);
                cosPhi = Math.cos(phi);

                x = cosPhi * sinTheta;
                y = cosTheta;
                z = sinPhi * sinTheta;
                u = 1.0 - lon / longitudeBands;
                v = 1.0 - lat / latitudeBands;

                positions.push(x * peakRadius, y * peakRadius - capOffset, z * peakRadius);
                normals.push(x, y, z);
                uvs.push(u, v);

                // Pack UV1 to 3rd third
                u /= 3;
                v /= 3;
                u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
                v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
                u += 2.0 / 3;
                uvs1.push(u, v);
            }
        }

        offset = (heightSegments + 1) * (capSegments + 1) + (longitudeBands + 1) * (latitudeBands + 1);
        for (lat = 0; lat < latitudeBands; ++lat) {
            for (lon = 0; lon < longitudeBands; ++lon) {
                first  = (lat * (longitudeBands + 1)) + lon;
                second = first + longitudeBands + 1;

                indices.push(offset + first + 1, offset + second, offset + first);
                indices.push(offset + first + 1, offset + second + 1, offset + second);
            }
        }
    } else {
        // Generate bottom cap
        offset = (heightSegments + 1) * (capSegments + 1);
        if (baseRadius > 0.0) {
            for (i = 0; i < capSegments; i++) {
                theta = (i / capSegments) * 2.0 * Math.PI;
                x = Math.sin(theta);
                y = -height / 2.0;
                z = Math.cos(theta);
                u = 1.0 - (x + 1.0) / 2.0;
                v = (z + 1.0) / 2.0;

                positions.push(x * baseRadius, y, z * baseRadius);
                normals.push(0.0, -1.0, 0.0);
                uvs.push(u, v);

                // Pack UV1 to 2nd third
                u /= 3;
                v /= 3;
                u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
                v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
                u += 1.0 / 3;
                uvs1.push(u, v);

                if (i > 1) {
                    indices.push(offset, offset + i, offset + i - 1);
                }
            }
        }

        // Generate top cap
        offset += capSegments;
        if (peakRadius > 0.0) {
            for (i = 0; i < capSegments; i++) {
                theta = (i / capSegments) * 2.0 * Math.PI;
                x = Math.sin(theta);
                y = height / 2.0;
                z = Math.cos(theta);
                u = 1.0 - (x + 1.0) / 2.0;
                v = (z + 1.0) / 2.0;

                positions.push(x * peakRadius, y, z * peakRadius);
                normals.push(0.0, 1.0, 0.0);
                uvs.push(u, v);

                // Pack UV1 to 3rd third
                u /= 3;
                v /= 3;
                u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
                v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
                u += 2.0 / 3;
                uvs1.push(u, v);

                if (i > 1) {
                    indices.push(offset, offset + i - 1, offset + i);
                }
            }
        }
    }

    return {
        positions: positions,
        normals: normals,
        uvs: uvs,
        uvs1: uvs1,
        indices: indices
    };
};

/**
 * @function
 * @name pc.createCylinder
 * @description Creates a procedural cylinder-shaped mesh.
 * The size, shape and tesselation properties of the cylinder can be controlled via function parameters.
 * By default, the function will create a cylinder standing vertically centred on the XZ-plane with a radius
 * of 0.5, a height of 1.0, 1 height segment and 20 cap segments.<br />
 * Note that the cylinder is created with UVs in the range of 0 to 1. Additionally, tangent information
 * is generated into the vertex buffer of the cylinder's mesh.<br />
 * @param {pc.GraphicsDevice} device The graphics device used to manage the mesh.
 * @param {Object} opts An object that specifies optional inputs for the function as follows:
 * @param {Number} opts.radius The radius of the tube forming the body of the cylinder (defaults to 0.5).
 * @param {Number} opts.height The length of the body of the cylinder (defaults to 1.0).
 * @param {Number} opts.heightSegments The number of divisions along the length of the cylinder (defaults to 5).
 * @param {Number} opts.capSegments The number of divisions around the tubular body of the cylinder (defaults to 20).
 * @returns {pc.Mesh} A new cylinder-shaped mesh.
 */
pc.createCylinder = function (device, opts) {
    // #ifdef DEBUG
    if (opts && opts.hasOwnProperty('baseRadius') && !opts.hasOwnProperty('radius')) {
        console.warn('DEPRECATED: "baseRadius" in arguments, use "radius" instead');
    }
    // #endif

    // Check the supplied options and provide defaults for unspecified ones
    var radius = opts && (opts.radius || opts.baseRadius);
    radius = radius !== undefined ? radius : 0.5;
    var height = opts && opts.height !== undefined ? opts.height : 1.0;
    var heightSegments = opts && opts.heightSegments !== undefined ? opts.heightSegments : 5;
    var capSegments = opts && opts.capSegments !== undefined ? opts.capSegments : 20;
    var calculateTangents = opts && opts.calculateTangents !== undefined ? opts.calculateTangents : false;

    // Create vertex data for a cone that has a base and peak radius that is the same (i.e. a cylinder)
    var options = pc._createConeData(radius, radius, height, heightSegments, capSegments, false);

    if (calculateTangents) {
        options.tangents = pc.calculateTangents(options.positions, options.normals, options.uvs, options.indices);
    }

    return pc.createMesh(device, options.positions, options);
};

/**
 * @function
 * @name pc.createCapsule
 * @description Creates a procedural capsule-shaped mesh.
 * The size, shape and tesselation properties of the capsule can be controlled via function parameters.
 * By default, the function will create a capsule standing vertically centred on the XZ-plane with a radius
 * of 0.25, a height of 1.0, 1 height segment and 10 cap segments.<br />
 * Note that the capsule is created with UVs in the range of 0 to 1. Additionally, tangent information
 * is generated into the vertex buffer of the capsule's mesh.<br />
 * @param {pc.GraphicsDevice} device The graphics device used to manage the mesh.
 * @param {Object} opts An object that specifies optional inputs for the function as follows:
 * @param {Number} opts.radius The radius of the tube forming the body of the capsule (defaults to 0.3).
 * @param {Number} opts.height The length of the body of the capsule from tip to tip (defaults to 1.0).
 * @param {Number} opts.heightSegments The number of divisions along the tubular length of the capsule (defaults to 1).
 * @param {Number} opts.sides The number of divisions around the tubular body of the capsule (defaults to 20).
 * @returns {pc.Mesh} A new cylinder-shaped mesh.
 */
pc.createCapsule = function (device, opts) {
    // Check the supplied options and provide defaults for unspecified ones
    var radius = opts && opts.radius !== undefined ? opts.radius : 0.3;
    var height = opts && opts.height !== undefined ? opts.height : 1.0;
    var heightSegments = opts && opts.heightSegments !== undefined ? opts.heightSegments : 1;
    var sides = opts && opts.sides !== undefined ? opts.sides : 20;
    var calculateTangents = opts && opts.calculateTangents !== undefined ? opts.calculateTangents : false;

    // Create vertex data for a cone that has a base and peak radius that is the same (i.e. a cylinder)
    var options = pc._createConeData(radius, radius, height - 2 * radius, heightSegments, sides, true);

    if (calculateTangents) {
        options.tangents = pc.calculateTangents(options.positions, options.normals, options.uvs, options.indices);
    }

    return pc.createMesh(device, options.positions, options);
};

/**
 * @function
 * @name pc.createCone
 * @description Creates a procedural cone-shaped mesh.</p>
 * The size, shape and tesselation properties of the cone can be controlled via function parameters.
 * By default, the function will create a cone standing vertically centred on the XZ-plane with a base radius
 * of 0.5, a height of 1.0, 5 height segments and 20 cap segments.<br />
 * Note that the cone is created with UVs in the range of 0 to 1. Additionally, tangent information
 * is generated into the vertex buffer of the cone's mesh.<br />
 * @param {pc.GraphicsDevice} device The graphics device used to manage the mesh.
 * @param {Object} opts An object that specifies optional inputs for the function as follows:
 * @param {Number} opts.baseRadius The base radius of the cone (defaults to 0.5).
 * @param {Number} opts.peakRadius The peak radius of the cone (defaults to 0.0).
 * @param {Number} opts.height The length of the body of the cone (defaults to 1.0).
 * @param {Number} opts.heightSegments The number of divisions along the length of the cone (defaults to 5).
 * @param {Number} opts.capSegments The number of divisions around the tubular body of the cone (defaults to 18).
 * @returns {pc.Mesh} A new cone-shaped mesh.
 */
pc.createCone = function (device, opts) {
    // Check the supplied options and provide defaults for unspecified ones
    var baseRadius = opts && opts.baseRadius !== undefined ? opts.baseRadius : 0.5;
    var peakRadius = opts && opts.peakRadius !== undefined ? opts.peakRadius : 0.0;
    var height = opts && opts.height !== undefined ? opts.height : 1.0;
    var heightSegments = opts && opts.heightSegments !== undefined ? opts.heightSegments : 5;
    var capSegments = opts && opts.capSegments !== undefined ? opts.capSegments : 18;
    var calculateTangents = opts && opts.calculateTangents !== undefined ? opts.calculateTangents : false;

    var options = pc._createConeData(baseRadius, peakRadius, height, heightSegments, capSegments, false);

    if (calculateTangents) {
        options.tangents = pc.calculateTangents(options.positions, options.normals, options.uvs, options.indices);
    }

    return pc.createMesh(device, options.positions, options);
};

/**
 * @function
 * @name pc.createSphere
 * @description Creates a procedural sphere-shaped mesh.
 * The size and tesselation properties of the sphere can be controlled via function parameters. By
 * default, the function will create a sphere centred on the object space origin with a radius of 0.5
 * and 16 segments in both longitude and latitude.<br />
 * Note that the sphere is created with UVs in the range of 0 to 1. Additionally, tangent information
 * is generated into the vertex buffer of the sphere's mesh.<br />
 * @param {pc.GraphicsDevice} device The graphics device used to manage the mesh.
 * @param {Object} opts An object that specifies optional inputs for the function as follows:
 * @param {Number} opts.radius The radius of the sphere (defaults to 0.5).
 * @param {Number} opts.segments The number of divisions along the longitudinal and latitudinal axes of the sphere (defaults to 16).
 * @returns {pc.Mesh} A new sphere-shaped mesh.
 */
pc.createSphere = function (device, opts) {
    // Check the supplied options and provide defaults for unspecified ones
    var radius = opts && opts.radius !== undefined ? opts.radius : 0.5;
    var latitudeBands = opts && opts.latitudeBands !== undefined ? opts.latitudeBands : 16;
    var longitudeBands = opts && opts.longitudeBands !== undefined ? opts.longitudeBands : 16;
    var calculateTangents = opts && opts.calculateTangents !== undefined ? opts.calculateTangents : false;

    // Variable declarations
    var lon, lat;
    var theta, sinTheta, cosTheta, phi, sinPhi, cosPhi;
    var first, second;
    var x, y, z, u, v;
    var positions = [];
    var normals = [];
    var uvs = [];
    var indices = [];

    for (lat = 0; lat <= latitudeBands; lat++) {
        theta = lat * Math.PI / latitudeBands;
        sinTheta = Math.sin(theta);
        cosTheta = Math.cos(theta);

        for (lon = 0; lon <= longitudeBands; lon++) {
            // Sweep the sphere from the positive Z axis to match a 3DS Max sphere
            phi = lon * 2 * Math.PI / longitudeBands - Math.PI / 2.0;
            sinPhi = Math.sin(phi);
            cosPhi = Math.cos(phi);

            x = cosPhi * sinTheta;
            y = cosTheta;
            z = sinPhi * sinTheta;
            u = 1.0 - lon / longitudeBands;
            v = 1.0 - lat / latitudeBands;

            positions.push(x * radius, y * radius, z * radius);
            normals.push(x, y, z);
            uvs.push(u, v);
        }
    }

    for (lat = 0; lat < latitudeBands; ++lat) {
        for (lon = 0; lon < longitudeBands; ++lon) {
            first  = (lat * (longitudeBands + 1)) + lon;
            second = first + longitudeBands + 1;

            indices.push(first + 1, second, first);
            indices.push(first + 1, second + 1, second);
        }
    }

    var options = {
        normals: normals,
        uvs: uvs,
        uvs1: uvs, // UV1 = UV0 for sphere
        indices: indices
    };

    if (calculateTangents) {
        options.tangents = pc.calculateTangents(positions, normals, uvs, indices);
    }

    return pc.createMesh(device, positions, options);
};

/**
 * @function
 * @name pc.createPlane
 * @description Creates a procedural plane-shaped mesh.
 * The size and tesselation properties of the plane can be controlled via function parameters. By
 * default, the function will create a plane centred on the object space origin with a width and
 * length of 1.0 and 5 segments in either axis (50 triangles). The normal vector of the plane is aligned
 * along the positive Y axis.<br />
 * Note that the plane is created with UVs in the range of 0 to 1. Additionally, tangent information
 * is generated into the vertex buffer of the plane's mesh.<br />
 * @param {pc.GraphicsDevice} device The graphics device used to manage the mesh.
 * @param {Object} opts An object that specifies optional inputs for the function as follows:
 * @param {pc.Vec2} opts.halfExtents The half dimensions of the plane in the X and Z axes (defaults to [0.5, 0.5]).
 * @param {Number} opts.widthSegments The number of divisions along the X axis of the plane (defaults to 5).
 * @param {Number} opts.lengthSegments The number of divisions along the Z axis of the plane (defaults to 5).
 * @returns {pc.Mesh} A new plane-shaped mesh.
 */
pc.createPlane = function (device, opts) {
    // Check the supplied options and provide defaults for unspecified ones
    var he = opts && opts.halfExtents !== undefined ? opts.halfExtents : new pc.Vec2(0.5, 0.5);
    var ws = opts && opts.widthSegments !== undefined ? opts.widthSegments : 5;
    var ls = opts && opts.lengthSegments !== undefined ? opts.lengthSegments : 5;
    var calculateTangents = opts && opts.calculateTangents !== undefined ? opts.calculateTangents : false;

    // Variable declarations
    var i, j;
    var x, y, z, u, v;
    var positions = [];
    var normals = [];
    var uvs = [];
    var indices = [];

    // Generate plane as follows (assigned UVs denoted at corners):
    // (0,1)x---------x(1,1)
    //      |         |
    //      |         |
    //      |    O--X |length
    //      |    |    |
    //      |    Z    |
    // (0,0)x---------x(1,0)
    // width
    var vcounter = 0;

    for (i = 0; i <= ws; i++) {
        for (j = 0; j <= ls; j++) {
            x = -he.x + 2.0 * he.x * i / ws;
            y = 0.0;
            z = -(-he.y + 2.0 * he.y * j / ls);
            u = i / ws;
            v = j / ls;

            positions.push(x, y, z);
            normals.push(0.0, 1.0, 0.0);
            uvs.push(u, v);

            if ((i < ws) && (j < ls)) {
                indices.push(vcounter + ls + 1, vcounter + 1, vcounter);
                indices.push(vcounter + ls + 1, vcounter + ls + 2, vcounter + 1);
            }

            vcounter++;
        }
    }

    var options = {
        normals: normals,
        uvs: uvs,
        uvs1: uvs, // UV1 = UV0 for plane
        indices: indices
    };

    if (calculateTangents) {
        options.tangents = pc.calculateTangents(positions, normals, uvs, indices);
    }

    return pc.createMesh(device, positions, options);
};

/**
 * @function
 * @name pc.createBox
 * @description Creates a procedural box-shaped mesh.
 * The size, shape and tesselation properties of the box can be controlled via function parameters. By
 * default, the function will create a box centred on the object space origin with a width, length and
 * height of 1.0 unit and 10 segments in either axis (50 triangles per face).<br />
 * Note that the box is created with UVs in the range of 0 to 1 on each face. Additionally, tangent
 * information is generated into the vertex buffer of the box's mesh.<br />
 * @param {pc.GraphicsDevice} device The graphics device used to manage the mesh.
 * @param {Object} opts An object that specifies optional inputs for the function as follows:
 * @param {pc.Vec3} opts.halfExtents The half dimensions of the box in each axis (defaults to [0.5, 0.5, 0.5]).
 * @param {Number} opts.widthSegments The number of divisions along the X axis of the box (defaults to 1).
 * @param {Number} opts.lengthSegments The number of divisions along the Z axis of the box (defaults to 1).
 * @param {Number} opts.heightSegments The number of divisions along the Y axis of the box (defaults to 1).
 * @returns {pc.Mesh} A new box-shaped mesh.
 */
pc.createBox = function (device, opts) {
    // Check the supplied options and provide defaults for unspecified ones
    var he = opts && opts.halfExtents !== undefined ? opts.halfExtents : new pc.Vec3(0.5, 0.5, 0.5);
    var ws = opts && opts.widthSegments !== undefined ? opts.widthSegments : 1;
    var ls = opts && opts.lengthSegments !== undefined ? opts.lengthSegments : 1;
    var hs = opts && opts.heightSegments !== undefined ? opts.heightSegments : 1;
    var calculateTangents = opts && opts.calculateTangents !== undefined ? opts.calculateTangents : false;

    var corners = [
        new pc.Vec3(-he.x, -he.y,  he.z),
        new pc.Vec3( he.x, -he.y,  he.z),
        new pc.Vec3( he.x,  he.y,  he.z),
        new pc.Vec3(-he.x,  he.y,  he.z),
        new pc.Vec3( he.x, -he.y, -he.z),
        new pc.Vec3(-he.x, -he.y, -he.z),
        new pc.Vec3(-he.x,  he.y, -he.z),
        new pc.Vec3( he.x,  he.y, -he.z)
    ];

    var faceAxes = [
        [0, 1, 3], // FRONT
        [4, 5, 7], // BACK
        [3, 2, 6], // TOP
        [1, 0, 4], // BOTTOM
        [1, 4, 2], // RIGHT
        [5, 0, 6]  // LEFT
    ];

    var faceNormals = [
        [0,  0,  1], // FRONT
        [0,  0, -1], // BACK
        [0,  1,  0], // TOP
        [0, -1,  0], // BOTTOM
        [1,  0,  0], // RIGHT
        [-1,  0,  0]  // LEFT
    ];

    var sides = {
        FRONT: 0,
        BACK: 1,
        TOP: 2,
        BOTTOM: 3,
        RIGHT: 4,
        LEFT: 5
    };

    var positions = [];
    var normals = [];
    var uvs = [];
    var uvs1 = [];
    var indices = [];
    var vcounter = 0;

    var generateFace = function (side, uSegments, vSegments) {
        var u, v;
        var i, j;

        for (i = 0; i <= uSegments; i++) {
            for (j = 0; j <= vSegments; j++) {
                var temp1 = new pc.Vec3();
                var temp2 = new pc.Vec3();
                var temp3 = new pc.Vec3();
                var r = new pc.Vec3();
                temp1.lerp(corners[faceAxes[side][0]], corners[faceAxes[side][1]], i / uSegments);
                temp2.lerp(corners[faceAxes[side][0]], corners[faceAxes[side][2]], j / vSegments);
                temp3.sub2(temp2, corners[faceAxes[side][0]]);
                r.add2(temp1, temp3);
                u = i / uSegments;
                v = j / vSegments;

                positions.push(r.x, r.y, r.z);
                normals.push(faceNormals[side][0], faceNormals[side][1], faceNormals[side][2]);
                uvs.push(u, v);
                // pack as 3x2
                // 1/3 will be empty, but it's either that or stretched pixels
                // TODO: generate non-rectangular lightMaps, so we could use space without stretching
                u /= 3;
                v /= 3;
                u = u * primitiveUv1PaddingScale + primitiveUv1Padding;
                v = v * primitiveUv1PaddingScale + primitiveUv1Padding;
                u += (side % 3) / 3;
                v += Math.floor(side / 3) / 3;
                uvs1.push(u, v);

                if ((i < uSegments) && (j < vSegments)) {
                    indices.push(vcounter + vSegments + 1, vcounter + 1, vcounter);
                    indices.push(vcounter + vSegments + 1, vcounter + vSegments + 2, vcounter + 1);
                }

                vcounter++;
            }
        }
    };

    generateFace(sides.FRONT, ws, hs);
    generateFace(sides.BACK, ws, hs);
    generateFace(sides.TOP, ws, ls);
    generateFace(sides.BOTTOM, ws, ls);
    generateFace(sides.RIGHT, ls, hs);
    generateFace(sides.LEFT, ls, hs);

    var options = {
        normals: normals,
        uvs: uvs,
        uvs1: uvs1,
        indices: indices
    };

    if (calculateTangents) {
        options.tangents = pc.calculateTangents(positions, normals, uvs, indices);
    }

    return pc.createMesh(device, positions, options);
};